From bde78114e50955abc983788ffc60ae433f6e90cf Mon Sep 17 00:00:00 2001 From: koubo Date: Thu, 26 Apr 2018 16:07:37 +0800 Subject: [PATCH 01/10] update devtool for debug main.js without source-map --- webpack/webpack.dev.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack/webpack.dev.js b/webpack/webpack.dev.js index 925f43a..5177df6 100644 --- a/webpack/webpack.dev.js +++ b/webpack/webpack.dev.js @@ -63,7 +63,7 @@ const config = { new ManifestPlugin(), new WebpackGenStatsPlugin(), ], - devtool: '#eval-source-map' + devtool: 'source-map' }; module.exports = config; \ No newline at end of file From 3174b684cf3bb70024acf2d8486760acd9116e94 Mon Sep 17 00:00:00 2001 From: koubo Date: Thu, 26 Apr 2018 17:47:13 +0800 Subject: [PATCH 02/10] update eslint package.json script --- .eslintignore | 1 + package.json | 1 + 2 files changed, 2 insertions(+) create mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..3e22129 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +/dist \ No newline at end of file diff --git a/package.json b/package.json index 218e190..61c8c73 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dev": "babel-node src/server/server.js", "prod": "node dist/server/server.js", "build": "npm run clean && npm run bundle && babel src/ -d dist/", + "build:dev": "npm run clean && webpack --config webpack/webpack.dev.js", "wds": "webpack-dev-server --config webpack/webpack.config.js --port 8388 --inline --history-api-fallback", "bundle": "cross-env NODE_ENV=production webpack --config webpack/webpack.prod.js", "clean": "rm -rf dist/", From 828c4218d3989c6f155c32061b19def535b07456 Mon Sep 17 00:00:00 2001 From: koubo Date: Fri, 27 Apr 2018 16:15:19 +0800 Subject: [PATCH 03/10] preload wrappedComponent at server side --- package.json | 1 - src/server/utils/render.js | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 61c8c73..de66048 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ }, "husky": { "hooks": { - "pre-commit": "npm run lint", "pre-push": "npm run lint" } } diff --git a/src/server/utils/render.js b/src/server/utils/render.js index 27294e2..ea23395 100644 --- a/src/server/utils/render.js +++ b/src/server/utils/render.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import React from 'react'; import { renderToString } from 'react-dom/server'; import { Provider } from 'react-redux'; @@ -29,8 +30,15 @@ function render(clientStats) { const context = {}; const location = req.url; const routeBranch = matchRoutes(routers, location); // 找到指定的 route 链路 + const universalComponentPreload = routeBranch.map(({ route }) => { + return route.component.preload(); + }) + Promise.all(universalComponentPreload).then(res => { + console.log(res); + }) render2String({ store, location, context }); // 需要先渲染一次,否则 match 的是 UniversalComponent,找不到正确组件的 getInitialData const promiseList = routeBranch.map(({ route }) => { + console.log(route); const { component } = route; const noop = Promise.resolve(); const { WrappedComponent } = component; From 4783240aabedf373c6c7b968c07318bbbe4a620d Mon Sep 17 00:00:00 2001 From: Mrkou47 Date: Fri, 8 Jun 2018 14:53:02 +0800 Subject: [PATCH 04/10] add webpack-dashboard --- package.json | 5 +- src/server/server.js | 3 +- src/server/utils/render.js | 12 +- yarn.lock | 372 ++++++++++++++++++++++++++++++++++++- 4 files changed, 377 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index de66048..c57f20d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "react server side render - step by step", "main": "/src/server/server.js", "scripts": { - "dev": "babel-node src/server/server.js", + "start": "npm run dev", + "dev": "webpack-dashboard -- babel-node src/server/server.js", "prod": "node dist/server/server.js", "build": "npm run clean && npm run bundle && babel src/ -d dist/", "build:dev": "npm run clean && webpack --config webpack/webpack.dev.js", @@ -52,6 +53,7 @@ "serve-favicon": "^2.4.5", "style-loader": "^0.20.1", "webpack": "^3.10.0", + "webpack-dashboard": "^2.0.0", "webpack-flush-chunks": "^1.2.3", "webpack-manifest-plugin": "^1.3.2", "write-file-webpack-plugin": "^4.2.0", @@ -69,6 +71,7 @@ "eslint-plugin-react": "^7.6.1", "html-webpack-plugin": "^2.30.1", "husky": "^0.15.0-rc.8", + "lifecycle-webpack-plugin": "^1.0.13", "webpack-dev-middleware": "^2.0.4", "webpack-dev-server": "^2.11.1", "webpack-hot-middleware": "^2.21.0" diff --git a/src/server/server.js b/src/server/server.js index e1112a9..3a22225 100644 --- a/src/server/server.js +++ b/src/server/server.js @@ -18,6 +18,7 @@ import webpackHotMiddleware from 'webpack-hot-middleware'; import webpackDevConfig from '../../webpack/webpack.dev'; import config from './config'; +const DashboardPlugin = require('webpack-dashboard/plugin'); const app = express(); const { PORT, DEV } = config; @@ -35,7 +36,7 @@ if (DEV) { let clientStats = null; // https://doc.webpack-china.org/api/stats/#src/components/Sidebar/Sidebar.jsx const compile = webpack(webpackDevConfig); - + compiler.apply(new DashboardPlugin()); // use webpack in dev enviroment app.use(webpackDevMiddleware(compile, { publicPath: webpackDevConfig.output.publicPath, diff --git a/src/server/utils/render.js b/src/server/utils/render.js index ea23395..19d36e9 100644 --- a/src/server/utils/render.js +++ b/src/server/utils/render.js @@ -30,12 +30,12 @@ function render(clientStats) { const context = {}; const location = req.url; const routeBranch = matchRoutes(routers, location); // 找到指定的 route 链路 - const universalComponentPreload = routeBranch.map(({ route }) => { - return route.component.preload(); - }) - Promise.all(universalComponentPreload).then(res => { - console.log(res); - }) + // const universalComponentPreload = routeBranch.map(({ route }) => { + // return route.component.preload(); + // }) + // Promise.all(universalComponentPreload).then(res => { + // console.log(res); + // }) render2String({ store, location, context }); // 需要先渲染一次,否则 match 的是 UniversalComponent,找不到正确组件的 getInitialData const promiseList = routeBranch.map(({ route }) => { console.log(route); diff --git a/yarn.lock b/yarn.lock index a0c503a..4b68528 100644 --- a/yarn.lock +++ b/yarn.lock @@ -69,6 +69,16 @@ lodash "^4.2.0" to-fast-properties "^2.0.0" +"@most/multicast@^1.2.5": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@most/multicast/-/multicast-1.3.0.tgz#e01574840df634478ac3fabd164c6e830fb3b966" + dependencies: + "@most/prelude" "^1.4.0" + +"@most/prelude@^1.4.0": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@most/prelude/-/prelude-1.7.1.tgz#7c70ab6a68bb20481db51ebfbba23fceb502d239" + "@types/history@*": version "4.6.2" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.6.2.tgz#12cfaba693ba20f114ed5765467ff25fdf67ddb0" @@ -127,6 +137,10 @@ acorn@^5.0.0, acorn@^5.4.0: version "5.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102" +after@0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" + ajv-keywords@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" @@ -178,6 +192,10 @@ alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + ansi-escapes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" @@ -204,6 +222,12 @@ ansi-styles@^3.1.0: dependencies: color-convert "^1.9.0" +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + dependencies: + color-convert "^1.9.0" + anymatch@^1.3.0: version "1.3.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" @@ -290,6 +314,10 @@ array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" +arraybuffer.slice@~0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" + arrify@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -332,7 +360,11 @@ async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" -async@^1.5.2: +async-limiter@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" + +async@^1.4.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -1148,6 +1180,10 @@ babylon@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" +backo2@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" + balanced-match@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" @@ -1156,10 +1192,18 @@ balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" +base64-arraybuffer@0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" + base64-js@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" +base64id@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -1182,6 +1226,12 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +better-assert@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" + dependencies: + callsite "1.0.0" + big.js@^3.1.3: version "3.2.0" resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" @@ -1190,6 +1240,14 @@ binary-extensions@^1.0.0: version "1.11.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" +blessed@^0.1.81: + version "0.1.81" + resolved "https://registry.yarnpkg.com/blessed/-/blessed-0.1.81.tgz#f962d687ec2c369570ae71af843256e6d0ca1129" + +blob@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" + block-stream@*: version "0.0.9" resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" @@ -1386,6 +1444,10 @@ caller-path@^0.1.0: dependencies: callsites "^0.2.0" +callsite@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" + callsites@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" @@ -1462,6 +1524,14 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0: escape-string-regexp "^1.0.5" supports-color "^4.0.0" +chalk@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" @@ -1561,6 +1631,14 @@ cliui@^3.2.0: strip-ansi "^3.0.1" wrap-ansi "^2.0.0" +cliui@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" + dependencies: + string-width "^2.1.1" + strip-ansi "^4.0.0" + wrap-ansi "^2.0.0" + clone@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f" @@ -1632,14 +1710,26 @@ commander@2.14.x, commander@^2.11.0, commander@~2.14.1: version "2.14.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" +commander@^2.15.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" -component-emitter@^1.2.1: +component-bind@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" + +component-emitter@1.2.1, component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" +component-inherit@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" + compressible@~2.0.11: version "2.0.12" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.12.tgz#c59a5c99db76767e9876500e271ef63b3493bd66" @@ -1775,6 +1865,16 @@ cross-spawn@^5.0.1, cross-spawn@^5.1.0: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -1945,7 +2045,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8: dependencies: ms "2.0.0" -debug@^3.0.1, debug@^3.1.0: +debug@^3.0.1, debug@^3.1.0, debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: @@ -2183,6 +2283,43 @@ encoding@^0.1.11: dependencies: iconv-lite "~0.4.13" +engine.io-client@~3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.2.1.tgz#6f54c0475de487158a1a7c77d10178708b6add36" + dependencies: + component-emitter "1.2.1" + component-inherit "0.0.3" + debug "~3.1.0" + engine.io-parser "~2.1.1" + has-cors "1.1.0" + indexof "0.0.1" + parseqs "0.0.5" + parseuri "0.0.5" + ws "~3.3.1" + xmlhttprequest-ssl "~1.5.4" + yeast "0.1.2" + +engine.io-parser@~2.1.0, engine.io-parser@~2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.1.2.tgz#4c0f4cff79aaeecbbdcfdea66a823c6085409196" + dependencies: + after "0.8.2" + arraybuffer.slice "~0.0.7" + base64-arraybuffer "0.1.5" + blob "0.0.4" + has-binary2 "~1.0.2" + +engine.io@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.2.0.tgz#54332506f42f2edc71690d2f2a42349359f3bf7d" + dependencies: + accepts "~1.3.4" + base64id "1.0.0" + cookie "0.3.1" + debug "~3.1.0" + engine.io-parser "~2.1.0" + ws "~3.3.1" + enhanced-resolve@^3.4.0: version "3.4.1" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" @@ -2632,6 +2769,10 @@ filesize@^3.2.1: version "3.6.0" resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.0.tgz#22d079615624bb6fd3c04026120628a41b3f4efa" +filesize@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" + fill-range@^2.1.0: version "2.2.3" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" @@ -2733,6 +2874,10 @@ forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" +fp-ts@^1.0.0, fp-ts@^1.0.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.6.1.tgz#83526f1d173387e5f686c57652f5b5fc580e6d1e" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -2913,6 +3058,16 @@ handle-thing@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" +handlebars@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" + dependencies: + async "^1.4.0" + optimist "^0.6.1" + source-map "^0.4.4" + optionalDependencies: + uglify-js "^2.6" + har-schema@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" @@ -2930,6 +3085,16 @@ has-ansi@^2.0.0: dependencies: ansi-regex "^2.0.0" +has-binary2@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" + dependencies: + isarray "2.0.1" + +has-cors@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" + has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" @@ -2938,6 +3103,10 @@ has-flag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -3244,6 +3413,16 @@ inquirer@^3.0.6: strip-ansi "^4.0.0" through "^2.3.6" +inspectpack@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/inspectpack/-/inspectpack-3.0.1.tgz#676134e139aef14de5c0d1d8ab482ba629ecef28" + dependencies: + chalk "^2.4.0" + io-ts "^1.0.5" + io-ts-reporters "^0.0.20" + pify "^3.0.0" + yargs "^11.0.0" + internal-ip@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c" @@ -3264,6 +3443,19 @@ invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" +io-ts-reporters@^0.0.20: + version "0.0.20" + resolved "https://registry.yarnpkg.com/io-ts-reporters/-/io-ts-reporters-0.0.20.tgz#2b8cbb6a2bc4562dae6917a3a413fa2c9851a644" + dependencies: + fp-ts "^1.0.1" + io-ts "^1.0.2" + +io-ts@^1.0.2, io-ts@^1.0.5: + version "1.1.3" + resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-1.1.3.tgz#577c7f11527e0850a95a25f2f240ee06fdda921a" + dependencies: + fp-ts "^1.0.0" + ip@^1.1.0, ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -3524,6 +3716,10 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" +isarray@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -3699,6 +3895,10 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +lifecycle-webpack-plugin@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/lifecycle-webpack-plugin/-/lifecycle-webpack-plugin-1.0.13.tgz#0234b5240b881a7bf87404f554efcd9998e29f1e" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -4044,6 +4244,10 @@ minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + mixin-deep@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" @@ -4061,6 +4265,14 @@ moment@^2.11.2: version "2.20.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.20.1.tgz#d6eb1a46cbcc14a2b2f9434112c1ff8907f313fd" +most@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/most/-/most-1.7.3.tgz#406c31a66d73aa16957816fdf96965e27df84f1a" + dependencies: + "@most/multicast" "^1.2.5" + "@most/prelude" "^1.4.0" + symbol-observable "^1.0.2" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -4114,6 +4326,10 @@ negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" +nice-try@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4" + no-case@^2.2.0: version "2.3.2" resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" @@ -4247,6 +4463,10 @@ object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" +object-component@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" + object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" @@ -4310,6 +4530,13 @@ opn@^5.1.0: dependencies: is-wsl "^1.1.0" +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + optionator@^0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" @@ -4441,6 +4668,18 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" +parseqs@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" + dependencies: + better-assert "~1.0.0" + +parseuri@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" + dependencies: + better-assert "~1.0.0" + parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" @@ -4475,7 +4714,7 @@ path-is-inside@^1.0.1, path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" -path-key@^2.0.0: +path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -5431,7 +5670,7 @@ selfsigned@^1.9.1: dependencies: node-forge "0.7.1" -"semver@2 || 3 || 4 || 5", semver@^5.3.0: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" @@ -5604,6 +5843,48 @@ sntp@1.x.x: dependencies: hoek "2.x.x" +socket.io-adapter@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b" + +socket.io-client@2.1.1, socket.io-client@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.1.1.tgz#dcb38103436ab4578ddb026638ae2f21b623671f" + dependencies: + backo2 "1.0.2" + base64-arraybuffer "0.1.5" + component-bind "1.0.0" + component-emitter "1.2.1" + debug "~3.1.0" + engine.io-client "~3.2.0" + has-binary2 "~1.0.2" + has-cors "1.1.0" + indexof "0.0.1" + object-component "0.0.3" + parseqs "0.0.5" + parseuri "0.0.5" + socket.io-parser "~3.2.0" + to-array "0.1.4" + +socket.io-parser@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.2.0.tgz#e7c6228b6aa1f814e6148aea325b51aa9499e077" + dependencies: + component-emitter "1.2.1" + debug "~3.1.0" + isarray "2.0.1" + +socket.io@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.1.1.tgz#a069c5feabee3e6b214a75b40ce0652e1cfb9980" + dependencies: + debug "~3.1.0" + engine.io "~3.2.0" + has-binary2 "~1.0.2" + socket.io-adapter "~1.1.0" + socket.io-client "2.1.1" + socket.io-parser "~3.2.0" + sockjs-client@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12" @@ -5656,6 +5937,12 @@ source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, sourc version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" +source-map@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + dependencies: + amdefine ">=0.0.4" + source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -5862,6 +6149,12 @@ supports-color@^5.1.0: dependencies: has-flag "^2.0.0" +supports-color@^5.3.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" + dependencies: + has-flag "^3.0.0" + svgo@^0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" @@ -5874,7 +6167,7 @@ svgo@^0.7.0: sax "~1.2.1" whet.extend "~0.9.9" -symbol-observable@^1.0.3: +symbol-observable@^1.0.2, symbol-observable@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -5946,6 +6239,10 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +to-array@0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" + to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" @@ -6043,7 +6340,7 @@ uglify-js@3.3.x: commander "~2.14.1" source-map "~0.6.1" -uglify-js@^2.8.29: +uglify-js@^2.6, uglify-js@^2.8.29: version "2.8.29" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" dependencies: @@ -6068,6 +6365,10 @@ uid-number@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" +ultron@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" + underscore.string@2.3.x: version "2.3.3" resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-2.3.3.tgz#71c08bf6b428b1133f37e78fa3a21c82f7329b0d" @@ -6247,6 +6548,20 @@ wbuf@^1.1.0, wbuf@^1.7.2: dependencies: minimalistic-assert "^1.0.0" +webpack-dashboard@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/webpack-dashboard/-/webpack-dashboard-2.0.0.tgz#70db22e7bb5b12b4a7fde82de87ce01eed8d921c" + dependencies: + blessed "^0.1.81" + commander "^2.15.1" + cross-spawn "^6.0.5" + filesize "^3.6.1" + handlebars "^4.0.11" + inspectpack "^3.0.1" + most "^1.7.3" + socket.io "^2.1.1" + socket.io-client "^2.1.1" + webpack-dev-middleware@1.12.2: version "1.12.2" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz#f8fc1120ce3b4fc5680ceecb43d777966b21105e" @@ -6411,6 +6726,10 @@ wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + wordwrap@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" @@ -6443,6 +6762,14 @@ write@^0.2.1: dependencies: mkdirp "^0.5.1" +ws@~3.3.1: + version "3.3.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" + dependencies: + async-limiter "~1.0.0" + safe-buffer "~5.1.0" + ultron "~1.1.0" + xhr-request@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/xhr-request/-/xhr-request-1.1.0.tgz#f4a7c1868b9f198723444d82dcae317643f2e2ed" @@ -6468,6 +6795,10 @@ xml-char-classes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/xml-char-classes/-/xml-char-classes-1.0.0.tgz#64657848a20ffc5df583a42ad8a277b4512bbc4d" +xmlhttprequest-ssl@~1.5.4: + version "1.5.5" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" + xtend@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -6492,6 +6823,12 @@ yargs-parser@^7.0.0: dependencies: camelcase "^4.1.0" +yargs-parser@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" + dependencies: + camelcase "^4.1.0" + yargs@6.6.0: version "6.6.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" @@ -6510,6 +6847,23 @@ yargs@6.6.0: y18n "^3.2.1" yargs-parser "^4.2.0" +yargs@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.0.0.tgz#c052931006c5eee74610e5fc0354bedfd08a201b" + dependencies: + cliui "^4.0.0" + decamelize "^1.1.1" + find-up "^2.1.0" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^9.0.2" + yargs@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" @@ -6536,3 +6890,7 @@ yargs@~3.10.0: cliui "^2.1.0" decamelize "^1.0.0" window-size "0.1.0" + +yeast@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" From 958030eafe001a714f033450e39f3840e0a056e0 Mon Sep 17 00:00:00 2001 From: Mrkou47 Date: Thu, 14 Jun 2018 20:29:48 +0800 Subject: [PATCH 05/10] add some package for test --- package.json | 4 + src/server/server.js | 2 +- yarn.lock | 179 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 171 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index c57f20d..a3fb086 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "/src/server/server.js", "scripts": { "start": "npm run dev", + "install:puppeteer": "export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true && yarn add puppeteer --dev", "dev": "webpack-dashboard -- babel-node src/server/server.js", "prod": "node dist/server/server.js", "build": "npm run clean && npm run bundle && babel src/ -d dist/", @@ -67,11 +68,14 @@ "babel-plugin-transform-class-properties": "^6.24.1", "babel-preset-stage-0": "^6.24.1", "babel-register": "^6.26.0", + "chai": "^4.1.2", "eslint": "^4.17.0", "eslint-plugin-react": "^7.6.1", "html-webpack-plugin": "^2.30.1", "husky": "^0.15.0-rc.8", "lifecycle-webpack-plugin": "^1.0.13", + "mocha": "^5.2.0", + "puppeteer": "^1.5.0", "webpack-dev-middleware": "^2.0.4", "webpack-dev-server": "^2.11.1", "webpack-hot-middleware": "^2.21.0" diff --git a/src/server/server.js b/src/server/server.js index 3a22225..52c28fd 100644 --- a/src/server/server.js +++ b/src/server/server.js @@ -36,7 +36,7 @@ if (DEV) { let clientStats = null; // https://doc.webpack-china.org/api/stats/#src/components/Sidebar/Sidebar.jsx const compile = webpack(webpackDevConfig); - compiler.apply(new DashboardPlugin()); + compile.apply(new DashboardPlugin()); // use webpack in dev enviroment app.use(webpackDevMiddleware(compile, { publicPath: webpackDevConfig.output.publicPath, diff --git a/yarn.lock b/yarn.lock index 4b68528..425f10d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -141,6 +141,12 @@ after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" +agent-base@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" + dependencies: + es6-promisify "^5.0.0" + ajv-keywords@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" @@ -352,6 +358,10 @@ assert@^1.1.1: dependencies: util "0.10.3" +assertion-error@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -1333,6 +1343,10 @@ brorand@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.1.1" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.1.1.tgz#38b7ab55edb806ff2dcda1a7f1620773a477c49f" @@ -1392,6 +1406,10 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" +buffer-from@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04" + buffer-indexof@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" @@ -1506,6 +1524,17 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" +chai@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c" + dependencies: + assertion-error "^1.0.1" + check-error "^1.0.1" + deep-eql "^3.0.0" + get-func-name "^2.0.0" + pathval "^1.0.0" + type-detect "^4.0.0" + chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -1536,6 +1565,10 @@ chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" +check-error@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + chokidar@^1.6.1, chokidar@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" @@ -1710,7 +1743,7 @@ commander@2.14.x, commander@^2.11.0, commander@~2.14.1: version "2.14.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" -commander@^2.15.1: +commander@2.15.1, commander@^2.15.1: version "2.15.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" @@ -1752,6 +1785,15 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" +concat-stream@1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + concat-stream@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" @@ -2045,7 +2087,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8: dependencies: ms "2.0.0" -debug@^3.0.1, debug@^3.1.0, debug@~3.1.0: +debug@3.1.0, debug@^3.0.1, debug@^3.1.0, debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: @@ -2065,6 +2107,12 @@ decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" +deep-eql@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + dependencies: + type-detect "^4.0.0" + deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -2164,6 +2212,10 @@ detect-node@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127" +diff@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + diffie-hellman@^5.0.0: version "5.0.2" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e" @@ -2389,6 +2441,16 @@ es6-map@^0.1.3: es6-symbol "~3.1.1" event-emitter "~0.3.5" +es6-promise@^4.0.3: + version "4.2.4" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29" + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + dependencies: + es6-promise "^4.0.3" + es6-set@~0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" @@ -2419,7 +2481,7 @@ escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -2700,6 +2762,15 @@ extract-css-chunks-webpack-plugin@^2.0.18: style-loader "^0.18.2" webpack-sources "^1.0.1" +extract-zip@^1.6.6: + version "1.6.7" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" + dependencies: + concat-stream "1.6.2" + debug "2.6.9" + mkdirp "0.5.1" + yauzl "2.4.1" + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -2748,6 +2819,12 @@ fbjs@^0.8.16: setimmediate "^1.0.5" ua-parser-js "^0.7.9" +fd-slicer@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" + dependencies: + pend "~1.2.0" + figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -2961,6 +3038,10 @@ get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" @@ -3003,7 +3084,7 @@ glob-to-regexp@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" -glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: +glob@7.1.2, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -3054,6 +3135,10 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6, graceful-fs@^4.1.9: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + handle-thing@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" @@ -3173,7 +3258,7 @@ hawk@3.1.3, hawk@~3.1.3: hoek "2.x.x" sntp "1.x.x" -he@1.1.x: +he@1.1.1, he@1.1.x: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" @@ -3309,6 +3394,13 @@ https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" +https-proxy-agent@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" + dependencies: + agent-base "^4.1.0" + debug "^3.1.0" + husky@^0.15.0-rc.8: version "0.15.0-rc.8" resolved "https://registry.yarnpkg.com/husky/-/husky-0.15.0-rc.8.tgz#b658c597a8f9bbcd00a5c039709e7c61e61238e7" @@ -4204,6 +4296,10 @@ mime@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" +mime@^2.0.3: + version "2.3.1" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369" + mime@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/mime/-/mime-2.2.0.tgz#161e541965551d3b549fa1114391e3a3d55b923b" @@ -4230,7 +4326,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" -minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4: +minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -4255,12 +4351,28 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: minimist "0.0.8" +mocha@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" + dependencies: + browser-stdout "1.3.1" + commander "2.15.1" + debug "3.1.0" + diff "3.5.0" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.5" + he "1.1.1" + minimatch "3.0.4" + mkdirp "0.5.1" + supports-color "5.4.0" + moment@^2.11.2: version "2.20.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.20.1.tgz#d6eb1a46cbcc14a2b2f9434112c1ff8907f313fd" @@ -4748,6 +4860,10 @@ path-type@^3.0.0: dependencies: pify "^3.0.0" +pathval@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" + pbkdf2@^3.0.3: version "3.0.14" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade" @@ -4758,6 +4874,10 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + performance-now@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" @@ -5147,6 +5267,10 @@ proxy-addr@~2.0.2: forwarded "~0.1.2" ipaddr.js "1.5.2" +proxy-from-env@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -5177,6 +5301,19 @@ pupa@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/pupa/-/pupa-1.0.0.tgz#9a9568a5af7e657b8462a6e9d5328743560ceff6" +puppeteer@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-1.5.0.tgz#e35db3f3ba3d41013feb65be02bdaa727ec7b8ec" + dependencies: + debug "^3.1.0" + extract-zip "^1.6.6" + https-proxy-agent "^2.2.1" + mime "^2.0.3" + progress "^2.0.0" + proxy-from-env "^1.0.0" + rimraf "^2.6.1" + ws "^5.1.1" + q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -6127,6 +6264,12 @@ style-loader@^0.20.1: loader-utils "^1.1.0" schema-utils "^0.4.3" +supports-color@5.4.0, supports-color@^5.3.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" + dependencies: + has-flag "^3.0.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -6149,12 +6292,6 @@ supports-color@^5.1.0: dependencies: has-flag "^2.0.0" -supports-color@^5.3.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" - dependencies: - has-flag "^3.0.0" - svgo@^0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" @@ -6318,6 +6455,10 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" +type-detect@^4.0.0: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + type-is@~1.6.15: version "1.6.15" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" @@ -6762,6 +6903,12 @@ write@^0.2.1: dependencies: mkdirp "^0.5.1" +ws@^5.1.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.0.tgz#9fd95e3ac7c76f6ae8bcc868a0e3f11f1290c33e" + dependencies: + async-limiter "~1.0.0" + ws@~3.3.1: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" @@ -6891,6 +7038,12 @@ yargs@~3.10.0: decamelize "^1.0.0" window-size "0.1.0" +yauzl@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" + dependencies: + fd-slicer "~1.0.1" + yeast@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" From 3e9cabe2c4359ba8d25dceb006dbf618dad6adca Mon Sep 17 00:00:00 2001 From: Mrkou47 Date: Fri, 15 Jun 2018 18:48:26 +0800 Subject: [PATCH 06/10] add some simple test --- .vscode/launch.json | 6 ++++++ tests/index.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tests/index.js diff --git a/.vscode/launch.json b/.vscode/launch.json index 9999648..2cf8fb3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -20,5 +20,11 @@ "NODE_ENV": "production", } }, + { + "type": "node", + "request": "launch", + "name": "Debug current file", + "program": "${file}", + } ] } \ No newline at end of file diff --git a/tests/index.js b/tests/index.js new file mode 100644 index 0000000..72c82bd --- /dev/null +++ b/tests/index.js @@ -0,0 +1,28 @@ +const puppeteer = require('puppeteer'); + +function sleep(time) { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + }, time); + }) +} + +(async () => { + const browser = await puppeteer.launch({ + headless: false, + slowMo: 300, + }); + const page = await browser.newPage(); + + await page.setViewport( { width: 1280, height: 800} ); + await page.goto('http://www.baidu.com'); + + await page.waitFor(3000); + + await page.screenshot({ + path: 'page.png', + }); + await browser.close(); +})(); + From cd723aae5590362d86e16f5629c25d3fd1d8bae4 Mon Sep 17 00:00:00 2001 From: Mrkou47 Date: Tue, 19 Jun 2018 12:02:47 +0800 Subject: [PATCH 07/10] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f08405d..60e62bc 100644 --- a/README.md +++ b/README.md @@ -757,7 +757,7 @@ export default serve; - [x] Include redux - [x] Include css module -- [ ] Visual Studio Code use nodemon +- [x] ~~Visual Studio Code use nodemon~~ use chokidar - [ ] Production useful - [ ] require.ensure - [ ] Code Splitting From b61971a56e035fbba6028bfece172df159ff1ca8 Mon Sep 17 00:00:00 2001 From: MrKou47 Date: Wed, 20 Jun 2018 22:35:22 +0800 Subject: [PATCH 08/10] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 60e62bc..392569e 100644 --- a/README.md +++ b/README.md @@ -752,6 +752,11 @@ export default serve; 在这一步我们需要初始化react的事件绑定。我们只需要在页面中引入webpack打包好的js文件就可以了。 +## TODO + +- customer react component +- css chunk plugin +- css-loader # Future From 9827551419d161ceec4dd9a98d988a396a461d48 Mon Sep 17 00:00:00 2001 From: Bo Kou Date: Fri, 25 Apr 2025 17:00:10 +0800 Subject: [PATCH 09/10] Rename README.md to README.zh.md --- README.md => README.zh.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README.md => README.zh.md (100%) diff --git a/README.md b/README.zh.md similarity index 100% rename from README.md rename to README.zh.md From 4d10d4b60a2d381d455f7653044fad4f5b983b86 Mon Sep 17 00:00:00 2001 From: Bo Kou Date: Fri, 25 Apr 2025 17:24:04 +0800 Subject: [PATCH 10/10] Create the English version of README.md --- README.md | 767 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 767 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..1d0bf6c --- /dev/null +++ b/README.md @@ -0,0 +1,767 @@ +# React SSR step-by-step + +Step by step to implement server-side rendering in React. +## What's the best isomorphism-react scaffold in my mind? + +- Smooth debugging - I hope to debug anywhere and in any environment +- On-demand loading - Implement code splitting +- Version control - The project's bundle and release should generate the correct version number +- Configurable - Optional programming methods (sass, less, js, jsx, ts, tsx, coffee) - same as yeoman prompt +- Fast publishing - Optimize packaging speed - webpack optimize +- Lower learning curve - It should only differ slightly from the projects I work on, or have comprehensive tutorials to guide me +- Updates, more stability - I don't want to use older modules, nor do I want to use newer modules +- Testable + +## Preliminary research + +- https://github.com/glenjamin/ultimate-hot-reloading-example/ is a relatively good example, but it does not include *react-router v4* and code splitting. +- https://github.com/justinjung04/universal-boilerplate handles CSS by ignoring it during server-side rendering in the development environment. +- https://github.com/faceyspacey/react-universal-component is quite well-developed and has a high level of integration, but the code is too invasive and requires a series of compilation tools. + +- The design of react router v4 has significant changes compared to v3. I researched [React Router v4 nearly ruined my life](https://zhuanlan.zhihu.com/p/27433116), [React Router v4 and code splitting: from giving up to getting started](http://www.wukai.me/2017/09/25/react-router-v4-code-splitting/), and also looked at the [v4 onEnter and onChange hooks](https://github.com/ReactTraining/react-router/issues/3854) issue. Later, I watched [React Router v4 with Michael Jackson and Ryan Florence - Modern Web](https://www.youtube.com/watch?v=Vur2dAFZ4GE) to learn about the design philosophy. My conclusion is that I have a very poor impression of the react router team but have no choice. +Moreover, looking at the v4 documentation, the section on [Code-splitting + server rendering](https://reacttraining.com/react-router/web/guides/code-splitting/code-splitting-server-rendering) surprisingly states that they tried several times and gave up?! orz... + +https://medium.com/airbnb-engineering/server-rendering-code-splitting-and-lazy-loading-with-react-router-v4-bfe596a6af70 + +## How to use + +> $ yarn && npm run build + +Then use *Visual studio code* to debug isomorphic react. + +![debugtool](./docs/images/debug.png) + +## Expectation + + +Implement server-side rendering for React, while supporting additional extensions, including react-router, redux, css-module, and react-addons-*. + +## First Step + +Before implementing server-side rendering, we need to first create a simple display process for a React component. + +This step includes the basic display of a React component while using webpack-dev-server as the server. + +```js +//container.jsx +import React, { Component } from 'react'; + +export class Hello extends Component { + render() { + return ( +
+ Hello world +
+ ) + } +} + +export default Hello; +``` + +```js +// render.js +import ReactDOM from 'react-dom'; +import React from 'react'; + +import Home from './container/home/container'; + +ReactDOM.render(, document.getElementById('app')); +``` + +```js +//webpack.config.js +const webpack = require('webpack'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const path = require('path'); + +const config = { + entry: path.resolve(__dirname, '../src/render.js'), + output: { + path: path.resolve(__dirname, '../statics'), + filename: 'bundle.js', + publicPath: '/', + }, + resolve: { + extensions: ['.js','.jsx'] + }, + module: { + rules: [{ + test: /jsx?/, + use: { + loader: 'babel-loader', + options: { + presets: ['es2015', 'react'], + plugins: [['transform-runtime']] + } + }, + exclude: /node_modules/, + }] + }, + plugins: [ + new HtmlWebpackPlugin({ + filename: 'index.html', + template: path.join(__dirname, './tmpl.html'), + inject: true, + }) + ], + devServer: { + port: 8388, + } +}; + +module.exports = config; +``` + +> webpack-dev-server --config webpack/webpack.config.js --port 8388 --inline + +## Step 2 - Simpe server side render + + +Server-side rendering essentially involves rendering components written in ES6 syntax as strings on the server using React. In this process, we first need to implement the use of ES6 syntax on the server. + +We can use `babel-node` to enable the use of the ES6 module system on the server. We use Visual Studio Code for debugging: + +```json +// .vscode/launch.json +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch via Babel", + "program": "${workspaceRoot}/server/server.js", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/babel-node", + "cwd": "${workspaceRoot}" + } + ] +} +``` + +Then we will implement the simplest SSR. We can use the `renderToString` method provided by `react-dom/server` to convert the React components we wrote using *ES6* syntax into a string. + +```js +import http from 'http'; +import path from 'path'; +import React from 'react'; +import { renderToString } from 'react-dom/server'; +import Home from '../src/container/home/index.js'; + +const PORT = 8388; + +const serve = http.createServer((req, res) => { + const dom = renderToString(); + res.end(dom); +}); + +serve.listen(PORT, () => { + console.log(`server start on port ${PORT}`); +}); + +export default serve; +``` +Execute script: +> node server/server.js + +This is an implementation of server-side rendering, but this is far from enough. We also need to implement routing, data storage, and data request functionalities. + +Before this, we can also conduct a small test to see whether server-side rendering is faster or client-side rendering is faster. We can use `window.performance.now()` or `console.time()` for the test. To standardize the method, we will use `console.time` here. We will render 10,000 divs in the *Hello* component for the experiment. +```js + +export class Hello extends Component { + constructor(props) { + super(props); + } + + componentWillMount() { + console.time('mount'); + } + + componentDidMount(){ + console.timeEnd('mount'); + } + + render() { + return ( +
+ Hello world + {Object.keys(Array.from({ length: 10000 })) + .map((i,index) =>
{index}
)} +
+ ) + } +}· +``` + +Similarly, let's modify the server-side code as well: + +```js +// server.js +... +const serve = http.createServer((req, res) => { + console.time('mount'); + const dom = renderToString(); + console.timeEnd('mount'); + res.end(dom); +}); +... +``` + +Using webpack-dev-server and `node server/server.js` to run our React code, we can see that the browser rendering takes about `mount: 303.55322265625ms`, while the server-side call to `renderToString` only takes `mount: 172.4598450064659ms`. It is evident that server-side rendering is indeed much faster. + +## Step 3 - Add react router + +We are now adding *react-router* to our code. We are using the latest version 4. Since there is a significant difference between version 4 and version 2, we will take this opportunity to learn about the new API. + +We designed the simplest routing structure: + + - / -> `` + - /home -> `` + - /profile -> `` + +```js +// routes.js + +import AppRoot from './container/root'; +import Home from './container/home'; +import Profile from './container/profile'; + +const routes = [ + { + path: '/', + exact: true, + component: AppRoot + }, + { + path: '/home', + component: Home + }, + { + path: '/profile', + component: Profile + } +]; + +export default routes; +``` + +For server-side code, we handle all requests with `Content-Type: text/html` using `react-dom/server`. In fact, when we render ``, we pass the user's current URL to `react-router`, which loads and renders different components. + +```js +// server.js + +import http from 'http'; +import path from 'path'; +import React from 'react'; +import { renderToString } from 'react-dom/server'; +import { renderRoutes } from 'react-router-config'; +import { StaticRouter } from 'react-router-dom'; +import routers from '../src/routers'; + +const PORT = 8388; + +const serve = http.createServer((req, res) => { + const context = {}; + const content = renderToString( + + {renderRoutes(routers)} + + ); + res.end(content); +}); + +serve.listen(PORT, () => { + console.log(`server start on port ${PORT}`); +}); + +export default serve; +``` +## Step 4 - Add CSS + +In this step, we need to handle CSS. In fact, we can treat CSS as ordinary text processing. We can write all styles in one file and then insert it into the `` tag when loading the webpage. However, we usually do not write everything in one file; each component may have its own CSS, and we write a CSS entry file to import them using `@import`. We might also choose to use the `css-loader` provided by Webpack to automatically bundle all CSS files into one file, or use tools or technologies like CSS Modules, PostCSS, or SCSS. Therefore, handling CSS is also a challenge in React SSR. + +We will write components using the CSS Modules technique. By directly using `import style from '*.[css/scss/less]'` at the top of the component file, we can achieve this. However, the server-side cannot directly import CSS files. Here, we use [babel-plugin-css-modules-transform](https://github.com/michalkvasnicak/babel-plugin-css-modules-transform) to implement CSS import while also enabling CSS Modules. This plugin can compile `.red { color: red }` into the format `Object {red: "home__red___1x-zZ"}` within the file and generate a CSS file in a specified directory. What we need to do is to write the components normally, then find the style files of the currently rendered components on the server, and add them to the page, which can be done using embedded CSS or linked CSS. We can mount a static property on the component to locate the specific style file. + +### .babelrc + +```json +{ + "plugins": [ + ["css-modules-transform", { + "extensions": [ + ".css" + ], + "extractCss": { + "dir": "./dist/css/", + "filename": "[name].css", + "generateScopedName": "[name]__[local]___[hash:base64:5]" + } + }] + ] +} +``` + +### Rewrite `` Component + +```js +// src/container/home/container.js +import React, { Component } from 'react'; +import styles from './home.css'; + +export class Hello extends Component { + constructor(props) { + super(props); + } + + componentWillMount() { + var global,window; + console.log(styles) + } + render() { + return ( +
+ Hello world + {Object.keys(Array.from({ length: 10000 })).map((i,index) =>
{index}
)} +
+ ) + } +} + +// Obtain the specified CSS file on the server side by accessing this property of the component. +Hello.getCssFile = 'home'; + +export default Hello; +``` + +```js +// server.js + +import http from 'http'; +import path from 'path'; +import React from 'react'; +import fs from 'fs'; +import st from 'st'; +import { renderToString } from 'react-dom/server'; +import { renderRoutes } from 'react-router-config'; +import { StaticRouter } from 'react-router-dom'; +import routers from '../src/routers'; + +import { tmpl } from './utils/tmpl'; + +const ROOTPATH = path.resolve('./'); +const PORT = 8388; + +const staticsService = st({ url: '/statics', path: path.join(ROOTPATH, 'dist') }) + +const serve = http.createServer((req, res) => { + const stHandled = staticsService(req, res); + if (stHandled) return; + const context = {}; + const currentRouter = routers.find(c => c.path === req.url); + if (currentRouter) { + let cssContext = ''; + const currentComponent = currentRouter.component; + const content = renderToString( + + {renderRoutes(routers)} + + ); + res.end(tmpl({ + header: currentComponent.getCssFile ? `` : '', + content, + })); + } else { + res.statusCode = 404; + res.end('404'); + } +}); + + +serve.listen(PORT, () => { + console.log(`server start on port ${PORT}`); +}); + +export default serve; +``` + +At this point, we have achieved the functionality of using CSS modules on the server side. `babel-plugins-css-modules-transform` can also be combined with preprocessors or PostCSS, which can be easily handled by referring to the documentation. + +## Step 5 - InitialData? + +"We usually make AJAX requests to initialize the component's state in the `componentDidMount` method, but the `componentDidMount` method does not execute on the server side, so we need to use other ways to initialize the component's state." + +### Unused Redux + +If you don't use Redux, the usual approach is similar to obtaining the component's styles: bind a static method on the component and call it during the server render process. When combined with *react-router* v4, there isn't currently an optimal method. The official example attaches a `loadData` method when declaring routes, which is then called during server matching. This method is similar in concept to declaring a static method on the component. Here, we will implement the method it describes. + + +### Rewrite `` + +We need to add a static method getInitialState to the Home component, which will simulate a call on the server side. There is a problem here: the client side uses xhr requests, so we need to simulate xhr requests on the server side. Here, I use the `xhr-request` module to achieve this. + +```jsx +// container/home/container.js +import request from 'xhr-request'; + +export class Home extends Component { + ... + + componentWillMount() { + var global,window; + console.log(styles) + if (window) { + this.t = window.performance.now(); + } + if (this.props.staticContext) { // https://reacttraining.com/react-router/web/api/StaticRouter/context-object 被react-router包裹的组件会从上层获得 staticContext 属性 + this.setState({ + list: this.props.staticContext.list, + }); + } + } + + render() { + const userListDOM = this.state.list.map((v,i) =>

