From 4fc5e384458925d3c1ac38ee1defedf2e3132bda Mon Sep 17 00:00:00 2001 From: sky Date: Sun, 13 Oct 2019 21:01:42 +0800 Subject: [PATCH] feat: implement node react web framework --- .autod.conf.js | 27 + .eslintrc | 3 + .gitignore | 11 + .travis.yml | 11 + CHANGELOG.md | 2 + README.md | 69 + app.js | 5 + app/extend/application.js | 5 + app/extend/context.js | 5 + app/middleware/locals.js | 7 + app/service/test.js | 16 + app/web/tsconfig.json | 16 + app/web/typings/vue.d.ts | 4 + app/web/typings/webpack.d.ts | 8 + app/web/typings/window.d.ts | 0 app/web/view/layout.html | 13 + appveyor.yml | 15 + bin/cli.js | 14 + config/config.default.js | 38 + config/config.local.js | 31 + config/plugin.js | 10 + config/plugin.local.js | 15 + index.d.ts | 3 + index.js | 3 + lib/framework.js | 22 + lib/utils.js | 16 + package.json | 73 + test/fixtures/example/app/controller/home.js | 18 + test/fixtures/example/app/router.js | 8 + test/fixtures/example/app/view/home/home.js | 11563 +++++++++++++++ .../example/app/web/componet/layout.tsx | 19 + .../example/app/web/framework/app.tsx | 22 + .../web/page/react-client-render/index.tsx | 15 + .../react-nunjucks-render/component/index.tsx | 46 + .../web/page/react-nunjucks-render/index.tsx | 22 + .../react-nunjucks-render/store/index.tsx | 23 + .../web/page/react-server-render/index.tsx | 17 + .../fixtures/example/app/web/view/layout.html | 13 + .../fixtures/example/config/config.default.js | 6 + .../example/config/config.unittest.js | 3 + test/fixtures/example/config/manifest.json | 21 + test/fixtures/example/config/plugin.js | 5 + test/fixtures/example/package.json | 22 + .../example/public/css/0.03cd5845.css | 9 + .../public/js/chunk/home/home.cd531b5c.js | 11636 ++++++++++++++++ .../example/public/js/runtime.49eef78a.js | 153 + test/framework.test.js | 43 + 47 files changed, 24106 insertions(+) create mode 100644 .autod.conf.js create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 app.js create mode 100644 app/extend/application.js create mode 100644 app/extend/context.js create mode 100644 app/middleware/locals.js create mode 100644 app/service/test.js create mode 100644 app/web/tsconfig.json create mode 100644 app/web/typings/vue.d.ts create mode 100644 app/web/typings/webpack.d.ts create mode 100644 app/web/typings/window.d.ts create mode 100644 app/web/view/layout.html create mode 100644 appveyor.yml create mode 100755 bin/cli.js create mode 100644 config/config.default.js create mode 100644 config/config.local.js create mode 100644 config/plugin.js create mode 100644 config/plugin.local.js create mode 100644 index.d.ts create mode 100644 index.js create mode 100644 lib/framework.js create mode 100644 lib/utils.js create mode 100644 package.json create mode 100644 test/fixtures/example/app/controller/home.js create mode 100644 test/fixtures/example/app/router.js create mode 100644 test/fixtures/example/app/view/home/home.js create mode 100644 test/fixtures/example/app/web/componet/layout.tsx create mode 100644 test/fixtures/example/app/web/framework/app.tsx create mode 100644 test/fixtures/example/app/web/page/react-client-render/index.tsx create mode 100644 test/fixtures/example/app/web/page/react-nunjucks-render/component/index.tsx create mode 100644 test/fixtures/example/app/web/page/react-nunjucks-render/index.tsx create mode 100644 test/fixtures/example/app/web/page/react-nunjucks-render/store/index.tsx create mode 100644 test/fixtures/example/app/web/page/react-server-render/index.tsx create mode 100644 test/fixtures/example/app/web/view/layout.html create mode 100644 test/fixtures/example/config/config.default.js create mode 100644 test/fixtures/example/config/config.unittest.js create mode 100644 test/fixtures/example/config/manifest.json create mode 100644 test/fixtures/example/config/plugin.js create mode 100644 test/fixtures/example/package.json create mode 100644 test/fixtures/example/public/css/0.03cd5845.css create mode 100644 test/fixtures/example/public/js/chunk/home/home.cd531b5c.js create mode 100644 test/fixtures/example/public/js/runtime.49eef78a.js create mode 100644 test/framework.test.js 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