├── .babelrc ├── .env ├── .env.development ├── .env.production ├── .env.test ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .vscode └── settings.json ├── 1.jpg ├── 2.jpg ├── 3.png ├── README.md ├── bin ├── cmd.js └── index.js ├── client ├── App │ ├── App.js │ ├── App.less │ ├── CreateApp.js │ └── index.js ├── assets │ ├── css │ │ └── base.less │ ├── img │ │ ├── 1.jpg │ │ └── 2.jpg │ └── js │ │ └── request │ │ ├── XMLHttpRequest.js │ │ ├── baseUrl.js │ │ ├── filterGraphqlData.js │ │ ├── index.js │ │ ├── redirect.js │ │ ├── request.js │ │ ├── requestApi.js │ │ ├── requestMessage.js │ │ ├── test.js │ │ ├── test1.js │ │ └── token.js ├── component │ ├── Head │ │ ├── index.js │ │ └── index.less │ ├── InitState │ │ └── index.js │ ├── LazyLoadingImg │ │ ├── index.js │ │ └── index.less │ ├── Loadable │ │ ├── LICENSE │ │ ├── README.md │ │ ├── babel.js │ │ ├── lib │ │ │ ├── babel.js │ │ │ ├── index.js │ │ │ └── webpack.js │ │ ├── package.json │ │ └── webpack.js │ ├── Loading │ │ └── index.js │ ├── Nav │ │ ├── index.js │ │ └── index.less │ ├── SetMetaProps │ │ └── index.js │ ├── Table │ │ ├── index.js │ │ └── index.less │ └── lazy │ │ └── index.js ├── favicon.ico ├── index.js ├── pages │ ├── Home │ │ ├── index.js │ │ └── index.less │ ├── User │ │ ├── index.js │ │ └── index.less │ └── marketing │ │ ├── index.js │ │ ├── pages │ │ └── DiscountCoupon │ │ │ └── index.js │ │ └── router │ │ └── routesConfig.js ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ └── logo512.png ├── redux │ ├── index.js │ └── models │ │ ├── head.js │ │ ├── home.js │ │ ├── index.js │ │ └── nav.js ├── router │ ├── Routers.js │ ├── addRouterApi.js │ ├── history.js │ ├── historyPush.js │ ├── index.js │ ├── react-lazy-router-dom │ │ ├── Route.js │ │ ├── Router.js │ │ ├── Switch.js │ │ ├── index.js │ │ ├── lazy.js │ │ ├── matchPath.js │ │ └── withRouter.js │ ├── routePaths.js │ ├── routesComponent.js │ └── routesConfig.js ├── static │ ├── 1 │ │ └── 2.txt │ ├── 3.txt │ ├── a.js │ ├── b.js │ ├── c.js │ ├── d.js │ ├── img │ │ └── logo512.png │ └── js │ │ └── errCatch.js └── utils │ ├── CheckDataType.js │ ├── FloatingBall.js │ ├── SubscribePublished.js │ ├── createStore.js │ ├── ergodic.js │ ├── getBaseInitState.js │ ├── getCssAttr.js │ ├── index.js │ ├── regular.js │ ├── resolvePath.js │ ├── stringToObject.js │ ├── throttlingStabilization.js │ └── transformRoutePaths.js ├── index.html ├── nodemon.json ├── package.json ├── server ├── app.js ├── controller │ ├── home.js │ └── user.js ├── index.js ├── middleware │ ├── clientRouter │ │ ├── index.js │ │ └── otherModules.js │ ├── index.js │ └── webpackHot │ │ └── index.js ├── router │ ├── api.js │ └── index.js ├── service │ └── user.js └── utils │ ├── copyFile.js │ ├── index.js │ ├── readFile.js │ └── watchFile.js ├── ssr搭建原理.md └── webpack ├── config ├── client │ ├── index.js │ ├── webpack.base.config.js │ ├── webpack.dev.config.js │ ├── webpack.prod.config.js │ └── webpack.server.config.js └── server │ ├── index.js │ ├── webpack.base.config.js │ ├── webpack.dev.config.js │ └── webpack.prod.config.js ├── defineLoader └── MyExampleWebpackLoader.js ├── definePlugin ├── HelloWorldCheckerPlugin │ └── index.js ├── MyExampleWebpackPlugin.js ├── mini-css-extract-plugin │ ├── LICENSE │ ├── README.md │ ├── cjs │ │ ├── cjs.js │ │ ├── hmr │ │ │ ├── hotModuleReplacement.js │ │ │ └── normalize-url.js │ │ ├── index.js │ │ ├── loader-options.json │ │ ├── loader.js │ │ ├── plugin-options.json │ │ └── utils.js │ └── package.json ├── react-loadable-ssr-addon │ ├── .babelrc │ ├── .circleci │ │ └── config.yml │ ├── .github │ │ ├── CODE_OF_CONDUCT.md │ │ ├── ISSUE_TEMPLATE.md │ │ └── PULL_REQUEST_TEMPLATE.md │ ├── AUTHORS.md │ ├── LICENSE │ ├── README.md │ ├── example │ │ ├── client.jsx │ │ ├── components │ │ │ ├── App.jsx │ │ │ ├── Content.jsx │ │ │ ├── ContentNested.jsx │ │ │ ├── Header.jsx │ │ │ ├── Loading.js │ │ │ └── multilevel │ │ │ │ ├── Multilevel.jsx │ │ │ │ ├── SharedMultilevel.jsx │ │ │ │ └── level-1 │ │ │ │ └── level-2 │ │ │ │ └── DeepLevel.jsx │ │ └── server.js │ ├── lib │ │ ├── ReactLoadableSSRAddon.js │ │ ├── ReactLoadableSSRAddon.test.js │ │ ├── getBundles.js │ │ ├── getBundles.test.js │ │ ├── index.js │ │ └── utils │ │ │ ├── computeIntegrity.js │ │ │ ├── getFileExtension.js │ │ │ ├── getFileExtension.test.js │ │ │ ├── hasEntry.js │ │ │ ├── hasEntry.test.js │ │ │ ├── index.js │ │ │ ├── unique.js │ │ │ └── unique.test.js │ ├── package.json │ ├── source │ │ ├── ReactLoadableSSRAddon.js │ │ ├── ReactLoadableSSRAddon.test.js │ │ ├── getBundles.js │ │ ├── getBundles.test.js │ │ ├── index.js │ │ └── utils │ │ │ ├── computeIntegrity.js │ │ │ ├── getFileExtension.js │ │ │ ├── getFileExtension.test.js │ │ │ ├── hasEntry.js │ │ │ ├── hasEntry.test.js │ │ │ ├── index.js │ │ │ ├── unique.js │ │ │ └── unique.test.js │ └── webpack.config.js ├── react-loadable │ ├── LICENSE │ ├── README.md │ ├── babel.js │ ├── lib │ │ ├── babel.js │ │ ├── index.js │ │ └── webpack.js │ ├── package.json │ └── webpack.js ├── webpack-plugin-copy-file │ └── index.js ├── webpack-plugin-no-require-css │ └── index.js ├── webpack-plugin-resolve-alias │ └── index.js └── webpack-plugin-router │ ├── diff.js │ └── index.js ├── index.js └── utils ├── alias.js ├── copyFile.js ├── index.js ├── readFile.js ├── readWriteFiles.js ├── stringToObject.js ├── watchFile.js └── writeFile.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "compact": true, 3 | "presets": [ 4 | [ 5 | "@babel/preset-env" 6 | // { 7 | // "targets": { 8 | // "node": true, 9 | // "chrome": "58", 10 | // "ie": "11" 11 | // } 12 | // }, 13 | // "stage-0" 14 | ], 15 | "@babel/preset-react" 16 | // "es2015" 17 | ], 18 | 19 | "plugins": [ 20 | "syntax-dynamic-import", 21 | "@babel/plugin-proposal-function-sent", 22 | "@babel/plugin-proposal-optional-chaining", 23 | ["@babel/plugin-transform-arrow-functions", { "loose": true }], 24 | [ 25 | "@babel/plugin-transform-runtime" 26 | // { 27 | // "absoluteRuntime": false, 28 | // "corejs": 3 29 | // } 30 | ], 31 | "@babel/plugin-syntax-dynamic-import", 32 | "@babel/plugin-syntax-import-meta", 33 | "@babel/plugin-proposal-async-generator-functions", 34 | "@babel/plugin-transform-dotall-regex", 35 | "@babel/plugin-proposal-export-default-from", 36 | "@babel/plugin-proposal-export-namespace-from", 37 | ["@babel/plugin-proposal-class-static-block", { "loose": true }], 38 | [ 39 | "@babel/plugin-proposal-decorators", 40 | { 41 | // "legacy": true, 42 | "version": "legacy" 43 | } 44 | ], 45 | [ 46 | "@babel/plugin-proposal-class-properties", 47 | { 48 | "loose": true 49 | } 50 | ], 51 | ["@babel/plugin-proposal-private-property-in-object", { "loose": true }], 52 | ["@babel/plugin-proposal-private-methods", { "loose": true }], 53 | "@babel/plugin-proposal-json-strings", 54 | [ 55 | "@babel/plugin-transform-regenerator", 56 | { 57 | "asyncGenerators": false, 58 | "generators": false, 59 | "async": false 60 | } 61 | ] 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | port=3002 2 | htmlWebpackPluginOptions={title:网页标题,webpackName:webpack-cli-util} -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | #环境变量 2 | NODE_ENV=development 3 | VITE_APP_BASEAPI=https://www.dev.com 4 | VITE_PROJECT_TITLE=标题 -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | #环境变量 2 | NODE_ENV=production 3 | VITE_APP_BASEAPI=https://www.production.com 4 | VITE_PROJECT_TITLE=标题2 -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | #环境变量 2 | NODE_ENV="test" 3 | 4 | VITE_APP_BASEAPI="https://www.test.com" 5 | VITE_PROJECT_TITLE="标题1" -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | yarn.lock 2 | dist 3 | dist/* 4 | dist/**/* 5 | **/dist 6 | **/dist/**/* 7 | **/dist/**/* 8 | node_modules 9 | node_modules/* 10 | node_modules/**/* 11 | **/node_modules 12 | **/node_modules/**/* 13 | **/node_modules/**/* 14 | 15 | webpack 16 | webpack/* 17 | webpack/**/* 18 | **/webpack 19 | **/webpack/**/* 20 | **/webpack/**/* 21 | 22 | 23 | @babel 24 | @babel/* 25 | @babel/**/* 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | package-lock.json 4 | 5 | yarn-error.log 6 | 7 | yarn.lock 8 | 9 | yarn 10 | 11 | 12 | client/router/routesComponent.js 13 | 14 | 15 | .idea/ 16 | 17 | dist/ 18 | 19 | *.lock 20 | 21 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | 5 | # Ignore all HTML files: 6 | *.html 7 | **/*.min.js 8 | **/*.min.css 9 | 10 | .idea/ 11 | node_modules/ 12 | dist/ 13 | build/ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.settings.editor": "json", 3 | "workbench.settings.openDefaultSettings": true, 4 | "workbench.settings.useSplitJSON": true 5 | } -------------------------------------------------------------------------------- /1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ygs-code/react-ssr-lazy-loading/3864111032b1fccb8524e755c3918e385fcb211e/1.jpg -------------------------------------------------------------------------------- /2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ygs-code/react-ssr-lazy-loading/3864111032b1fccb8524e755c3918e385fcb211e/2.jpg -------------------------------------------------------------------------------- /3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ygs-code/react-ssr-lazy-loading/3864111032b1fccb8524e755c3918e385fcb211e/3.png -------------------------------------------------------------------------------- /bin/cmd.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Date: 2022-08-09 11:24:59 3 | * @Author: Yao guan shou 4 | * @LastEditors: Yao guan shou 5 | * @LastEditTime: 2022-08-10 14:16:33 6 | * @FilePath: /react-loading-ssr/bin/cmd.js 7 | * @Description: 8 | */ 9 | import os from "os"; 10 | import { spawn, SpawnOptions, exec, execSync } from "child_process"; 11 | import moment from "moment"; 12 | 13 | export default class Cmd { 14 | text = ""; 15 | 16 | runNodeModule(moduleName, params, options) { 17 | if (os.type() == "Windows_NT" && !moduleName.match(/\.cmd$/)) { 18 | moduleName += ".cmd"; 19 | } 20 | return this.run(moduleName, params, options); 21 | } 22 | 23 | run(command, params, options) { 24 | this.text = ""; 25 | // options = Object.assign(options || {}, { cwd: this.cfg.cwd }); 26 | return new Promise((resolve, reject) => { 27 | console.log(`run command: ${command}, params:`, params, options); 28 | 29 | if (!options) { 30 | options = { 31 | stdio: "inherit" 32 | }; 33 | } 34 | if (!params) { 35 | params = []; 36 | } 37 | options.stdio = "pipe"; 38 | 39 | let proc = spawn(command, params, options); 40 | // console.log('proc===', proc) 41 | 42 | proc.stdout.on("data", (data) => { 43 | let dataStr = String(data); 44 | if (options.logPrefix) { 45 | dataStr = options.logPrefix + dataStr; 46 | } 47 | this.text += dataStr; 48 | if (!options?.silent) { 49 | process.stdout.write(moment().format("HH:mm:ss:SSS ") + dataStr); 50 | } 51 | }); 52 | 53 | proc.stderr.on("data", (data) => { 54 | // 不一定代表进程exitcode != 0,可能只是进程调用了console.error 55 | let dataStr = String(data); 56 | if (options?.logPrefix) { 57 | dataStr = options.logPrefix + dataStr; 58 | } 59 | if (!options?.silent) { 60 | process.stderr.write(moment().format("HH:mm:ss:SSS ") + dataStr); 61 | } 62 | }); 63 | 64 | // 进程错误 65 | proc.on("error", (error) => { 66 | if (!options?.silent) { 67 | console.error(error); 68 | } 69 | reject(error); 70 | }); 71 | 72 | // 进程关闭 73 | proc.on("close", (code) => { 74 | console.log(`process closed with exit code: ${code}`); 75 | if (code === 0) { 76 | resolve(this.text || ""); 77 | } else { 78 | let errMsg = `process closed with exit code: ${code}`; 79 | if (options?.logPrefix) { 80 | errMsg = options.logPrefix + errMsg; 81 | } 82 | reject(new Error(errMsg)); 83 | } 84 | }); 85 | 86 | proc.on("exit", (code, signal) => { 87 | console.log(`process exits`); 88 | }); 89 | }); 90 | } 91 | } 92 | 93 | // let cmd = new Cmd().runNodeModule( 94 | // process.platform === 'win32' ? 'npm.cmd' : 'npm', 95 | // ['run', 'ssr:dev', '--progress', 'bar:force'], 96 | // ) 97 | 98 | export const execute = (command, options = { stdio: "inherit" }) => { 99 | command = command.split(" ").filter((item) => item); 100 | 101 | if (os.type() === "Windows_NT" && !command[0].match(/\.cmd$/)) { 102 | command[0] += ".cmd"; 103 | } 104 | 105 | const proc = spawn(command[0], command.slice(1), options); 106 | 107 | // 进程错误 108 | proc.on("error", (error) => { 109 | if (error) { 110 | console.error("process error:", error); 111 | } 112 | }); 113 | 114 | // 进程关闭 115 | proc.on("close", (code) => { 116 | console.log(`process closed with exit code: ${code}`); 117 | // process.exit(code); 118 | }); 119 | 120 | // 退出 121 | proc.on("exit", (code, signal) => { 122 | console.log(`process exits`); 123 | // process.exit(code); 124 | }); 125 | 126 | return proc; 127 | }; 128 | 129 | /** 130 | * 判断端口是否被占用 131 | * @param port 端口号 132 | * @returns 该端口是否被占用 133 | */ 134 | export const iSportTake = (port) => { 135 | const cmd = 136 | process.platform === "win32" 137 | ? `netstat -aon|findstr ${port}` 138 | : `lsof -i:${port}`; 139 | try { 140 | const res = execSync(cmd); 141 | return true; 142 | } catch (error) { 143 | // console.log('error:', error); 144 | return false; 145 | } 146 | }; 147 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Date: 2022-08-09 09:35:04 3 | * @Author: Yao guan shou 4 | * @LastEditors: Yao guan shou 5 | * @LastEditTime: 2022-08-10 16:18:28 6 | * @FilePath: /react-loading-ssr/bin/index.js 7 | * @Description: 8 | */ 9 | import path from "path"; 10 | import { compiler } from "../webpack"; 11 | import { alias, readWriteFiles } from "../webpack/utils"; 12 | import ResolveAlias from "../webpack/definePlugin/webpack-plugin-resolve-alias"; 13 | import kill from "kill-port"; // 杀死端口包 14 | import { execute, iSportTake } from "./cmd"; // 杀死端口包 15 | import { stabilization } from "../client/utils"; 16 | 17 | import * as dotenv from "dotenv"; 18 | dotenv.config({ path: ".env" }); 19 | 20 | // 如果是开发环境 先拷贝 服务器文件到 dist 21 | let { 22 | NODE_ENV, // 环境参数 23 | target, // 环境参数 24 | htmlWebpackPluginOptions = "", 25 | port 26 | } = process.env; // 环境参数 27 | 28 | const isSsr = target === "ssr"; 29 | // 是否是生产环境 30 | const isEnvProduction = NODE_ENV === "production"; 31 | // 是否是测试开发环境 32 | const isEnvDevelopment = NODE_ENV === "development"; 33 | 34 | class Bin { 35 | constructor() { 36 | this.counter = 0; 37 | this.child = null; 38 | this.init(); 39 | } 40 | init() { 41 | this.executeScript(); 42 | } 43 | development() { 44 | let $ResolveAlias = new ResolveAlias({ 45 | resolve: { 46 | // 路径配置 47 | alias 48 | } 49 | }); 50 | 51 | readWriteFiles({ 52 | from: [ 53 | path.join(process.cwd(), "/server/**/*").replace(/\\/gi, "/"), 54 | `!${path 55 | .join(process.cwd(), "/server/middleware/clientRouter/index.js") 56 | .replace(/\\/gi, "/")}` 57 | ], 58 | to: path.join(process.cwd(), "/dist/server").replace(/\\/gi, "/"), 59 | transform(content, absoluteFrom) { 60 | let reg = /.jsx|.js$/g; 61 | if (reg.test(absoluteFrom)) { 62 | return $ResolveAlias.alias(content.toString(), ""); 63 | } 64 | return content; 65 | }, 66 | callback: async () => { 67 | if (this.child && this.child.kill) { 68 | this.child.kill(); 69 | } 70 | if (iSportTake(port)) { 71 | await kill(port, "tcp"); 72 | } 73 | stabilization(1500, async () => { 74 | this.counter = this.counter >= 10 ? 2 : this.counter + 1; 75 | if (this.counter === 1) { 76 | const cmd = isSsr 77 | ? "cross-env target='ssr' npx babel-node -r @babel/register ./dist/server/index.js -r dotenv/config dotenv_config_path=.env.development" 78 | : "cross-env target='client' npx babel-node -r @babel/register ./dist/server/index.js -r dotenv/config dotenv_config_path=.env.development"; 79 | this.child = execute(cmd); 80 | } else { 81 | this.child = execute("npm run bin"); 82 | this.counter = 0; 83 | } 84 | }); 85 | } 86 | }); 87 | } 88 | production() { 89 | compiler(); 90 | } 91 | executeScript() { 92 | if (isEnvDevelopment) { 93 | this.development(); 94 | } else { 95 | this.production(); 96 | } 97 | } 98 | } 99 | 100 | new Bin(); 101 | -------------------------------------------------------------------------------- /client/App/App.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Date: 2022-08-05 09:22:30 3 | * @Author: Yao guan shou 4 | * @LastEditors: Yao guan shou 5 | * @LastEditTime: 2022-08-16 19:07:47 6 | * @FilePath: /react-ssr-lazy-loading/client/App/App.js 7 | * @Description: 8 | */ 9 | import React, { Component } from "react"; 10 | import { Provider } from "react-redux"; 11 | import Routers from "client/router"; 12 | // import { stringToObject } from "client/utils"; 13 | import "./App.less"; 14 | import "client/assets/css/base.less"; 15 | import "bootstrap/dist/css/bootstrap.css"; 16 | 17 | // let { 18 | // NODE_ENV, // 环境参数 19 | // target, // 环境参数 20 | // htmlWebpackPluginOptions = "" 21 | // } = process.env; // 环境参数 22 | 23 | class App extends Component { 24 | render() { 25 | const { history, store, routesComponent } = this.props; 26 | 27 | /* 28 | Warning: Detected multiple renderers concurrently rendering the same context provider. This is currently unsupported. 29 | 来自Provider组件 30 | */ 31 | return ( 32 | 33 | 34 | 35 | ); 36 | } 37 | componentDidCatch(error, info) { 38 | console.error("Error:", error); 39 | console.error("错误发生的文件栈:", info.componentStack); 40 | } 41 | } 42 | 43 | // App.propTypes = { 44 | // location: PropTypes.string, 45 | // store: PropTypes.object, 46 | // history: PropTypes.object, 47 | // dispatch: PropTypes.func, 48 | // state: PropTypes.object, 49 | // }; 50 | 51 | export default App; 52 | -------------------------------------------------------------------------------- /client/App/App.less: -------------------------------------------------------------------------------- 1 | html , body { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | .app { 7 | 8 | } 9 | 10 | -------------------------------------------------------------------------------- /client/App/CreateApp.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Date: 2022-08-09 09:35:04 3 | * @Author: Yao guan shou 4 | * @LastEditors: Yao guan shou 5 | * @LastEditTime: 2022-08-16 19:22:55 6 | * @FilePath: /react-ssr-lazy-loading/client/App/CreateApp.js 7 | * @Description: 8 | */ 9 | import React from "react"; 10 | import PropTypes from "prop-types"; 11 | // import { Capture } from "client/component/Loadable"; 12 | import App from "./App.js"; 13 | import "./App.less"; 14 | 15 | // const { 16 | // // target // 环境参数 17 | // } = process.env; // 环境参数 18 | 19 | const CreateApp = (props = {}) => { 20 | // const { modules } = props; 21 | 22 | return ; 23 | }; 24 | 25 | CreateApp.propTypes = { 26 | modules: PropTypes.object 27 | }; 28 | export default CreateApp; 29 | -------------------------------------------------------------------------------- /client/App/index.js: -------------------------------------------------------------------------------- 1 | import CreateApp from "./CreateApp"; 2 | export default CreateApp; 3 | -------------------------------------------------------------------------------- /client/assets/css/base.less: -------------------------------------------------------------------------------- 1 | .center-box{ 2 | width: 1400px; 3 | margin: 0 auto; 4 | height: auto; 5 | overflow: visible; 6 | } -------------------------------------------------------------------------------- /client/assets/img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ygs-code/react-ssr-lazy-loading/3864111032b1fccb8524e755c3918e385fcb211e/client/assets/img/1.jpg -------------------------------------------------------------------------------- /client/assets/img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ygs-code/react-ssr-lazy-loading/3864111032b1fccb8524e755c3918e385fcb211e/client/assets/img/2.jpg -------------------------------------------------------------------------------- /client/assets/js/request/baseUrl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2020-11-11 11:21:09 4 | * @LastEditTime: 2022-08-11 19:45:04 5 | * @LastEditors: Yao guan shou 6 | * @Description: In User Settings Edit 7 | * @FilePath: /react-loading-ssr/client/assets/js/request/baseUrl.js 8 | */ 9 | const env = process.env.NODE_ENV; // 环境参数 10 | let baseUrl = ""; 11 | if (env === "development") { 12 | baseUrl = "https://api.apiopen.top"; 13 | } 14 | if (env === "production") { 15 | baseUrl = "https://api.apiopen.top"; 16 | } 17 | 18 | export default baseUrl; 19 | -------------------------------------------------------------------------------- /client/assets/js/request/filterGraphqlData.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-08-20 10:51:16 4 | * @LastEditTime: 2022-08-10 18:45:31 5 | * @LastEditors: Yao guan shou 6 | * @Description: In User Settings Edit 7 | * @FilePath: /react-loading-ssr/client/assets/js/request/filterGraphqlData.js 8 | */ 9 | export default (data) => { 10 | for (const key in data) { 11 | if (data.hasOwnProperty(key)) { 12 | const { code } = data[key]; 13 | if (code === 200) { 14 | return data[key]; 15 | } 16 | } 17 | } 18 | return {}; 19 | }; 20 | -------------------------------------------------------------------------------- /client/assets/js/request/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2020-11-11 11:21:09 4 | * @LastEditTime: 2021-09-30 12:01:16 5 | * @LastEditors: your name 6 | * @Description: In User Settings Edit 7 | * @FilePath: /error-sytem/client/src/common/js/request/index.js 8 | */ 9 | import XMLHttpRequest from "./XMLHttpRequest"; 10 | import baseUrl from "./baseUrl"; 11 | // 合并成一个对象 12 | // import * as requestApi from './requestApi'; 13 | // 改名接口导入 14 | // export { register as Register } from './requestApi'; 15 | 16 | export { 17 | XMLHttpRequest, 18 | baseUrl, 19 | // 改名导出 20 | // baseUrl as $baseUrl 21 | }; 22 | 23 | // 整体输出 24 | export * from "./requestApi"; 25 | -------------------------------------------------------------------------------- /client/assets/js/request/redirect.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-08-12 14:33:50 4 | * @LastEditTime: 2022-08-11 19:45:17 5 | * @LastEditors: Yao guan shou 6 | * @Description: In User Settings Edit 7 | * @FilePath: /react-loading-ssr/client/assets/js/request/redirect.js 8 | */ 9 | import token from "./token"; 10 | 11 | export const codeMap = { 12 | // 没有权限跳转到登录页面 13 | 401: (errorInfo) => { 14 | const XHRQueue = (errorInfo && errorInfo[2] && errorInfo[2].XHRQueue) || []; 15 | // localStorage.removeItem("token"); 16 | token.clearQueue(); 17 | // 停止剩余的请求 18 | for (let index = XHRQueue.length - 1; index >= 0; index--) { 19 | XHRQueue[index].xmlHttp && XHRQueue[index].xmlHttp.abort(); 20 | XHRQueue.splice(index, 1); 21 | } 22 | // 重定向到登录页面 23 | // historyPush({ 24 | // url: routePaths.logLn, 25 | // }); 26 | }, 27 | 415: () => { 28 | // historyPush( 29 | // url: routePaths.logLn, 30 | // }); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /client/assets/js/request/requestApi.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2020-12-14 10:03:45 4 | * @LastEditTime: 2022-08-11 19:09:21 5 | * @LastEditors: Yao guan shou 6 | * @Description: In User Settings Edit 7 | * @FilePath: /react-loading-ssr/client/assets/js/request/requestApi.js 8 | */ 9 | import Request from "./request"; 10 | // import filterGraphqlData from "./filterGraphqlData"; 11 | 12 | // const userId = "559645cd1a38532d14349246"; 13 | 14 | // 获取验证码 15 | export const getHaoKanVideo = (parameter) => 16 | Request.get("/api/getHaoKanVideo", parameter); 17 | 18 | // 19 | export const getWeather = (parameter) => 20 | Request.get("/v3/weather/weatherInfo", parameter, { 21 | baseUrl: "https://restapi.amap.com" 22 | }); 23 | 24 | // 登录 25 | export const login = (parameter) => Request.post("/set/user/login", parameter); 26 | -------------------------------------------------------------------------------- /client/assets/js/request/requestMessage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2020-12-03 17:37:54 4 | * @LastEditTime: 2022-08-05 16:52:54 5 | * @LastEditors: Yao guan shou 6 | * @Description: In User Settings Edit 7 | * @FilePath: /react-loading-ssr/client/assets/js/request/requestMessage.js 8 | */ 9 | // import { message, Button, Space } from "antd"; 10 | 11 | export const error = (msg) => { 12 | console.error(msg); 13 | }; 14 | 15 | export const warning = (msg) => { 16 | console.warning(msg); 17 | }; 18 | 19 | export const success = (msg) => { 20 | console.success(msg); 21 | }; 22 | -------------------------------------------------------------------------------- /client/assets/js/request/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Date: 2022-08-06 11:29:32 3 | * @Author: Yao guan shou 4 | * @LastEditors: Yao guan shou 5 | * @LastEditTime: 2022-08-06 14:39:04 6 | * @FilePath: /react-loading-ssr/client/assets/js/request/test.js 7 | * @Description: 8 | */ 9 | 10 | // const superagent = require('superagent'); 11 | import superagent from "superagent"; 12 | import FormData from "form-data"; 13 | import fetch from "node-fetch"; 14 | import XMLHttpRequest from "./XMLHttpRequest"; 15 | 16 | new XMLHttpRequest().xhRequest({ 17 | url: "http://127.0.0.1:3100/api/set/user/login", 18 | parameter: { 19 | password: "guan13688426", 20 | username: "qq281113270", 21 | verificationCode: "U8C2W" 22 | }, 23 | method: "POST", 24 | headers: { "Content-Type": "application/json" }, 25 | success: (data) => { 26 | console.log("data1==========", data); 27 | } 28 | }); 29 | 30 | // const ajax = async () => { 31 | // const body = { 32 | // password: 'guan13688426', 33 | // username: 'qq281113270', 34 | // verificationCode: 'FCfq6', 35 | // } 36 | 37 | // const formData = new FormData() 38 | // formData.append('greeting', 'Hello, world!') 39 | 40 | // // const formData = new FormData() 41 | // // // const parameters = new URLSearchParams(await this.text()); 42 | 43 | // // for (const [name, value] of body) { 44 | // // formData.append(name, value) 45 | // // } 46 | 47 | // // console.log('formData====', formData) 48 | 49 | // // const body = {a: 1}; 50 | // const response = await fetch('http://127.0.0.1:3100/api/set/user/login', { 51 | // method: 'POST', 52 | // body: formData, //JSON.stringify( body), 53 | // headers: { 'Content-Type': 'application/json' }, 54 | // }) 55 | // // const data = await response.json() 56 | // console.log(response) 57 | 58 | // // superagent 59 | // // .post('http://127.0.0.1:3100/api/set/user/login') 60 | // // .send(body) // sends a JSON post body 61 | 62 | // // .end(function (err, res) { 63 | // // console.log('err========',err) 64 | // // console.log('res========',res) 65 | 66 | // // // Calling the end function will send the request 67 | // // }); 68 | // } 69 | 70 | // ajax() 71 | -------------------------------------------------------------------------------- /client/assets/js/request/test1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Date: 2022-08-12 10:01:17 3 | * @Author: Yao guan shou 4 | * @LastEditors: Yao guan shou 5 | * @LastEditTime: 2022-08-15 13:01:18 6 | * @FilePath: /react-ssr-lazy-loading/client/assets/js/request/test1.js 7 | * @Description: 8 | */ 9 | new Promise((resolve, reject) => { 10 | let appliance = new window.XDomainRequest(); 11 | appliance.onprogress = function () {}; // no aborting 12 | appliance.ontimeout = function () { 13 | // alert("timeout") 14 | reject({ eror: "timeout" }); 15 | }; // " 16 | appliance.onload = function (e) { 17 | // do something with appliance.responseText 18 | // alert("onload" + appliance.responseText) 19 | resolve(appliance.responseText); 20 | }; 21 | appliance.onerror = function (e, b) { 22 | // error handling 23 | // alert("error" + JSON.stringify(e) + JSON.stringify(b)) 24 | reject({ eror: e }); 25 | }; 26 | //appliance.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 27 | appliance.withCredentials = true; // to support sending cookies with CORS 28 | appliance.open("POST", axios.defaults.baseURL + url); 29 | appliance.send(dataToString); 30 | }); 31 | -------------------------------------------------------------------------------- /client/assets/js/request/token.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-09-29 11:46:06 4 | * @LastEditTime: 2022-08-11 19:17:43 5 | * @LastEditors: Yao guan shou 6 | * @Description: In User Settings Edit 7 | * @FilePath: /react-loading-ssr/client/assets/js/request/token.js 8 | */ 9 | 10 | class Token { 11 | constructor(doNotToken = []) { 12 | this.queue = []; 13 | // 配置不需要token的请求 14 | this.doNotToken = [ 15 | ...doNotToken, 16 | "/v3/weather/weatherInfo", 17 | "/set/user/getVerifyCode", 18 | "/set/user/login", 19 | "/api/getHaoKanVideo" 20 | ]; 21 | } 22 | 23 | subscribeQueue(resolve) { 24 | this.queue.push(resolve); 25 | } 26 | 27 | publishQueue(token) { 28 | this.queue.forEach((item) => { 29 | const { resolve } = item; 30 | resolve(token); 31 | }); 32 | this.queue = []; 33 | } 34 | 35 | clearQueue() { 36 | this.queue.forEach((item) => { 37 | const { reject } = item; 38 | reject(null); 39 | }); 40 | this.queue = []; 41 | } 42 | 43 | get(url) { 44 | const token = ""; // localStorage.getItem("token"); 45 | 46 | if (!url) { 47 | return token; 48 | } 49 | return new Promise((resolve, reject) => { 50 | if (token) { 51 | return resolve(token); 52 | } 53 | if (this.doNotToken.includes(url)) { 54 | return resolve(""); 55 | } 56 | this.subscribeQueue({ resolve, reject }); 57 | }); 58 | } 59 | } 60 | 61 | export { Token }; 62 | export default new Token(); 63 | -------------------------------------------------------------------------------- /client/component/Head/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Date: 2022-08-05 09:22:30 3 | * @Author: Yao guan shou 4 | * @LastEditors: Yao guan shou 5 | * @LastEditTime: 2022-08-15 13:01:26 6 | * @FilePath: /react-ssr-lazy-loading/client/component/Head/index.js 7 | * @Description: 8 | */ 9 | import React, { useEffect } from "react"; 10 | import PropTypes from "prop-types"; 11 | import { mapRedux } from "client/redux"; 12 | import "./index.less"; 13 | import { getWeather } from "client/assets/js/request/requestApi"; 14 | const Index = (props) => { 15 | const { state: { head: { weather = {} } = {} } = {} } = props; 16 | useEffect(() => { 17 | if (Object.keys(weather).length === 0) { 18 | Index.getInitPropsState(); 19 | } 20 | }, []); 21 | const { city, province, casts = [] } = weather; 22 | return ( 23 |
24 |
25 |
26 |
27 | {province}-{city} 28 |
29 | 30 | {casts.map((item, index) => { 31 | const { 32 | date, 33 | nighttemp, 34 | daytemp, 35 | nightpower, 36 | nightweather, 37 | dayweather 38 | } = item; 39 | return ( 40 |
41 |
42 | 日期: 43 | {date} 44 |
45 |
46 | 气温: 47 | {nighttemp}~{daytemp} 48 |
49 |
无持续风向:{nightpower}级
50 |
{nightweather}
51 |
52 | {dayweather}转{nightweather} 53 |
54 |
55 | ); 56 | })} 57 |
58 |
59 |
60 | ); 61 | }; 62 | 63 | Index.getInitPropsState = async (props = {}) => { 64 | const { 65 | dispatch: { head: { setWeather } = {} } = {}, 66 | match: { params: { page = 1, size = 10 } = {} } = {} 67 | } = props; 68 | 69 | return await getWeather({ 70 | key: "2d935fc56c5f9ab2ef2165822cedff56", 71 | city: "440300", 72 | extensions: "all" 73 | }) 74 | .then((data) => { 75 | setWeather({ 76 | weather: data.forecasts[0] 77 | }); 78 | return data; 79 | }) 80 | .catch((err) => { 81 | console.log("Error: ", err.message); 82 | }); 83 | }; 84 | 85 | Index.propTypes = { 86 | history: PropTypes.object, 87 | dispatch: PropTypes.func, 88 | state: PropTypes.object 89 | }; 90 | export default mapRedux()(Index); 91 | -------------------------------------------------------------------------------- /client/component/Head/index.less: -------------------------------------------------------------------------------- 1 | .head-box { 2 | background: white; 3 | width: 100%; 4 | border-bottom: 1px #ccc solid; 5 | .head { 6 | padding: 20px 0; 7 | dl { 8 | padding: 0; 9 | margin: 0; 10 | display: flex; 11 | dt { 12 | flex: 1; 13 | padding: 0; 14 | margin: 0; 15 | text-align: center; 16 | } 17 | dd { 18 | flex: 1; 19 | padding: 0; 20 | margin: 0; 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/component/InitState/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useCallback } from "react"; 2 | import routesComponent, { routesConfigs } from "client/router/routesComponent"; 3 | import { matchPath } from "client/router/react-lazy-router-dom"; 4 | import { mapRedux } from "client/redux"; 5 | import { findTreeData } from "client/utils"; 6 | 7 | // 注入initState 8 | const InitState = (props) => { 9 | const { 10 | children = () => {}, 11 | history: { location: { pathname } = {} } = {}, 12 | state = {}, 13 | dispatch 14 | } = props; 15 | const { baseInitState = {} } = state; 16 | 17 | const getMatch = useCallback((routesArray, url) => { 18 | for (let router of routesArray) { 19 | let $router = matchPath(url, { 20 | path: router.path, 21 | exact: router.exact 22 | }); 23 | 24 | if ($router) { 25 | return { 26 | ...router, 27 | ...$router 28 | }; 29 | } 30 | } 31 | }, []); 32 | 33 | // 获取组件初始化数据 34 | const findInitData = useCallback((routesConfigs, value, key) => { 35 | return (findTreeData(routesConfigs, value, key) || {}).initState; 36 | }, []); 37 | 38 | const getInitState = useCallback(async () => { 39 | let { name } = getMatch(routesComponent, pathname); 40 | if ( 41 | state[name]?.initState && 42 | state[name]?.initState instanceof Object && 43 | Object.keys(state[name]?.initState).length 44 | ) { 45 | return false; 46 | } 47 | let initStateFn = findInitData(routesConfigs, name, "name"); 48 | if (initStateFn && initStateFn instanceof Function) { 49 | let data = await initStateFn(); 50 | dispatch[name].setInitState({ 51 | initState: data 52 | }); 53 | } 54 | }, []); 55 | 56 | useEffect(() => { 57 | // getBaseInitState(dispatch, state); 58 | getInitState(); 59 | }, []); 60 | return <> {children(props)}; 61 | }; 62 | 63 | export default mapRedux()(InitState); 64 | -------------------------------------------------------------------------------- /client/component/LazyLoadingImg/index.less: -------------------------------------------------------------------------------- 1 | .lazy-loading-img { 2 | .card-group-box { 3 | height: auto; 4 | overflow: visible; 5 | list-style: none; 6 | padding: 0; 7 | margin: 0; 8 | li { 9 | box-sizing: border-box; 10 | width: 33.33%; 11 | display: inline-block; 12 | vertical-align: top; 13 | 14 | } 15 | li:nth-child(1) { 16 | padding-right: 10px; 17 | } 18 | li:nth-child(2) { 19 | padding: 0 5px; 20 | } 21 | li:nth-child(3) { 22 | padding-left: 10px; 23 | } 24 | 25 | .card-box { 26 | display: block; 27 | margin-top: 10px; 28 | margin-bottom: 10px; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client/component/Loadable/LICENSE: -------------------------------------------------------------------------------- 1 | COPYRIGHT (c) 2017-present James Kyle 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /client/component/Loadable/babel.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/babel'); 2 | -------------------------------------------------------------------------------- /client/component/Loadable/lib/babel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.__esModule = true; 4 | 5 | exports.default = function (_ref) { 6 | var t = _ref.types, 7 | template = _ref.template; 8 | 9 | return { 10 | visitor: { 11 | ImportDeclaration: function ImportDeclaration(path) { 12 | var source = path.node.source.value; 13 | if (source !== 'react-loadable') return; 14 | 15 | var defaultSpecifier = path.get('specifiers').find(function (specifier) { 16 | return specifier.isImportDefaultSpecifier(); 17 | }); 18 | 19 | if (!defaultSpecifier) return; 20 | 21 | var bindingName = defaultSpecifier.node.local.name; 22 | var binding = path.scope.getBinding(bindingName); 23 | 24 | binding.referencePaths.forEach(function (refPath) { 25 | var callExpression = refPath.parentPath; 26 | 27 | if (callExpression.isMemberExpression() && callExpression.node.computed === false && callExpression.get('property').isIdentifier({ name: 'Map' })) { 28 | callExpression = callExpression.parentPath; 29 | } 30 | 31 | if (!callExpression.isCallExpression()) return; 32 | 33 | var args = callExpression.get('arguments'); 34 | if (args.length !== 1) throw callExpression.error; 35 | 36 | var options = args[0]; 37 | if (!options.isObjectExpression()) return; 38 | 39 | var properties = options.get('properties'); 40 | var propertiesMap = {}; 41 | 42 | properties.forEach(function (property) { 43 | var key = property.get('key'); 44 | propertiesMap[key.node.name] = property; 45 | }); 46 | 47 | if (propertiesMap.webpack) { 48 | return; 49 | } 50 | 51 | var loaderMethod = propertiesMap.loader.get('value'); 52 | var dynamicImports = []; 53 | 54 | loaderMethod.traverse({ 55 | Import: function Import(path) { 56 | dynamicImports.push(path.parentPath); 57 | } 58 | }); 59 | 60 | if (!dynamicImports.length) return; 61 | 62 | propertiesMap.loader.insertAfter(t.objectProperty(t.identifier('webpack'), t.arrowFunctionExpression([], t.arrayExpression(dynamicImports.map(function (dynamicImport) { 63 | return t.callExpression(t.memberExpression(t.identifier('require'), t.identifier('resolveWeak')), [dynamicImport.get('arguments')[0].node]); 64 | }))))); 65 | 66 | propertiesMap.loader.insertAfter(t.objectProperty(t.identifier('modules'), t.arrayExpression(dynamicImports.map(function (dynamicImport) { 67 | return dynamicImport.get('arguments')[0].node; 68 | })))); 69 | }); 70 | } 71 | } 72 | }; 73 | }; -------------------------------------------------------------------------------- /client/component/Loadable/lib/webpack.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function _classCallCheck(instance, Constructor) { 4 | if (!(instance instanceof Constructor)) { 5 | throw new TypeError('Cannot call a class as a function'); 6 | } 7 | } 8 | 9 | var fs = require('fs'); 10 | var path = require('path'); 11 | var url = require('url'); 12 | 13 | function buildManifest(compiler, compilation) { 14 | var context = compiler.options.context; 15 | var manifest = {}; 16 | 17 | compilation.chunks.forEach(function (chunk) { 18 | chunk.files.forEach(function (file) { 19 | chunk.forEachModule(function (module) { 20 | var id = module.id; 21 | var name = 22 | typeof module.libIdent === 'function' 23 | ? module.libIdent({ context: context }) 24 | : null; 25 | var publicPath = url.resolve( 26 | compilation.outputOptions.publicPath || '', 27 | file 28 | ); 29 | 30 | var currentModule = module; 31 | if (module.constructor.name === 'ConcatenatedModule') { 32 | currentModule = module.rootModule; 33 | } 34 | if (!manifest[currentModule.rawRequest]) { 35 | manifest[currentModule.rawRequest] = []; 36 | } 37 | 38 | manifest[currentModule.rawRequest].push({ 39 | id: id, 40 | name: name, 41 | file: file, 42 | publicPath: publicPath, 43 | }); 44 | }); 45 | }); 46 | }); 47 | 48 | return manifest; 49 | } 50 | 51 | var ReactLoadablePlugin = (function () { 52 | function ReactLoadablePlugin() { 53 | var opts = 54 | arguments.length > 0 && arguments[0] !== undefined 55 | ? arguments[0] 56 | : {}; 57 | 58 | _classCallCheck(this, ReactLoadablePlugin); 59 | 60 | this.filename = opts.filename; 61 | } 62 | 63 | ReactLoadablePlugin.prototype.apply = function apply(compiler) { 64 | var _this = this; 65 | 66 | compiler.plugin('emit', function (compilation, callback) { 67 | var manifest = buildManifest(compiler, compilation); 68 | var json = JSON.stringify(manifest, null, 2); 69 | var outputDirectory = path.dirname(_this.filename); 70 | try { 71 | fs.mkdirSync(outputDirectory); 72 | } catch (err) { 73 | if (err.code !== 'EEXIST') { 74 | throw err; 75 | } 76 | } 77 | fs.writeFileSync(_this.filename, json); 78 | callback(); 79 | }); 80 | }; 81 | 82 | return ReactLoadablePlugin; 83 | })(); 84 | 85 | function getBundles(manifest, moduleIds) { 86 | return moduleIds.reduce(function (bundles, moduleId) { 87 | return bundles.concat(manifest[moduleId]); 88 | }, []); 89 | } 90 | 91 | exports.ReactLoadablePlugin = ReactLoadablePlugin; 92 | exports.getBundles = getBundles; 93 | -------------------------------------------------------------------------------- /client/component/Loadable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-loadable", 3 | "version": "5.5.0", 4 | "description": "A higher order component for loading components with promises", 5 | "main": "lib/index.js", 6 | "author": "James Kyle ", 7 | "license": "MIT", 8 | "repository": "thejameskyle/react-loadable", 9 | "files": [ 10 | "babel.js", 11 | "webpack.js", 12 | "lib/**" 13 | ], 14 | "scripts": { 15 | "test": "jest --coverage", 16 | "build": "babel src -d lib", 17 | "start": "yarn build && webpack && babel-node example/server.js", 18 | "prepublish": "yarn build" 19 | }, 20 | "dependencies": { 21 | "prop-types": "^15.5.0" 22 | }, 23 | "devDependencies": { 24 | "babel-cli": "^6.24.1", 25 | "babel-loader": "^7.1.2", 26 | "babel-plugin-dynamic-import-node": "^1.1.0", 27 | "babel-plugin-module-resolver": "^2.7.1", 28 | "babel-plugin-transform-async-to-generator": "^6.24.1", 29 | "babel-plugin-transform-class-properties": "^6.24.1", 30 | "babel-plugin-transform-object-assign": "^6.22.0", 31 | "babel-preset-es2015": "^6.24.1", 32 | "babel-preset-react": "^6.24.1", 33 | "express": "^4.16.1", 34 | "flow-bin": "^0.41.0", 35 | "jest": "^21.2.1", 36 | "react": "^16.0.0", 37 | "react-dom": "^16.0.0", 38 | "react-test-renderer": "^16.0.0", 39 | "webpack": "^3.6.0" 40 | }, 41 | "peerDependencies": { 42 | "react": "*" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /client/component/Loadable/webpack.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/webpack'); 2 | -------------------------------------------------------------------------------- /client/component/Loading/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Date: 2022-08-01 09:57:50 3 | * @Author: Yao guan shou 4 | * @LastEditors: Yao guan shou 5 | * @LastEditTime: 2022-08-11 19:16:17 6 | * @FilePath: /react-loading-ssr/client/component/Loading/index.js 7 | * @Description: 8 | */ 9 | import React from "react"; 10 | 11 | export default (props) => { 12 | const { error } = props; 13 | 14 | return error ?
Error:{error}
:
Loading...
; 15 | }; 16 | -------------------------------------------------------------------------------- /client/component/Nav/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Date: 2022-08-05 09:22:30 3 | * @Author: Yao guan shou 4 | * @LastEditors: Yao guan shou 5 | * @LastEditTime: 2022-08-15 13:01:55 6 | * @FilePath: /react-ssr-lazy-loading/client/component/Nav/index.js 7 | * @Description: 8 | */ 9 | import React from "react"; 10 | import { Nav, NavItem, NavLink } from "reactstrap"; 11 | import PropTypes from "prop-types"; 12 | import { mapRedux } from "client/redux"; 13 | import addRouterApi from "client/router/addRouterApi"; 14 | import "./index.less"; 15 | 16 | const Index = (props) => { 17 | const { 18 | dispatch: { nav: { setMenuActive } = {} } = {}, 19 | match: { params = {}, path: matchPath } = {}, 20 | pushRoute 21 | } = props; 22 | 23 | return ( 24 |
25 | 71 |
72 | ); 73 | }; 74 | 75 | Index.getInitPropsState = async (props = {}) => { 76 | const { 77 | dispatch: { nav: { setMenuActive } = {} } = {}, 78 | match: { path: matchPath } = {} 79 | } = props; 80 | setMenuActive({ 81 | menuActive: matchPath 82 | }); 83 | }; 84 | 85 | Index.propTypes = { 86 | history: PropTypes.object, 87 | dispatch: PropTypes.func, 88 | state: PropTypes.object 89 | }; 90 | export default mapRedux()(addRouterApi(Index)); 91 | -------------------------------------------------------------------------------- /client/component/Nav/index.less: -------------------------------------------------------------------------------- 1 | .navigate-box { 2 | margin-top: 10px; 3 | .navigate { 4 | width: 800px; 5 | .nav-link{ 6 | cursor: pointer; 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /client/component/SetMetaProps/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import hoistStatics from "hoist-non-react-statics"; 3 | export default (options = {}) => { 4 | return (Traget) => { 5 | const displayName = 6 | "SetMetaProps(" + (Traget.displayName || Traget.name) + ")"; 7 | class SetMetaProps extends Component { 8 | componentDidMount() { 9 | const { 10 | title = "", 11 | keywords = "", 12 | description = "" 13 | } = Traget.getMetaProps ? Traget.getMetaProps() : options || {}; 14 | this.setAttribute( 15 | window.document.querySelector('meta[name="keywords"]'), 16 | "content", 17 | keywords 18 | ); 19 | this.setAttribute( 20 | window.document.querySelector('meta[name="description"]'), 21 | "content", 22 | description 23 | ); 24 | window.document.title = title; 25 | } 26 | setAttribute = (el, key, value) => { 27 | el.setAttribute(key, value); 28 | }; 29 | render() { 30 | return ; 31 | } 32 | } 33 | SetMetaProps.displayName = displayName; 34 | SetMetaProps.WrappedComponent = Traget; 35 | Traget.getMetaProps = Traget.getMetaProps 36 | ? Traget.getMetaProps 37 | : () => options; 38 | return hoistStatics(SetMetaProps, Traget); 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /client/component/Table/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Date: 2022-08-11 09:41:40 3 | * @Author: Yao guan shou 4 | * @LastEditors: Yao guan shou 5 | * @LastEditTime: 2022-08-15 13:02:13 6 | * @FilePath: /react-ssr-lazy-loading/client/component/Table/index.js 7 | * @Description: 8 | */ 9 | import React from "react"; 10 | import "./index.less"; 11 | 12 | const Index = (props) => { 13 | const { columns = [], dataSource = [], rowKey } = props; 14 | 15 | return ( 16 |
17 |
18 |
19 |
    20 | {columns.map((item, index) => { 21 | const { title } = item; 22 | return
  • {title}
  • ; 23 | })} 24 |
25 |
26 |
27 | {dataSource.map((item, index) => { 28 | return ( 29 |
    30 | {columns.map(($item, $index) => { 31 | const { key, render } = $item; 32 | return ( 33 |
  • 34 | {render ? render(item[key], item, index) : item[key]} 35 |
  • 36 | ); 37 | })} 38 |
39 | ); 40 | })} 41 |
42 |
43 |
44 | ); 45 | }; 46 | 47 | export default Index; 48 | -------------------------------------------------------------------------------- /client/component/Table/index.less: -------------------------------------------------------------------------------- 1 | .table { 2 | dl { 3 | dt { 4 | background: rgb(250, 250, 250); 5 | li { 6 | border: 1px solid rgba(0, 0, 0, 0.06); 7 | } 8 | } 9 | 10 | dd { 11 | } 12 | ul { 13 | list-style: none; 14 | display: flex; 15 | padding: 0; 16 | margin: 0; 17 | height: 50px; 18 | line-height: 50px; 19 | text-indent: 5px; 20 | 21 | li { 22 | flex: 1; 23 | border: 1px solid rgba(0, 0, 0, 0.06); 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client/component/lazy/index.js: -------------------------------------------------------------------------------- 1 | const lazy = (loader) => { 2 | lazy.loaderArr = [...lazy.loaderArr, loader]; 3 | 4 | return () => { 5 | return loader() 6 | .then((res) => { 7 | return res.default; 8 | }) 9 | .catch((e) => { 10 | console.error("Error:", e); 11 | }); 12 | }; 13 | }; 14 | lazy.loaderArr = []; 15 | 16 | const preloadReady = (onSuccess = () => {}, onError = () => {}) => { 17 | const promiseArr = []; 18 | for (let item of lazy.loaderArr) { 19 | promiseArr.push(item()); 20 | } 21 | 22 | return Promise.all(promiseArr) 23 | .then(() => { 24 | onSuccess(); 25 | }) 26 | .catch((error) => { 27 | console.log("error:", error); 28 | onError(error); 29 | }); 30 | }; 31 | 32 | export { preloadReady }; 33 | export default lazy; 34 | -------------------------------------------------------------------------------- /client/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ygs-code/react-ssr-lazy-loading/3864111032b1fccb8524e755c3918e385fcb211e/client/favicon.ico -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Date: 2022-08-04 09:21:17 3 | * @Author: Yao guan shou 4 | * @LastEditors: Yao guan shou 5 | * @LastEditTime: 2022-08-16 19:12:21 6 | * @FilePath: /react-ssr-lazy-loading/client/index.js 7 | * @Description: 8 | */ 9 | import React from "react"; 10 | import { createRoot, hydrateRoot } from "react-dom/client"; 11 | import App from "./App/index.js"; 12 | import { getBrowserHistory } from "client/router/history"; 13 | import store from "client/redux"; 14 | import routesComponent from "client/router/routesComponent"; 15 | 16 | // 如果是开发环境 先拷贝 服务器文件到 dist 17 | let { 18 | target // 环境参数 19 | } = process.env; // 环境参数 20 | 21 | const isSsr = target === "ssr"; 22 | 23 | const renderApp = () => { 24 | const history = getBrowserHistory(); 25 | 26 | if (isSsr && !module.hot) { 27 | hydrateRoot( 28 | document.getElementById("root"), 29 | 30 | 37 | ); 38 | } else { 39 | createRoot(document.getElementById("root")).render( 40 | 47 | ); 48 | } 49 | }; 50 | 51 | // node 服务器中只能在这个页面使用window 52 | window.main = () => { 53 | // preloadReady().then(() => { 54 | renderApp(); 55 | // }); 56 | }; 57 | 58 | // // // // 只有当开启了模块热替换时 module.hot 才存在 59 | // if (module.hot) { 60 | // // accept 函数的第一个参数指出当前文件接受哪些子模块的替换,这里表示只接受 ./AppComponent 这个子模块 61 | // // 第2个参数用于在新的子模块加载完毕后需要执行的逻辑 62 | // module.hot.accept(["./App/index.js"], () => { 63 | // console.log("有个模块更新"); 64 | // renderApp(); 65 | // // 新的 AppComponent 加载成功后重新执行下组建渲染逻辑 66 | // // let App=require('./App').default; 67 | // // ReactDOM.render(, document.getElementById('root')); 68 | // }); 69 | // } 70 | 71 | // window.store = store; 72 | -------------------------------------------------------------------------------- /client/pages/Home/index.less: -------------------------------------------------------------------------------- 1 | .home { 2 | } 3 | -------------------------------------------------------------------------------- /client/pages/User/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Date: 2022-08-05 09:22:30 3 | * @Author: Yao guan shou 4 | * @LastEditors: Yao guan shou 5 | * @LastEditTime: 2022-08-15 13:03:13 6 | * @FilePath: /react-ssr-lazy-loading/client/pages/User/index.js 7 | * @Description: 8 | */ 9 | import React, { useEffect } from "react"; 10 | import setMetaProps from "client/component/SetMetaProps"; 11 | import Nav from "client/component/Nav"; 12 | import Head from "client/component/Head"; 13 | import Table from "client/component/Table"; 14 | import { mapRedux } from "client/redux"; 15 | import "./index.less"; 16 | 17 | const Index = () => { 18 | useEffect(() => {}, []); 19 | 20 | return ( 21 |
22 | 23 |