diff --git a/.autod.conf.js b/.autod.conf.js new file mode 100644 index 0000000..caf2edc --- /dev/null +++ b/.autod.conf.js @@ -0,0 +1,27 @@ +'use strict'; + +module.exports = { + write: true, + prefix: '^', + plugin: 'autod-egg', + test: [ + 'test', + 'benchmark', + ], + dep: [ + 'egg' + ], + devdep: [ + 'egg-ci', + 'egg-bin', + 'autod', + 'eslint', + 'eslint-config-egg', + 'webstorm-disable-index', + ], + exclude: [ + './test/fixtures', + './dist', + ], +}; + diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..c799fe5 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "eslint-config-egg" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a71ac1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +logs/ +npm-debug.log +node_modules/ +coverage/ +.idea/ +run/ +.DS_Store +*.swp +package-lock.json +yarn.lock +.nyc_output/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ce21122 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +sudo: false +language: node_js +node_js: + - '8' + - '10' +install: + - npm i npminstall && npminstall +script: + - npm run ci +after_script: + - npminstall codecov && codecov diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..139597f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..cde8f04 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# res + +[![NPM version][npm-image]][npm-url] +[![build status][travis-image]][travis-url] +[![Test coverage][codecov-image]][codecov-url] +[![David deps][david-image]][david-url] +[![Known Vulnerabilities][snyk-image]][snyk-url] +[![npm download][download-image]][download-url] + +[npm-image]: https://img.shields.io/npm/v/res.svg?style=flat-square +[npm-url]: https://npmjs.org/package/res +[travis-image]: https://img.shields.io/travis/easy-team/res.svg?style=flat-square +[travis-url]: https://travis-ci.org/easy-team/res +[codecov-image]: https://img.shields.io/codecov/c/github/easy-team/res.svg?style=flat-square +[codecov-url]: https://codecov.io/github/easy-team/res?branch=master +[david-image]: https://img.shields.io/david/easy-team/res.svg?style=flat-square +[david-url]: https://david-dm.org/easy-team/res +[snyk-image]: https://snyk.io/test/npm/res/badge.svg?style=flat-square +[snyk-url]: https://snyk.io/test/npm/res +[download-image]: https://img.shields.io/npm/dm/res.svg?style=flat-square +[download-url]: https://npmjs.org/package/res + + +A Powerful, Simple React Node Web Framework, Front-End and Node of The Application are Written in TypeScript or JavaScript! Everything is Simple! + +> You don't need to care about React server-side rendering implementation details and the Webpack + Babel + TypeScript build process. You just need to write React isomorphic applications just like you would write a React front-end application. + +## Installation + +```bash +$ npm install @easy-team/res --save +``` + +Node.js >= 8.0.0 required. + +## Features + +- ✔︎ Based on [Egg](https://eggjs.org/en/intro/index.html) Framework, Powerful, Easy to Expand +- ✔︎ Support React Server Side Render and Client Side Render Modes, Nunjucks & React Render Mode, Rendering Cache, Automatic Downgrade, +- ✔︎ Front-End and Node of The Application are Written in TypeScript or JavaScript +- ✔︎ Build with Webpack, Auto Building, Hot Reload, Code Splitting, High Speed, Performance Optimization +- ✔︎ Powerful Tool Chain [res-cli](https://github.com/easy-team/res-cli) + +## Document + +- https://www.yuque.com/easy-team/res + +## QuickStart + +```bash +$ npm install -g @easy-team/res-cli +$ res init +$ npm install +$ npm run dev +$ open http://localhost:7001 +``` + +## Examples + +See [res-awesome](https://github.com/easy-team/res-awesome) + +## Links + +- https://eggjs.org/en/advanced/framework.html +- https://www.yuque.com/easy-team/res + +## License + +[MIT](LICENSE) diff --git a/app.js b/app.js new file mode 100644 index 0000000..f701fbc --- /dev/null +++ b/app.js @@ -0,0 +1,5 @@ +'use strict'; +// https://github.com/eggjs/egg/issues/1520 +module.exports = app => { + app.config.coreMiddleware.push('locals'); +}; diff --git a/app/extend/application.js b/app/extend/application.js new file mode 100644 index 0000000..0e903a8 --- /dev/null +++ b/app/extend/application.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + +}; diff --git a/app/extend/context.js b/app/extend/context.js new file mode 100644 index 0000000..0e903a8 --- /dev/null +++ b/app/extend/context.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + +}; diff --git a/app/middleware/locals.js b/app/middleware/locals.js new file mode 100644 index 0000000..d7303b5 --- /dev/null +++ b/app/middleware/locals.js @@ -0,0 +1,7 @@ +'use strict'; +module.exports = () => { + return async function(ctx, next) { + ctx.locals.title = 'res'; + await next(); + }; +}; diff --git a/app/service/test.js b/app/service/test.js new file mode 100644 index 0000000..26a61fc --- /dev/null +++ b/app/service/test.js @@ -0,0 +1,16 @@ +'use strict'; + +const Service = require('egg').Service; + +class TestService extends Service { + constructor(ctx) { + super(ctx); + this.config = this.app.config.test; + } + + async get(id) { + return { id, name: this.config.key }; + } +} + +module.exports = TestService; diff --git a/app/web/tsconfig.json b/app/web/tsconfig.json new file mode 100644 index 0000000..ec03a65 --- /dev/null +++ b/app/web/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../config/tsconfig.json", + "compilerOptions": { + "target": "es5", + "module": "esnext", + "lib": [ + "es6", + "dom" + ] + }, + + "exclude": [ + "node_modules", + "**/*.spec.ts" + ] +} \ No newline at end of file diff --git a/app/web/typings/vue.d.ts b/app/web/typings/vue.d.ts new file mode 100644 index 0000000..dc48246 --- /dev/null +++ b/app/web/typings/vue.d.ts @@ -0,0 +1,4 @@ +declare module "*.vue" { + import Vue from 'vue' + export default Vue +} \ No newline at end of file diff --git a/app/web/typings/webpack.d.ts b/app/web/typings/webpack.d.ts new file mode 100644 index 0000000..357c0a8 --- /dev/null +++ b/app/web/typings/webpack.d.ts @@ -0,0 +1,8 @@ +declare var EASY_ENV_IS_NODE: boolean; +declare var EASY_ENV_IS_BROWSER: boolean; +declare var EASY_ENV_IS_DEV: boolean; +declare var process : { + env: { + NODE_ENV: string + } +} \ No newline at end of file diff --git a/app/web/typings/window.d.ts b/app/web/typings/window.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/app/web/view/layout.html b/app/web/view/layout.html new file mode 100644 index 0000000..3c4e4d5 --- /dev/null +++ b/app/web/view/layout.html @@ -0,0 +1,13 @@ + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..981e82b --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,15 @@ +environment: + matrix: + - nodejs_version: '8' + - nodejs_version: '10' + +install: + - ps: Install-Product node $env:nodejs_version + - npm i npminstall && node_modules\.bin\npminstall + +test_script: + - node --version + - npm --version + - npm run test + +build: off diff --git a/bin/cli.js b/bin/cli.js new file mode 100755 index 0000000..ceffc5e --- /dev/null +++ b/bin/cli.js @@ -0,0 +1,14 @@ +#!/usr/bin/env node + +'use strict'; +const path = require('path'); +const fs = require('fs'); +const EggScriptCommand = require('egg-scripts'); +const cliFilePath = path.join(process.cwd(), 'node_modules', '@easy-team/res-cli'); +// fix when production mode, ves-cli not exists, start app error by ves start +if (fs.existsSync(cliFilePath)) { + const ResCLI = require(cliFilePath); + new ResCLI.Command().start(); +} else { + new EggScriptCommand().start(); +} diff --git a/config/config.default.js b/config/config.default.js new file mode 100644 index 0000000..6b49990 --- /dev/null +++ b/config/config.default.js @@ -0,0 +1,38 @@ +'use strict'; +const path = require('path'); +module.exports = appInfo => { + const config = {}; + + config.view = { + defaultViewEngine: 'nunjucks', + mapping: { + '.tpl': 'nunjucks' + }, + }; + + /** + * some description + * @member Config#test + * @property {String} key - some description + */ + config.test = { + key: appInfo.name + '_123456', + }; + + /** + * cookie secret key + */ + config.keys = appInfo.name + '_123456'; + + /** + * vue ssr plugin config + * @member Config#vuessr + * @property {String} layout - client render default html layout file path + * @property {renderOptions} Object - vue server side render options + */ + config.reactssr = { + layout: path.resolve(__dirname, '../app/web/view/layout.html') + }; + + return config; +}; diff --git a/config/config.local.js b/config/config.local.js new file mode 100644 index 0000000..68a4582 --- /dev/null +++ b/config/config.local.js @@ -0,0 +1,31 @@ +'use strict'; +const utils = require('../lib/utils'); +module.exports = app => { + + const exports = {}; + + exports.view = { + cache: false, + }; + + exports.static = { + maxAge: 0, // maxAge cache, default one year, local disabled + }; + + exports.development = { + watchDirs: [], + ignoreDirs: [ 'app/web', 'public', 'config/manifest.json' ], + }; + + /** + * egg-webpack plugin config + * @member Config#webpack + * @property {Array} webpackConfigList - webpack config + */ + exports.webpack = { + cli: utils.getCli(), + webpackConfigList: utils.getWebpackConfig(app), + }; + + return exports; +}; diff --git a/config/plugin.js b/config/plugin.js new file mode 100644 index 0000000..ccf43e2 --- /dev/null +++ b/config/plugin.js @@ -0,0 +1,10 @@ +'use strict'; +exports.reactssr = { + enable: true, + package: 'egg-view-react-ssr', +}; + +exports.nunjucks = { + enable: true, + package: 'egg-view-nunjucks', +}; \ No newline at end of file diff --git a/config/plugin.local.js b/config/plugin.local.js new file mode 100644 index 0000000..cc1fde5 --- /dev/null +++ b/config/plugin.local.js @@ -0,0 +1,15 @@ +'use strict'; +exports.cors = { + enable: true, + package: 'egg-cors', +}; + +exports.webpack = { + enable: true, + package: 'egg-webpack', +}; + +exports.webpackreact = { + enable: true, + package: 'egg-webpack-react', +}; diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..fb7b84c --- /dev/null +++ b/index.d.ts @@ -0,0 +1,3 @@ +import * as Egg from 'egg'; +export = Egg; +export as namespace Egg; \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..1166f5f --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./lib/framework.js'); diff --git a/lib/framework.js b/lib/framework.js new file mode 100644 index 0000000..db18eb4 --- /dev/null +++ b/lib/framework.js @@ -0,0 +1,22 @@ +'use strict'; + +const path = require('path'); +const egg = require('egg'); +const EGG_PATH = Symbol.for('egg#eggPath'); + +class Application extends egg.Application { + get [EGG_PATH]() { + return path.dirname(__dirname); + } +} + +class Agent extends egg.Agent { + get [EGG_PATH]() { + return path.dirname(__dirname); + } +} + +module.exports = Object.assign(egg, { + Application, + Agent, +}); diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..0fb3b15 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,16 @@ +'use strict'; +const path = require('path'); +const fs = require('fs'); +exports.getCli = () => { + return { name: 'res-cli', cmd: 'res', package: '@easy-team/res-cli' }; +}; +exports.getWebpackConfig = appInfo => { + const cli = exports.getCli(); + const filepath = path.join(appInfo.baseDir, 'node_modules', cli.package); + const cwdFilepath = path.join(process.cwd(), 'node_modules', cli.package); + if (fs.existsSync(filepath) || fs.existsSync(cwdFilepath)) { + const resCli = require(cli.package); + return resCli.getWebpackConfig(); + } + return []; +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..83ac89a --- /dev/null +++ b/package.json @@ -0,0 +1,73 @@ +{ + "name": "@easy-team/res", + "version": "1.0.0", + "description": "A Powerful, Simple Node React Web Framework", + "dependencies": { + "egg": "^2.3.0", + "egg-cors": "^2.1.1", + "egg-scripts": "^2.10.0", + "egg-view-nunjucks": "^2.2.0", + "egg-view-react-ssr": "^2.5.0", + "react": "^16.0.0", + "react-dom": "^16.0.0" + }, + "devDependencies": { + "autod": "^3.0.1", + "autod-egg": "^1.0.0", + "egg-bin": "^4.4.0", + "egg-ci": "^1.8.0", + "egg-mock": "^3.14.0", + "eslint": "^4.18.0", + "eslint-config-egg": "^7.0.0", + "webstorm-disable-index": "^1.2.0", + "conventional-changelog-cli": "^1.3.5" + }, + "engines": { + "node": ">=8.0.0" + }, + "scripts": { + "test": "npm run lint -- --fix && egg-bin pkgfiles && npm run test-local", + "test-local": "egg-bin test --full-trace", + "cov": "egg-bin cov", + "lint": "eslint .", + "ci": "npm run lint && egg-bin pkgfiles --check && npm run cov", + "autod": "autod", + "pkgfiles": "egg-bin pkgfiles", + "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" + }, + "ci": { + "version": "8, 10, 12" + }, + "repository": { + "type": "git", + "url": "" + }, + "keywords": [ + "egg", + "egg-framework", + "egg-webpack", + "res", + "react server render", + "react-Isomorphic", + "react-server-render" + ], + "author": "sky", + "files": [ + "index.d.ts", + "index.js", + "bin", + "lib", + "app", + "config", + "app.js", + "index.d.ts" + ], + "eslintIgnore": [ + "coverage", + "dist", + "test/fixtures/example/node_modules", + "test/fixtures/example/public", + "test/fixtures/example/app/view" + ], + "license": "MIT" +} diff --git a/test/fixtures/example/app/controller/home.js b/test/fixtures/example/app/controller/home.js new file mode 100644 index 0000000..6884699 --- /dev/null +++ b/test/fixtures/example/app/controller/home.js @@ -0,0 +1,18 @@ +'use strict'; + +const Controller = require('egg').Controller; + +class HomeController extends Controller { + async index() { + const data = await this.service.test.get(123); + this.ctx.body = data.name; + } + async render() { + await this.ctx.render('home/home.js', { message: 'res server side render' }); + } + async renderClient() { + await this.ctx.renderClient('home/home.js', { message: 'res client side render' }); + } +} + +module.exports = HomeController; diff --git a/test/fixtures/example/app/router.js b/test/fixtures/example/app/router.js new file mode 100644 index 0000000..f368bf2 --- /dev/null +++ b/test/fixtures/example/app/router.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = app => { + const { router, controller } = app; + router.get('/', controller.home.index); + router.get('/render', controller.home.render); + router.get('/renderClient', controller.home.renderClient); +}; diff --git a/test/fixtures/example/app/view/home/home.js b/test/fixtures/example/app/view/home/home.js new file mode 100644 index 0000000..39d2297 --- /dev/null +++ b/test/fixtures/example/app/view/home/home.js @@ -0,0 +1,11563 @@ +(function(e, a) { for(var i in a) e[i] = a[i]; }(exports, /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = "/public/"; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 4); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports) { + +exports.sync = function (store, router, options) { + var moduleName = (options || {}).moduleName || 'route' + + store.registerModule(moduleName, { + namespaced: true, + state: cloneRoute(router.currentRoute), + mutations: { + 'ROUTE_CHANGED': function ROUTE_CHANGED (state, transition) { + store.state[moduleName] = cloneRoute(transition.to, transition.from) + } + } + }) + + var isTimeTraveling = false + var currentPath + + // sync router on store change + var storeUnwatch = store.watch( + function (state) { return state[moduleName]; }, + function (route) { + var fullPath = route.fullPath; + if (fullPath === currentPath) { + return + } + if (currentPath != null) { + isTimeTraveling = true + router.push(route) + } + currentPath = fullPath + }, + { sync: true } + ) + + // sync store on router navigation + var afterEachUnHook = router.afterEach(function (to, from) { + if (isTimeTraveling) { + isTimeTraveling = false + return + } + currentPath = to.fullPath + store.commit(moduleName + '/ROUTE_CHANGED', { to: to, from: from }) + }) + + return function unsync () { + // On unsync, remove router hook + if (afterEachUnHook != null) { + afterEachUnHook() + } + + // On unsync, remove store watch + if (storeUnwatch != null) { + storeUnwatch() + } + + // On unsync, unregister Module with store + store.unregisterModule(moduleName) + } +} + +function cloneRoute (to, from) { + var clone = { + name: to.name, + path: to.path, + hash: to.hash, + query: to.query, + params: to.params, + fullPath: to.fullPath, + meta: to.meta + } + if (from) { + clone.from = cloneRoute(from) + } + return Object.freeze(clone) +} + + + +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + +// style-loader: Adds some css to the DOM by adding a ' + } + return css +} + + +/***/ }) +/******/ ]))); \ No newline at end of file diff --git a/test/fixtures/example/app/web/componet/layout.tsx b/test/fixtures/example/app/web/componet/layout.tsx new file mode 100644 index 0000000..bcbb63f --- /dev/null +++ b/test/fixtures/example/app/web/componet/layout.tsx @@ -0,0 +1,19 @@ +import React, { Component } from 'react'; +export default class Layout extends Component { + render() { + if(EASY_ENV_IS_NODE) { + return + + {this.props.title} + + + + + + +
{this.props.children}
+ ; + } + return this.props.children; + } +} \ No newline at end of file diff --git a/test/fixtures/example/app/web/framework/app.tsx b/test/fixtures/example/app/web/framework/app.tsx new file mode 100644 index 0000000..c1119f7 --- /dev/null +++ b/test/fixtures/example/app/web/framework/app.tsx @@ -0,0 +1,22 @@ +import React, { Component } from 'react'; +import ReactDOM from 'react-dom'; + +const clientRender = (Com, method) => { + const state = window.__INITIAL_STATE__; + const root = document.getElementById('app'); + ReactDOM[method](, root); +}; + +const serverRender = Com => { + return Com; +}; + +export function SSR(Com) { + return EASY_ENV_IS_NODE ? serverRender(Com) : clientRender(Com, 'hydrate'); +} + +export function CSR(Com) { + return clientRender(Com, 'render'); +} + + diff --git a/test/fixtures/example/app/web/page/react-client-render/index.tsx b/test/fixtures/example/app/web/page/react-client-render/index.tsx new file mode 100644 index 0000000..1a6adbb --- /dev/null +++ b/test/fixtures/example/app/web/page/react-client-render/index.tsx @@ -0,0 +1,15 @@ +import React, { Component } from 'react'; +import Layout from '../../component/layout.jsx'; +import { CSR } from '../../framework/app'; + +class Index extends Component { + componentDidMount() { + console.log('client render'); + } + render() { + return

{this.props.text}

; + } +} + + +export default CSR(Index); \ No newline at end of file diff --git a/test/fixtures/example/app/web/page/react-nunjucks-render/component/index.tsx b/test/fixtures/example/app/web/page/react-nunjucks-render/component/index.tsx new file mode 100644 index 0000000..ffa96e9 --- /dev/null +++ b/test/fixtures/example/app/web/page/react-nunjucks-render/component/index.tsx @@ -0,0 +1,46 @@ +import React, { Component, CSSProperties } from 'react'; +import { observable } from 'mobx'; +import { Observer, observer, inject } from 'mobx-react'; + +const info = observable({ text: "Mobx Observer Local Test" }); + +class LocalReactive extends Component { + + style: CSSProperties = { + textAlign: 'center', + fontSize: '20px', + color: 'red', + marginTop: '24px' + } + + handle() { + info.text = `${info.text } Local Update`; + console.log('click'); + } + + render() { + return
+
{info.text}
+ {() =>
{ this.handle()}}>点我:{info.text}
}
+
+ } +} + +@inject('appStore') +@observer +class MobXApp extends Component { + constructor(props) { + super(props); + } + + render() { + const { appStore } = this.props; + // [mobx] Dynamic observable objects cannot be frozen https://github.com/mobxjs/mobx/blob/master/CHANGELOG.md + return
+

{appStore.state.text}

+
{ appStore.plus()}}>点我:{appStore.number}
+ +
+ } +}; +export default MobXApp; \ No newline at end of file diff --git a/test/fixtures/example/app/web/page/react-nunjucks-render/index.tsx b/test/fixtures/example/app/web/page/react-nunjucks-render/index.tsx new file mode 100644 index 0000000..431fc8b --- /dev/null +++ b/test/fixtures/example/app/web/page/react-nunjucks-render/index.tsx @@ -0,0 +1,22 @@ + +import React, { Component } from 'react'; +import { Provider } from 'mobx-react'; +import AppStore from './store'; +import MobXApp from './component'; +import { CSR } from '../../framework/app' + +const stores = window.stores = window.stores || { + appStore: new AppStore() +}; + +class Index extends Component { + componentDidMount() { + console.log('Nunjucks Render'); + } + render() { + return; + } +} + + +export default CSR(Index); \ No newline at end of file diff --git a/test/fixtures/example/app/web/page/react-nunjucks-render/store/index.tsx b/test/fixtures/example/app/web/page/react-nunjucks-render/store/index.tsx new file mode 100644 index 0000000..29fed53 --- /dev/null +++ b/test/fixtures/example/app/web/page/react-nunjucks-render/store/index.tsx @@ -0,0 +1,23 @@ +import { CSSProperties } from 'react'; +import { observable, action } from 'mobx'; +class AppStore { + @observable state = window.__INITIAL_STATE__ || {}; + // Test 定义状态并使其可观察 + @observable number = 0; + @observable size = 24; + @observable style: CSSProperties ={ + textAlign: 'center', + marginTop: '40px', + fontSize: `${this.size}px` + } + + @action + plus(){ + this.number++; + this.style = { + ...this.style, + fontSize: `${this.size++}px` + } + } +} +export default AppStore; \ No newline at end of file diff --git a/test/fixtures/example/app/web/page/react-server-render/index.tsx b/test/fixtures/example/app/web/page/react-server-render/index.tsx new file mode 100644 index 0000000..8143dc8 --- /dev/null +++ b/test/fixtures/example/app/web/page/react-server-render/index.tsx @@ -0,0 +1,17 @@ +import React, { Component } from 'react'; +import Layout from '../../component/layout'; +import { SSR } from '../../framework/app'; + +class Index extends Component { + componentDidMount() { + console.log('render'); + } + render() { + return +

{this.props.text}

+
; + } +} + + +export default SSR(Index); \ No newline at end of file diff --git a/test/fixtures/example/app/web/view/layout.html b/test/fixtures/example/app/web/view/layout.html new file mode 100644 index 0000000..3c4e4d5 --- /dev/null +++ b/test/fixtures/example/app/web/view/layout.html @@ -0,0 +1,13 @@ + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/test/fixtures/example/config/config.default.js b/test/fixtures/example/config/config.default.js new file mode 100644 index 0000000..853091e --- /dev/null +++ b/test/fixtures/example/config/config.default.js @@ -0,0 +1,6 @@ +'use strict'; +// const path = require('path'); +module.exports = () => { + const config = {}; + return config; +}; diff --git a/test/fixtures/example/config/config.unittest.js b/test/fixtures/example/config/config.unittest.js new file mode 100644 index 0000000..a6ada2b --- /dev/null +++ b/test/fixtures/example/config/config.unittest.js @@ -0,0 +1,3 @@ +'use strict'; + +// exports.keys = '123456'; diff --git a/test/fixtures/example/config/manifest.json b/test/fixtures/example/config/manifest.json new file mode 100644 index 0000000..1549a57 --- /dev/null +++ b/test/fixtures/example/config/manifest.json @@ -0,0 +1,21 @@ +{ + "home/home.css": "/public/css/0.03cd5845.css", + "home/home.js": "/public/js/chunk/home/home.cd531b5c.js", + "runtime.js": "/public/js/runtime.49eef78a.js", + "deps": { + "home/home.js": { + "js": [ + "/public/js/runtime.49eef78a.js", + "/public/js/chunk/home/home.cd531b5c.js" + ], + "css": [ + "/public/css/0.03cd5845.css" + ] + } + }, + "info": { + "publicPath": "/public/", + "buildPath": "public", + "mapped": true + } +} \ No newline at end of file diff --git a/test/fixtures/example/config/plugin.js b/test/fixtures/example/config/plugin.js new file mode 100644 index 0000000..fb08d05 --- /dev/null +++ b/test/fixtures/example/config/plugin.js @@ -0,0 +1,5 @@ +// 'use strict'; +// exports.vuessr = { +// enable: true, +// package: 'egg-view-vue-ssr', +// }; diff --git a/test/fixtures/example/package.json b/test/fixtures/example/package.json new file mode 100644 index 0000000..3cd5c16 --- /dev/null +++ b/test/fixtures/example/package.json @@ -0,0 +1,22 @@ +{ + "name": "framework-example", + "version": "1.0.0", + "scripts": { + "dev": "egg-bin dev", + "start": "egg-scripts start" + }, + "dependencies": {}, + "devDependencies": { + "axios": "^0.17.1", + "egg": "^2.3.0", + "egg-scripts": "^2.10.0", + "egg-cors": "^2.1.1", + "egg-webpack": "^4.4.7", + "egg-webpack-vue": "^2.0.0", + "egg-view-vue-ssr": "^3.0.5", + "vue": "^2.5.0", + "vue-router": "^3.0.1", + "vuex": "^3.0.1", + "vuex-router-sync": "^5.0.0" + } +} diff --git a/test/fixtures/example/public/css/0.03cd5845.css b/test/fixtures/example/public/css/0.03cd5845.css new file mode 100644 index 0000000..e3c0074 --- /dev/null +++ b/test/fixtures/example/public/css/0.03cd5845.css @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/test/fixtures/example/public/js/chunk/home/home.cd531b5c.js b/test/fixtures/example/public/js/chunk/home/home.cd531b5c.js new file mode 100644 index 0000000..1c818ad --- /dev/null +++ b/test/fixtures/example/public/js/chunk/home/home.cd531b5c.js @@ -0,0 +1,11636 @@ +(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{ + +/***/ "UQ5/": +/***/ (function(module, exports) { + +var g; + +// This works in non-strict mode +g = (function() { + return this; +})(); + +try { + // This works if eval is allowed (see CSP) + g = g || Function("return this")() || (1, eval)("this"); +} catch (e) { + // This works if the window reference is available + if (typeof window === "object") g = window; +} + +// g can still be undefined, but nothing to do about it... +// We return undefined, instead of nothing here, so it's +// easier to handle this case. if(!global) { ...} + +module.exports = g; + + +/***/ }), + +/***/ "Wzic": +/***/ (function(module, exports, __webpack_require__) { + +// extracted by mini-css-extract-plugin + +/***/ }), + +/***/ "iX8O": +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); + +// EXTERNAL MODULE: ../ves/node_modules/_vue@2.5.17@vue/dist/vue.esm.js +var vue_esm = __webpack_require__("tgp/"); + +// CONCATENATED MODULE: ../ves/test/fixtures/example/node_modules/babel-loader/lib?{"cacheDirectory":"/var/folders/hj/fs_pkpfn7vdcg176058mb1540000gn/T/easywebpack/framework-example/test/cache/client/babel-loader","env":{"node":{"presets":[["env",{"modules":false,"targets":{"node":"current"}}]],"plugins":["transform-object-rest-spread","syntax-dynamic-import"]},"web":{"presets":[["env",{"modules":false,"targets":{"browsers":["last 2 versions","safari >= 7"]}}]],"plugins":["transform-object-rest-spread","syntax-dynamic-import","transform-object-assign","transform-runtime"]}},"comments":false}!../ves/test/fixtures/example/node_modules/vue-loader/lib/selector.js?type=script&index=0!../ves/test/fixtures/example/app/web/page/home/home.vue + + +/* harmony default export */ var home = ({}); +// CONCATENATED MODULE: ../ves/test/fixtures/example/node_modules/vue-loader/lib/template-compiler?{"id":"data-v-42669c5e","hasScoped":false,"optionsId":"1","buble":{"transforms":{}}}!../ves/test/fixtures/example/node_modules/vue-loader/lib/selector.js?type=template&index=0!../ves/test/fixtures/example/app/web/page/home/home.vue +var render = function() { + var _vm = this + var _h = _vm.$createElement + var _c = _vm._self._c || _h + return _c("div", [ + _c("div", [_vm._v("ves -- The Vue Isomorphic Node Framework")]), + _vm._v(" "), + _c("h1", [_vm._v(_vm._s(_vm.message))]) + ]) +} +var staticRenderFns = [] +render._withStripped = true + +if (false) {} +// CONCATENATED MODULE: ../ves/test/fixtures/example/node_modules/vue-loader/lib/runtime/component-normalizer.js +/* globals __VUE_SSR_CONTEXT__ */ + +// IMPORTANT: Do NOT use ES2015 features in this file (except for modules). +// This module is a runtime utility for cleaner component module output and will +// be included in the final webpack user bundle. + +function normalizeComponent ( + scriptExports, + render, + staticRenderFns, + functionalTemplate, + injectStyles, + scopeId, + moduleIdentifier, /* server only */ + shadowMode /* vue-cli only */ +) { + scriptExports = scriptExports || {} + + // ES6 modules interop + var type = typeof scriptExports.default + if (type === 'object' || type === 'function') { + scriptExports = scriptExports.default + } + + // Vue.extend constructor export interop + var options = typeof scriptExports === 'function' + ? scriptExports.options + : scriptExports + + // render functions + if (render) { + options.render = render + options.staticRenderFns = staticRenderFns + options._compiled = true + } + + // functional template + if (functionalTemplate) { + options.functional = true + } + + // scopedId + if (scopeId) { + options._scopeId = scopeId + } + + var hook + if (moduleIdentifier) { // server build + hook = function (context) { + // 2.3 injection + context = + context || // cached call + (this.$vnode && this.$vnode.ssrContext) || // stateful + (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional + // 2.2 with runInNewContext: true + if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') { + context = __VUE_SSR_CONTEXT__ + } + // inject component styles + if (injectStyles) { + injectStyles.call(this, context) + } + // register component module identifier for async chunk inferrence + if (context && context._registeredComponents) { + context._registeredComponents.add(moduleIdentifier) + } + } + // used by ssr in case component is cached and beforeCreate + // never gets called + options._ssrRegister = hook + } else if (injectStyles) { + hook = shadowMode + ? function () { injectStyles.call(this, this.$root.$options.shadowRoot) } + : injectStyles + } + + if (hook) { + if (options.functional) { + // for template-only hot-reload because in that case the render fn doesn't + // go through the normalizer + options._injectStyles = hook + // register for functioal component in vue file + var originalRender = options.render + options.render = function renderWithStyleInjection (h, context) { + hook.call(context) + return originalRender(h, context) + } + } else { + // inject component registration as beforeCreate hook + var existing = options.beforeCreate + options.beforeCreate = existing + ? [].concat(existing, hook) + : [hook] + } + } + + return { + exports: scriptExports, + options: options + } +} + +// CONCATENATED MODULE: ../ves/test/fixtures/example/app/web/page/home/home.vue +var disposed = false +function injectStyle (context) { + if (disposed) return + __webpack_require__("Wzic") +} +/* script */ + + +/* template */ + +/* template functional */ +var __vue_template_functional__ = false +/* styles */ +var __vue_styles__ = injectStyle +/* scopeId */ +var __vue_scopeId__ = null +/* moduleIdentifier (server only) */ +var __vue_module_identifier__ = null + +var Component = normalizeComponent( + home, + render, + staticRenderFns, + __vue_template_functional__, + __vue_styles__, + __vue_scopeId__, + __vue_module_identifier__ +) +Component.options.__file = "ves/test/fixtures/example/app/web/page/home/home.vue" + +/* hot reload */ +if (false) {} + +/* harmony default export */ var home_home = (Component.exports); + +// CONCATENATED MODULE: ../ves/test/fixtures/example/node_modules/babel-loader/lib?{"cacheDirectory":"/var/folders/hj/fs_pkpfn7vdcg176058mb1540000gn/T/easywebpack/framework-example/test/cache/client/babel-loader","env":{"node":{"presets":[["env",{"modules":false,"targets":{"node":"current"}}]],"plugins":["transform-object-rest-spread","syntax-dynamic-import"]},"web":{"presets":[["env",{"modules":false,"targets":{"browsers":["last 2 versions","safari >= 7"]}}]],"plugins":["transform-object-rest-spread","syntax-dynamic-import","transform-object-assign","transform-runtime"]}},"comments":false,"babelrc":"/Users/caoli/dev/github/ves/ves/test/fixtures/example/node_modules/easywebpack-vue/config/.babelrc"}!../ves/test/fixtures/example/node_modules/vue-entry-loader?templateFile=!../ves/test/fixtures/example/app/web/page/home/home.vue +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + + + + +const data = window.__INITIAL_STATE__ || {}; +const context = { state: data }; +const hook = home_home.hook || vue_esm["a" /* default */].hook || {}; +const home_render = hook.render || home_home.hookRender || vue_esm["a" /* default */].hookRender; +if (home_render) { + home_render(context, home_home); +} +const store = typeof home_home.store === 'function' ? home_home.store(data) : home_home.store; +const router = typeof home_home.router === 'function' ? home_home.router() : home_home.router; +const options = store && router ? _extends({}, home_home, { + store, + router +}) : _extends({}, home_home, { data }); +const app = new vue_esm["a" /* default */](options); +app.$mount('#app'); + +/***/ }), + +/***/ "oPdv": +/***/ (function(module, exports, __webpack_require__) { + +/* WEBPACK VAR INJECTION */(function(global, process) {(function (global, undefined) { + "use strict"; + + if (global.setImmediate) { + return; + } + + var nextHandle = 1; // Spec says greater than zero + var tasksByHandle = {}; + var currentlyRunningATask = false; + var doc = global.document; + var registerImmediate; + + function setImmediate(callback) { + // Callback can either be a function or a string + if (typeof callback !== "function") { + callback = new Function("" + callback); + } + // Copy function arguments + var args = new Array(arguments.length - 1); + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i + 1]; + } + // Store and register the task + var task = { callback: callback, args: args }; + tasksByHandle[nextHandle] = task; + registerImmediate(nextHandle); + return nextHandle++; + } + + function clearImmediate(handle) { + delete tasksByHandle[handle]; + } + + function run(task) { + var callback = task.callback; + var args = task.args; + switch (args.length) { + case 0: + callback(); + break; + case 1: + callback(args[0]); + break; + case 2: + callback(args[0], args[1]); + break; + case 3: + callback(args[0], args[1], args[2]); + break; + default: + callback.apply(undefined, args); + break; + } + } + + function runIfPresent(handle) { + // From the spec: "Wait until any invocations of this algorithm started before this one have completed." + // So if we're currently running a task, we'll need to delay this invocation. + if (currentlyRunningATask) { + // Delay by doing a setTimeout. setImmediate was tried instead, but in Firefox 7 it generated a + // "too much recursion" error. + setTimeout(runIfPresent, 0, handle); + } else { + var task = tasksByHandle[handle]; + if (task) { + currentlyRunningATask = true; + try { + run(task); + } finally { + clearImmediate(handle); + currentlyRunningATask = false; + } + } + } + } + + function installNextTickImplementation() { + registerImmediate = function(handle) { + process.nextTick(function () { runIfPresent(handle); }); + }; + } + + function canUsePostMessage() { + // The test against `importScripts` prevents this implementation from being installed inside a web worker, + // where `global.postMessage` means something completely different and can't be used for this purpose. + if (global.postMessage && !global.importScripts) { + var postMessageIsAsynchronous = true; + var oldOnMessage = global.onmessage; + global.onmessage = function() { + postMessageIsAsynchronous = false; + }; + global.postMessage("", "*"); + global.onmessage = oldOnMessage; + return postMessageIsAsynchronous; + } + } + + function installPostMessageImplementation() { + // Installs an event handler on `global` for the `message` event: see + // * https://developer.mozilla.org/en/DOM/window.postMessage + // * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages + + var messagePrefix = "setImmediate$" + Math.random() + "$"; + var onGlobalMessage = function(event) { + if (event.source === global && + typeof event.data === "string" && + event.data.indexOf(messagePrefix) === 0) { + runIfPresent(+event.data.slice(messagePrefix.length)); + } + }; + + if (global.addEventListener) { + global.addEventListener("message", onGlobalMessage, false); + } else { + global.attachEvent("onmessage", onGlobalMessage); + } + + registerImmediate = function(handle) { + global.postMessage(messagePrefix + handle, "*"); + }; + } + + function installMessageChannelImplementation() { + var channel = new MessageChannel(); + channel.port1.onmessage = function(event) { + var handle = event.data; + runIfPresent(handle); + }; + + registerImmediate = function(handle) { + channel.port2.postMessage(handle); + }; + } + + function installReadyStateChangeImplementation() { + var html = doc.documentElement; + registerImmediate = function(handle) { + // Create a