├── .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 |
24 |
92 |
93 | );
94 | };
95 |
96 | Index.getInitPropsState = async (props = {}) => {
97 | const {
98 | dispatch: { home: { setInitState } = {} } = {},
99 | match: { params: { page = 1, size = 10 } = {} } = {}
100 | } = props;
101 |
102 | await Head.getInitPropsState(props);
103 | await Nav.getInitPropsState(props);
104 | };
105 |
106 | Index.getMetaProps = () => {
107 | return {
108 | title: "用户页面",
109 | keywords: "用户页面网站关键词",
110 | description: "用户页面网站描述"
111 | };
112 | };
113 | export default mapRedux()(setMetaProps()(Index));
114 |
--------------------------------------------------------------------------------
/client/pages/User/index.less:
--------------------------------------------------------------------------------
1 | .user {
2 | }
3 |
--------------------------------------------------------------------------------
/client/pages/marketing/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-15 13:02:51
6 | * @FilePath: /react-ssr-lazy-loading/client/pages/marketing/index.js
7 | * @Description:
8 | */
9 | import React from "react";
10 |
11 | const Index = () => {
12 | return 营销菜单页面
;
13 | };
14 |
15 | export default Index;
16 |
--------------------------------------------------------------------------------
/client/pages/marketing/pages/DiscountCoupon/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import Nav from "client/component/Nav";
3 | import Head from "client/component/Head";
4 | import setMetaProps from "client/component/SetMetaProps";
5 | import { mapRedux } from "client/redux";
6 | const Index = () => {
7 | useEffect(() => {
8 | console.log("DiscountCoupon");
9 | }, []);
10 | return (
11 |
12 |
13 |
14 |
15 | 当前页面是DiscountCoupon页面
16 |
17 | );
18 | };
19 |
20 | Index.getInitPropsState = async (props = {}) => {
21 | const {
22 | dispatch: { home: { setInitState } = {} },
23 | match: { params: { page = 1, size = 10 } = {} } = {}
24 | } = props;
25 |
26 | await Head.getInitPropsState(props);
27 | await Nav.getInitPropsState(props);
28 | };
29 |
30 | export default mapRedux()(
31 | setMetaProps({
32 | title: "优惠券页面",
33 | keywords: "优惠券页面网站关键词",
34 | description: "优惠券页面网站描述"
35 | })(Index)
36 | );
37 |
--------------------------------------------------------------------------------
/client/pages/marketing/router/routesConfig.js:
--------------------------------------------------------------------------------
1 | // 路由配置
2 | export default [
3 | // {
4 | // path: '/',
5 | // exact: true,
6 | // name: 'home',
7 | // entry: `/pages/marketing/index.js`,
8 | // level: 2,
9 | // },
10 | {
11 | path: "/marketing",
12 | exact: true,
13 | name: "marketing",
14 | entry: "/pages/marketing/index.js",
15 | level: 2,
16 | children: [
17 | {
18 | path: "/discount-coupon/:id",
19 | name: "DiscountCoupon",
20 | entry: "/pages/marketing/pages/DiscountCoupon/index.js",
21 | level: 2,
22 | children: []
23 | }
24 | ]
25 | }
26 | ];
27 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ygs-code/react-ssr-lazy-loading/3864111032b1fccb8524e755c3918e385fcb211e/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | <%= htmlWebpackPlugin.options.title %>
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ygs-code/react-ssr-lazy-loading/3864111032b1fccb8524e755c3918e385fcb211e/client/public/logo192.png
--------------------------------------------------------------------------------
/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ygs-code/react-ssr-lazy-loading/3864111032b1fccb8524e755c3918e385fcb211e/client/public/logo512.png
--------------------------------------------------------------------------------
/client/redux/index.js:
--------------------------------------------------------------------------------
1 | import { init } from "@rematch/core";
2 | import { connect } from "react-redux";
3 | import * as models from "./models";
4 | import { CheckDataType } from "client/utils";
5 |
6 | const mapRedux = (modelsName) => (Component) => {
7 | const mapStateToProps = (state) => {
8 | let newState = {};
9 | if (CheckDataType.isUndefined(modelsName)) {
10 | newState = state;
11 | } else if (CheckDataType.isArray(modelsName)) {
12 | for (const key of modelsName) {
13 | if (state[key]) {
14 | newState[key] = state[key];
15 | }
16 | }
17 | } else if (CheckDataType.isString(modelsName)) {
18 | if (state[modelsName]) {
19 | newState[modelsName] = state[modelsName];
20 | }
21 | }
22 |
23 | return {
24 | state: newState
25 | };
26 | };
27 |
28 | const mapDispatchToProps = (dispatch) => {
29 | let newDispatch = {};
30 | if (CheckDataType.isUndefined(modelsName)) {
31 | newDispatch = dispatch;
32 | } else if (CheckDataType.isArray(modelsName)) {
33 | for (const key of modelsName) {
34 | if (dispatch[key]) {
35 | newDispatch[key] = dispatch[key];
36 | }
37 | }
38 | } else if (CheckDataType.isString(modelsName)) {
39 | if (dispatch[modelsName]) {
40 | newDispatch[modelsName] = dispatch[modelsName];
41 | }
42 | }
43 |
44 | return {
45 | dispatch: newDispatch
46 | };
47 | };
48 | return connect(mapStateToProps, mapDispatchToProps)(Component);
49 | };
50 |
51 | const getPropsState = (global, modelKey, dataKey = "initState") => {
52 | let initState = {};
53 | if (
54 | global &&
55 | global.__INITIAL_STATE__ &&
56 | global.__INITIAL_STATE__[modelKey] &&
57 | global.__INITIAL_STATE__[modelKey][dataKey]
58 | ) {
59 | initState = global.__INITIAL_STATE__[modelKey][dataKey];
60 | }
61 |
62 | return initState;
63 | };
64 |
65 | export { mapRedux, getPropsState };
66 |
67 | export const createStore = (global) => {
68 | const newModels = {};
69 | /* eslint-disable */
70 | for (const key in models) {
71 | // if (models.hasOwnProperty(key)) {
72 | newModels[key] = models[key](global);
73 | // }
74 | }
75 | /* eslint-enable */
76 | // 文档: https://www.icode9.com/content-4-1343821.html
77 | return init({
78 | models: newModels
79 | });
80 | };
81 |
82 | let $global = {};
83 | try {
84 | if (window) {
85 | $global = window;
86 | }
87 | } catch (error) {
88 | // 不要挂载在global中不然会很慢
89 | $global = {};
90 | }
91 |
92 | export default createStore($global); // (global || {})
93 |
--------------------------------------------------------------------------------
/client/redux/models/head.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-08-15 09:17:55
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-15 14:03:23
6 | * @FilePath: /react-ssr-lazy-loading/client/redux/models/home.js
7 | * @Description:
8 | */
9 | import { getPropsState } from "../index";
10 |
11 | export default (global) => ({
12 | state: {
13 | weather: getPropsState(global, "head", "weather"),
14 | count: 0
15 | },
16 | reducers: {
17 | setCount(state, newState) {
18 | return {
19 | ...state,
20 | count: newState
21 | };
22 | },
23 |
24 | setWeather(state, newState) {
25 | return {
26 | ...state,
27 | ...newState
28 | };
29 | }
30 | },
31 | effects: {
32 | // async incrementAsync(num1, rootState, num2) {
33 | // /*
34 | // 第二个变量rootState, 为当前model的state的值
35 | // 第一个变量num1, 第三个变量num2分别, 调用incrementAsync时传递进来的第一个参数, 第二个参数,后面依次类推。
36 | // 例如:dispatch.count.incrementAsync(10, 20)时,num1 = 10, num2 = 20
37 | // */
38 | // // await new Promise((resolve) => setTimeout(resolve, 2000));
39 | // // this.increment(num1);
40 | // },
41 | // },
42 | // effects: (dispatch) => ({
43 | // async incrementAsync(num1, rootState, num2) {
44 | // await new Promise((resolve) => setTimeout(resolve, 2000))
45 | // // 方式一
46 | // // this.increment(num1);
47 | // // 方式二
48 | // dispatch.count.increment(num1)
49 | // },
50 | // }),
51 | }
52 | });
53 |
--------------------------------------------------------------------------------
/client/redux/models/home.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-08-15 09:17:55
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-15 14:03:23
6 | * @FilePath: /react-ssr-lazy-loading/client/redux/models/home.js
7 | * @Description:
8 | */
9 | import { getPropsState } from "../index";
10 |
11 | export default (global) => ({
12 | state: {
13 | imgData: getPropsState(global, "home", "imgData"),
14 | count: 0
15 | },
16 | reducers: {
17 | setCount(state, newState) {
18 | return {
19 | ...state,
20 | count: newState
21 | };
22 | },
23 |
24 | setImgData(state, newState) {
25 | return {
26 | ...state,
27 | ...newState
28 | };
29 | }
30 | },
31 | effects: {
32 | // async incrementAsync(num1, rootState, num2) {
33 | // /*
34 | // 第二个变量rootState, 为当前model的state的值
35 | // 第一个变量num1, 第三个变量num2分别, 调用incrementAsync时传递进来的第一个参数, 第二个参数,后面依次类推。
36 | // 例如:dispatch.count.incrementAsync(10, 20)时,num1 = 10, num2 = 20
37 | // */
38 | // // await new Promise((resolve) => setTimeout(resolve, 2000));
39 | // // this.increment(num1);
40 | // },
41 | // },
42 | // effects: (dispatch) => ({
43 | // async incrementAsync(num1, rootState, num2) {
44 | // await new Promise((resolve) => setTimeout(resolve, 2000))
45 | // // 方式一
46 | // // this.increment(num1);
47 | // // 方式二
48 | // dispatch.count.increment(num1)
49 | // },
50 | // }),
51 | }
52 | });
53 |
--------------------------------------------------------------------------------
/client/redux/models/index.js:
--------------------------------------------------------------------------------
1 | export { default as home } from "./home";
2 | export { default as head } from "./head";
3 | export { default as nav } from "./nav";
4 |
--------------------------------------------------------------------------------
/client/redux/models/nav.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-08-15 09:17:55
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-15 14:03:23
6 | * @FilePath: /react-ssr-lazy-loading/client/redux/models/home.js
7 | * @Description:
8 | */
9 | import { getPropsState } from "../index";
10 | export default (global) => ({
11 | state: {
12 | menuActive: getPropsState(global, "nav", "menuActive")
13 | },
14 | reducers: {
15 | setMenuActive(state, newState) {
16 | return {
17 | ...state,
18 | ...newState
19 | };
20 | }
21 | },
22 | effects: {
23 | // async incrementAsync(num1, rootState, num2) {
24 | // /*
25 | // 第二个变量rootState, 为当前model的state的值
26 | // 第一个变量num1, 第三个变量num2分别, 调用incrementAsync时传递进来的第一个参数, 第二个参数,后面依次类推。
27 | // 例如:dispatch.count.incrementAsync(10, 20)时,num1 = 10, num2 = 20
28 | // */
29 | // // await new Promise((resolve) => setTimeout(resolve, 2000));
30 | // // this.increment(num1);
31 | // },
32 | // },
33 | // effects: (dispatch) => ({
34 | // async incrementAsync(num1, rootState, num2) {
35 | // await new Promise((resolve) => setTimeout(resolve, 2000))
36 | // // 方式一
37 | // // this.increment(num1);
38 | // // 方式二
39 | // dispatch.count.increment(num1)
40 | // },
41 | // }),
42 | }
43 | });
44 |
--------------------------------------------------------------------------------
/client/router/Routers.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-08-11 09:41:40
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-16 19:13:35
6 | * @FilePath: /react-ssr-lazy-loading/client/router/Routers.js
7 | * @Description:
8 | */
9 | import React from "react";
10 | import PropTypes from "prop-types";
11 | import Loading from "client/component/Loading";
12 | import InitState from "client/component/InitState";
13 | import {
14 | Router,
15 | Switch as Routes,
16 | Route
17 | } from "client/router/react-lazy-router-dom";
18 |
19 | const Routers = (props) => {
20 | const { history, routesComponent = [] } = props;
21 | return (
22 |
23 |
24 | {routesComponent.map((route) => {
25 | let { path, exact = true, Component } = route;
26 | return (
27 |
28 | );
29 | })}
30 |
34 | There s nothing here!
35 |
36 | }
37 | />
38 |
39 |
40 | );
41 | };
42 |
43 | Routers.propTypes = {
44 | history: PropTypes.object.isRequired,
45 | dispatch: PropTypes.func,
46 | state: PropTypes.object,
47 | context: PropTypes.object
48 | };
49 | export default Routers;
50 |
--------------------------------------------------------------------------------
/client/router/addRouterApi.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-08-11 09:41:40
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-11 19:11:14
6 | * @FilePath: /react-loading-ssr/client/router/addRouterApi.js
7 | * @Description:
8 | */
9 | import React from "react";
10 | import hoistStatics from "hoist-non-react-statics";
11 | import { withRouter } from "client/router/react-lazy-router-dom";
12 | import routePaths from "./routePaths";
13 | import { historyPush } from "./historyPush";
14 |
15 | const addRouterApi = (Component) => {
16 | const displayName =
17 | "withRouter(" + (Component.displayName || Component.name) + ")";
18 | class AddRouter extends React.Component {
19 | constructor(props) {
20 | super(props);
21 | }
22 |
23 | pushRoute = (parameter) => {
24 | const { name, url, path } = parameter;
25 | const { history } = this.props;
26 | historyPush({
27 | history,
28 | ...parameter,
29 | url: routePaths[name] || url || path
30 | });
31 | };
32 |
33 | render() {
34 | return (
35 |
40 | );
41 | }
42 | }
43 |
44 | AddRouter.displayName = displayName;
45 | AddRouter.WrappedComponent = Component;
46 | return hoistStatics(withRouter(AddRouter), Component);
47 | };
48 |
49 | @withRouter
50 | class AddRouterApi extends React.Component {
51 | constructor(props) {
52 | super(props);
53 | }
54 |
55 | pushRoute = (parameter) => {
56 | const { name, url, path } = parameter;
57 | const { history } = this.props;
58 |
59 | historyPush({
60 | history,
61 | ...parameter,
62 | url: routePaths[name] || url || path
63 | });
64 | };
65 |
66 | render() {
67 | const { children } = this.props;
68 | return (
69 | <>
70 | {children({
71 | ...this.props,
72 | routePaths: routePaths,
73 | pushRoute: this.pushRoute
74 | })}
75 | >
76 | );
77 | }
78 | }
79 |
80 | export { AddRouterApi };
81 |
82 | export default addRouterApi;
83 |
--------------------------------------------------------------------------------
/client/router/history.js:
--------------------------------------------------------------------------------
1 | import { createBrowserHistory, createMemoryHistory } from "history";
2 |
3 | export const getBrowserHistory = (props = {}) =>
4 | createBrowserHistory({
5 | basename: "/", // 基链接
6 | forceRefresh: false, // 是否强制刷新整个页面
7 | // keyLength: 10, // location.key的长度
8 | // getUserConfirmation: (message,callback) => callback(window.confirm(message)) // 跳转拦截函数
9 | ...props
10 | });
11 |
12 | export const getMemoryHistory = (props = {}) => createMemoryHistory(props);
13 |
--------------------------------------------------------------------------------
/client/router/index.js:
--------------------------------------------------------------------------------
1 | import Routers from "./Routers";
2 | import addRouterApi from "./addRouterApi";
3 |
4 | export { addRouterApi };
5 | export default Routers;
6 |
--------------------------------------------------------------------------------
/client/router/react-lazy-router-dom/Route.js:
--------------------------------------------------------------------------------
1 | import React, { Children } from "react";
2 | import PropTypes from "prop-types";
3 | const Route = (props) => {
4 | const { children } = props;
5 | return children
6 | ? Children.map(children, (child) => {
7 | return <>{child} >;
8 | })
9 | : null;
10 | };
11 |
12 | Route.propTypes = {
13 | // key: PropTypes.string.isRequired,
14 | // exact: PropTypes.bool.isRequired,
15 | component: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired
16 | };
17 |
18 | export { Route };
19 |
--------------------------------------------------------------------------------
/client/router/react-lazy-router-dom/Router.js:
--------------------------------------------------------------------------------
1 | import {
2 | createContext,
3 | Component,
4 | createElement,
5 | Children
6 | // isValidElement
7 | } from "react";
8 | import PropTypes from "prop-types";
9 | var createNamedContext = function createNamedContext(name) {
10 | var context = createContext();
11 | context.displayName = name;
12 | return context;
13 | };
14 |
15 | const RouterContext = createNamedContext("Router");
16 |
17 | class Router extends Component {
18 | constructor(props) {
19 | super(props);
20 |
21 | const { history, staticContext } = this.props;
22 |
23 | this.state = {
24 | location: history.location
25 | };
26 |
27 | this._isMounted = false;
28 | this._pendingLocation = null;
29 |
30 | if (!staticContext) {
31 | this.unlisten = history.listen(({ location }) => {
32 | this._pendingLocation = location;
33 | });
34 | }
35 |
36 | return this;
37 | }
38 | componentDidMount() {
39 | this._isMounted = true;
40 |
41 | if (this.unlisten) {
42 | this.unlisten();
43 | }
44 |
45 | if (!this.props.staticContext) {
46 | this.unlisten = this.props.history.listen(({ location }) => {
47 | if (this._isMounted) {
48 | this.setState({
49 | location: location
50 | });
51 | }
52 | });
53 | }
54 |
55 | if (this._pendingLocation) {
56 | this.setState({
57 | location: this._pendingLocation
58 | });
59 | }
60 | }
61 |
62 | componentWillUnmount() {
63 | if (this.unlisten) {
64 | this.unlisten();
65 | this._isMounted = false;
66 | this._pendingLocation = null;
67 | }
68 | }
69 | computeRootMatch = (pathname) => {
70 | return {
71 | path: "/",
72 | url: "/",
73 | params: {},
74 | isExact: pathname === "/"
75 | };
76 | };
77 | render() {
78 | const { children, staticContext, loading, history } = this.props;
79 | const { location } = this.state;
80 | /* eslint-disable */
81 | return createElement(RouterContext.Provider, {
82 | value: {
83 | history,
84 | location,
85 | staticContext,
86 | loading
87 | },
88 | children: children ? Children.only(children) : null
89 | });
90 | /* eslint-enable */
91 | }
92 | }
93 |
94 | Router.propTypes = {
95 | history: PropTypes.object.isRequired,
96 | staticContext: PropTypes.object,
97 | loading: function (props, propName, componentName) {
98 | if (!props[propName]) {
99 | return new Error(
100 | `In component ${componentName} props ${propName} type is invalid -- expected a ${propName} is component`
101 | );
102 | }
103 | try {
104 | createElement(props[propName]);
105 | } catch (error) {
106 | return new Error(
107 | `In component ${componentName} props ${propName} type is invalid -- expected a ${propName} is component`
108 | );
109 | }
110 | }
111 | };
112 |
113 | export { Router, RouterContext as __RouterContext };
114 |
--------------------------------------------------------------------------------
/client/router/react-lazy-router-dom/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | generatePath,
3 | Prompt,
4 | MemoryRouter,
5 | Redirect,
6 | StaticRouter,
7 | useHistory,
8 | useLocation,
9 | useParams,
10 | useRouteMatch
11 | } from "react-router-dom";
12 | export * from "./Switch";
13 | export * from "./Router";
14 | export * from "./Route";
15 | export * from "./withRouter";
16 | export * from "./matchPath";
17 | export * from "./lazy";
18 |
--------------------------------------------------------------------------------
/client/router/react-lazy-router-dom/lazy.js:
--------------------------------------------------------------------------------
1 | const lazy = (loader) => {
2 | lazy.loaderArr = [...lazy.loaderArr, loader];
3 | return () => {
4 | return loader()
5 | .then((res) => {
6 | return res.default;
7 | })
8 | .catch((e) => {
9 | throw new Error(e);
10 | });
11 | };
12 | };
13 | lazy.loaderArr = [];
14 |
15 | const preloadReady = (onSuccess = () => {}, onError = () => {}) => {
16 | const promiseArr = [];
17 | for (let item of lazy.loaderArr) {
18 | promiseArr.push(item());
19 | }
20 |
21 | return Promise.all(promiseArr)
22 | .then(() => {
23 | onSuccess();
24 | })
25 | .catch((error) => {
26 | // console.log("error:", error);
27 | onError(error);
28 | throw new Error(error);
29 | });
30 | };
31 |
32 | export { preloadReady };
33 | export { lazy };
34 | export default lazy;
35 |
--------------------------------------------------------------------------------
/client/router/react-lazy-router-dom/matchPath.js:
--------------------------------------------------------------------------------
1 | import pathToRegexp from "path-to-regexp";
2 |
3 | var cache = {};
4 | var cacheLimit = 10000;
5 | var cacheCount = 0;
6 |
7 | const compilePath = (path, options) => {
8 | var cacheKey = "" + options.end + options.strict + options.sensitive;
9 | var pathCache = cache[cacheKey] || (cache[cacheKey] = {});
10 | if (pathCache[path]) {
11 | return pathCache[path];
12 | }
13 | var keys = [];
14 | var regexp = pathToRegexp(path, keys, options);
15 | var result = {
16 | regexp: regexp,
17 | keys: keys
18 | };
19 |
20 | if (cacheCount < cacheLimit) {
21 | pathCache[path] = result;
22 | cacheCount++;
23 | }
24 |
25 | return result;
26 | };
27 |
28 | export const matchPath = (pathname, options = {}) => {
29 | if (typeof options === "string" || Array.isArray(options)) {
30 | options = {
31 | path: options
32 | };
33 | }
34 |
35 | const {
36 | path = false,
37 | exact = false,
38 | strict = false,
39 | sensitive = false
40 | } = options;
41 |
42 | var paths = [].concat(path);
43 | return paths.reduce(function (matched, path) {
44 | if (!path && path !== "") {
45 | return null;
46 | }
47 | if (matched) {
48 | return matched;
49 | }
50 |
51 | var { regexp, keys } = compilePath(path, {
52 | end: exact,
53 | strict: strict,
54 | sensitive: sensitive
55 | });
56 |
57 | var match = regexp.exec(pathname);
58 | if (!match) {
59 | return null;
60 | }
61 | var url = match[0],
62 | values = match.slice(1);
63 | var isExact = pathname === url;
64 | if (exact && !isExact) {
65 | return null;
66 | }
67 | return {
68 | path: path,
69 | // the path used to match
70 | url: path === "/" && url === "" ? "/" : url,
71 | // the matched portion of the URL
72 | isExact: isExact,
73 | // whether or not we matched exactly
74 | params: keys.reduce(function (memo, key, index) {
75 | memo[key.name] = values[index];
76 | return memo;
77 | }, {})
78 | };
79 | }, null);
80 | };
81 |
--------------------------------------------------------------------------------
/client/router/react-lazy-router-dom/withRouter.js:
--------------------------------------------------------------------------------
1 | import React, { Component, createElement } from "react";
2 | import invariant from "tiny-invariant";
3 | import hoistStatics from "hoist-non-react-statics";
4 | import { MatchContext } from "./Switch";
5 |
6 | export const withRouter = (Target) => {
7 | const displayName = "withRouter(" + (Target.displayName || Target.name) + ")";
8 | class WithRouter extends Component {
9 | render() {
10 | return createElement(MatchContext.Consumer, null, (context) => {
11 | !context
12 | ? invariant(
13 | false,
14 | "You should not use <" + displayName + " /> outside a "
15 | )
16 | : void 0;
17 |
18 | return ;
19 | });
20 | }
21 | }
22 |
23 | WithRouter.displayName = displayName;
24 | WithRouter.WrappedComponent = Target;
25 | return hoistStatics(WithRouter, Target);
26 | };
27 |
--------------------------------------------------------------------------------
/client/router/routePaths.js:
--------------------------------------------------------------------------------
1 | export default {
2 | DiscountCoupon: "/marketing/discount-coupon/:id",
3 | marketing: "/marketing",
4 | home: "/",
5 | user: "/user"
6 | };
7 |
--------------------------------------------------------------------------------
/client/router/routesComponent.js:
--------------------------------------------------------------------------------
1 | // 按需加载插件
2 | import { lazy } from "client/router/react-lazy-router-dom";
3 | import pagesMarketingRouterRoutesconfig from "client/pages/marketing/router/routesConfig.js";
4 | import routerRoutesconfig from "client/router/routesConfig.js";
5 |
6 | let routesComponentConfig = [
7 | {
8 | path: "/marketing/discount-coupon/:id",
9 | exact: false,
10 | name: "DiscountCoupon",
11 | entry: "/pages/marketing/pages/DiscountCoupon/index.js",
12 | Component: lazy(() =>
13 | import(
14 | /* webpackChunkName:"DiscountCoupon" */ "client/pages/marketing/pages/DiscountCoupon/index.js"
15 | )
16 | ),
17 | // syncComponent:Discountcoupon,
18 | level: 2,
19 | routesConfigPath:
20 | "/Users/admin/Documents/code/react-ssr-lazy-loading/client/pages/marketing/router/routesConfig.js"
21 | },
22 | {
23 | path: "/marketing",
24 | exact: true,
25 | name: "marketing",
26 | entry: "/pages/marketing/index.js",
27 | Component: lazy(() =>
28 | import(
29 | /* webpackChunkName:"marketing" */ "client/pages/marketing/index.js"
30 | )
31 | ),
32 | // syncComponent:Marketing,
33 | level: 2,
34 | routesConfigPath:
35 | "/Users/admin/Documents/code/react-ssr-lazy-loading/client/pages/marketing/router/routesConfig.js"
36 | },
37 | {
38 | path: "/",
39 | exact: true,
40 | name: "home",
41 | entry: "/pages/Home/index.js",
42 | Component: lazy(() =>
43 | import(/* webpackChunkName:"home" */ "client/pages/Home/index.js")
44 | ),
45 | // syncComponent:Home,
46 | level: 1,
47 | routesConfigPath:
48 | "/Users/admin/Documents/code/react-ssr-lazy-loading/client/router/routesConfig.js"
49 | },
50 | {
51 | path: "/user",
52 | exact: false,
53 | name: "user",
54 | entry: "/pages/User/index.js",
55 | Component: lazy(() =>
56 | import(/* webpackChunkName:"user" */ "client/pages/User/index.js")
57 | ),
58 | // syncComponent:User,
59 | level: 1,
60 | routesConfigPath:
61 | "/Users/admin/Documents/code/react-ssr-lazy-loading/client/router/routesConfig.js"
62 | }
63 | ];
64 |
65 | export const routesConfigs = [
66 | ...pagesMarketingRouterRoutesconfig,
67 | ...routerRoutesconfig
68 | ];
69 |
70 | export default routesComponentConfig;
71 |
--------------------------------------------------------------------------------
/client/router/routesConfig.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-08-01 17:29:00
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-11 15:55:18
6 | * @FilePath: /react-loading-ssr/client/router/routesConfig.js
7 | * @Description:
8 | */
9 | // 路由配置
10 | export default [
11 | {
12 | path: "/",
13 | exact: true,
14 | name: "home",
15 | entry: "/pages/Home/index.js",
16 | level: 1
17 | },
18 | {
19 | path: "/user",
20 | name: "user",
21 | entry: "/pages/User/index.js",
22 | level: 1
23 | }
24 | ];
25 |
--------------------------------------------------------------------------------
/client/static/1/2.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ygs-code/react-ssr-lazy-loading/3864111032b1fccb8524e755c3918e385fcb211e/client/static/1/2.txt
--------------------------------------------------------------------------------
/client/static/3.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ygs-code/react-ssr-lazy-loading/3864111032b1fccb8524e755c3918e385fcb211e/client/static/3.txt
--------------------------------------------------------------------------------
/client/static/a.js:
--------------------------------------------------------------------------------
1 |
2 | import b from "./b";
3 |
4 |
5 | b()
--------------------------------------------------------------------------------
/client/static/b.js:
--------------------------------------------------------------------------------
1 | import c from "./c";
2 |
3 |
4 | export default c
--------------------------------------------------------------------------------
/client/static/c.js:
--------------------------------------------------------------------------------
1 | import d from "./d";
2 |
3 |
4 | export default d
--------------------------------------------------------------------------------
/client/static/d.js:
--------------------------------------------------------------------------------
1 | export default () => {
2 | console.log("ddddddddd");
3 | };
4 |
--------------------------------------------------------------------------------
/client/static/img/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ygs-code/react-ssr-lazy-loading/3864111032b1fccb8524e755c3918e385fcb211e/client/static/img/logo512.png
--------------------------------------------------------------------------------
/client/static/js/errCatch.js:
--------------------------------------------------------------------------------
1 | var errorMessageStr = "Uncaught SyntaxError: Unexpected token ' < '";
2 |
3 | window.addEventListener("unhandledrejection", function (event) {
4 | var { reason: { message } = {} } = event;
5 | var isErrorReload = sessionStorage.getItem("pageErrorReload");
6 | if (
7 | message &&
8 | (message.search(errorMessageStr) >= 0 ||
9 | message.search("ChunkLoadError") >= 0) &&
10 | (!isErrorReload || isErrorReload !== "true")
11 | ) {
12 | sessionStorage.setItem("pageErrorReload", "true");
13 | window.location.reload();
14 | }
15 | });
16 |
17 | window.addEventListener("error", function (event) {
18 | var eventMsg = event.message || "";
19 | var isErrorReload = sessionStorage.getItem("pageErrorReload");
20 | if (
21 | (eventMsg.includes(errorMessageStr) ||
22 | eventMsg.includes("ChunkLoadError")) &&
23 | (!isErrorReload || isErrorReload !== "true")
24 | ) {
25 | sessionStorage.setItem("pageErrorReload", "true");
26 | window.location.reload();
27 | }
28 | });
29 |
--------------------------------------------------------------------------------
/client/utils/CheckDataType.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-08-01 09:57:50
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-10 19:39:48
6 | * @FilePath: /react-loading-ssr/client/utils/CheckDataType.js
7 | * @Description:
8 | */
9 | // js校验数据类型
10 |
11 | export class CheckDataType {
12 | static init(type) {
13 | // 校验的数据类型
14 | this.dataType = {
15 | string: "string",
16 | number: "number",
17 | boolean: "boolean",
18 | null: "null",
19 | undefined: "undefined",
20 | symbol: "symbol",
21 | object: "object",
22 | array: "array",
23 | regexp: "regexp",
24 | function: "function",
25 | promise: "promise"
26 | };
27 | return (
28 | (type in this.dataType &&
29 | this.dataType.hasOwnProperty(type) &&
30 | this.dataType[type]) ||
31 | false
32 | );
33 | }
34 |
35 | static isString(data) {
36 | return !!(
37 | this.init("string") && this.init("string") === this.checkType(data)
38 | );
39 | }
40 |
41 | static isNumber(data) {
42 | return !!(
43 | this.init("number") && this.init("number") === this.checkType(data)
44 | );
45 | }
46 |
47 | static isBoolean(data) {
48 | return !!(
49 | this.init("boolean") && this.init("boolean") === this.checkType(data)
50 | );
51 | }
52 |
53 | static isNull(data) {
54 | return !!(this.init("null") && this.init("null") === this.checkType(data));
55 | }
56 |
57 | static isUndefined(data) {
58 | return !!(
59 | this.init("undefined") && this.init("undefined") === this.checkType(data)
60 | );
61 | }
62 |
63 | static isSymbol(data) {
64 | return !!(
65 | this.init("symbol") && this.init("symbol") === this.checkType(data)
66 | );
67 | }
68 |
69 | static isObject(data) {
70 | return !!(
71 | this.init("object") && this.init("object") === this.checkType(data)
72 | );
73 | }
74 |
75 | static isArray(data) {
76 | return !!(
77 | this.init("array") && this.init("array") === this.checkType(data)
78 | );
79 | }
80 |
81 | static isRegexp(data) {
82 | return !!(
83 | this.init("regexp") && this.init("regexp") === this.checkType(data)
84 | );
85 | }
86 |
87 | static isFunction(data) {
88 | return !!(
89 | this.init("function") && this.init("function") === this.checkType(data)
90 | );
91 | }
92 |
93 | static isPromise(data) {
94 | return !!(
95 | this.init("promise") && this.init("promise") === this.checkType(data)
96 | );
97 | }
98 |
99 | static checkType(data) {
100 | return Object.prototype.toString.call(data).toLowerCase().slice(8, -1);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/client/utils/SubscribePublished.js:
--------------------------------------------------------------------------------
1 |
2 | import {CheckDataType} from './CheckDataType';
3 | //按方式 kye 导出
4 |
5 | const SubscribePublished = function (type = null, options = {}) {
6 | return this instanceof SubscribePublished || new SubscribePublished(...arguments).init(...arguments);
7 | };
8 |
9 | SubscribePublished.prototype = {
10 | init(type = null, options = {}) {
11 | const defaultOptions = {
12 | queueMaxLength: 1000, //防止内存溢出限制数组长度
13 | };
14 | this.options = {
15 | ...defaultOptions,
16 | ...options,
17 | };
18 | this.type = type;
19 | this.queue = {}; // 记录回调函数队列
20 | return this;
21 | },
22 | getData(type) {
23 | type = type || this.type;
24 | // 获取数据
25 | return (type in this.queue && this.queue.hasOwnProperty(type) && this.queue[type].data) || null;
26 | },
27 | setData(type, data) {
28 | if (this.getData(type) === null || this.getData(type) !== data) {
29 | this.queue[type] = {
30 | ...(this.queue[type] || {}),
31 | data,
32 | };
33 | }
34 | },
35 | on(
36 | type, //类型
37 | callback, //回调函数
38 | isSuperposition = true //队列中函数是否叠加
39 | ) {
40 | if ((CheckDataType.isFunction(type) && CheckDataType.isBoolean(callback)) || !callback) {
41 | isSuperposition = callback;
42 | callback = type;
43 | type = this.type;
44 | }
45 |
46 | if (type in this.queue && this.queue.hasOwnProperty(type) && this.queue[type].fn instanceof Array && isSuperposition) {
47 | // 如果大于设置长度我们将踢掉一些旧的数据
48 | this.queue[type].fn.length >= this.options.queueMaxLength && this.queue[type].fn.shift();
49 | this.queue[type].fn.push(callback);
50 | } else {
51 | this.queue[type] = {
52 | ...(this.queue[type] || {}),
53 | fn: [callback],
54 | };
55 | }
56 | return this;
57 | },
58 | emit(type) {
59 | let args = [...arguments].slice(1);
60 | if (type !== this.type || args.length === 1) {
61 | args = [type];
62 | type = this.type;
63 | }
64 | if (
65 | type in this.queue &&
66 | this.queue.hasOwnProperty(type) &&
67 | this.queue[type] &&
68 | this.queue[type].fn &&
69 | this.queue[type].fn instanceof Array &&
70 | (this.getData(type) === null || this.getData(type) !== args)
71 | ) {
72 | this.queue[type].fn.forEach((callback) => {
73 | callback.apply(null, args);
74 | });
75 | // delete this.queue[type]
76 | }
77 | this.setData(type, args);
78 | return this;
79 | },
80 | };
81 |
82 | export {SubscribePublished};
83 |
--------------------------------------------------------------------------------
/client/utils/createStore.js:
--------------------------------------------------------------------------------
1 | import {
2 | SubscribePublished
3 | } from './SubscribePublished'
4 |
5 | export const IndateStore = SubscribePublished('indate');
--------------------------------------------------------------------------------
/client/utils/getBaseInitState.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-08-01 17:29:00
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-11 19:52:33
6 | * @FilePath: /react-loading-ssr/client/utils/getBaseInitState.js
7 | * @Description: ()
8 | */
9 | // 这种方式需要前后命名一致才行能做到这样效果
10 | export const getBaseInitState = async (dispatch, state, props) => {
11 | const dispatchBaseInitState = dispatch.baseInitState;
12 | // 函数命名必须是这样
13 | const reg = /(?<=^get)(.+?)(?=Async$)/g;
14 | // let reg1 = /Async$/g;
15 | for (const key in dispatchBaseInitState) {
16 | if (Object.prototype.hasOwnProperty.call(dispatchBaseInitState, key)) {
17 | let dataKey = key.match(reg);
18 | if (dataKey) {
19 | dataKey =
20 | dataKey[0].substr(0, 1).toLocaleLowerCase() + dataKey[0].substr(1);
21 | if (state.baseInitState[dataKey]) {
22 | return false;
23 | }
24 | await dispatchBaseInitState[key](props);
25 | }
26 | }
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/client/utils/getCssAttr.js:
--------------------------------------------------------------------------------
1 | const getStyle = (ele, attr) => {
2 | let style = null;
3 | try {
4 | if (window.getComputedStyle) {
5 | style = window.getComputedStyle(ele, null);
6 | } else {
7 | style = ele.currentStyle;
8 | }
9 | return attr ? style[attr] : style;
10 | } catch (error) {
11 | return {};
12 | }
13 | };
14 |
15 | export { getStyle };
16 |
--------------------------------------------------------------------------------
/client/utils/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-08-01 17:29:00
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-06 13:40:32
6 | * @FilePath: /react-loading-ssr/client/utils/index.js
7 | * @Description:
8 | */
9 | import {
10 | throttle,
11 | stabilization,
12 | statusThrottle
13 | } from "./throttlingStabilization";
14 | // import {
15 | // deepCopy
16 | // } from './deepCopy'
17 | // import {SubscribePublished} from './SubscribePublished';
18 | import { CheckDataType } from "./CheckDataType";
19 | import {
20 | filterTreeData,
21 | recursionTreeData,
22 | deepCopy,
23 | diffData,
24 | findTreeData
25 | } from "./ergodic";
26 | // import {FloatingBall} from './FloatingBall';
27 | import { getStyle } from "./getCssAttr";
28 | import {
29 | checkPhone,
30 | checkUser,
31 | checkPassword,
32 | checkVerificationCode,
33 | firstToUpper
34 | } from "./regular";
35 | import { getBaseInitState } from "./getBaseInitState";
36 | import stringToObject from "./stringToObject";
37 |
38 | export {
39 | stringToObject,
40 | getBaseInitState,
41 | // FloatingBall, // 浮动球 类
42 | throttle, // 节流函数
43 | stabilization, // 防抖函数
44 | statusThrottle, // 状态拦截器
45 | deepCopy, // 深度拷贝
46 | // SubscribePublished, // 订阅发布
47 | CheckDataType, // 检查数据类型
48 | filterTreeData, // 过滤树数据结构
49 | recursionTreeData, // 递归循环树数据
50 | diffData, // 比较新旧两个数据
51 | findTreeData, // 搜索到树数据的某一条数据单条 不包括父层数据的
52 | getStyle, // 获取样式
53 | checkPhone,
54 | checkUser,
55 | checkPassword,
56 | checkVerificationCode,
57 | firstToUpper
58 | };
59 | // 整体输出
60 | export * from "./regular.js";
61 |
--------------------------------------------------------------------------------
/client/utils/regular.js:
--------------------------------------------------------------------------------
1 | const checkPhone = (phone) => /^1{1}[3456789]{1}\d{9}$/.test(phone);
2 | const checkUser = (name) => /^[a-z A-Z]+\w{5,}/.test(name);
3 | // 最少8个字符,包含数字和字母 最长为16个字符 ^(?=.*\d)(?=.*[a-zA-Z])[0-9a-zA-Z]{8,16}$
4 | // 最少8个字符,包含数字和字母特殊字符 最长为32个字符 (?=^.{8,32}$)(?=(?:.*?\d){1})(?=.*[a-z])(?=(?:.*?[!@#$%*()_+^&}{:;?.]){1})(?!.*\s)[0-9a-zA-Z!@#$%*()_+^&]*$
5 | const checkPassword = (name) =>
6 | /^(?=.*\d)(?=.*[a-zA-Z])[0-9a-zA-Z]{8,16}$/.test(name);
7 | const checkVerificationCode = (code) => /^([a-z A-Z]|\d){5}$/.test(code);
8 | const firstToUpper = (str) =>
9 | str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase());
10 |
11 | export {
12 | checkPhone,
13 | checkUser,
14 | checkPassword,
15 | checkVerificationCode,
16 | firstToUpper
17 | };
18 |
--------------------------------------------------------------------------------
/client/utils/resolvePath.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | function resolvePath(relative, moduleKey) {
4 | const moduleKeys = {
5 | marketing: '/pages/marketing',
6 | }
7 |
8 | const cwd = process.cwd()
9 | return path.join(cwd, relative + moduleKeys[moduleKey] || '')
10 | }
11 | module.exports = resolvePath
--------------------------------------------------------------------------------
/client/utils/stringToObject.js:
--------------------------------------------------------------------------------
1 | export default (string) => {
2 | const regex = /(?<=\{)(.+?)(?=\})/g; // {} 花括号,大括号
3 | string = string.match(regex);
4 | let obj = {};
5 | if (string) {
6 | string = string[0];
7 | let stringArr = string.split(",");
8 | for (let item of stringArr) {
9 | let [key, value] = item.split(":");
10 | obj[`${key}`] = value;
11 | }
12 | }
13 | return obj;
14 | };
15 |
--------------------------------------------------------------------------------
/client/utils/throttlingStabilization.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-08-11 09:41:40
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-11 15:54:43
6 | * @FilePath: /react-loading-ssr/client/utils/throttlingStabilization.js
7 | * @Description:
8 | */
9 | import lodash from "lodash";
10 |
11 | // 节流函数
12 | export const throttle = (() => {
13 | let startTime = null;
14 | return (time, callback) =>
15 | new Promise((resolve) => {
16 | const nowTime = new Date().getTime();
17 | if (!startTime || nowTime - startTime > time) {
18 | startTime = nowTime;
19 | if (callback && callback instanceof Function) {
20 | callback();
21 | }
22 |
23 | resolve();
24 | // callback&&callback()
25 | }
26 | });
27 | })();
28 |
29 | // 防抖函数
30 | export const stabilization = (() => {
31 | let timer = null;
32 | return (time, callback) =>
33 | new Promise((resolve) => {
34 | if (timer) {
35 | clearTimeout(timer);
36 | }
37 | timer = setTimeout(() => {
38 | if (callback && callback instanceof Function) {
39 | callback();
40 | }
41 |
42 | resolve();
43 | }, time);
44 | });
45 | })();
46 |
47 | // 因为状态拦截需要传递的是地址,所以只能传对象参数
48 | export const statusThrottle = (() => {
49 | const objParameter = {
50 | status: true
51 | };
52 | return (callback) =>
53 | new Promise((resolve, reject) => {
54 | if (!lodash.isObject(objParameter)) {
55 | console.error("objParameter参数必须是一个对象");
56 | reject();
57 | return;
58 | }
59 |
60 | if (objParameter.status === false) {
61 | reject();
62 | return;
63 | }
64 | objParameter.status = false;
65 | if (callback && callback instanceof Function) {
66 | callback(objParameter);
67 | }
68 |
69 | resolve(objParameter);
70 | });
71 | // callback && callback(objParameter)
72 | })();
73 |
--------------------------------------------------------------------------------
/client/utils/transformRoutePaths.js:
--------------------------------------------------------------------------------
1 | const removeRepeatLine = (str) => str.replace(/\/\//g, '/');
2 | export const getRoutePaths = (
3 | route,
4 | routePaths = {},
5 | prefix = '',
6 | filePath = null
7 | ) => {
8 | route.forEach((target) => {
9 | if (target['path'] && target['name']) {
10 | if (
11 | target['name'] in routePaths &&
12 | routePaths.hasOwnProperty(target['name'])
13 | ) {
14 | throw (target['filePath'] || filePath) ===
15 | routePaths[target['name']].filePath
16 | ? `在${
17 | routePaths[target['name']].filePath
18 | }路由文件中,路由name为:${
19 | target['name']
20 | }命名有相同,发生路由name冲突!`
21 | : `在${target['filePath'] || filePath}路由文件与${
22 | routePaths[target['name']].filePath
23 | }路由文件中,路由name为:${
24 | target['name']
25 | }命名有相同,发生路由name冲突!`;
26 | }
27 | routePaths[target['name']] = {
28 | filePath: target.filePath || filePath,
29 | path: removeRepeatLine(prefix + '/' + target['path']),
30 | };
31 | }
32 | if (target.children && target.children.length >= 1) {
33 | routePaths = {
34 | ...routePaths,
35 | ...getRoutePaths(
36 | target.children,
37 | routePaths,
38 | prefix + '/' + target['path'],
39 | target.filePath || filePath
40 | ),
41 | };
42 | }
43 | });
44 | return routePaths;
45 | };
46 |
47 | export const transformRoutePaths = (routePaths) => {
48 | let newRoutePaths = {};
49 | for (let key in routePaths) {
50 | if (routePaths.hasOwnProperty(key)) {
51 | newRoutePaths[key] = routePaths[key].path;
52 | }
53 | }
54 | return newRoutePaths;
55 | };
56 |
--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "delay": 1000,
3 | "watch": [
4 | "dist/*",
5 | "dist/**",
6 | "dist/**/*"
7 | ],
8 | "ignore": []
9 | }
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-08-09 09:35:05
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-16 19:11:11
6 | * @FilePath: /react-ssr-lazy-loading/server/app.js
7 | * @Description:
8 | */
9 | // import Loadable from "react-loadable"; // "client/component/Loadable";
10 | import Koa from "koa";
11 | import Router from "./router";
12 | import Middleware from "./middleware";
13 |
14 | let {
15 | NODE_ENV, // 环境参数
16 | target, // 环境参数
17 | htmlWebpackPluginOptions,
18 | port
19 | } = process.env; // 环境参数
20 |
21 | // 是否是生产环境
22 | const isEnvProduction = NODE_ENV === "production";
23 | // 是否是测试开发环境
24 | const isEnvDevelopment = NODE_ENV === "development";
25 |
26 | class App {
27 | constructor() {
28 | this.init();
29 | }
30 | init() {
31 | this.app = new Koa();
32 | this.addRouter();
33 | this.addMiddleware();
34 | this.listen();
35 | }
36 | addRouter() {
37 | new Router(this.app);
38 | }
39 | addMiddleware() {
40 | new Middleware(this.app);
41 | }
42 | listen() {
43 | const server = this.app.listen(port, function () {
44 | var port = server.address().port;
45 | console.log(
46 | `\n==> 🌎 node服务器启动成功,监听端口:${port}. 请打开浏览器 http://localhost:${port}/ \n`
47 | );
48 | });
49 | }
50 | }
51 |
52 | export default App;
53 |
--------------------------------------------------------------------------------
/server/controller/home.js:
--------------------------------------------------------------------------------
1 | import service from "../service/home";
2 |
3 | class Controller {
4 | static hello(ctx, next) {
5 | var page = ctx.params.page; // 获取请求参数
6 | //添加service
7 | const data = service.list(page);
8 |
9 | ctx.response.body = `
10 | - title:${data.title}
11 | - time:${data.time}
12 | - id:${data.id}
13 | - url:${data.url}
14 |
`;
15 | }
16 | }
17 |
18 | export default Controller;
19 |
--------------------------------------------------------------------------------
/server/controller/user.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: your name
3 | * @Date: 2020-12-07 09:39:49
4 | * @LastEditTime: 2022-08-15 13:46:09
5 | * @LastEditors: Yao guan shou
6 | * @Description: In User Settings Edit
7 | * @FilePath: /react-ssr-lazy-loading/server/controller/user.js
8 | */
9 | import userService from "../service/user";
10 | class Controller {
11 | static async add(ctx, next) {
12 | // ctx.set("Content-Type", "application/json")
13 | const parameter = ctx.request.body; // 获取请求参数
14 | //添加service
15 | const data = await userService.add(ctx, next, parameter);
16 | const getMessage = (data) => {
17 | const { status } = data;
18 | const message = {
19 | 1: () => ({
20 | ...unsupported,
21 | message: "该用户名已经被注册过,请重新输入用户名"
22 | }),
23 | 2: () => ({
24 | ...unsupported,
25 | message: "该手机号码已经被注册过,请重新输入手机号码"
26 | }),
27 | 3: () => ({
28 | code: 200,
29 | message: "注册成功"
30 | })
31 | };
32 | return message[status]();
33 | };
34 | ctx.response.body = getMessage(data);
35 | }
36 | static edit(ctx, next) {
37 | ctx.set("Content-Type", "application/json");
38 |
39 | var page = ctx.params.page; // 获取请求参数
40 | //添加service
41 | // const data = userService.list(page);
42 |
43 | // ctx.response.body = "d";
44 | }
45 |
46 | static async login(ctx, next) {
47 | // ctx.set("Content-Type", "application/json")
48 | var parameter = ctx.request.body; // 获取请求参数
49 | //添加service
50 | const data = await userService.login(ctx, next, parameter);
51 | const getMessage = (data) => {
52 | const { status, token, userInfo } = data;
53 | const message = {
54 | 1: () => ({
55 | ...unauthorized,
56 | message: "用户名错误,请重新输入用户名"
57 | }),
58 | 2: () => ({
59 | ...unauthorized,
60 | message: "密码错误请重新输入密码"
61 | }),
62 | 3: () => ({
63 | code: 200,
64 | message: "登录成功",
65 | data: {
66 | token,
67 | userInfo
68 | }
69 | })
70 | };
71 | return message[status]();
72 | };
73 | ctx.response.body = getMessage(data);
74 | }
75 | }
76 |
77 | export default Controller;
78 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-08-01 17:17:17
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-10 10:24:28
6 | * @FilePath: /react-loading-ssr/server/index.js
7 | * @Description:
8 | */
9 | // import '@babel/polyfill';
10 | // import "core-js/stable";
11 | import cluster from "cluster";
12 | import App from "./app";
13 | import os from "os";
14 | import * as dotenv from "dotenv"; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import
15 | import { Worker } from "worker_threads";
16 | // 注入环境变量
17 | dotenv.config({ path: ".env" });
18 |
19 | let {
20 | NODE_ENV, // 环境参数
21 | target, // 环境参数
22 | htmlWebpackPluginOptions
23 | } = process.env; // 环境参数
24 |
25 | const isSsr = target === "ssr";
26 | // 是否是生产环境
27 | const isEnvProduction = NODE_ENV === "production";
28 | // 是否是测试开发环境
29 | const isEnvDevelopment = NODE_ENV === "development";
30 |
31 | // 开启的子进程数
32 | const workerNum = isEnvProduction ? os.cpus().length - 1 : 1;
33 | // 如果是主进程
34 | if (cluster.isMaster) {
35 | // 创建子进程
36 | for (let i = 0; i < workerNum; i++) {
37 | // 通过cluster.fork创建子进程
38 | cluster.fork();
39 | }
40 | cluster.on("exit", (worker, code, signal) => {
41 | console.log(`worker ${worker.process.pid} died`);
42 | });
43 | // 如果有子进程,就启动相关服务,这里会使用cpu多个进程来执行http服务
44 | } else {
45 | new App();
46 | }
47 |
--------------------------------------------------------------------------------
/server/middleware/clientRouter/otherModules.js:
--------------------------------------------------------------------------------
1 | export default ["0"];
2 |
--------------------------------------------------------------------------------
/server/middleware/index.js:
--------------------------------------------------------------------------------
1 | import staticCache from "koa-static-cache";
2 | import cors from "koa2-cors";
3 | import path, { resolve } from "path";
4 |
5 | let {
6 | NODE_ENV, // 环境参数
7 | target // 环境参数
8 | } = process.env; // 环境参数
9 | // 是否是ssr
10 | const isSsr = target === "ssr";
11 | // 是否是生产环境
12 | const isEnvProduction = NODE_ENV === "production";
13 | // 是否是测试开发环境
14 | const isEnvDevelopment = NODE_ENV === "development";
15 | export default class Middleware {
16 | constructor(app) {
17 | this.app = app;
18 | this.init();
19 | }
20 | init() {
21 | this.addMiddleware();
22 | }
23 |
24 | addStaticCacheMiddleware() {
25 | this.app.use(
26 | staticCache(path.join(resolve("./"), "/dist/client"), {
27 | maxAge: 365 * 24 * 60 * 60,
28 | gzip: true
29 | })
30 | );
31 | }
32 | addClientRouterMiddleware() {
33 | const clientRouter = require("./clientRouter").default;
34 | this.app.use(clientRouter());
35 | }
36 | addCorsMiddleware() {
37 | this.app.use(cors());
38 | }
39 |
40 | async addWebpackHotMiddleware() {
41 | // 把这个代码注释掉
42 | const webpackHotPromise = await import("./webpackHot");
43 | const WebpackHot = webpackHotPromise.default;
44 | new WebpackHot(this.app);
45 | }
46 |
47 | addMiddleware() {
48 | this.addCorsMiddleware();
49 | if (isEnvDevelopment) {
50 | // 如果是生产不在这里编译
51 | this.addWebpackHotMiddleware();
52 | } else if (isSsr) {
53 | this.addStaticCacheMiddleware();
54 | this.addClientRouterMiddleware();
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/server/router/api.js:
--------------------------------------------------------------------------------
1 | import koaRouter from "koa-router";
2 | export default class Api {
3 | constructor(app) {
4 | this.app = app;
5 | this.init();
6 | }
7 | init() {
8 | this.addRouter();
9 | }
10 |
11 | // 添加菜单路由
12 | addMenuRouter() {
13 | // 修改为其他请求类型,只需要将get改成需要的类型即可
14 | this.router.get("/menu", async (ctx, next) => {
15 | ctx.body = JSON.stringify({
16 | code: 200,
17 | message: "请求成功"
18 | // data: menu(),
19 | });
20 | await next();
21 | });
22 | }
23 | addRouter() {
24 | this.router = koaRouter({
25 | prefix: "/api" // 路由前缀
26 | });
27 |
28 | this.addMenuRouter();
29 | // 加载路由中间件
30 | this.app.use(this.router.routes());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/server/router/index.js:
--------------------------------------------------------------------------------
1 | import Api from "./api";
2 |
3 | export default class Router {
4 | constructor(app) {
5 | this.app = app;
6 | this.init();
7 | }
8 | init() {
9 | this.addRouter();
10 | }
11 |
12 | addRouter() {
13 | // new Api(this.app);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/server/service/user.js:
--------------------------------------------------------------------------------
1 | import { addUser, removeUser, queryUser } from '../db/user';
2 | import { unsupported, unauthorized } from '../constant';
3 | import { merge } from '../utils';
4 | import { createToken, verifyToken, destroyToken, getUserIfo } from '@/redis';
5 | import { setExpirationTime } from '../config';
6 |
7 | class Service {
8 | static list(page) {
9 | console.log('page=', page);
10 | const dataList = {
11 | list: [
12 | {
13 | time: '2019-7-10',
14 | id: 1,
15 | title: 'this is news 1',
16 | url: '/news/1',
17 | },
18 | {
19 | time: '2019-8-10',
20 | id: 2,
21 | title: 'this is news 2',
22 | url: '/news/2',
23 | },
24 | ],
25 | };
26 |
27 | return dataList.list[page] || {};
28 | }
29 |
30 | //注册用户
31 | static async add(ctx, next, parameter) {
32 | const { username: name, phone, password } = parameter;
33 | /*
34 | 1 查询用户名是否被注册过,
35 | 2 查询手机号码是否被注册过
36 | 3 如果都没有被注册那么就可以注册
37 | */
38 | let userInfo = await this.queryUser({
39 | name,
40 | });
41 |
42 | userInfo = userInfo.length >= 1 ? userInfo[0] : null;
43 | if (userInfo && userInfo.id) {
44 | return {
45 | status: 1,
46 | };
47 | }
48 |
49 | userInfo = await this.queryUser({
50 | phone,
51 | });
52 | userInfo = userInfo.length >= 1 ? userInfo[0] : null;
53 | if (userInfo && userInfo.id) {
54 | return {
55 | status: 2,
56 | };
57 | }
58 | const data = await addUser({
59 | name,
60 | phone,
61 | password,
62 | });
63 | if (data) {
64 | return {
65 | status: 3,
66 | };
67 | }
68 | }
69 | // 编辑用户
70 | static async edit(ctx, next, parameter) {}
71 | // 数据库中查询用户
72 | static async queryUser(...ags) {
73 | const userData = await queryUser(...ags);
74 | return userData;
75 | }
76 | // 登录
77 | static async login(ctx, next, parameter = {}) {
78 | const { username: name, phone, password } = parameter;
79 | const { request, response, cookies } = ctx;
80 |
81 | /*
82 | 1.先查询用户名是否正确,
83 | 2.查询用户和密码是否正确
84 | 3.创建token,存储到redis中
85 | 4.把用户信息挂载response中
86 | */
87 | let userInfo = await this.queryUser({
88 | name,
89 | });
90 |
91 | userInfo = userInfo.length >= 1 ? userInfo[0] : null;
92 | if (!userInfo) {
93 | return {
94 | status: 1,
95 | };
96 | }
97 |
98 | userInfo = await this.queryUser({
99 | password,
100 | });
101 | userInfo = userInfo.length >= 1 ? userInfo[0] : null;
102 | if (!userInfo) {
103 | return {
104 | status: 2,
105 | };
106 | }
107 | userInfo = await queryUser({
108 | name,
109 | password,
110 | });
111 | userInfo = userInfo.length >= 1 ? userInfo[0] : null;
112 |
113 | /*
114 | 创建 createToken
115 | */
116 | const token = await createToken(userInfo);
117 | delete userInfo.password;
118 | ctx.response.userInfo = userInfo;
119 |
120 |
121 |
122 | cookies.set('token', token, {
123 | httpOnly: false,
124 | overwrite: false,
125 | expires: setExpirationTime(),
126 | // domain: 'http://localhost/',
127 | });
128 | if (userInfo) {
129 | //登录成功
130 | return {
131 | status: 3,
132 | token,
133 | userInfo,
134 | };
135 | }
136 | }
137 | }
138 |
139 | export default Service;
140 |
--------------------------------------------------------------------------------
/server/utils/index.js:
--------------------------------------------------------------------------------
1 | var copyFile = require('./copyFile');
2 | var readFile = require('./readFile');
3 | var watchFile = require('./watchFile');
4 | module.exports = {
5 | copyFile,
6 | readFile,
7 | watchFile,
8 | };
9 |
--------------------------------------------------------------------------------
/server/utils/readFile.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-05-11 11:23:58
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-05-11 11:44:42
6 | * @FilePath: /webpack-cli/delete_node_modules.js
7 | * @Description:
8 | */
9 | var fs = require("fs");
10 | var path = require("path");
11 | const { readdirSync, stat, statSync } = fs;
12 | const readFile = (path, callback = () => {}) => {
13 | var files = readdirSync(path);
14 | files.forEach((item, index) => {
15 | var reg = /(\\\\)|(\\)/g;
16 | var src = path + "/" + item;
17 | src = src.replace(reg, "/");
18 | var stat = statSync(src);
19 | if (stat.isDirectory()) {
20 | //递归读取文件
21 | readFile(src, callback);
22 | } else if (stat.isFile()) {
23 | callback({
24 | path: src,
25 | filename: item
26 | });
27 | }
28 | });
29 | };
30 | module.exports = readFile;
31 |
32 | // readFile(path.join(process.cwd(), '../../../node_modules'),(value)=>{
33 | // console.log('value=',value)
34 | // });
35 |
--------------------------------------------------------------------------------
/server/utils/watchFile.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-05-24 15:49:12
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-05-24 16:05:44
6 | * @FilePath: /webpack-cli/watchFile.js
7 | * @Description:
8 | */
9 | const chokidar = require("chokidar");
10 | const path = require("path");
11 |
12 | class watchFile {
13 | constructor(filepath, callback) {
14 | this.filepath = filepath;
15 | this.callback = callback;
16 | try {
17 | this.init();
18 | } catch (error) {}
19 | }
20 | init() {
21 | if (!this.watcher) {
22 | this.watcher = chokidar.watch(this.filepath);
23 | }
24 | this.watcher
25 | .on("add", (path) => this.addFileListener(path))
26 | .on("addDir", (path) => this.addDirecotryListener(path))
27 | .on("change", (path) => this.fileChangeListener(path))
28 | .on("unlink", (path) => this.fileRemovedListener(path))
29 | .on("unlinkDir", (path) => this.directoryRemovedListener(path))
30 | .on("error", (error) => {
31 | // console.info('watchFile error:', error);
32 | })
33 | .on("ready", () => {
34 | // console.info('准备监听');
35 | this.ready = true;
36 | });
37 | }
38 | // 转换路径
39 | transformPath(path) {
40 | let reg = /(\\\\)|(\\)/g;
41 | return path.replace(reg, "/");
42 | }
43 | // 文件新增时
44 | addFileListener(path) {
45 | path = this.transformPath(path);
46 | if (this.ready) {
47 | this.callback({
48 | path,
49 | message: `文件${path}有新增`,
50 | type: "file",
51 | action: "add"
52 | });
53 | }
54 | }
55 |
56 | // 文件内容改变时
57 | fileChangeListener(path) {
58 | path = this.transformPath(path);
59 | if (this.ready) {
60 | this.callback({
61 | path,
62 | message: `文件${path}有修改`,
63 | type: "file",
64 | action: "change"
65 | });
66 | }
67 | }
68 |
69 | // 删除文件
70 | fileRemovedListener(path) {
71 | path = this.transformPath(path);
72 | if (this.ready) {
73 | this.callback({
74 | path,
75 | message: `文件${path}被删除了`,
76 | type: "file",
77 | action: "delete"
78 | });
79 | }
80 | }
81 |
82 | // 添加目录
83 | addDirecotryListener(path) {
84 | path = this.transformPath(path);
85 | if (this.ready) {
86 | this.callback({
87 | path,
88 | message: `目录${path}有新增`,
89 | type: "directory",
90 | action: "add"
91 | });
92 | }
93 | }
94 | // 删除目录时
95 | directoryRemovedListener(path) {
96 | path = this.transformPath(path);
97 | if (this.ready) {
98 | this.callback({
99 | path,
100 | message: `目录${path}被删除了`,
101 | type: "directory",
102 | action: "delete"
103 | });
104 | }
105 | }
106 | }
107 |
108 | // new watchFile(
109 | // 'C:/Users/86185/Desktop/React-universal-ssr-master/server',
110 | // (value) => {
111 | // // console.log('path===', path);
112 | // console.log('value===', value);
113 | // }
114 | // );
115 | module.exports = watchFile;
116 | // watch(path.join(process.cwd(), '/src'));
117 |
--------------------------------------------------------------------------------
/webpack/config/client/index.js:
--------------------------------------------------------------------------------
1 | const baseConfig = require('./webpack.base.config.js');
2 | const devConfig = require('./webpack.dev.config.js');
3 | const prodConfig = require('./webpack.prod.config.js');
4 | const webpackMerge = require('webpack-merge');
5 | require('dotenv').config({ path: '.env' });
6 |
7 | let {
8 | NODE_ENV, // 环境参数
9 | target, // 环境参数
10 | } = process.env; // 环境参数
11 | let config = {};
12 | if (NODE_ENV == 'development') {
13 | config = webpackMerge(baseConfig, devConfig);
14 | } else {
15 | config = webpackMerge(baseConfig, prodConfig);
16 | }
17 |
18 | module.exports = config;
19 |
--------------------------------------------------------------------------------
/webpack/config/client/webpack.server.config.js:
--------------------------------------------------------------------------------
1 | const CopyWebpackPlugin = require('copy-webpack-plugin');
2 | const ResolveAlias = require('../../definePlugin/webpack-plugin-resolve-alias');
3 |
4 | const path = require('path');
5 |
6 | let $ResolveAlias = new ResolveAlias({
7 | resolve: {
8 | // 路径配置
9 | alias: {
10 | "@": path.join(process.cwd()),
11 | client: path.join(process.cwd(), "/client"),
12 | server: path.join(process.cwd(), "/server")
13 | },
14 |
15 | },
16 | });
17 |
18 | let {
19 | NODE_ENV, // 环境参数
20 | target, // 环境参数
21 | } = process.env; // 环境参数
22 |
23 |
24 | module.exports = {
25 | // mode: NODE_ENV,
26 | ...(NODE_ENV = 'development'
27 | ? {
28 | watch: true,
29 | watchOptions: {
30 | //延迟监听时间
31 | aggregateTimeout: 500,
32 | //忽略监听文件夹
33 | ignored: '/node_modules/',
34 | },
35 | }
36 | : {}),
37 | devtool: 'source-map',
38 | // context: path.join(process.cwd(), '/client'),
39 | entry: {
40 | test: path.join(process.cwd(), '/server/test.js'),
41 | },
42 | output: {
43 | path: path.join(process.cwd(), '/dist/server'),
44 | filename: '[name].js',
45 | chunkFilename: '[name][contenthash].js',
46 |
47 | // filename: 'static/[name].[hash:8].js',
48 | // path: path.resolve(process.cwd(), './dist/client'),
49 | // publicPath: '/',
50 | // chunkFilename: 'static/[name]-[hash:8].js',
51 | // libraryTarget: isServer?'commonjs2':'umd',
52 | },
53 | // resolve: {
54 | // // 路径配置
55 | // alias: {
56 | // '@': path.join(process.cwd(), '/client'),
57 | // },
58 | // extensions: ['.js', '.jsx', '.css', '.less', '.scss', '.png', '.jpg'],
59 | // modules: [path.resolve(process.cwd(), 'client'), 'node_modules'],
60 | // },
61 | module: {
62 | rules: [],
63 | },
64 | plugins: [
65 | // 复制
66 | new CopyWebpackPlugin([
67 | {
68 | from: path
69 | .join(process.cwd(), '/server/**/*')
70 | .replace(/\\/gi, '/'),
71 | to: path
72 | .join(process.cwd(), '/dist/server')
73 | .replace(/\\/gi, '/'),
74 | transform(content, absoluteFrom) {
75 | let reg = /.jsx|.js$/g;
76 | if (reg.test(absoluteFrom)) {
77 | return $ResolveAlias.alias(content.toString(), '');
78 | }
79 |
80 | return content;
81 | },
82 | },
83 | ]),
84 | new CopyWebpackPlugin([
85 | {
86 | from: path
87 | .join(process.cwd(), '/client/**/*')
88 | .replace(/\\/gi, '/'),
89 | to: path
90 | .join(process.cwd(), '/dist/server')
91 | .replace(/\\/gi, '/'),
92 | transform(content, absoluteFrom) {
93 | let reg = /.jsx|.js$/g;
94 | if (reg.test(absoluteFrom)) {
95 | return $ResolveAlias.alias(content.toString(), '');
96 | }
97 |
98 | return content;
99 | },
100 | },
101 | ]),
102 | ],
103 | };
104 |
--------------------------------------------------------------------------------
/webpack/config/server/index.js:
--------------------------------------------------------------------------------
1 | const baseConfig = require('./webpack.base.config.js');
2 | const devConfig = require('./webpack.dev.config.js');
3 | const prodConfig = require('./webpack.prod.config.js');
4 | const webpackMerge = require('webpack-merge');
5 |
6 | let {
7 | NODE_ENV, // 环境参数
8 | WEB_ENV, // 环境参数
9 | target, // 环境参数
10 | htmlWebpackPluginOptions = '',
11 | } = process.env; // 环境参数
12 | let config = {};
13 | if (NODE_ENV == 'development') {
14 | config = webpackMerge(baseConfig, devConfig);
15 | } else {
16 | config = webpackMerge(baseConfig, prodConfig);
17 | }
18 |
19 | module.exports = config;
20 |
--------------------------------------------------------------------------------
/webpack/config/server/webpack.dev.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const webpack = require("webpack");
3 | const { CleanWebpackPlugin } = require("clean-webpack-plugin");
4 | const HtmlWebpackPlugin = require("html-webpack-plugin");
5 | const ProgressBarPlugin = require("progress-bar-webpack-plugin");
6 | const CopyWebpackPlugin = require("copy-webpack-plugin");
7 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
8 | const rootPath = process.cwd();
9 | let {
10 | NODE_ENV, // 环境参数
11 | target // 环境参数
12 | } = process.env; // 环境参数
13 |
14 | module.exports = {
15 | entry: {
16 | main: [
17 | // '@babel/polyfill',
18 | // "core-js/stable",
19 | // "regenerator-runtime/runtime",
20 | //添加编译缓存
21 | // "webpack/hot/poll?1000",
22 | path.join(process.cwd(), "/server/middleware/clientRouter/index.js")
23 | ]
24 | },
25 | devServer: {
26 | open: true,
27 | contentBase: "assets",
28 | hot: true,
29 | historyApiFallback: true,
30 | liveReload: true, // 编译之后是否自动刷新浏览器
31 | writeToDisk: true, // 写入硬盘
32 | port: 5000
33 | },
34 | watch: true,
35 | watchOptions: {
36 | //延迟监听时间
37 | aggregateTimeout: 500,
38 | //忽略监听文件夹
39 | ignored: "/node_modules/"
40 | },
41 | // context: path.join(process.cwd(), '/client'),
42 | devtool: "source-map",
43 | module: {
44 | rules: []
45 | },
46 | plugins: [
47 | new webpack.HotModuleReplacementPlugin(),
48 | new CleanWebpackPlugin({
49 | cleanOnceBeforeBuildPatterns: [
50 | path.join(process.cwd(), "/dist/server/*.js"),
51 | path.join(process.cwd(), "/dist/server/static/**/*"),
52 | "!" + path.join(process.cwd(), "/dist/server/app.js"),
53 | "!" + path.join(process.cwd(), "/dist/server/index.js")
54 | ]
55 | })
56 | ]
57 | };
58 |
--------------------------------------------------------------------------------
/webpack/config/server/webpack.prod.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const webpack = require("webpack");
3 | const { CleanWebpackPlugin } = require("clean-webpack-plugin");
4 | const HtmlWebpackPlugin = require("html-webpack-plugin");
5 | const ProgressBarPlugin = require("progress-bar-webpack-plugin");
6 | const CopyWebpackPlugin = require("copy-webpack-plugin");
7 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
8 | const rootPath = process.cwd();
9 | let {
10 | NODE_ENV, // 环境参数
11 | target // 环境参数
12 | } = process.env; // 环境参数
13 |
14 | module.exports = {
15 | entry: {
16 | index: [
17 | // '@babel/polyfill',
18 | // "core-js/stable",
19 | // "regenerator-runtime/runtime",
20 | //添加编译缓存
21 | // "webpack/hot/poll?1000",
22 | path.join(process.cwd(), "/server/index.js")
23 | ]
24 | },
25 | watch: false,
26 | // context: path.join(process.cwd(), '/client'),
27 | devtool: "source-map",
28 | module: {
29 | rules: []
30 | },
31 | plugins: [
32 | new CleanWebpackPlugin(),
33 | ]
34 | };
35 |
--------------------------------------------------------------------------------
/webpack/defineLoader/MyExampleWebpackLoader.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-04-28 10:55:26
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-04-28 13:40:50
6 | * @FilePath: /webpack-config/user-webpack-config/defineLoader/MyExampleWebpackLoader.js
7 | * @Description:
8 | */
9 |
10 | module.exports = function (source) {
11 | const callback = this.async();
12 | const options = this.query;
13 | const { name } = options;
14 | // // // 获取参数
15 | // console.log('this.query============', this.query);
16 | // // // 获取代码
17 | // console.log('source========', source);
18 | callback(null, source);
19 | return source
20 | };
21 |
--------------------------------------------------------------------------------
/webpack/definePlugin/HelloWorldCheckerPlugin/index.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | class HelloWorldCheckerPlugin {
5 | constructor(options) {
6 | this.options = options;
7 | }
8 |
9 | apply(compiler) {
10 | compiler.plugin('make', (compilation, cb) =>
11 | this._make(compilation, cb)
12 | );
13 | }
14 |
15 | _make(compilation, cb) {
16 | // compilation.errors.push('路由重名错误');
17 | cb()
18 | // try {
19 | // const file = fs.readFileSync(path.resolve('/', this.options.path), 'utf8');
20 | // if (file.includes('Hello World!')) {
21 | // console.log(`The file ${this.options.path} contains 'Hello World!' string`);
22 | // } else {
23 | // console.log(`The file ${this.options.path} doesn't contain 'Hello World!' string`)
24 | // }
25 | // cb();
26 | // } catch (e) {
27 | // compilation.errors.push('路由重名错误');
28 | // cb();
29 | // }
30 | }
31 | }
32 |
33 | module.exports = HelloWorldCheckerPlugin;
34 |
--------------------------------------------------------------------------------
/webpack/definePlugin/MyExampleWebpackPlugin.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-04-28 10:55:26
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-07-04 13:36:09
6 | * @FilePath: /webpack-cli/user-webpack-config/definePlugin/MyExampleWebpackPlugin.js
7 | * @Description:
8 | */
9 | const fs = require("fs");
10 | class MyExampleWebpackPlugin {
11 | constructor(options) {
12 | this.options = options;
13 | }
14 |
15 | write(data) {
16 | let { outputPath } = this.options;
17 | outputPath = outputPath + "/myVue.js";
18 | fs.writeFileSync(outputPath, data);
19 | }
20 | // // 做兼容
21 | hook(compiler, hookName, pluginName, fn) {
22 | if (arguments.length === 3) {
23 | fn = pluginName;
24 | pluginName = hookName;
25 | }
26 | if (compiler.hooks) {
27 | compiler.hooks[hookName].tap(pluginName, fn);
28 | } else {
29 | compiler.plugin(pluginName, fn);
30 | }
31 | }
32 |
33 | apply(compiler) {
34 |
35 | // this.hook(compiler, "entryOption", () => {
36 | // console.log("entryOption======== 开始");
37 |
38 | // });
39 |
40 |
41 | // compiler.plugin('make', (compilation, cb) => {
42 | // console.log("make======== 开始");
43 | // // compilation.errors.push('路由重名错误2');
44 | // cb()
45 | // });
46 | // // this.hook(compiler, "make", (compilation, cb) => {
47 | // // console.log("make======== 开始");
48 | // // cb()
49 | // // });
50 |
51 |
52 |
53 | // this.hook(compiler, "entryOption", () => {
54 | // console.log("entryOption======== 开始");
55 |
56 | // });
57 |
58 | // this.hook(compiler, "beforeCompile", () => {
59 | // //编译中
60 | // console.log("beforeCompile======");
61 | // });
62 |
63 | // this.hook(compiler, "shouldEmit", () => {
64 | // console.log("shouldEmit==========");
65 | // });
66 |
67 | // this.hook(compiler, "assetEmitted", () => {
68 | // console.log("assetEmitted==========");
69 | // });
70 | // // // // 编译完成
71 | // this.hook(compiler, "done", () => {
72 | // console.log("done:编译完成");
73 | // });
74 |
75 | // // // 开始编译 只会调用一次
76 | // this.hook(compiler, "afterPlugins", () => {
77 | // console.log("afterPlugins======== 开始");
78 | // // this.write(data);
79 | // });
80 | // // // 开始编译
81 | // this.hook(compiler, "compile", () => {
82 | // //编译中
83 | // console.log("compile======");
84 | // });
85 | // // // 编译完成
86 | // this.hook(compiler, "watchRun", () => {
87 | // console.log("watchRun==========");
88 | // });
89 | // // // 编译中期
90 | // this.hook(compiler, "invalid", () => {
91 | // console.log("invalid==========");
92 | // });
93 |
94 | // compiler.hooks.compile.tap("MyPlugin", (params) => {
95 | // console.log("以同步方式触及 compile 钩子。");
96 | // });
97 |
98 | // compiler.hooks.emit.tapAsync("MyPlugin", (compilation, callback) => {
99 | // // console.log("以异步方式触及 run 钩子。");
100 | // // 在生成文件中,创建一个头部字符串:
101 | // // var filelist = "In this build:\n\n";
102 | // // for (var filename in compilation.assets) {
103 | // // filelist += "- " + filename + "\n";
104 | // // }
105 | // // // 将这个作为一个新的文件资源,插入到 webpack 构建中:
106 | // // // 写入一个新文件
107 | // // compilation.assets["filelist.md"] = {
108 | // // source: function () {
109 | // // return filelist;
110 | // // },
111 | // // size: function () {
112 | // // return filelist.length;
113 | // // },
114 | // // };
115 | // callback();
116 | // });
117 |
118 | // compiler.hooks.emit.tapPromise("MyPlugin", (compiler) => {
119 | // return new Promise((resolve) => setTimeout(resolve, 10000)).then(() => {
120 | // console.log("以具有延迟的异步方式触及 run 钩子");
121 | // });
122 | // });
123 | }
124 | }
125 |
126 | module.exports = MyExampleWebpackPlugin;
127 |
--------------------------------------------------------------------------------
/webpack/definePlugin/mini-css-extract-plugin/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright JS Foundation and other contributors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | 'Software'), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/webpack/definePlugin/mini-css-extract-plugin/cjs/cjs.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = require('./index').default;
--------------------------------------------------------------------------------
/webpack/definePlugin/mini-css-extract-plugin/cjs/hmr/normalize-url.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /* eslint-disable */
4 | function normalizeUrl(pathComponents) {
5 | return pathComponents.reduce(function (accumulator, item) {
6 | switch (item) {
7 | case '..':
8 | accumulator.pop();
9 | break;
10 |
11 | case '.':
12 | break;
13 |
14 | default:
15 | accumulator.push(item);
16 | }
17 |
18 | return accumulator;
19 | }, []).join('/');
20 | }
21 |
22 | module.exports = function (urlString) {
23 | urlString = urlString.trim();
24 |
25 | if (/^data:/i.test(urlString)) {
26 | return urlString;
27 | }
28 |
29 | var protocol = urlString.indexOf('//') !== -1 ? urlString.split('//')[0] + '//' : '';
30 | var components = urlString.replace(new RegExp(protocol, 'i'), '').split('/');
31 | var host = components[0].toLowerCase().replace(/\.$/, '');
32 | components[0] = '';
33 | var path = normalizeUrl(components);
34 | return protocol + host + path;
35 | };
--------------------------------------------------------------------------------
/webpack/definePlugin/mini-css-extract-plugin/cjs/loader-options.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "object",
3 | "additionalProperties": false,
4 | "properties": {
5 | "publicPath": {
6 | "anyOf": [
7 | {
8 | "type": "string"
9 | },
10 | {
11 | "instanceof": "Function"
12 | }
13 | ]
14 | },
15 | "emit": {
16 | "type": "boolean"
17 | },
18 | "esModule": {
19 | "type": "boolean"
20 | },
21 | "layer": {
22 | "type": "string"
23 | },
24 | "modules": {
25 | "type": "object",
26 | "additionalProperties": false,
27 | "properties": {
28 | "namedExport": {
29 | "description": "Enables/disables ES modules named export for locals (https://webpack.js.org/plugins/mini-css-extract-plugin/#namedexport).",
30 | "type": "boolean"
31 | }
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/webpack/definePlugin/mini-css-extract-plugin/cjs/plugin-options.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "object",
3 | "additionalProperties": false,
4 | "properties": {
5 | "filename": {
6 | "anyOf": [
7 | {
8 | "type": "string"
9 | },
10 | {
11 | "instanceof": "Function"
12 | }
13 | ]
14 | },
15 | "chunkFilename": {
16 | "anyOf": [
17 | {
18 | "type": "string"
19 | },
20 | {
21 | "instanceof": "Function"
22 | }
23 | ]
24 | },
25 | "experimentalUseImportModule": {
26 | "description": "Enable the experimental importModule approach instead of using child compilers. This uses less memory and is faster.",
27 | "type": "boolean"
28 | },
29 | "ignoreOrder": {
30 | "type": "boolean"
31 | },
32 | "insert": {
33 | "description": "Inserts `` at the given position (https://github.com/webpack-contrib/mini-css-extract-plugin#insert).",
34 | "anyOf": [
35 | {
36 | "type": "string"
37 | },
38 | {
39 | "instanceof": "Function"
40 | }
41 | ]
42 | },
43 | "attributes": {
44 | "description": "Adds custom attributes to tag (https://github.com/webpack-contrib/mini-css-extract-plugin#attributes).",
45 | "type": "object"
46 | },
47 | "linkType": {
48 | "anyOf": [
49 | {
50 | "enum": ["text/css"]
51 | },
52 | {
53 | "type": "boolean"
54 | }
55 | ]
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/webpack/definePlugin/mini-css-extract-plugin/cjs/utils.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.trueFn = trueFn;
7 | exports.findModuleById = findModuleById;
8 | exports.evalModuleCode = evalModuleCode;
9 | exports.compareModulesByIdentifier = compareModulesByIdentifier;
10 | exports.MODULE_TYPE = void 0;
11 |
12 | var _module = _interopRequireDefault(require("module"));
13 |
14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15 |
16 | function trueFn() {
17 | return true;
18 | }
19 |
20 | function findModuleById(compilation, id) {
21 | const {
22 | modules,
23 | chunkGraph
24 | } = compilation;
25 |
26 | for (const module of modules) {
27 | const moduleId = typeof chunkGraph !== 'undefined' ? chunkGraph.getModuleId(module) : module.id;
28 |
29 | if (moduleId === id) {
30 | return module;
31 | }
32 | }
33 |
34 | return null;
35 | }
36 |
37 | function evalModuleCode(loaderContext, code, filename) {
38 | const module = new _module.default(filename, loaderContext);
39 | module.paths = _module.default._nodeModulePaths(loaderContext.context); // eslint-disable-line no-underscore-dangle
40 |
41 | module.filename = filename;
42 |
43 | module._compile(code, filename); // eslint-disable-line no-underscore-dangle
44 |
45 |
46 | return module.exports;
47 | }
48 |
49 | function compareIds(a, b) {
50 | if (typeof a !== typeof b) {
51 | return typeof a < typeof b ? -1 : 1;
52 | }
53 |
54 | if (a < b) {
55 | return -1;
56 | }
57 |
58 | if (a > b) {
59 | return 1;
60 | }
61 |
62 | return 0;
63 | }
64 |
65 | function compareModulesByIdentifier(a, b) {
66 | return compareIds(a.identifier(), b.identifier());
67 | }
68 |
69 | const MODULE_TYPE = 'css/mini-extract';
70 | exports.MODULE_TYPE = MODULE_TYPE;
--------------------------------------------------------------------------------
/webpack/definePlugin/mini-css-extract-plugin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mini-css-extract-plugin",
3 | "version": "1.6.2",
4 | "description": "extracts CSS into separate files",
5 | "license": "MIT",
6 | "repository": "webpack-contrib/mini-css-extract-plugin",
7 | "author": "Tobias Koppers @sokra",
8 | "homepage": "https://github.com/webpack-contrib/mini-css-extract-plugin",
9 | "bugs": "https://github.com/webpack-contrib/mini-css-extract-plugin/issues",
10 | "funding": {
11 | "type": "opencollective",
12 | "url": "https://opencollective.com/webpack"
13 | },
14 | "main": "cjs/cjs.js",
15 | "engines": {
16 | "node": ">= 10.13.0"
17 | },
18 | "scripts": {
19 | "start": "npm run build -- -w",
20 | "prebuild": "npm run clean",
21 | "build": "cross-env NODE_ENV=production babel src -d dist --ignore \"src/**/*.test.js\" --copy-files",
22 | "postbuild": "es-check es5 dist/hmr/hotModuleReplacement.js",
23 | "clean": "del-cli dist",
24 | "commitlint": "commitlint --from=master",
25 | "lint:prettier": "prettier \"{**/*,*}.{js,json,md,yml,css,ts}\" --list-different",
26 | "lint:js": "eslint --cache .",
27 | "lint": "npm-run-all -l -p \"lint:**\"",
28 | "prepare": "husky install && npm run build",
29 | "release": "standard-version",
30 | "security": "npm audit --production",
31 | "test:only": "cross-env NODE_ENV=test jest",
32 | "test:watch": "npm run test:only -- --watch",
33 | "test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage",
34 | "test:manual": "npm run build && webpack serve ./test/manual/src/index.js --open --config ./test/manual/webpack.config.js",
35 | "pretest": "npm run lint",
36 | "test": "cross-env NODE_ENV=test npm run test:coverage"
37 | },
38 | "files": [
39 | "dist"
40 | ],
41 | "peerDependencies": {
42 | "webpack": "^4.4.0 || ^5.0.0"
43 | },
44 | "dependencies": {
45 | "loader-utils": "^2.0.0",
46 | "schema-utils": "^3.0.0",
47 | "webpack-sources": "^1.1.0"
48 | },
49 | "devDependencies": {
50 | "@babel/cli": "^7.14.5",
51 | "@babel/core": "^7.14.6",
52 | "@babel/preset-env": "^7.14.5",
53 | "@commitlint/cli": "^12.1.4",
54 | "@commitlint/config-conventional": "^12.1.4",
55 | "@webpack-contrib/eslint-config-webpack": "^3.0.0",
56 | "babel-eslint": "^10.1.0",
57 | "babel-jest": "^27.0.2",
58 | "bootstrap": "^4.6.0",
59 | "cross-env": "^7.0.3",
60 | "css-loader": "^5.2.6",
61 | "del": "^6.0.0",
62 | "del-cli": "^3.0.1",
63 | "es-check": "5.2.3",
64 | "eslint": "^7.28.0",
65 | "eslint-config-prettier": "^8.1.0",
66 | "eslint-plugin-import": "^2.23.4",
67 | "file-loader": "^6.2.0",
68 | "husky": "^5.2.0",
69 | "jest": "^27.0.4",
70 | "jsdom": "^16.5.1",
71 | "lint-staged": "^10.5.4",
72 | "memfs": "^3.0.2",
73 | "npm-run-all": "^4.1.5",
74 | "prettier": "^2.3.1",
75 | "standard-version": "^9.1.0",
76 | "webpack": "^5.40.0",
77 | "webpack-cli": "^4.7.2",
78 | "webpack-dev-server": "^3.11.2"
79 | },
80 | "keywords": [
81 | "webpack",
82 | "css",
83 | "extract",
84 | "hmr"
85 | ]
86 | }
87 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "comments": false,
3 | "presets": [
4 | ["@babel/preset-env", {
5 | "loose": true
6 | }],
7 | "@babel/preset-react"
8 | ],
9 | "plugins": [
10 | "@babel/plugin-transform-runtime",
11 | "@babel/plugin-proposal-class-properties",
12 | "@babel/plugin-transform-object-assign",
13 | "dynamic-import-node",
14 | "react-loadable/babel",
15 | ["module-resolver", {
16 | "alias": {
17 | "react-loadable-ssr-addon": "./source/index.js"
18 | }
19 | }]
20 | ],
21 | "env": {
22 | "production": {
23 | "plugins": [
24 | ["transform-remove-console", {
25 | "exclude": [ "error", "warn"]
26 | }]
27 | ]
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | defaults: &defaults
4 | docker:
5 | - image: circleci/node:8.9.1
6 |
7 | jobs:
8 | test:
9 | <<: *defaults
10 | steps:
11 | - checkout
12 |
13 | - restore_cache:
14 | keys:
15 | - react-loadable-ssr-addon-{{ checksum "package.json" }}
16 | # fallback to using the latest cache if no exact match is found
17 | - react-loadable-ssr-addon-
18 |
19 | - run: npm install
20 | - run:
21 | name: Run Tests
22 | command: npm run test
23 |
24 | - save_cache:
25 | paths:
26 | - node_modules
27 | key: react-loadable-ssr-addon-{{ checksum "package.json" }}
28 |
29 | - persist_to_workspace:
30 | root: .
31 | paths: .
32 |
33 | publish:
34 | <<: *defaults
35 | steps:
36 | - attach_workspace:
37 | at: .
38 | - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ./.npmrc
39 | - run: npm publish
40 |
41 | workflows:
42 | version: 2
43 | main:
44 | jobs:
45 | - test:
46 | filters:
47 | tags:
48 | only: /^v.*/
49 | - publish:
50 | requires:
51 | - test
52 | filters:
53 | tags:
54 | only: /^v.*/
55 | branches:
56 | ignore: /.*/
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@themgoncalves.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Issue Title
2 |
3 |
4 | ## Expected Behavior
5 |
6 |
7 | ## Current Behavior
8 |
9 |
10 | ## Possible Solution
11 |
12 |
13 | ## Steps to Reproduce
14 |
15 |
16 | 1.
17 | 2.
18 | 3.
19 | 4.
20 |
21 | ## Context (Environment)
22 |
23 |
24 |
25 |
26 |
27 | ### Environment
28 |
29 | 1. OS running:
30 | 2. Node version:
31 | 3. Webpack version:
32 |
33 | ## Detailed Description
34 |
35 |
36 | ## Possible Implementation
37 |
38 |
39 | ## Other Comments
40 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Summary
2 | A few sentences describing the overall goals of the pull request's commits.
3 |
4 | ## Why
5 | A few sentences describing the reasons behind this pull request.
6 |
7 | ## Checklist
8 |
9 | - [ ] Your code builds clean without any `errors` or `warnings`
10 | - [ ] You are using `approved terminology`
11 | - [ ] You have added `unit tests`, if apply.
12 |
13 | ## Emojis for categorizing pull requests:
14 |
15 | ⚡️ New feature (`:zap:`)
16 | 🐛 Bug fix (`:bug:`)
17 | 🔥 P0 fix (`:fire:`)
18 | ✅ Tests (`:white_check_mark:`)
19 | 🚀 Performance improvements (`:rocket:`)
20 | 🖍 CSS / Styling (`:crayon:`)
21 | ♿ Accessibility (`:wheelchair:`)
22 | 🌐 Internationalization (`:globe_with_meridians:`)
23 | 📖 Documentation (`:book:`)
24 | 🏗 Infrastructure / Tooling / Builds / CI (`:building_construction:`)
25 | ⏪ Reverting a previous change (`:rewind:`)
26 | ♻️ Refactoring (like moving around code w/o any changes) (`:recycle:`)
27 | 🚮 Deleting code (`:put_litter_in_its_place:`)
28 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/AUTHORS.md:
--------------------------------------------------------------------------------
1 | react-loadable-ssr-addon is authored by:
2 |
3 | * Alexey Pyltsyn
4 | * Endi
5 | * Jérémie Parker
6 | * Marcos
7 | * Marcos Gonçalves
8 | * Ngoc Phuong
9 | * Phuong Nguyen
10 | * Reece Dunham
11 | * Sébastien Lorber
12 | * Troy Rhinehart <81650390+trhinehart-godaddy@users.noreply.github.com>
13 | * dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
14 | * endiliey
15 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Marcos Gonçalves
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/example/client.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-08-15 19:23:52
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-16 18:52:54
6 | * @FilePath: /react-ssr-lazy-loading/webpack/definePlugin/react-loadable-ssr-addon/example/client.jsx
7 | * @Description:
8 | */
9 | import React from "react";
10 | import ReactDOM from "react-dom";
11 | import Loadable from "react-loadable"//"client/component/Loadable";
12 | import App from "./components/App";
13 |
14 | window.onload = () => {
15 | Loadable.preloadReady().then(() => {
16 | ReactDOM.hydrate(, document.getElementById("app"));
17 | });
18 | };
19 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/example/components/App.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-08-15 19:23:52
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-16 18:53:32
6 | * @FilePath: /react-ssr-lazy-loading/webpack/definePlugin/react-loadable-ssr-addon/example/components/App.jsx
7 | * @Description:
8 | */
9 | import React from "react";
10 | import Loadable from "react-loadable"//"client/component/Loadable";
11 | import Loading from "./Loading";
12 |
13 | const HeaderExample = Loadable({
14 | loader: () => import(/* webpackChunkName: "header" */ "./Header"),
15 | loading: Loading
16 | });
17 |
18 | const ContentExample = Loadable({
19 | loader: () => import(/* webpackChunkName: "content" */ "./Content"),
20 | loading: Loading
21 | });
22 |
23 | const MultilevelExample = Loadable({
24 | loader: () =>
25 | import(/* webpackChunkName: "multilevel" */ "./multilevel/Multilevel"),
26 | loading: Loading
27 | });
28 |
29 | export default function App() {
30 | return (
31 |
32 |
33 |
34 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/example/components/Content.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Loadable from "react-loadable";
3 | import Loading from "./Loading";
4 |
5 | const ContentNestedExample = Loadable({
6 | loader: () => import(/* webpackChunkName: "content-nested" */'./ContentNested'),
7 | loading: Loading,
8 | });
9 |
10 | export default function Content() {
11 | return (
12 |
13 | Bacon ipsum dolor amet pork belly minim pork loin reprehenderit incididunt aliquip hamburger chuck culpa mollit officia nisi pig duis.
14 | Buffalo laboris duis ullamco flank.
15 | Consectetur in excepteur elit ut aute adipisicing et tongue veniam labore dolore exercitation.
16 | Swine consectetur boudin landjaeger, t-bone pork belly laborum.
17 | Bacon ex ham ribeye sirloin et venison pariatur dolor non fugiat consequat.
18 | Velit kevin non, jerky alcatra flank ball tip.
19 |
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/example/components/ContentNested.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function Content() {
4 | return (
5 |
6 |
7 | Eu prosciutto fugiat, meatloaf beef ribs jerky dolore commodo est chicken t-bone meatball capicola magna ipsum. Ribeye shankle mollit venison.
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/example/components/Header.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function Header() {
4 | return (
5 |
6 |
React Loadable SSR Add-on
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/example/components/Loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function Loading(props) {
4 | if (props.isLoading) {
5 | if (props.timedOut) {
6 | return Loader timed out!
;
7 | } else if (props.pastDelay) {
8 | return Loading...
;
9 | }
10 | return null;
11 | } else if (props.error) {
12 | return Error! Component failed to load
;
13 | }
14 | return null;
15 | }
16 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/example/components/multilevel/Multilevel.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Loadable from "react-loadable";
3 | import Loading from "../Loading";
4 |
5 | const SharedMultilevelExample = Loadable({
6 | loader: () => import(/* webpackChunkName: "shared-multilevel" */'./SharedMultilevel'),
7 | loading: Loading,
8 | });
9 |
10 | const DeeplevelExample = Loadable({
11 | loader: () => import(/* webpackChunkName: "deeplevel" */'./level-1/level-2/DeepLevel'),
12 | loading: Loading,
13 | });
14 |
15 | export default function Multilevel() {
16 | return (
17 |
18 |
19 | Multilevel with Shared Component Example.
20 |
21 | Loading from a DeepLevel
22 |
23 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/example/components/multilevel/SharedMultilevel.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | /* eslint-disable react/jsx-one-expression-per-line */
4 | export default function SharedMultilevel() {
5 | return (
6 |
7 | -
8 | this is a shared multilevel component
9 |
10 |
11 | );
12 | }
13 | /* eslint-enable react/jsx-one-expression-per-line */
14 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/example/components/multilevel/level-1/level-2/DeepLevel.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Loadable from "react-loadable";
3 | import Loading from "../../../Loading";
4 |
5 | const SharedMultilevelExample = Loadable({
6 | loader: () => import(/* webpackChunkName: "shared-multilevel" */'../../SharedMultilevel'),
7 | loading: Loading,
8 | });
9 |
10 | export default function DeepLevel() {
11 | return (
12 |
13 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/example/server.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-08-15 19:23:52
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-16 18:53:16
6 | * @FilePath: /react-ssr-lazy-loading/webpack/definePlugin/react-loadable-ssr-addon/example/server.js
7 | * @Description:
8 | */
9 | import express from "express";
10 | import path from "path";
11 | import React from "react";
12 | import { renderToString } from "react-dom/server";
13 | import Loadable from "react-loadable"//"client/component/Loadable";
14 | import { getBundles } from "react-loadable-ssr-addon";
15 | import App from "./components/App";
16 |
17 | const manifest = require("./dist/react-loadable-ssr-addon.json");
18 | const server = express();
19 |
20 | server.use("/dist", express.static(path.join(__dirname, "dist")));
21 |
22 | server.get("*", (req, res) => {
23 | const modules = new Set();
24 | const html = renderToString(
25 | modules.add(moduleName)}>
26 |
27 |
28 | );
29 |
30 | const bundles = getBundles(manifest, [
31 | ...manifest.entrypoints,
32 | ...Array.from(modules)
33 | ]);
34 |
35 | const styles = bundles.css || [];
36 | const scripts = bundles.js || [];
37 |
38 | res.send(`
39 |
40 |
41 |
42 |
43 |
44 |
45 | React Loadable SSR Add-on Example
46 | ${styles
47 | .map((style) => {
48 | return ``;
49 | })
50 | .join("\n")}
51 |
52 |
53 | ${html}
54 | ${scripts
55 | .map((script) => {
56 | return ``;
57 | })
58 | .join("\n")}
59 |
60 |
61 | `);
62 | });
63 |
64 | Loadable.preloadAll()
65 | .then(() => {
66 | server.listen(3003, () => {
67 | console.log("Running on http://localhost:3003/");
68 | });
69 | })
70 | .catch((err) => {
71 | console.log(err);
72 | });
73 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/lib/ReactLoadableSSRAddon.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
4 |
5 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
6 |
7 | var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
8 |
9 | var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
10 |
11 | var _ava = _interopRequireDefault(require("ava"));
12 |
13 | var _path = _interopRequireDefault(require("path"));
14 |
15 | var _fs = _interopRequireDefault(require("fs"));
16 |
17 | var _waitForExpect = _interopRequireDefault(require("wait-for-expect"));
18 |
19 | var _webpack = _interopRequireDefault(require("webpack"));
20 |
21 | var _webpack2 = _interopRequireDefault(require("../webpack.config"));
22 |
23 | var _ReactLoadableSSRAddon = _interopRequireWildcard(require("./ReactLoadableSSRAddon"));
24 |
25 | var outputPath;
26 | var manifestOutputPath;
27 |
28 | var runWebpack = function runWebpack(configuration, end, callback) {
29 | (0, _webpack["default"])(configuration, function (err, stats) {
30 | if (err) {
31 | return end(err);
32 | }
33 |
34 | if (stats.hasErrors()) {
35 | return end(stats.toString());
36 | }
37 |
38 | callback();
39 | end();
40 | });
41 | };
42 |
43 | _ava["default"].beforeEach(function () {
44 | var publicPathSanitized = _webpack2["default"].output.publicPath.slice(1, -1);
45 |
46 | outputPath = _path["default"].resolve('./example', publicPathSanitized);
47 | manifestOutputPath = _path["default"].resolve(outputPath, _ReactLoadableSSRAddon.defaultOptions.filename);
48 | });
49 |
50 | _ava["default"].cb('outputs with default settings', function (t) {
51 | _webpack2["default"].plugins = [new _ReactLoadableSSRAddon["default"]()];
52 | runWebpack(_webpack2["default"], t.end, function () {
53 | var feedback = _fs["default"].existsSync(manifestOutputPath) ? 'pass' : 'fail';
54 | t[feedback]();
55 | });
56 | });
57 |
58 | _ava["default"].cb('outputs with custom filename', function (t) {
59 | var filename = 'new-assets-manifest.json';
60 | _webpack2["default"].plugins = [new _ReactLoadableSSRAddon["default"]({
61 | filename: filename
62 | })];
63 | runWebpack(_webpack2["default"], t.end, function () {
64 | var feedback = _fs["default"].existsSync(manifestOutputPath.replace(_ReactLoadableSSRAddon.defaultOptions.filename, filename)) ? 'pass' : 'fail';
65 | t[feedback]();
66 | });
67 | });
68 |
69 | _ava["default"].cb('outputs with integrity', function (t) {
70 | _webpack2["default"].plugins = [new _ReactLoadableSSRAddon["default"]({
71 | integrity: true
72 | })];
73 | runWebpack(_webpack2["default"], t.end, (0, _asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee() {
74 | var manifest;
75 | return _regenerator["default"].wrap(function _callee$(_context) {
76 | while (1) {
77 | switch (_context.prev = _context.next) {
78 | case 0:
79 | manifest = require("" + manifestOutputPath);
80 | _context.next = 3;
81 | return (0, _waitForExpect["default"])(function () {
82 | Object.keys(manifest.assets).forEach(function (asset) {
83 | manifest.assets[asset].js.forEach(function (_ref2) {
84 | var integrity = _ref2.integrity;
85 | t.truthy(integrity);
86 | });
87 | });
88 | });
89 |
90 | case 3:
91 | case "end":
92 | return _context.stop();
93 | }
94 | }
95 | }, _callee);
96 | })));
97 | });
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/lib/getBundles.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | exports.__esModule = true;
4 | exports["default"] = void 0;
5 |
6 | var _utils = require("./utils");
7 |
8 | function getBundles(manifest, chunks) {
9 | if (!manifest || !chunks) {
10 | return {};
11 | }
12 |
13 | var assetsKey = chunks.reduce(function (key, chunk) {
14 | if (manifest.origins[chunk]) {
15 | key = (0, _utils.unique)([].concat(key, manifest.origins[chunk]));
16 | }
17 |
18 | return key;
19 | }, []);
20 | return assetsKey.reduce(function (bundle, asset) {
21 | Object.keys(manifest.assets[asset] || {}).forEach(function (key) {
22 | var content = manifest.assets[asset][key];
23 |
24 | if (!bundle[key]) {
25 | bundle[key] = [];
26 | }
27 |
28 | bundle[key] = (0, _utils.unique)([].concat(bundle[key], content));
29 | });
30 | return bundle;
31 | }, {});
32 | }
33 |
34 | var _default = getBundles;
35 | exports["default"] = _default;
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/lib/getBundles.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4 |
5 | var _ava = _interopRequireDefault(require("ava"));
6 |
7 | var _path = _interopRequireDefault(require("path"));
8 |
9 | var _getBundles = _interopRequireDefault(require("./getBundles"));
10 |
11 | var _webpack = _interopRequireDefault(require("../webpack.config"));
12 |
13 | var _reactLoadableSsrAddon = _interopRequireDefault(require("../example/dist/react-loadable-ssr-addon"));
14 |
15 | var modules = ['./Header', './multilevel/Multilevel', './SharedMultilevel', '../../SharedMultilevel'];
16 | var fileType = ['js'];
17 | var bundles;
18 |
19 | _ava["default"].beforeEach(function () {
20 | bundles = (0, _getBundles["default"])(_reactLoadableSsrAddon["default"], [].concat(_reactLoadableSsrAddon["default"].entrypoints, modules));
21 | });
22 |
23 | (0, _ava["default"])('returns the correct bundle size and content', function (t) {
24 | t["true"](Object.keys(bundles).length === fileType.length);
25 | fileType.forEach(function (type) {
26 | return !!bundles[type];
27 | });
28 | });
29 | (0, _ava["default"])('returns the correct bundle infos', function (t) {
30 | fileType.forEach(function (type) {
31 | bundles[type].forEach(function (bundle) {
32 | var expectedPublichPath = _path["default"].resolve(_webpack["default"].output.publicPath, bundle.file);
33 |
34 | t["true"](bundle.file !== '');
35 | t["true"](bundle.hash !== '');
36 | t["true"](bundle.publicPath === expectedPublichPath);
37 | });
38 | });
39 | });
40 | (0, _ava["default"])('returns nothing when there is no match', function (t) {
41 | bundles = (0, _getBundles["default"])(_reactLoadableSsrAddon["default"], ['foo-bar', 'foo', null, undefined]);
42 | t["true"](Object.keys(bundles).length === 0);
43 | });
44 | (0, _ava["default"])('should work even with null/undefined manifest or modules', function (t) {
45 | bundles = (0, _getBundles["default"])(_reactLoadableSsrAddon["default"], null);
46 | t["true"](Object.keys(bundles).length === 0);
47 | bundles = (0, _getBundles["default"])(null, []);
48 | t["true"](Object.keys(bundles).length === 0);
49 | bundles = (0, _getBundles["default"])([], null);
50 | t["true"](Object.keys(bundles).length === 0);
51 | });
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/lib/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4 |
5 | var _ReactLoadableSSRAddon = _interopRequireDefault(require("./ReactLoadableSSRAddon"));
6 |
7 | var _getBundles = _interopRequireDefault(require("./getBundles"));
8 |
9 | module.exports = _ReactLoadableSSRAddon["default"];
10 | module.exports.getBundles = _getBundles["default"];
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/lib/utils/computeIntegrity.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4 |
5 | exports.__esModule = true;
6 | exports["default"] = void 0;
7 |
8 | var _crypto = _interopRequireDefault(require("crypto"));
9 |
10 | function computeIntegrity(algorithms, source) {
11 | return Array.isArray(algorithms) ? algorithms.map(function (algorithm) {
12 | var hash = _crypto["default"].createHash(algorithm).update(source, 'utf8').digest('base64');
13 |
14 | return algorithm + "-" + hash;
15 | }).join(' ') : '';
16 | }
17 |
18 | var _default = computeIntegrity;
19 | exports["default"] = _default;
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/lib/utils/getFileExtension.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | exports.__esModule = true;
4 | exports["default"] = void 0;
5 |
6 | function getFileExtension(filename) {
7 | if (!filename || typeof filename !== 'string') {
8 | return '';
9 | }
10 |
11 | var fileExtRegex = /\.\w{2,4}\.(?:map|gz)$|\.\w+$/i;
12 | var name = filename.split(/[?#]/)[0];
13 | var ext = name.match(fileExtRegex);
14 | return ext && ext.length ? ext[0] : '';
15 | }
16 |
17 | var _default = getFileExtension;
18 | exports["default"] = _default;
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/lib/utils/getFileExtension.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4 |
5 | var _ava = _interopRequireDefault(require("ava"));
6 |
7 | var _getFileExtension = _interopRequireDefault(require("./getFileExtension"));
8 |
9 | (0, _ava["default"])('returns the correct file extension', function (t) {
10 | var extensions = ['.jpeg', '.js', '.css', '.json', '.xml'];
11 | var filePath = 'source/static/images/hello-world';
12 | extensions.forEach(function (ext) {
13 | t["true"]((0, _getFileExtension["default"])("" + filePath + ext) === ext);
14 | });
15 | });
16 | (0, _ava["default"])('sanitize file hash', function (t) {
17 | var hashes = ['?', '#'];
18 | var filePath = 'source/static/images/hello-world.jpeg';
19 | hashes.forEach(function (hash) {
20 | t["true"]((0, _getFileExtension["default"])("" + filePath + hash + "d587bbd6e38337f5accd") === '.jpeg');
21 | });
22 | });
23 | (0, _ava["default"])('returns empty string when there is no file extension', function (t) {
24 | var filePath = 'source/static/resource';
25 | t["true"]((0, _getFileExtension["default"])(filePath) === '');
26 | });
27 | (0, _ava["default"])('should work even with null/undefined arg', function (t) {
28 | var filePaths = ['', null, undefined];
29 | filePaths.forEach(function (path) {
30 | t["true"]((0, _getFileExtension["default"])(path) === '');
31 | });
32 | });
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/lib/utils/hasEntry.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | exports.__esModule = true;
4 | exports["default"] = hasEntry;
5 |
6 | function hasEntry(target, targetKey, searchFor) {
7 | if (!target) {
8 | return false;
9 | }
10 |
11 | for (var i = 0; i < target.length; i += 1) {
12 | var file = target[i][targetKey];
13 |
14 | if (file === searchFor) {
15 | return true;
16 | }
17 | }
18 |
19 | return false;
20 | }
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/lib/utils/hasEntry.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4 |
5 | var _ava = _interopRequireDefault(require("ava"));
6 |
7 | var _hasEntry = _interopRequireDefault(require("./hasEntry"));
8 |
9 | var assets = [{
10 | file: 'content.chunk.js',
11 | hash: 'd41d8cd98f00b204e9800998ecf8427e',
12 | publicPath: './',
13 | integrity: null
14 | }, {
15 | file: 'header.chunk.js',
16 | hash: '699f4bd49870f2b90e1d1596d362efcb',
17 | publicPath: './',
18 | integrity: null
19 | }, {
20 | file: 'shared-multilevel.chunk.js',
21 | hash: 'ab7b8b1c1d5083c17a39ccd2962202e1',
22 | publicPath: './',
23 | integrity: null
24 | }];
25 | (0, _ava["default"])('should flag as has entry', function (t) {
26 | var fileName = 'header.chunk.js';
27 | t["true"]((0, _hasEntry["default"])(assets, 'file', fileName));
28 | });
29 | (0, _ava["default"])('should flag as has no entry', function (t) {
30 | var fileName = 'footer.chunk.js';
31 | t["false"]((0, _hasEntry["default"])(assets, 'file', fileName));
32 | });
33 | (0, _ava["default"])('should work even with null/undefined target', function (t) {
34 | var targets = [[], null, undefined];
35 | targets.forEach(function (target) {
36 | t["false"]((0, _hasEntry["default"])(target, 'file', 'foo.js'));
37 | });
38 | });
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/lib/utils/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4 |
5 | exports.__esModule = true;
6 | exports.hasEntry = exports.unique = exports.getFileExtension = exports.computeIntegrity = void 0;
7 |
8 | var _computeIntegrity = _interopRequireDefault(require("./computeIntegrity"));
9 |
10 | exports.computeIntegrity = _computeIntegrity["default"];
11 |
12 | var _getFileExtension = _interopRequireDefault(require("./getFileExtension"));
13 |
14 | exports.getFileExtension = _getFileExtension["default"];
15 |
16 | var _unique = _interopRequireDefault(require("./unique"));
17 |
18 | exports.unique = _unique["default"];
19 |
20 | var _hasEntry = _interopRequireDefault(require("./hasEntry"));
21 |
22 | exports.hasEntry = _hasEntry["default"];
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/lib/utils/unique.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | exports.__esModule = true;
4 | exports["default"] = unique;
5 |
6 | function unique(array) {
7 | return array.filter(function (elem, pos, arr) {
8 | return arr.indexOf(elem) === pos;
9 | });
10 | }
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/lib/utils/unique.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4 |
5 | var _ava = _interopRequireDefault(require("ava"));
6 |
7 | var _unique = _interopRequireDefault(require("./unique"));
8 |
9 | (0, _ava["default"])('it filters duplicated entries', function (t) {
10 | var duplicated = ['two', 'four'];
11 | var raw = ['one', 'two', 'three', 'four'];
12 | var filtered = (0, _unique["default"])([].concat(raw, duplicated));
13 | duplicated.forEach(function (dup) {
14 | t["true"](filtered.filter(function (item) {
15 | return item === dup;
16 | }).length === 1);
17 | });
18 | });
19 | (0, _ava["default"])('should work with null/undefined values', function (t) {
20 | var falsy = [null, undefined];
21 | var raw = ['one', 'two', 'three', 'four'];
22 | var filtered = (0, _unique["default"])([].concat(raw, falsy));
23 | falsy.forEach(function (value) {
24 | t["true"](filtered.includes(value));
25 | });
26 | });
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-loadable-ssr-addon",
3 | "version": "1.0.2",
4 | "description": "Server Side Render add-on for React Loadable. Load splitted chunks was never that easy.",
5 | "main": "lib/index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/themgoncalves/react-loadable-ssr-addon.git"
9 | },
10 | "keywords": [
11 | "react",
12 | "react-loadable",
13 | "webpack",
14 | "splitted-chunks",
15 | "assets-manifest",
16 | "server-side-render",
17 | "ssr"
18 | ],
19 | "author": "Marcos Gonçalves ",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/themgoncalves/react-loadable-ssr-addon/issues"
23 | },
24 | "scripts": {
25 | "authors": "echo 'react-loadable-ssr-addon is authored by: \n' > AUTHORS.md | git log --format='* %aN <%aE>' | sort -u >> AUTHORS.md",
26 | "prepare": "npm run build && npm run authors",
27 | "prepublishOnly": "npm test && npm run lint",
28 | "preversion": "npm run lint",
29 | "postversion": "git push && git push --tags",
30 | "start": "npm run clean:example && npm run build && webpack && babel-node example/server.js",
31 | "build": "NODE_ENV=production && rm -rf lib && babel source -d lib",
32 | "clean:example": "rm -rf ./example/dist/",
33 | "lint": "eslint --ext js --ext jsx source || exit 0",
34 | "lint:fix": "eslint --ext js --ext jsx source --fix|| exit 0",
35 | "test": "npm run clean:example && npm run build && webpack && ava; npm run clean:example",
36 | "stats": "NODE_ENV=development webpack --profile --json > compilation-stats.json"
37 | },
38 | "engines": {
39 | "node": ">=10.13.0"
40 | },
41 | "resolutions": {
42 | "yargs-parser": "13.1.2",
43 | "mem": "4.0.0"
44 | },
45 | "peerDependencies": {
46 | "react-loadable": "*",
47 | "webpack": ">=4.41.1 || 5.x"
48 | },
49 | "devDependencies": {
50 | "@babel/cli": "^7.10.1",
51 | "@babel/core": "^7.10.1",
52 | "@babel/node": "^7.10.1",
53 | "@babel/plugin-proposal-class-properties": "^7.10.1",
54 | "@babel/plugin-proposal-object-rest-spread": "^7.10.1",
55 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
56 | "@babel/plugin-transform-classes": "^7.10.1",
57 | "@babel/plugin-transform-object-assign": "^7.10.1",
58 | "@babel/plugin-transform-runtime": "^7.10.1",
59 | "@babel/preset-env": "^7.10.1",
60 | "@babel/preset-react": "^7.10.1",
61 | "@babel/register": "^7.10.1",
62 | "ava": "^2.4.0",
63 | "babel-loader": "^8.1.0",
64 | "babel-plugin-dynamic-import-node": "^2.3.3",
65 | "babel-plugin-module-resolver": "^4.0.0",
66 | "babel-plugin-transform-remove-console": "^6.9.4",
67 | "babel-preset-minify": "^0.5.1",
68 | "eslint": "^6.5.1",
69 | "eslint-config-airbnb": "18.1.0",
70 | "eslint-plugin-import": "^2.20.2",
71 | "eslint-plugin-jsx-a11y": "^6.2.3",
72 | "eslint-plugin-react": "^7.20.0",
73 | "express": "^4.17.1",
74 | "husky": "^3.0.9",
75 | "react": "^16.13.1",
76 | "react-dom": "^16.13.1",
77 | "react-loadable": "^5.5.0",
78 | "wait-for-expect": "^3.0.2",
79 | "webpack": "4.44.1",
80 | "webpack-cli": "^4.5.0"
81 | },
82 | "husky": {
83 | "hooks": {
84 | "pre-commit": "npm run lint",
85 | "pre-push": "npm run test"
86 | }
87 | },
88 | "ava": {
89 | "files": [
90 | "source/**/*.test.js"
91 | ],
92 | "require": [
93 | "@babel/register"
94 | ],
95 | "concurrency": 5
96 | },
97 | "dependencies": {
98 | "@babel/runtime": "^7.10.3"
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/source/ReactLoadableSSRAddon.test.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import path from 'path';
3 | import fs from 'fs';
4 | import waitForExpect from 'wait-for-expect';
5 | import webpack from 'webpack';
6 | import config from '../webpack.config';
7 | import ReactLoadableSSRAddon, { defaultOptions } from './ReactLoadableSSRAddon';
8 |
9 | /* eslint-disable consistent-return, import/no-dynamic-require, global-require */
10 | let outputPath;
11 | let manifestOutputPath;
12 |
13 | const runWebpack = (configuration, end, callback) => {
14 | webpack(configuration, (err, stats) => {
15 | if (err) {
16 | return end(err);
17 | }
18 | if (stats.hasErrors()) {
19 | return end(stats.toString());
20 | }
21 |
22 | callback();
23 |
24 | end();
25 | });
26 | };
27 |
28 | test.beforeEach(() => {
29 | const publicPathSanitized = config.output.publicPath.slice(1, -1);
30 | outputPath = path.resolve('./example', publicPathSanitized);
31 | manifestOutputPath = path.resolve(outputPath, defaultOptions.filename);
32 | });
33 |
34 | test.cb('outputs with default settings', (t) => {
35 | config.plugins = [
36 | new ReactLoadableSSRAddon(),
37 | ];
38 |
39 | runWebpack(config, t.end, () => {
40 | const feedback = fs.existsSync(manifestOutputPath) ? 'pass' : 'fail';
41 |
42 | t[feedback]();
43 | });
44 | });
45 |
46 | test.cb('outputs with custom filename', (t) => {
47 | const filename = 'new-assets-manifest.json';
48 |
49 | config.plugins = [
50 | new ReactLoadableSSRAddon({
51 | filename,
52 | }),
53 | ];
54 |
55 | runWebpack(config, t.end, () => {
56 | const feedback = fs.existsSync(manifestOutputPath.replace(defaultOptions.filename, filename)) ? 'pass' : 'fail';
57 |
58 | t[feedback]();
59 | });
60 | });
61 |
62 | test.cb('outputs with integrity', (t) => {
63 | config.plugins = [
64 | new ReactLoadableSSRAddon({
65 | integrity: true,
66 | }),
67 | ];
68 |
69 | runWebpack(config, t.end, async () => {
70 | const manifest = require(`${manifestOutputPath}`);
71 |
72 | await waitForExpect(() => {
73 | Object.keys(manifest.assets).forEach((asset) => {
74 | manifest.assets[asset].js.forEach(({ integrity }) => {
75 | t.truthy(integrity);
76 | });
77 | });
78 | });
79 | });
80 | });
81 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/source/getBundles.js:
--------------------------------------------------------------------------------
1 | /**
2 | * react-loadable-ssr-addon
3 | * @author Marcos Gonçalves
4 | * @version 1.0.1
5 | */
6 |
7 | import { unique } from './utils';
8 |
9 | /**
10 | * getBundles
11 | * @param {object} manifest - The assets manifest content generate by ReactLoadableSSRAddon
12 | * @param {array} chunks - Chunks list to be loaded
13 | * @returns {array} - Assets list group by file type
14 | */
15 | /* eslint-disable no-param-reassign */
16 | function getBundles(manifest, chunks) {
17 | if (!manifest || !chunks) { return {}; }
18 |
19 | const assetsKey = chunks.reduce((key, chunk) => {
20 | if (manifest.origins[chunk]) {
21 | key = unique([...key, ...manifest.origins[chunk]]);
22 | }
23 | return key;
24 | }, []);
25 |
26 | return assetsKey.reduce((bundle, asset) => {
27 | Object.keys(manifest.assets[asset] || {}).forEach((key) => {
28 | const content = manifest.assets[asset][key];
29 | if (!bundle[key]) { bundle[key] = []; }
30 | bundle[key] = unique([...bundle[key], ...content]);
31 | });
32 | return bundle;
33 | }, {});
34 | }
35 | /* eslint-enabled */
36 |
37 | export default getBundles;
38 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/source/getBundles.test.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import path from 'path';
3 | import getBundles from './getBundles';
4 | import config from '../webpack.config';
5 | import manifest from '../example/dist/react-loadable-ssr-addon'; // eslint-disable-line import/no-unresolved, import/extensions
6 |
7 | const modules = ['./Header', './multilevel/Multilevel', './SharedMultilevel', '../../SharedMultilevel'];
8 | const fileType = ['js'];
9 | let bundles;
10 |
11 | test.beforeEach(() => {
12 | bundles = getBundles(manifest, [...manifest.entrypoints, ...modules]);
13 | });
14 |
15 | test('returns the correct bundle size and content', (t) => {
16 | t.true(Object.keys(bundles).length === fileType.length);
17 | fileType.forEach((type) => !!bundles[type]);
18 | });
19 |
20 | test('returns the correct bundle infos', (t) => {
21 | fileType.forEach((type) => {
22 | bundles[type].forEach((bundle) => {
23 | const expectedPublichPath = path.resolve(config.output.publicPath, bundle.file);
24 |
25 | t.true(bundle.file !== '');
26 | t.true(bundle.hash !== '');
27 | t.true(bundle.publicPath === expectedPublichPath);
28 | });
29 | });
30 | });
31 |
32 | test('returns nothing when there is no match', (t) => {
33 | bundles = getBundles(manifest, ['foo-bar', 'foo', null, undefined]);
34 |
35 | t.true(Object.keys(bundles).length === 0);
36 | });
37 |
38 | test('should work even with null/undefined manifest or modules', (t) => {
39 | bundles = getBundles(manifest, null);
40 | t.true(Object.keys(bundles).length === 0);
41 |
42 | bundles = getBundles(null, []);
43 | t.true(Object.keys(bundles).length === 0);
44 |
45 | bundles = getBundles([], null);
46 | t.true(Object.keys(bundles).length === 0);
47 | });
48 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/source/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * react-loadable-ssr-addon
3 | * @author Marcos Gonçalves
4 | * @version 1.0.1
5 | */
6 |
7 | import ReactLoadableSSRAddon from './ReactLoadableSSRAddon';
8 | import getBundles from './getBundles';
9 |
10 | module.exports = ReactLoadableSSRAddon;
11 | module.exports.getBundles = getBundles;
12 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/source/utils/computeIntegrity.js:
--------------------------------------------------------------------------------
1 | /**
2 | * react-loadable-ssr-addon
3 | * @author Marcos Gonçalves
4 | * @version 1.0.1
5 | */
6 |
7 | import crypto from 'crypto';
8 |
9 | /**
10 | * Compute SRI Integrity
11 | * @func computeIntegrity
12 | * See {@link https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity Subresource Integrity} at MDN
13 | * @param {array} algorithms - The algorithms you want to use when hashing `content`
14 | * @param {string} source - File contents you want to hash
15 | * @return {string} SRI hash
16 | */
17 | function computeIntegrity(algorithms, source) {
18 | return Array.isArray(algorithms)
19 | ? algorithms.map((algorithm) => {
20 | const hash = crypto
21 | .createHash(algorithm)
22 | .update(source, 'utf8')
23 | .digest('base64');
24 | return `${algorithm}-${hash}`;
25 | }).join(' ')
26 | : '';
27 | }
28 |
29 | export default computeIntegrity;
30 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/source/utils/getFileExtension.js:
--------------------------------------------------------------------------------
1 | /**
2 | * react-loadable-ssr-addon
3 | * @author Marcos Gonçalves
4 | * @version 1.0.1
5 | */
6 |
7 | /**
8 | * Get file extension
9 | * @method getFileExtension
10 | * @static
11 | * @param {string} filename - File name
12 | * @returns {string} - File extension
13 | */
14 | function getFileExtension(filename) {
15 | if (!filename || typeof filename !== 'string') { return ''; }
16 |
17 | const fileExtRegex = /\.\w{2,4}\.(?:map|gz)$|\.\w+$/i;
18 |
19 | const name = filename.split(/[?#]/)[0]; // eslint-disable-line prefer-destructuring
20 | const ext = name.match(fileExtRegex);
21 |
22 | return ext && ext.length ? ext[0] : '';
23 | }
24 |
25 | export default getFileExtension;
26 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/source/utils/getFileExtension.test.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import getFileExtension from './getFileExtension';
3 |
4 | test('returns the correct file extension', (t) => {
5 | const extensions = ['.jpeg', '.js', '.css', '.json', '.xml'];
6 | const filePath = 'source/static/images/hello-world';
7 |
8 | extensions.forEach((ext) => {
9 | t.true(getFileExtension(`${filePath}${ext}`) === ext);
10 | });
11 | });
12 |
13 | test('sanitize file hash', (t) => {
14 | const hashes = ['?', '#'];
15 | const filePath = 'source/static/images/hello-world.jpeg';
16 |
17 | hashes.forEach((hash) => {
18 | t.true(getFileExtension(`${filePath}${hash}d587bbd6e38337f5accd`) === '.jpeg');
19 | });
20 | });
21 |
22 | test('returns empty string when there is no file extension', (t) => {
23 | const filePath = 'source/static/resource';
24 |
25 | t.true(getFileExtension(filePath) === '');
26 | });
27 |
28 | test('should work even with null/undefined arg', (t) => {
29 | const filePaths = ['', null, undefined];
30 |
31 | filePaths.forEach((path) => {
32 | t.true(getFileExtension(path) === '');
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/source/utils/hasEntry.js:
--------------------------------------------------------------------------------
1 | /**
2 | * react-loadable-ssr-addon
3 | * @author Marcos Gonçalves
4 | * @version 1.0.1
5 | */
6 |
7 | /**
8 | * Checks if object array already contains given value
9 | * @method hasEntry
10 | * @function
11 | * @param {array} target - Object array to be inspected
12 | * @param {string} targetKey - Object key to look for
13 | * @param {string} searchFor - Value to search existence
14 | * @returns {boolean}
15 | */
16 | export default function hasEntry(target, targetKey, searchFor) {
17 | if (!target) { return false; }
18 |
19 | for (let i = 0; i < target.length; i += 1) {
20 | const file = target[i][targetKey];
21 | if (file === searchFor) { return true; }
22 | }
23 |
24 | return false;
25 | }
26 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/source/utils/hasEntry.test.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import hasEntry from './hasEntry';
3 |
4 | const assets = [
5 | {
6 |
7 | file: 'content.chunk.js',
8 | hash: 'd41d8cd98f00b204e9800998ecf8427e',
9 | publicPath: './',
10 | integrity: null,
11 | },
12 | {
13 |
14 | file: 'header.chunk.js',
15 | hash: '699f4bd49870f2b90e1d1596d362efcb',
16 | publicPath: './',
17 | integrity: null,
18 | },
19 | {
20 |
21 | file: 'shared-multilevel.chunk.js',
22 | hash: 'ab7b8b1c1d5083c17a39ccd2962202e1',
23 | publicPath: './',
24 | integrity: null,
25 | },
26 | ];
27 |
28 | test('should flag as has entry', (t) => {
29 | const fileName = 'header.chunk.js';
30 |
31 | t.true(hasEntry(assets, 'file', fileName));
32 | });
33 |
34 | test('should flag as has no entry', (t) => {
35 | const fileName = 'footer.chunk.js';
36 |
37 | t.false(hasEntry(assets, 'file', fileName));
38 | });
39 |
40 | test('should work even with null/undefined target', (t) => {
41 | const targets = [[], null, undefined];
42 |
43 | targets.forEach((target) => {
44 | t.false(hasEntry(target, 'file', 'foo.js'));
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/source/utils/index.js:
--------------------------------------------------------------------------------
1 | export { default as computeIntegrity } from './computeIntegrity';
2 | export { default as getFileExtension } from './getFileExtension';
3 | export { default as unique } from './unique';
4 | export { default as hasEntry } from './hasEntry';
5 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/source/utils/unique.js:
--------------------------------------------------------------------------------
1 | /**
2 | * react-loadable-ssr-addon
3 | * @author Marcos Gonçalves
4 | * @version 1.0.1
5 | */
6 |
7 | /**
8 | * Clean array to unique values
9 | * @method unique
10 | * @function
11 | * @param {array} array - Array to be inspected
12 | * @returns {array} - Array with unique values
13 | */
14 | export default function unique(array) {
15 | return array.filter((elem, pos, arr) => arr.indexOf(elem) === pos);
16 | }
17 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/source/utils/unique.test.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import unique from './unique';
3 |
4 | test('it filters duplicated entries', (t) => {
5 | const duplicated = ['two', 'four'];
6 | const raw = ['one', 'two', 'three', 'four'];
7 | const filtered = unique([...raw, ...duplicated]);
8 |
9 | duplicated.forEach((dup) => {
10 | t.true(filtered.filter((item) => item === dup).length === 1);
11 | });
12 | });
13 |
14 | test('should work with null/undefined values', (t) => {
15 | const falsy = [null, undefined];
16 | const raw = ['one', 'two', 'three', 'four'];
17 | const filtered = unique([...raw, ...falsy]);
18 |
19 | falsy.forEach((value) => {
20 | t.true(filtered.includes(value));
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable-ssr-addon/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const ReactLoadableSSRAddon = require('./lib');
4 |
5 | const HOST = process.env.HOST || '127.0.0.1';
6 | const PORT = process.env.PORT || '8080';
7 |
8 | module.exports = {
9 | mode: 'production',
10 | target: 'web',
11 | entry: {
12 | index: './example/client.jsx',
13 | },
14 | devtool: 'eval-cheap-module-source-map',
15 | output: {
16 | publicPath: '/dist/',
17 | path: path.join(__dirname, 'example', 'dist'),
18 | filename: '[name].js',
19 | chunkFilename: '[name].chunk.js',
20 | },
21 | resolve: {
22 | extensions: ['.js', '.jsx'],
23 | alias: {
24 | 'react-loadable-ssr-addon': path.resolve(__dirname, './source'),
25 | },
26 | },
27 | module: {
28 | rules: [
29 | {
30 | test: /\.jsx?$/,
31 | exclude: /(node_modules|bower_components|public\/)/,
32 | use: {
33 | loader: 'babel-loader',
34 | options: {
35 | babelrc: false,
36 | presets: [
37 | '@babel/preset-env',
38 | '@babel/preset-react',
39 | ],
40 | plugins: [
41 | require('@babel/plugin-proposal-class-properties'),
42 | require('@babel/plugin-proposal-object-rest-spread'),
43 | require('@babel/plugin-syntax-dynamic-import'),
44 | require('react-loadable/babel'),
45 | ],
46 | },
47 | },
48 | },
49 | ],
50 | },
51 | plugins: [
52 | new ReactLoadableSSRAddon({
53 | filename: 'react-loadable-ssr-addon.json',
54 | }),
55 | ],
56 | performance: false,
57 | };
58 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-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 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable/babel.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/babel');
2 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-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 | };
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable/lib/webpack.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
4 |
5 | var fs = require('fs');
6 | var path = require('path');
7 | var url = require('url');
8 |
9 | function buildManifest(compiler, compilation) {
10 | var context = compiler.options.context;
11 | var manifest = {};
12 |
13 | compilation.chunks.forEach(function (chunk) {
14 | chunk.files.forEach(function (file) {
15 | chunk.forEachModule(function (module) {
16 | var id = module.id;
17 | var name = typeof module.libIdent === 'function' ? module.libIdent({ context: context }) : null;
18 | var publicPath = url.resolve(compilation.outputOptions.publicPath || '', file);
19 |
20 | var currentModule = module;
21 | if (module.constructor.name === 'ConcatenatedModule') {
22 | currentModule = module.rootModule;
23 | }
24 | if (!manifest[currentModule.rawRequest]) {
25 | manifest[currentModule.rawRequest] = [];
26 | }
27 |
28 | manifest[currentModule.rawRequest].push({ id: id, name: name, file: file, publicPath: publicPath });
29 | });
30 | });
31 | });
32 |
33 | return manifest;
34 | }
35 |
36 | var ReactLoadablePlugin = function () {
37 | function ReactLoadablePlugin() {
38 | var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
39 |
40 | _classCallCheck(this, ReactLoadablePlugin);
41 |
42 | this.filename = opts.filename;
43 | }
44 |
45 | ReactLoadablePlugin.prototype.apply = function apply(compiler) {
46 | var _this = this;
47 |
48 | compiler.plugin('emit', function (compilation, callback) {
49 | var manifest = buildManifest(compiler, compilation);
50 | var json = JSON.stringify(manifest, null, 2);
51 | var outputDirectory = path.dirname(_this.filename);
52 | try {
53 | fs.mkdirSync(outputDirectory);
54 | } catch (err) {
55 | if (err.code !== 'EEXIST') {
56 | throw err;
57 | }
58 | }
59 | fs.writeFileSync(_this.filename, json);
60 | callback();
61 | });
62 | };
63 |
64 | return ReactLoadablePlugin;
65 | }();
66 |
67 | function getBundles(manifest, moduleIds) {
68 | return moduleIds.reduce(function (bundles, moduleId) {
69 | return bundles.concat(manifest[moduleId]);
70 | }, []);
71 | }
72 |
73 | exports.ReactLoadablePlugin = ReactLoadablePlugin;
74 | exports.getBundles = getBundles;
--------------------------------------------------------------------------------
/webpack/definePlugin/react-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 |
--------------------------------------------------------------------------------
/webpack/definePlugin/react-loadable/webpack.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/webpack');
2 |
--------------------------------------------------------------------------------
/webpack/definePlugin/webpack-plugin-no-require-css/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 删除 css 的引入
3 | * 可能社区已经有现成的插件但是不想费劲儿找了,还是自己写一个吧。
4 | */
5 | module.exports = function ({ types: babelTypes }) {
6 | return {
7 | name: "no-require-css",
8 | visitor: {
9 | ImportDeclaration(path, state) {
10 | let importFile = path.node.source.value;
11 | if(importFile.indexOf('.scss')>-1){
12 | // 干掉css 导入
13 | path.remove();
14 | }
15 | }
16 | }
17 | };
18 | };
19 |
--------------------------------------------------------------------------------
/webpack/definePlugin/webpack-plugin-router/diff.js:
--------------------------------------------------------------------------------
1 | class diffData {
2 | diff(newData, oldData) {
3 | let flag = true;
4 | if (newData instanceof Object) {
5 | if (!this.isObject(newData, oldData)) {
6 | flag = false;
7 | return false;
8 | }
9 | for (let key in newData) {
10 | if (
11 | newData[key] instanceof Object ||
12 | newData[key] instanceof Array ||
13 | newData[key] instanceof Function
14 | ) {
15 | flag = this.diff(newData[key], oldData[key]);
16 | if (!flag) {
17 | return flag;
18 | }
19 | } else if (newData[key] !== oldData[key]) {
20 | flag = false;
21 | return flag;
22 | }
23 | }
24 | }
25 |
26 | if (newData instanceof Array) {
27 | if (!this.isArray(newData, oldData)) {
28 | return false;
29 | }
30 | for (let [index, item] in newData.entries()) {
31 | if (
32 | newData[index] instanceof Object ||
33 | newData[index] instanceof Array ||
34 | newData[index] instanceof Function
35 | ) {
36 | flag = this.diff(newData[index], oldData[index]);
37 | if (!flag) {
38 | return flag;
39 | }
40 | } else if (newData[index] !== oldData[index]) {
41 | flag = false;
42 | return flag;
43 | }
44 | }
45 | }
46 |
47 | if (newData instanceof Function) {
48 | if (!this.isFunction(newData, oldData)) {
49 | flag = false;
50 | return flag;
51 | }
52 | }
53 |
54 | return flag;
55 | }
56 | isFunction(newData, oldData) {
57 | if (
58 | newData instanceof Function &&
59 | oldData instanceof Function &&
60 | newData.toString() === oldData.toString()
61 | ) {
62 | return true;
63 | }
64 | return false;
65 | }
66 | isObject(newData, oldData) {
67 | if (
68 | newData instanceof Object &&
69 | oldData instanceof Object &&
70 | Object.keys(newData).length === Object.keys(oldData).length
71 | ) {
72 | return true;
73 | }
74 | return false;
75 | }
76 | isArray(newData, oldData) {
77 | if (
78 | newData instanceof Array &&
79 | oldData instanceof Array &&
80 | newData.length === oldData.length
81 | ) {
82 | return true;
83 | }
84 | return false;
85 | }
86 | }
87 |
88 | module.exports = (newData, oldData) => {
89 | return new diffData().diff(newData, oldData);
90 | };
91 |
--------------------------------------------------------------------------------
/webpack/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-09 10:39:13
6 | * @FilePath: /react-loading-ssr/webpack/index.js
7 | * @Description:
8 | */
9 | // chalk插件,用来在命令行中输入不同颜色的文字
10 | const chalk = require("chalk");
11 | const webpack = require("webpack");
12 | const client = require("./config/client");
13 | const server = require("./config/server");
14 | const webpackMerge = require("webpack-merge");
15 |
16 | require("dotenv").config({ path: ".env" });
17 |
18 | let {
19 | NODE_ENV, // 环境参数
20 | target, // 环境参数
21 | } = process.env; // 环境参数
22 |
23 | const isSsr = target === "ssr";
24 | // 是否是生产环境
25 | const isEnvProduction = NODE_ENV === "production";
26 | // 是否是测试开发环境
27 | const isEnvDevelopment = NODE_ENV === "development";
28 |
29 | const compileRes = async (config) => {
30 | await new Promise((reslove, reject) => {
31 | webpack(config, (err, stats) => {
32 | if (err) {
33 | console.log("Errors:" + chalk.red(err.stack || err));
34 | reject(chalk.red(err.stack || err));
35 | if (err.details) {
36 | console.log("Errors:" + chalk.red(err.details));
37 | }
38 | reject(chalk.red(err.details));
39 | }
40 | if (stats.hasErrors()) {
41 | console.log(
42 | "Errors:" +
43 | chalk.red(
44 | stats.toString({
45 | colors: true,
46 | chunks: false // Makes the build much quieter
47 | }) + "\n\n"
48 | )
49 | );
50 | reject(
51 | chalk.red(
52 | stats.toString({
53 | colors: true,
54 | chunks: false // Makes the build much quieter
55 | }) + "\n\n"
56 | )
57 | );
58 | }
59 | reslove();
60 | });
61 | });
62 | };
63 |
64 | module.exports = {
65 | compiler: async () => {
66 |
67 | if (isEnvProduction) {
68 | await compileRes(client);
69 | if (isSsr) {
70 | await compileRes(server);
71 | }
72 | }
73 | return true;
74 | },
75 | config: isSsr ? [client, server] : [client]
76 | };
77 |
--------------------------------------------------------------------------------
/webpack/utils/alias.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | module.exports = {
4 | // 路径配置
5 | "@": path.join(process.cwd()),
6 | client: path.join(process.cwd(), "/client"),
7 | server: path.join(process.cwd(), "/server")
8 | };
9 |
--------------------------------------------------------------------------------
/webpack/utils/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var copyFile = require("./copyFile");
3 | var readFile = require("./readFile");
4 | var writeFile = require("./writeFile");
5 | var watchFile = require("./watchFile");
6 | var readWriteFiles = require("./readWriteFiles");
7 | var stringToObject = require("./stringToObject");
8 | var alias = require("./alias");
9 |
10 |
11 | module.exports = {
12 | alias,
13 | copyFile,
14 | readFile,
15 | writeFile,
16 | watchFile,
17 | readWriteFiles,
18 | stringToObject
19 | };
20 |
--------------------------------------------------------------------------------
/webpack/utils/readFile.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-05-11 11:23:58
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-05-11 11:44:42
6 | * @FilePath: /webpack-cli/delete_node_modules.js
7 | * @Description:
8 | */
9 | var fs = require('fs');
10 | var path = require('path');
11 | const { readdirSync, stat, statSync } = fs;
12 | const readFile = (path, callback = () => {}) => {
13 | var files = readdirSync(path);
14 | files.forEach((item, index) => {
15 | var reg = /(\\\\)|(\\)/g;
16 | var src = path + '/' + item;
17 | src = src.replace(reg, '/');
18 | var stat = statSync(src);
19 | if (stat.isDirectory()) {
20 | //递归读取文件
21 | readFile(src, callback);
22 | } else if (stat.isFile()) {
23 | callback({
24 | path: src,
25 | filename: item,
26 | });
27 | }
28 | });
29 | };
30 | module.exports = readFile;
31 |
32 | // readFile(path.join(process.cwd(), '../../../node_modules'),(value)=>{
33 | // console.log('value=',value)
34 | // });
35 |
--------------------------------------------------------------------------------
/webpack/utils/readWriteFiles.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-08-04 09:21:17
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-09 12:07:17
6 | * @FilePath: /react-loading-ssr/webpack/utils/readWriteFiles.js
7 | * @Description:
8 | */
9 | const map = require('map-stream')
10 | const fs = require('vinyl-fs')
11 | const stream = require('stream')
12 | const watch = require('glob-watcher')
13 |
14 | // 编写中间件转换代码
15 | const transformCode = ($callback = (data, path) => data) => {
16 | return new stream.Transform({
17 | objectMode: true,
18 | transform(file, encoding, callback) {
19 | if (file.isStream()) {
20 | return callback(new Error('Streaming is not supported.'))
21 | }
22 | if (file.isNull() || !file.contents) {
23 | return callback(undefined, file)
24 | }
25 | if (!file.path) {
26 | return callback(
27 | new Error(
28 | 'Received file with no path. Files must have path to be resolved.',
29 | ),
30 | )
31 | }
32 |
33 | if (file.contents.toString() === '') {
34 | return callback(undefined, file)
35 | }
36 |
37 | file.contents = Buffer.from(
38 | $callback(file.contents.toString(), file.path),
39 | )
40 | callback(undefined, file)
41 | },
42 | })
43 | }
44 | // var log = function (file, cb) {
45 | // console.log(file);
46 | // cb(null, file);
47 | // };
48 | const readWriteFile = ({ from, to, transform }) => {
49 | fs.src(from)
50 | // .pipe(map(log))
51 | .pipe(
52 | transformCode((contents, path) => {
53 | return transform(contents, path)
54 | }),
55 | )
56 | .pipe(fs.dest(to))
57 | }
58 |
59 | module.exports = ({
60 | from,
61 | to,
62 | transform,
63 | isWatch = true,
64 | callback = () => {},
65 | }) => {
66 | if (isWatch) {
67 | watch(from, {}, function () {
68 | readWriteFile({ from, to, transform, isWatch })
69 | callback()
70 | })
71 | }
72 | readWriteFile({ from, to, transform, isWatch })
73 | callback()
74 | }
75 |
--------------------------------------------------------------------------------
/webpack/utils/stringToObject.js:
--------------------------------------------------------------------------------
1 | module.exports = function (string) {
2 | const regex = /(?<=\{)(.+?)(?=\})/g; // {} 花括号,大括号
3 | string = string.match(regex);
4 | let obj={}
5 | if (string) {
6 | string = string[0];
7 | let stringArr = string.split(",");
8 | for (let item of stringArr) {
9 | let [key, value] = item.split(":");
10 | obj[`${key}`] = value;
11 | }
12 | }
13 | return obj;
14 | };
15 |
--------------------------------------------------------------------------------
/webpack/utils/watchFile.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Date: 2022-05-24 15:49:12
3 | * @Author: Yao guan shou
4 | * @LastEditors: Yao guan shou
5 | * @LastEditTime: 2022-08-06 16:31:33
6 | * @FilePath: /react-loading-ssr/webpack/utils/watchFile.js
7 | * @Description:
8 | */
9 | const chokidar = require('chokidar');
10 | const path = require('path');
11 |
12 | class watchFile {
13 | constructor(filepath, callback) {
14 | this.filepath = filepath;
15 | this.callback = callback;
16 | try {
17 | this.init();
18 | } catch (error) {}
19 | }
20 | init() {
21 | if (!this.watcher) {
22 | this.watcher = chokidar.watch(this.filepath);
23 | }
24 | this.watcher
25 | .on('add', (path) => this.addFileListener(path))
26 | .on('addDir', (path) => this.addDirecotryListener(path))
27 | .on('change', (path) => this.fileChangeListener(path))
28 | .on('unlink', (path) => this.fileRemovedListener(path))
29 | .on('unlinkDir', (path) => this.directoryRemovedListener(path))
30 | .on('error', (error) => {
31 | // console.info('watchFile error:', error);
32 | })
33 | .on('ready', () => {
34 | // console.info('准备监听');
35 | this.ready = true;
36 | });
37 | }
38 | // 转换路径
39 | transformPath(path) {
40 | let reg = /(\\\\)|(\\)/g;
41 | return path.replace(reg, '/');
42 | }
43 | // 文件新增时
44 | addFileListener(path) {
45 | path = this.transformPath(path);
46 | if (this.ready) {
47 | this.callback({
48 | path,
49 | message: `文件${path}有新增`,
50 | type: 'file',
51 | action: 'add',
52 | });
53 | }
54 | }
55 |
56 | // 文件内容改变时
57 | fileChangeListener(path) {
58 | path = this.transformPath(path);
59 | if (this.ready) {
60 | this.callback({
61 | path,
62 | message: `文件${path}有修改`,
63 | type: 'file',
64 | action: 'change',
65 | });
66 | }
67 | }
68 |
69 | // 删除文件
70 | fileRemovedListener(path) {
71 | path = this.transformPath(path);
72 | if (this.ready) {
73 | this.callback({
74 | path,
75 | message: `文件${path}被删除了`,
76 | type: 'file',
77 | action: 'delete',
78 | });
79 | }
80 | }
81 |
82 | // 添加目录
83 | addDirecotryListener(path) {
84 | path = this.transformPath(path);
85 | if (this.ready) {
86 | this.callback({
87 | path,
88 | message: `目录${path}有新增`,
89 | type: 'directory',
90 | action: 'add',
91 | });
92 | }
93 | }
94 | // 删除目录时
95 | directoryRemovedListener(path) {
96 | path = this.transformPath(path);
97 | if (this.ready) {
98 | this.callback({
99 | path,
100 | message: `目录${path}被删除了`,
101 | type: 'directory',
102 | action: 'delete',
103 | });
104 | }
105 | }
106 | }
107 |
108 | // new watchFile(
109 | // 'C:/Users/86185/Desktop/React-universal-ssr-master/server',
110 | // (value) => {
111 | // // console.log('path===', path);
112 | // console.log('value===', value);
113 | // }
114 | // );
115 | module.exports = watchFile;
116 | // watch(path.join(process.cwd(), '/src'));
117 |
--------------------------------------------------------------------------------
/webpack/utils/writeFile.js:
--------------------------------------------------------------------------------
1 | var fs = require("fs");
2 | var path = require("path");
3 | var glob = require("glob");
4 |
5 | class WriteFile {
6 | constructor() {}
7 | writeFile(path, content) {
8 | path = this.transformPath(path);
9 | // 写入文件
10 | fs.writeFileSync(path, content);
11 | }
12 | statSync(path) {
13 | path = this.transformPath(path);
14 | let stats = {};
15 | try {
16 | stats = fs.statSync(path);
17 | } catch (error) {
18 | stats = null;
19 | }
20 | // stats.isFile()
21 | // stats.isDirectory()
22 | return stats;
23 | }
24 | readFile(path) {
25 | // 读取文件
26 | path = this.transformPath(path);
27 | return fs.readFileSync(path, "UTF-8");
28 | }
29 |
30 | // 转换路径
31 | transformPath(path) {
32 | let reg = /(\\\\)|(\\)/g;
33 | return path.replace(reg, "/");
34 | }
35 | //判断文件夹是否存在
36 | existsSync(path) {
37 | path = this.transformPath(path);
38 |
39 | return fs.existsSync(path);
40 | }
41 | // 创建目录
42 | mkdir(path) {
43 | path = this.transformPath(path);
44 | return fs.mkdirSync(path);
45 | }
46 |
47 | // 检测目录如果不存在则创建
48 | checkDirectory(path, callback = () => {}) {
49 | path = this.transformPath(path);
50 | if (!this.existsSync(path)) {
51 | //不存在则写入创建努力
52 | this.mkdir(path);
53 | callback();
54 | } else {
55 | callback();
56 | }
57 | }
58 |
59 | init(path, content) {
60 | path = path.split("/");
61 | let paths = path.filter((item) => item !== "");
62 | let $path = "";
63 | for (let [index, path] of paths.entries()) {
64 | $path += index == 0 && this.statSync(path) ? path : "/" + path;
65 | let stat = this.statSync($path);
66 | // 最后一个
67 | if (index == paths.length - 1) {
68 | this.writeFile($path, content);
69 | } else if (!stat || (stat && !stat.isDirectory())) {
70 | this.checkDirectory($path);
71 | }
72 | }
73 | }
74 | }
75 |
76 | module.exports = (path, content) => {
77 | return new WriteFile().init(path, content);
78 | };
79 |
--------------------------------------------------------------------------------