name: {v.name}

); + return ( +
+ Hello world +
+ {userListDOM} +
+ {Object.keys(Array.from({ length: 10000 })).map((i,index) =>
{index}
)} +
+ ) + } + +} + + +// ... +Home.getInitialData = function () { + return new Promise((resolve, reject) => { + request('http://localhost:8388/user/list', { + json: true, + method: 'post', + }, function (err, data) { + if (err) { + reject(err); + } else { + resolve(data); + } + }) + }); +}; +``` + +Server implementation of the `/user/list` interface: + +```js +// server/api/user.js +function userList(req, res) { + if (req.url !== '/user/list') { + res.writeHead(502); + res.end(); + return false; + } else { + let body = ''; + req.on('data', data => body += data); + req.on('end', () => { + try { + const obj = JSON.parse(body); + } catch (error) { + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ list: [{ name: 'bob' }, { name: 'John' }] })) + } + }); + } +} + +export { userList }; +``` + +Then, modify the entry file of the server side: + +```js +import { userList } from './api/user'; + +// server.js +const serve = http.createServer((req, res) => { + const stHandled = staticsService(req, res); + if (stHandled) return; + if (req.url === '/user/list') { // 这里先简单判断一下url + userList(req,res); + } else { + const currentRouter = routers.find(c => c.path === req.url); + if (currentRouter) { + let cssContext = ''; + const currentComponent = currentRouter.component; + // get data + const promises = []; + + routers.some(route => { + const match = matchPath(req.url, route) + if (match) + promises.push(route.loadData(match)) + return match + }); + + if (promises.length) { + Promise.all(promises).then(data => { + console.log(data); + render(data[0]); + }); + } else { + render(); + } + + function render(data = {}) { + // render component + const content = renderToString( + + {renderRoutes(routers)} + + ); + + // send + res.end(tmpl({ + header: currentComponent.getCssFile ? `` : '', + content, + })); + } + + } else { + res.statusCode = 404; + res.end('404'); + } + } +}); +``` + +Start debugging, and we can see that we can already obtain data from the server side and use it within the component. + +### Use redux + +If using Redux, it is largely the same. We need to refactor both the component and the server-side code. + +### Components + +We first need to place the component's state into the Redux store and use the `connect` method provided by `react-redux` to correctly associate the store and the component. Then, we encapsulate the `getInitialData` method as an action to be called during server-side rendering. + +For simplicity, the code that tests render speed has been removed. Additionally, `` has been modified to a stateless component. + +**PS:** In fact, we have always been striving for the server-side call to `renderToString`. This is solely to ensure the correct rendering of the initial screen's **DOM** on the server side. This process does not include the initialization of Redux on the client side or event binding. Here, we also wrote a simple example of a toggle button that controls the visibility of a div. In reality, it does not bind events during server-side rendering. + +```jsx +// ... + +const Home = (props) => { + const { list, blankVisible } = props; + const userListDOM = list.map((v, i) =>

name: {v.name}

); + return ( +
+ Hello world +
+ {userListDOM} +
+ /** onClick 并不起作用 **/ + + {blankVisible ? +
blank
+ : null} +
+ ); +}; + +Home.getCssFile = 'home'; + +/** + * "This method is called on the server side, with the passed dispatch being store.dispatch. It also returns a Promise to facilitate initial data handling on the server." + * ${dispatch} function store.dispatch + * return Promise + */ +Home.getInitialData = function (dispatch) { + return dispatch(getUserList()); +} + +const mapState2Props = store => { + return {...store.home}; +} + +const mapDispatch2Props = dispatch => { + return { + getUserList, + toogleBlankVisible, + } +} + +export default connect(mapState2Props)(Home); +``` + +```js +// action.js +import request from 'xhr-request'; +import THROW_ERR from '../../components/error/action'; + +export const TOOGLE_BLANK_VISIBLE = 'TOOGLE_BLANK_VISIBLE'; + +export const UPDATE_USER_LIST = 'UPDATE_USER_LIST'; + +export const toogleBlankVisible = () => (dispatch, getState) => { + const blankVisible = getState().home; + + dispatch({ + type: TOOGLE_BLANK_VISIBLE, + payload: !blankVisible + }); +} + +/** + * return Promise + * https://stackoverflow.com/questions/36189448/want-to-do-dispatch-then + */ +export const getUserList = () => (dispath, getState) => { + return new Promise((resolve, reject) => { + request('http://localhost:8388/user/list', { + json: true, + method: 'post', + }, function (err, data) { + if (err) { + dispath({ + type: THROW_ERR, + payload: err, + }); + reject(err); + } else { + dispath({ + type: UPDATE_USER_LIST, + payload: data.list, + }); + resolve(data); + } + }); + }) +} +``` + +```js +// src/home/reducer.js +import { UPDATE_USER_LIST, TOOGLE_BLANK_VISIBLE } from './action'; +const initialState = { + list: [], + blankVisible: true, +}; + +export default (state = initialState, action) => { + switch (action.type) { + case UPDATE_USER_LIST: + return { + ...state, + list: action.payload, + }; + break; + case TOOGLE_BLANK_VISIBLE: + return { + ...state, + blankVisible: payload, + }; + break; + default: + return state; + break; + } +}; +``` + +Then, on the server side, after matching the correct component, we need to obtain the `getInitialData` method from all components and place it in a queue. The completion of all requests also means that all components' `getInitialData` have been called, triggering the correct actions and updating the data in the store. At this point, we can use the renderToString method to render the correct DOM. + +```js +import http from 'http'; +import path from 'path'; +import React from 'react'; +import fs from 'fs'; +import st from 'st'; +import { renderToString } from 'react-dom/server'; +import { renderRoutes, matchRoutes } from 'react-router-config'; +import { Provider } from 'react-redux'; +import { StaticRouter, matchPath } from 'react-router-dom'; + +import routers from '../src/routers'; +import initialStore from '../src/store'; + +import { tmpl } from './utils/tmpl'; + +import { userList } from './api/user'; + +const ROOTPATH = path.resolve('./'); +const PORT = 8388; + +const store = initialStore(); + +const staticsService = st({ url: '/statics', path: path.join(ROOTPATH, 'dist') }) + +const serve = http.createServer((req, res) => { + const stHandled = staticsService(req, res); + if (stHandled) return; + if (req.url === '/user/list') { + userList(req,res); + } else { + const { dispatch } = store; + const branch = matchRoutes(routers, req.url); // Find the correct component (which may include parent components) + const styleList = []; // Find all style files. + const promiseList = branch.map(({ route }) => { // create promise list + const { component } = route; + if (component.getCssFile) { + styleList.push(``); + } + return route.component.getInitialData ? route.component.getInitialData(dispatch) : Promise.resolve(); + }); + + Promise.all(promiseList).then(v => { // Waiting for initialization data to complete + console.log(store.getState()); // The store has been updated. + const content = renderToString( + + + {renderRoutes(routers)} + + + ); + res.end( + tmpl({ + title: '', + header: styleList.join('\n'), + content, + initialState: store.getState(), // Used to initialize the store tree for the client + }) + ) + }); + } +}); + +serve.listen(PORT, () => { + console.log(`server start on port ${PORT}`); +}); + +export default serve; +``` + +Restart the server, and you can see that the server side has correctly rendered our components. + +## Step 6 - Event & Redux init + +In this step, we need to initialize the event binding in React. We only need to include the webpack bundled JS file in the page + +## TODO + +- customer react component +- css chunk plugin +- css-loader + +# Future + +- [x] Include redux +- [x] Include css module +- [x] ~~Visual Studio Code use nodemon~~ use chokidar +- [ ] Production useful +- [ ] require.ensure +- [ ] Code Splitting +- [ ] Optmize webpack config