├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── mock
└── mock-data.js
├── package.json
├── postcss.config.js
├── public
├── favicon.png
└── index.html
├── server.js
├── src
├── assets
│ ├── demo.wasm
│ ├── favicon-144.png
│ ├── index.wasm
│ ├── loading.gif
│ ├── optimized.wasm
│ ├── react-logo.jpg
│ ├── starSky.mp3
│ └── test.jpg
├── component
│ ├── footer
│ │ ├── index.js
│ │ └── index.less
│ ├── loading
│ │ ├── index.js
│ │ └── index.less
│ └── menu
│ │ ├── index.js
│ │ └── index.less
├── container
│ ├── features
│ │ ├── index.js
│ │ └── index.less
│ ├── home
│ │ ├── index.js
│ │ └── index.less
│ ├── notfound
│ │ ├── index.js
│ │ └── index.less
│ ├── routers
│ │ ├── index.js
│ │ └── index.less
│ └── test
│ │ ├── container
│ │ ├── page1.js
│ │ ├── page2.js
│ │ └── page3.js
│ │ ├── index.js
│ │ └── index.less
├── index.js
├── models
│ ├── app.js
│ └── test.js
├── root
│ └── index.js
├── store
│ └── index.js
├── styles
│ ├── css.css
│ └── less.less
└── util
│ ├── server.js
│ └── tools.js
├── webpack.dev.config.js
├── webpack.production.config.js
└── 依赖清单.md
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react"],
3 | "plugins": [
4 | "@babel/plugin-transform-runtime",
5 | "@babel/plugin-proposal-object-rest-spread",
6 | "@babel/plugin-syntax-dynamic-import",
7 | ["@babel/plugin-proposal-decorators", { "legacy": true }],
8 | "@babel/plugin-proposal-class-properties",
9 | "@babel/plugin-proposal-optional-chaining",
10 | "@babel/plugin-proposal-nullish-coalescing-operator",
11 | "react-loadable/babel",
12 | [
13 | "import",
14 | {
15 | "libraryName": "antd",
16 | "style": true
17 | }
18 | ]
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "es6": true,
6 | "mocha": true
7 | },
8 | "parser": "@babel/eslint-parser",
9 | "parserOptions": {
10 | "ecmaVersion": 9,
11 | "ecmaFeatures": {
12 | "impliedStrict": true,
13 | "jsx": true
14 | },
15 | "allowImportExportEverywhere": true,
16 | "sourceType": "module"
17 | },
18 | "plugins": ["react", "react-hooks", "prettier"],
19 | "rules": {
20 | "semi": "warn",
21 | "no-unused-vars": "off",
22 | "no-cond-assign": "error",
23 | "no-debugger": "warn",
24 | "no-dupe-args": "error",
25 | "no-caller": "error",
26 | "no-unmodified-loop-condition": "error",
27 | "no-with": "error",
28 | "no-catch-shadow": "error",
29 | "react/no-unescaped-entities": "off",
30 | "react-hooks/rules-of-hooks": "error",
31 | "prettier/prettier": "warn",
32 | "no-unused-expressions": "warn"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # mac .DS_Store
2 | .DS_Store
3 |
4 | # Webstorm
5 | .idea
6 |
7 | # Logs
8 | logs
9 | *.log
10 | npm-debug.log*
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 |
17 | # Directory for instrumented libs generated by jscoverage/JSCover
18 | lib-cov
19 |
20 | # Coverage directory used by tools like istanbul
21 | coverage
22 |
23 | # nyc test coverage
24 | .nyc_output
25 |
26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
27 | .grunt
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules
37 | jspm_packages
38 | build
39 | .happypack
40 | yarn.lock
41 | .vscode/*
42 | dll
43 | pnpm-lock.yaml
44 |
45 | # Optional npm cache directory
46 | .npm
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # bower
52 | # bower_components
53 |
54 | # access log
55 | access.log
56 |
57 | # backup
58 | backup
59 |
60 | # config.json
61 | config.json
62 |
63 | # build
64 | .build
65 |
66 | # app config
67 |
68 | # tests
69 | mytest/*
70 |
71 | # build
72 | dist/*
73 | !dist/.gitkeep
74 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "14"
4 | - "10"
5 | script:
6 | - yarn build
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 java_luo
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React-Luo
2 |
3 | [](https://travis-ci.org/javaLuo/react-luo)
4 | [](https://codebeat.co/projects/github-com-javaluo-react-luo-master)
5 | [](https://github.com/prettier/prettier) 
6 |
7 | ## what is this?
8 |
9 | react automatically
10 | 这是一个 React 脚手架,没有使用 create-react-app
11 | 标准的 React+Redux 分层结构
12 | 经过了多个项目的实践,不停的更新和优化出来的。目前自己做项目也在用。
13 |
14 | - PWA、Hooks、代码分割、热替换、HappyPack 多线程构建、ES6+语法
15 |
16 | ## 构建 Start
17 |
18 | ```
19 | yarn install # 安装依赖模块
20 |
21 | yarn start # 运行开发环境: http://localhost:8888
22 | yarn build # 正式打包,用于生产环境
23 |
24 | yarn dist # 运行正式打包后的最终文件(build目录下的文件): http://localhost:8889
25 | yarn distmac # MAC下运行最终文件:http://localhost:8889
26 |
27 | yarn prettier # 自动格式化src、mock目录下的所有.js/.css/.scss/.less文件
28 | ```
29 |
30 | ## 更新日志 Update log
31 |
32 | 见Wiki
33 |
34 | ## 目录结构 Structure
35 |
36 | ```
37 | .
38 | ├── build # 正式打包后,会自动生成该文件夹,其中会包含最终用于生产环境的文件
39 | │ ├── dist # 编译后的资源文件
40 | │ ├── icons # 编译后自动生成的各尺寸favicon图标,有的会用于PWA配置
41 | │ ├── asset-manifets.json # 记录了将会被缓存的资源
42 | │ ├── index.html # 编译后的主页html
43 | │ ├── manifest.json # PWA配置文件,配置了桌面图标,以APP方式启动时的启动页面相关参数
44 | │ └── service-worker.js # PWA核心worker, 用于离线访问,缓存不变的资源文件
45 | ├── mock # mock测试数据
46 | ├── public # 静态文件,index.html等
47 | ├── src # 项目代码目录
48 | │ ├── component # 所有的公共类UI组件
49 | │ ├── container # 所有的页面级容器组件
50 | | ├── ...
51 | | └── router # 根组件,里面配置了顶级的路由
52 | | ├── models # 模块(包含store数据/reducers/actions)
53 | │ ├── assets # 所有的图片、文件等静态资源
54 | │ ├── styles # 所有的样式文件
55 | │ ├── store # store数据中心
56 | │ ├── root # 根页
57 | │ ├── store # store数据中心
58 | │ ├── util # 自定义工具
59 | │ ├── index.js # 项目入口JS
60 | │ └── index.html # 主页html文件,开发环境和生产打包共用
61 | ├── server.js # 用于开发环境的服务部署
62 | ├── webpack.dev.config.js # 用于开发环境的webpack配置
63 | └── webpack.production.config.js # 用于生产环境正式打包的webpack配置
64 | ```
65 |
66 | ## 预览地址 Demo
67 |
68 | https://isluo.com/work/pwa/
69 |
70 | ## 参阅资料
71 |
72 | React 英文官网:https://reactjs.org
73 | React 中文文档:https://doc.react-china.org
74 | React GitHub 地址:https://github.com/facebook/react
75 | React 官方更新日志:https://github.com/facebook/react/releases
76 | React 生命周期:https://reactjs.org/docs/react-component.html
77 | mockjs 官网:http://mockjs.com/
78 | Eslint 中文站:http://eslint.cn/
79 | Babel GitHub 地址:https://github.com/babel/babel
80 |
--------------------------------------------------------------------------------
/mock/mock-data.js:
--------------------------------------------------------------------------------
1 | /** MOCK 模拟数据拦截ajax请求 **/
2 |
3 | const Mock = require("mockjs");
4 |
5 | /** 数据模版 **/
6 | const ajaxTest = {
7 | status: 200,
8 | "data|1-10": [
9 | {
10 | "id|+1": 1,
11 | email: "@EMAIL",
12 | },
13 | ],
14 | };
15 |
16 | exports.mockApi = (url, params) => {
17 | return Mock.mock(ajaxTest);
18 | };
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react_luo",
3 | "version": "1.0.0",
4 | "description": "react脚手架,最新技术",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node server.js --max_old_space_size=4096",
8 | "build": "webpack --config webpack.production.config.js",
9 | "dist": "set NODE_ENV=production&& node server.js",
10 | "distmac": "export NODE_ENV=production&& node server.js",
11 | "prettier": "prettier --write \"{src,mock}/**/*.{js,css,less}\"",
12 | "cover": "./node_modules/.bin/istanbul cover _mocha",
13 | "coveralls": "npm run cover -- --report lcovonly && cat ./coverage/lcov.info | ./node_modules/.bin/coveralls"
14 | },
15 | "author": "admin",
16 | "license": "ISC",
17 | "private": true,
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/javaLuo/react-luo.git"
21 | },
22 | "installConfig": {
23 | "pnp": false
24 | },
25 | "dependencies": {
26 | "@ant-design/icons": "^4.7.0",
27 | "@rematch/core": "^2.2.0",
28 | "antd": "^4.24.2",
29 | "axios": "^1.1.3",
30 | "core-js": "^3.26.1",
31 | "favicons": "6.2.2",
32 | "lodash": "^4.17.21",
33 | "react": "^18.2.0",
34 | "react-dom": "^18.2.0",
35 | "react-loadable": "^5.5.0",
36 | "react-redux": "^8.0.5",
37 | "react-router-dom": "^6.4.3",
38 | "redux": "^4.2.0"
39 | },
40 | "devDependencies": {
41 | "@babel/core": "^7.20.2",
42 | "@babel/eslint-parser": "^7.19.1",
43 | "@babel/plugin-proposal-class-properties": "^7.18.6",
44 | "@babel/plugin-proposal-decorators": "^7.20.2",
45 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
46 | "@babel/plugin-proposal-object-rest-spread": "^7.20.2",
47 | "@babel/plugin-proposal-optional-chaining": "^7.18.9",
48 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
49 | "@babel/plugin-transform-runtime": "^7.19.6",
50 | "@babel/preset-env": "^7.20.2",
51 | "@babel/preset-react": "^7.18.6",
52 | "@babel/runtime": "^7.20.1",
53 | "antd-dayjs-webpack-plugin": "^1.0.6",
54 | "autoprefixer": "^10.4.13",
55 | "babel-loader": "^9.1.0",
56 | "babel-plugin-import": "^1.13.5",
57 | "clean-webpack-plugin": "^4.0.0",
58 | "copy-webpack-plugin": "^11.0.0",
59 | "css-loader": "^6.7.2",
60 | "css-minimizer-webpack-plugin": "^4.2.2",
61 | "dayjs": "^1.11.6",
62 | "eslint": "^8.27.0",
63 | "eslint-plugin-prettier": "^4.2.1",
64 | "eslint-plugin-react": "^7.31.10",
65 | "eslint-plugin-react-hooks": "^4.6.0",
66 | "eslint-webpack-plugin": "^3.2.0",
67 | "express": "^4.18.2",
68 | "favicons-webpack-plugin": "^5.0.2",
69 | "happypack": "^5.0.1",
70 | "html-webpack-plugin": "^5.5.0",
71 | "less": "^4.1.3",
72 | "less-loader": "^11.1.0",
73 | "mini-css-extract-plugin": "^2.6.1",
74 | "mockjs": "^1.1.0",
75 | "postcss": "^8.4.19",
76 | "postcss-loader": "^7.0.1",
77 | "prettier": "^2.7.1",
78 | "style-loader": "^3.3.1",
79 | "terser-webpack-plugin": "^5.3.6",
80 | "webpack": "^5.75.0",
81 | "webpack-bundle-analyzer": "^4.7.0",
82 | "webpack-cli": "^4.10.0",
83 | "webpack-dev-middleware": "^5.3.3",
84 | "webpack-hot-middleware": "^2.25.3",
85 | "webpackbar": "^5.0.2",
86 | "workbox-webpack-plugin": "^6.5.4",
87 | "xml-loader": "^1.2.1"
88 | },
89 | "browserslist": [
90 | "iOS >= 8",
91 | "last 1 versions",
92 | "> 2%",
93 | "not dead",
94 | "not op_mini all"
95 | ]
96 | }
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | /** postcss-loader 解析器所需的配置文件 **/
2 | module.exports = {
3 | plugins: [
4 | require('autoprefixer')
5 | ]
6 | };
7 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaLuo/react-luo/a551c868257830db93ae04908984407611475033/public/favicon.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React
8 |
9 |
10 |
11 | <%= htmlWebpackPlugin.options.registerServiceWorker %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | /** 用于开发环境的服务启动 **/
2 | const path = require("path"); // 获取绝对路径有用
3 | const express = require("express"); // express服务器端框架
4 | const env = process.env.NODE_ENV; // 模式(dev开发环境,production生产环境)
5 | const webpack = require("webpack"); // webpack核心
6 | const webpackDevMiddleware = require("webpack-dev-middleware"); // webpack服务器
7 | const webpackHotMiddleware = require("webpack-hot-middleware"); // HMR热更新中间件
8 | const webpackConfig = require("./webpack.dev.config.js"); // webpack开发环境的配置文件
9 |
10 | const mock = require("./mock/mock-data"); // mock模拟数据,模拟后台业务
11 |
12 | const app = express(); // 实例化express服务
13 | const DIST_DIR = webpackConfig.output.path; // webpack配置中设置的文件输出路径,所有文件存放在内存中
14 | let PORT = 8888; // 服务启动端口号
15 |
16 | app.use(express.urlencoded({ extended: false }));
17 | app.use(express.json());
18 |
19 | /** 监听POST请求,返回MOCK模拟数据 **/
20 | app.post(/\/api.*/, (req, res, next) => {
21 | const result = mock.mockApi({ url: req.originalUrl, body: req.body });
22 | res.send(result);
23 | });
24 | app.get(/\/api.*/, (req, res, next) => {
25 | const result = mock.mockApi({ url: req.originalUrl, body: req.body });
26 | res.send(result);
27 | });
28 |
29 | if (env === "production") {
30 | // 如果是生产环境,则运行build文件夹中的代码
31 | PORT = 8889;
32 | app.use(express.static("build"));
33 | app.get("*", (req, res) => {
34 | res.sendFile(path.join(__dirname, "build", "index.html"));
35 | });
36 | } else {
37 | const compiler = webpack(webpackConfig); // 实例化webpack
38 | app.use(express.static("dll"));
39 | app.use(
40 | webpackDevMiddleware(compiler, {
41 | publicPath: webpackConfig.output.publicPath, // 对应webpack配置中的publicPath
42 | }),
43 | );
44 |
45 | app.use(webpackHotMiddleware(compiler)); // 挂载HMR热更新中间件
46 | // 所有请求都返回index.html
47 | app.get("*", (req, res, next) => {
48 | const filename = path.join(DIST_DIR, "index.html");
49 |
50 | // 由于index.html是由html-webpack-plugin生成到内存中的,所以使用下面的方式获取
51 | compiler.outputFileSystem.readFile(filename, (err, result) => {
52 | if (err) {
53 | return next(err);
54 | }
55 | res.set("content-type", "text/html");
56 | res.send(result);
57 | res.end();
58 | });
59 | });
60 | }
61 |
62 | /** 启动服务 **/
63 | app.listen(PORT, () => {
64 | console.log("本地服务启动地址: http://localhost:%s", PORT);
65 | });
66 |
--------------------------------------------------------------------------------
/src/assets/demo.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaLuo/react-luo/a551c868257830db93ae04908984407611475033/src/assets/demo.wasm
--------------------------------------------------------------------------------
/src/assets/favicon-144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaLuo/react-luo/a551c868257830db93ae04908984407611475033/src/assets/favicon-144.png
--------------------------------------------------------------------------------
/src/assets/index.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaLuo/react-luo/a551c868257830db93ae04908984407611475033/src/assets/index.wasm
--------------------------------------------------------------------------------
/src/assets/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaLuo/react-luo/a551c868257830db93ae04908984407611475033/src/assets/loading.gif
--------------------------------------------------------------------------------
/src/assets/optimized.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaLuo/react-luo/a551c868257830db93ae04908984407611475033/src/assets/optimized.wasm
--------------------------------------------------------------------------------
/src/assets/react-logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaLuo/react-luo/a551c868257830db93ae04908984407611475033/src/assets/react-logo.jpg
--------------------------------------------------------------------------------
/src/assets/starSky.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaLuo/react-luo/a551c868257830db93ae04908984407611475033/src/assets/starSky.mp3
--------------------------------------------------------------------------------
/src/assets/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaLuo/react-luo/a551c868257830db93ae04908984407611475033/src/assets/test.jpg
--------------------------------------------------------------------------------
/src/component/footer/index.js:
--------------------------------------------------------------------------------
1 | /** Footer 页面底部 **/
2 | import React from "react";
3 | import "./index.less";
4 |
5 | export default function Footer() {
6 | return (
7 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/component/footer/index.less:
--------------------------------------------------------------------------------
1 | .footer {
2 | text-align: center;
3 | padding: 5px 0;
4 | a:hover {
5 | text-decoration: underline;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/component/loading/index.js:
--------------------------------------------------------------------------------
1 | /** Loading组件 用于按需加载时过渡显示等 **/
2 | import React from "react";
3 | import "./index.less";
4 | import ImgLoading from "../../assets/loading.gif";
5 | export default function LoadingComponent(props) {
6 | function makeType(p) {
7 | let msg;
8 | if (p.error) {
9 | msg = "加载出错,请刷新页面";
10 | } else if (p.timedOut) {
11 | msg = "加载超时";
12 | } else if (p.pastDelay) {
13 | msg = "加载中…";
14 | }
15 | return msg;
16 | }
17 |
18 | return (
19 |
20 |

21 |
{makeType(props)}
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/src/component/loading/index.less:
--------------------------------------------------------------------------------
1 | .loading {
2 | text-align: center;
3 | padding: 50px;
4 | font-size: 14px;
5 | position: relative;
6 | margin: 0 auto;
7 | img {
8 | margin-bottom: 12px;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/component/menu/index.js:
--------------------------------------------------------------------------------
1 | /** 导航 **/
2 |
3 | import React from "react";
4 | import { Link } from "react-router-dom";
5 | import { useNavigate } from "react-router-dom";
6 | import "./index.less";
7 |
8 | export default function Menu() {
9 | const navigate = useNavigate();
10 |
11 | function goToTest() {
12 | navigate("/test?a=123&b=abc", { state: { c: 456, d: "ABC" } });
13 | }
14 |
15 | return (
16 |
17 |
首页|
构建与特性|
18 |
19 | 测试:Link跳转
20 |
21 | |
22 |
goToTest()}
25 | to={{
26 | pathname: "/test",
27 | search: "?a=123&b=abc",
28 | }}
29 | state={{ c: 456, d: "ABC" }}
30 | >
31 | 测试:api跳转
32 |
33 | |
34 |
39 | GitHub
40 |
41 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/src/component/menu/index.less:
--------------------------------------------------------------------------------
1 | .menu {
2 | margin-top: 10px;
3 | color: #888;
4 | text-align: center;
5 | .active {
6 | color: #67daf9;
7 | }
8 | a,
9 | .link {
10 | padding: 0 5px;
11 | color: #0066cc;
12 | text-decoration: none;
13 | cursor: pointer;
14 | &:hover {
15 | text-decoration: underline;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/container/features/index.js:
--------------------------------------------------------------------------------
1 | /** 构建与特性页 **/
2 |
3 | /** 所需的各种插件 **/
4 | import React from "react";
5 |
6 | /** 所需的所有资源 **/
7 | import "./index.less";
8 |
9 | export default function FeaturesPageContainer(props) {
10 | return (
11 |
12 |
构建与特性
13 |
14 |
15 |
安装依赖文件
16 |
npm install
17 |
18 |
19 |
启动开发环境
20 |
npm run start
21 |
代码打包编译,默认监听8888端口
22 |
访问http://localhost:8888 即可查看
23 |
24 |
25 |
正式打包
26 |
npm run build
27 |
会将最终代码打包至/build文件夹中
28 |
29 |
30 |
运行生产环境的代码
31 |
npm run dist (windows系统)
32 |
npm run distmac (Mac/Linux系统)
33 |
运行build文件夹下生成好的最终代码
34 |
35 |
36 |
代码自动格式化
37 |
npm run prettier
38 |
自动美化所有js/css/less等文件
39 |
40 |
41 |
HMR局部热更新
42 |
43 | 使用webpack-dev-middleware 和 webpack-hot-middleware设置了热更新
44 |
45 |
46 |
47 |
代码分割
48 |
react-loadable实现的代码分割
49 |
src/container/root/index.js中能查看例子
50 |
51 |
52 |
webpack5.x
53 |
使用了最新版本的webpack,编译速度更快
54 |
55 |
56 |
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/src/container/features/index.less:
--------------------------------------------------------------------------------
1 | .page-features {
2 | width: 100%;
3 | max-width: 1200px;
4 | margin: 0 auto;
5 | padding: 40px;
6 | letter-spacing: 1px;
7 | .box {
8 | margin-top: 40px;
9 | .list {
10 | margin-bottom: 20px;
11 | p {
12 | font-size: 14px;
13 | padding: 5px 10px;
14 | margin: 10px 0;
15 | background-color: #eeeeff;
16 | }
17 | div {
18 | color: #888;
19 | font-size: 12px;
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/container/home/index.js:
--------------------------------------------------------------------------------
1 | /** 主页 **/
2 |
3 | /** 所需的各种插件 **/
4 | import React from "react";
5 | import { useStore } from "react-redux";
6 |
7 | /** 所需的各种资源 **/
8 | import "./index.less";
9 | import ImgLogo from "../../assets/react-logo.jpg";
10 |
11 | export default function HomePageContainer(props) {
12 | const store = useStore();
13 | console.log("store:", store);
14 | console.log("what props:", props);
15 | return (
16 |
17 |
18 |

19 |
React-Luo
20 |
21 | react17、redux4、router6、webpack5、eslint、babel7、antd4
22 |
23 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/src/container/home/index.less:
--------------------------------------------------------------------------------
1 | .page-home {
2 | padding: 100px 0 0 0;
3 | .box {
4 | margin: 0 auto;
5 | text-align: center;
6 | .title {
7 | font-size: 24px;
8 | }
9 | .info {
10 | margin-top: 10px;
11 | color: #888;
12 | }
13 | .link {
14 | margin-top: 40px;
15 | font-size: 12px;
16 | color: #888;
17 | a {
18 | color: #888;
19 | text-decoration: none;
20 | &:hover {
21 | text-decoration: underline;
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/container/notfound/index.js:
--------------------------------------------------------------------------------
1 | /** 404 NotFound **/
2 |
3 | /** 所需的各种插件 **/
4 | import React from "react";
5 |
6 | /** 所需的所有资源 **/
7 | import "./index.less";
8 |
9 | export default function NotFoundPageContainer() {
10 | return (
11 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/src/container/notfound/index.less:
--------------------------------------------------------------------------------
1 | .page-notfound {
2 | height: calc(100vh - 62px);
3 | .box {
4 | position: absolute;
5 | top: 40%;
6 | left: 50%;
7 | transform: translate(-50%, -50%);
8 | font-size: 16px;
9 | color: #888;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/container/routers/index.js:
--------------------------------------------------------------------------------
1 | /** 路由页 - 真正意义上的根组件,已挂载到redux上,可获取store中的内容 **/
2 |
3 | /** 所需的各种插件 **/
4 | import React, { useEffect } from "react";
5 |
6 | // import { Router, Route, Switch, Redirect } from "react-router-dom";
7 | import {
8 | HashRouter as Router,
9 | Routes,
10 | Route,
11 | Navigate,
12 | } from "react-router-dom";
13 |
14 | // antd的多语言
15 | import { ConfigProvider } from "antd";
16 | import zhCN from "antd/lib/locale-provider/zh_CN";
17 |
18 | // import { createBrowserHistory as createHistory } from "history/"; // URL模式的history
19 | // import { createHashHistory as createHistory } from "history"; // 锚点模式的history
20 |
21 | import Loadable from "react-loadable"; // 用于代码分割时动态加载模块
22 |
23 | /** 普通组件 **/
24 | import Menu from "../../component/menu";
25 | import Footer from "../../component/footer";
26 | import Loading from "../../component/loading"; // loading动画,用于动态加载模块进行中时显示
27 |
28 | import "./index.less";
29 |
30 | /** 下面是代码分割异步加载的方式引入各页面 webpackChunkName设置生成后的js名字 **/
31 | const Home = Loadable({
32 | loader: () => import(/* webpackChunkName:'home' */ "../home"),
33 | loading: Loading, // 自定义的Loading动画组件
34 | timeout: 10000, // 可以设置一个超时时间(s)来应对网络慢的情况(在Loading动画组件中可以配置error信息)
35 | });
36 | const Test = Loadable({
37 | loader: () => import(/* webpackChunkName:'test' */ "../test"),
38 | loading: Loading,
39 | });
40 | const Page1 = Loadable({
41 | loader: () =>
42 | import(/* webpackChunkName:'testclass' */ "../test/container/page1"),
43 | loading: Loading,
44 | });
45 | const Page2 = Loadable({
46 | loader: () =>
47 | import(/* webpackChunkName:'testclass' */ "../test/container/page2"),
48 | loading: Loading,
49 | });
50 | const Page3 = Loadable({
51 | loader: () =>
52 | import(/* webpackChunkName:'testclass' */ "../test/container/page3"),
53 | loading: Loading,
54 | });
55 | const Features = Loadable({
56 | loader: () => import(/* webpackChunkName:'features' */ "../features"),
57 | loading: Loading,
58 | });
59 | const NotFound = Loadable({
60 | loader: () => import(/* webpackChunkName:'notfound' */ "../notfound"),
61 | loading: Loading,
62 | });
63 |
64 | // const history = createHistory(); // 实例化history对象
65 |
66 | /** 组件 **/
67 | export default function RootRouterContainer(props) {
68 | // 在组件加载完毕后触发
69 | useEffect(() => {
70 | // 可以手动在此预加载指定的模块:
71 | // Features.preload(); // 预加载Features页面
72 | // Test.preload(); // 预加载Test页面
73 | // 也可以直接预加载所有的异步模块
74 | // Loadable.preloadAll();
75 | }, []);
76 |
77 | /** 简单权限控制 路由守卫 **/
78 | function onEnter(Component) {
79 | // 例子:如果没有登录,直接跳转至login页
80 | // if (sessionStorage.getItem('userInfo')) {
81 | // return Component;
82 | // } else {
83 | // return ;
84 | // }
85 | return Component;
86 | }
87 |
88 | return (
89 |
90 | <>
91 |
92 |
93 |
94 | } />
95 | )} />
96 | )} />
97 | )}>
98 | )} />
99 | )} />
100 | )} />
101 |
102 | } />
103 |
104 |
105 |
106 |
107 |
108 | >
109 |
110 | );
111 | }
112 |
--------------------------------------------------------------------------------
/src/container/routers/index.less:
--------------------------------------------------------------------------------
1 | .boss {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
--------------------------------------------------------------------------------
/src/container/test/container/page1.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function Page1(props) {
4 | return A 子container 1
;
5 | }
6 |
--------------------------------------------------------------------------------
/src/container/test/container/page2.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function Page2(props) {
4 | return B 子container 2
;
5 | }
6 |
--------------------------------------------------------------------------------
/src/container/test/container/page3.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function Page3(props) {
4 | return C 子container 3
;
5 | }
6 |
--------------------------------------------------------------------------------
/src/container/test/index.js:
--------------------------------------------------------------------------------
1 | /** 测试页 **/
2 |
3 | /** 所需的各种插件 **/
4 | import React, { useState, useEffect } from "react";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import { useLocation, Link, Outlet } from "react-router-dom";
7 |
8 | /** 所需的所有资源 **/
9 | import { Modal, Form, Button, message, Input } from "antd";
10 | import { UserOutlined, KeyOutlined } from "@ant-design/icons";
11 | import "./index.less";
12 |
13 | import ImgTest from "../../assets/test.jpg";
14 | import Mp3 from "../../assets/starSky.mp3";
15 |
16 | /** 组件 **/
17 | export default function TestPageContainer(props) {
18 | const dispatch = useDispatch();
19 |
20 | const count = useSelector((state) => state.test.count); // 引入test model中的count数据
21 |
22 | const location = useLocation();
23 | console.log("location:=", location);
24 | // 引入test model中的add
25 | const onTestAdd = () => {
26 | dispatch({
27 | type: "test/onTestAdd",
28 | });
29 | };
30 |
31 | // 引入test model中的fetch异步请求action
32 | const serverFetch = async () => {
33 | const res = await dispatch({
34 | type: "test/serverFetch",
35 | });
36 | if (res.status === 200) {
37 | setMokeFetch(res.data);
38 | } else {
39 | message.error("获取数据失败");
40 | }
41 | };
42 |
43 | const [visible, setVisible] = useState(false); // 模态框隐藏和显示
44 | const [mokeFetch, setMokeFetch] = useState([]); // 用于测试异步请求
45 |
46 | const layout = {
47 | labelCol: { span: 8 },
48 | wrapperCol: { span: 16 },
49 | };
50 |
51 | // 仅组件加载完毕时触发一次
52 | useEffect(() => {
53 | // console.log("所有页面默认拥有的3个对象:", location, match, history);
54 |
55 | const set = new Set([1, 2, 3]);
56 | const map = new Map();
57 | console.log("Set 和 Map 测试:", set, map);
58 |
59 | const a = { a: 1, b: 2, c: 3 };
60 | const b = { d: 4, ...a };
61 | console.log("obj的扩展运算符测试:", b);
62 |
63 | // 获取用户信息测试
64 | async function getUserinfo() {
65 | const userInfo = await dispatch({
66 | type: "app/getUserinfo",
67 | payload: { id: 1 },
68 | });
69 | console.log("获取到userInfo:", userInfo);
70 | }
71 | getUserinfo();
72 | }, []);
73 |
74 | // 表单提交且验证通过时触发
75 | function handleSubmit(e) {
76 | message.success("执行了登录操作");
77 | }
78 |
79 | return (
80 |
81 |
功能测试
82 |
83 |
84 |
引入图片
85 |
86 |
87 |
88 | 上方图片,一张是img,一张是background
89 |
90 |
91 | 请特别注意,现在webpack.production.config.js中的publicPath配置为"/",
92 |
93 |
94 |
95 | 如果你的项目最终打包后放到服务器上的访问路径为https://xxx.com,这没有问题
96 |
97 |
98 |
99 | 如果你的项目访问路径为https://xxx.com/aaa,请把webpack.production.config.js中的publicPath配置为"/aaa/"
100 |
101 |
102 |
103 |
104 |
引入其他种类的资源
105 |
106 |
107 |
108 |
109 |
110 |
LESS测试
111 |
112 | 来自LESS样式
113 |
114 |
115 |
116 |
Antd组件测试
117 |
118 |
119 |
120 |
123 |
124 |
127 |
128 |
129 |
130 |
161 |
162 |
location对象测试
163 |
164 | 当前路由:
165 | {location.pathname}
166 |
167 | search参数:
168 | {location.search}
169 |
170 | state参数:
171 | {JSON.stringify(location.state)}
172 |
173 |
所有页面都自动被注入location、match、history对象
174 |
175 |
176 |
action测试
177 |
178 |
181 |
182 | store中数据num:
183 | {count}
184 |
185 |
186 |
187 |
异步请求测试(Mock模拟数据)
188 |
189 |
192 |
193 | 数据:
194 |
195 | {mokeFetch.map((item, index) => (
196 | - {`id: ${item.id}, email: ${item.email}`}
197 | ))}
198 |
199 |
200 |
201 |
202 |
嵌套路由测试
203 |
第一页
204 |
第二页
205 |
第三页
206 |
207 |
208 |
209 |
210 |
211 |
setVisible(false)}
215 | onCancel={() => setVisible(false)}
216 | >
217 | 内容...
218 |
219 |
220 | );
221 | }
222 |
--------------------------------------------------------------------------------
/src/container/test/index.less:
--------------------------------------------------------------------------------
1 | .page-test {
2 | width: 100%;
3 | max-width: 1200px;
4 | margin: 0 auto;
5 | padding: 40px;
6 | letter-spacing: 1px;
7 | .box {
8 | margin-top: 40px;
9 | .list {
10 | margin-bottom: 20px;
11 | .backImage {
12 | display: block;
13 | background-image: url(../../assets/test.jpg);
14 | margin-top: 8px;
15 | width: 326px;
16 | height: 150px;
17 | max-width: 100%;
18 | }
19 | p {
20 | font-size: 14px;
21 | padding: 5px 10px;
22 | margin: 10px 0;
23 | background-color: #eeeeff;
24 | img {
25 | max-width: 100%;
26 | max-height: 150px;
27 | }
28 | audio {
29 | display: block;
30 | max-width: 100%;
31 | }
32 | }
33 | div {
34 | color: #888;
35 | font-size: 12px;
36 | }
37 | }
38 | .sonTest {
39 | a {
40 | margin-right: 5px;
41 | }
42 | }
43 | }
44 | .pbox {
45 | font-size: 14px;
46 | padding: 5px 10px;
47 | margin: 10px 0;
48 | background-color: #eeeeff;
49 | }
50 | }
51 | .son {
52 | padding: 20px;
53 | }
54 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /** APP入口 **/
2 | // import "core-js/stable";
3 | // import "regenerator-runtime/runtime";
4 |
5 | import React from "react";
6 | import ReactDOM from "react-dom/client";
7 | import Root from "./root";
8 |
9 | /** 公共样式 **/
10 | import "./styles/css.css";
11 | import "./styles/less.less";
12 |
13 | ReactDOM.createRoot(document.getElementById("app-root")).render();
14 |
15 | if (module.hot) {
16 | module.hot.accept();
17 | }
18 |
--------------------------------------------------------------------------------
/src/models/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 基本Model app.js, 在src/store/index.js中被挂载到store上,命名为app
3 | * 可用于存放通用信息,比如用户数据、角色、权限、省市区等通用数据
4 | * **/
5 | export default {
6 | state: {
7 | userinfo: null, // 用户信息
8 | },
9 |
10 | reducers: {
11 | setUserInfo(state, payload) {
12 | return { ...state, userinfo: payload };
13 | },
14 | },
15 |
16 | /** actions 可以是一个对象,也可以是一个函数,函数的第1个参数自动被注入dispatch(见models/test.js) **/
17 | effects: {
18 | // 模拟获取用户信息
19 | async getUserinfo(params = {}) {
20 | const user = { id: params.id, username: "admin" };
21 | this.setUserInfo(user); // 这个setUserInfo就是上面reducers中的setUserInfo
22 | return user;
23 | },
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/src/models/test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 测试页的Model 在src/store/index.js中被挂载到store上,命名为test
3 | * 项目中不同的大模块可以创建不同的js作为model
4 | * 此model中包含了用于src/container/test模块的数据和方法
5 | * **/
6 | import { message } from "antd";
7 | import Server from "../util/server"; // 自己封装的异步请求方法
8 |
9 | export default {
10 | state: {
11 | count: 0, // 测试数字
12 | fetchvalue: [], // 异步请求的测试数据
13 | },
14 |
15 | reducers: {
16 | setCount(state, payload) {
17 | return { ...state, count: payload };
18 | },
19 | setFetchValue(state, payload) {
20 | return { ...state, fetchvalue: payload };
21 | },
22 | },
23 |
24 | /** actions **/
25 | effects: (dispatch) => ({
26 | // 测试 - 数字加1
27 | onTestAdd(params, rootState) {
28 | this.setCount(rootState.test.count + 1); //. 这里会指向上面reducers中的setCount
29 | },
30 |
31 | // 测试 - 异步请求
32 | async serverFetch(params = {}) {
33 | try {
34 | const res = await Server(
35 | "/api/url.ajax",
36 | null,
37 | { a: 123, b: "456" },
38 | "POST"
39 | );
40 | if (res && res.data.status === 200) {
41 | dispatch({ type: "test/setFetchValue", payload: res.data.data }); // test/setFetchValue对应上面reducers中的setFetchValue
42 | }
43 | return res.data;
44 | } catch (e) {
45 | message.error("请求错误,请稍后重试");
46 | }
47 | },
48 | }),
49 | };
50 |
--------------------------------------------------------------------------------
/src/root/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * 根页
3 | * 这里单独分一层是为了热更新能正常监听store的变化
4 | * 而把Routers放在了另外一个文件里,这样Routers中的内容就能使用connect挂载到redux上了
5 | * */
6 |
7 | /** 所需的各种插件 **/
8 | import React from "react";
9 | import { Provider } from "react-redux";
10 | import store from "../store";
11 | import Routers from "../container/routers";
12 |
13 | export default function RootContainer() {
14 | return (
15 |
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | /** 全局唯一数据中心 **/
2 | import { init } from "@rematch/core";
3 |
4 | import app from "../models/app";
5 | import test from "../models/test";
6 |
7 | export default init({
8 | models: {
9 | app,
10 | test,
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/src/styles/css.css:
--------------------------------------------------------------------------------
1 | /* 全局配置 */
2 | * {
3 | -webkit-overflow-scrolling: touch; /* 允许独立滚动区域,解决IOS上的非body元素滚动条滚动时卡顿 */
4 | -webkit-tap-highlight-color: transparent; /* 覆盖IOS上用户点击连接时的默认高亮颜色 */
5 | outline: none;
6 | }
7 |
8 | body {
9 | margin: 0;
10 | padding: 0;
11 | background-color: #fff;
12 | -webkit-font-smoothing: antialiased; /* 平滑字体 */
13 | -moz-osx-font-smoothing: grayscale; /* 平滑字体 */
14 | -ms-touch-action: manipulation; /* 允许触摸驱动的平移和拖拽缩放 */
15 | touch-action: manipulation; /* 允许触摸驱动的平移和拖拽缩放 */
16 | -webkit-text-size-adjust: 100%; /* 关闭IOS横屏时,自动调整字体大小的功能 */
17 | font-family: "Segoe UI", "Lucida Grande", "Microsoft YaHei", sans-serif;
18 | font-size: 14px;
19 | scroll-behavior: smooth; /* 平滑滚动 */
20 | }
21 | ul {
22 | position: relative;
23 | margin: 0;
24 | padding: 0;
25 | } /* ul样式重置 */
26 | li {
27 | position: relative;
28 | list-style: none;
29 | margin: 0;
30 | padding: 0;
31 | } /* li样式重置 */
32 | img {
33 | border: 0;
34 | }
35 | input {
36 | line-height: normal;
37 | }
38 | img,
39 | a:focus,
40 | a:hover,
41 | a:active {
42 | outline: none;
43 | }
44 | label {
45 | cursor: pointer;
46 | }
47 | sub,
48 | sup {
49 | font-size: 75%;
50 | line-height: 0;
51 | position: relative;
52 | vertical-align: baseline;
53 | } /* 重新定义上标和下标的表现 */
54 | sup {
55 | top: -0.5em;
56 | }
57 | sub {
58 | bottom: -0.25em;
59 | }
60 |
61 | /* 常用功能 */
62 | .all_nowarp {
63 | white-space: nowrap;
64 | overflow: hidden;
65 | text-overflow: ellipsis;
66 | } /* 禁止换行,末尾省略 */
67 | .all_nowarp2 {
68 | display: -webkit-box;
69 | -webkit-box-orient: vertical;
70 | overflow: hidden;
71 | text-overflow: ellipsis;
72 | -webkit-line-clamp: 2;
73 | } /* 禁止换行,2行,末尾省略,div的高度需要正好是2行的高度 */
74 | .all_nowarp3 {
75 | display: -webkit-box;
76 | -webkit-box-orient: vertical;
77 | overflow: hidden;
78 | text-overflow: ellipsis;
79 | -webkit-line-clamp: 3;
80 | } /* 禁止换行,3行,末尾省略,div的高度需要正好是2行的高度 */
81 | .all_warp {
82 | word-break: break-all;
83 | word-wrap: break-word;
84 | } /* 强制换行 */
85 | .all_b {
86 | -moz-box-sizing: border-box;
87 | -webkit-box-sizing: border-box;
88 | box-sizing: border-box;
89 | } /* 内框模式 */
90 | .all_clear:after {
91 | content: ".";
92 | height: 0;
93 | overflow: hidden;
94 | visibility: hidden;
95 | display: block;
96 | clear: both;
97 | } /* 清楚浮动 */
98 | .all_trans {
99 | transition: all 200ms;
100 | -webkit-transition: all 200ms;
101 | } /* 过渡效果 */
102 |
103 | /* 美化滚动条 */
104 | ::-webkit-scrollbar {
105 | width: 6px;
106 | height: 6px;
107 | background-color: transparent;
108 | }
109 |
110 | /*定义滑块 内阴影+圆角*/
111 | ::-webkit-scrollbar-thumb {
112 | background-color: #333;
113 | }
114 |
--------------------------------------------------------------------------------
/src/styles/less.less:
--------------------------------------------------------------------------------
1 | /* LESS测试 */
2 | .less_btn {
3 | @color: #0000ff;
4 | background-color: @color;
5 | color: #fff;
6 | padding: 2px;
7 | }
8 |
--------------------------------------------------------------------------------
/src/util/server.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 自己封装的异步请求函数
3 | * 所有http请求都将经过这里
4 | * **/
5 | import axios from "axios";
6 |
7 | // 不需要下面这些mock配置,仅本地发布DEMO用
8 | // import Mock from "mockjs";
9 | // const mock = require("../../mock/mock-data");
10 | // Mock.mock(/\/api.*/, options => {
11 | // const res = mock.mockApi(options);
12 | // return res;
13 | // });
14 |
15 | /**
16 | * 发起http请求
17 | * @param {String} url 请求的api地址
18 | * @param {Object} params 这些参数将直接加在api地址后面,如?a=123&b=456
19 | * @param {Object} body 这些参数将用于POST请求,加到请求的request body中去
20 | * @param {String} method 请求的方法,如POST/GET
21 | * @return {Promise} 返回一个Promise对象
22 | */
23 | export default (url, params = {}, body = {}, method = "POST") => {
24 | return axios({
25 | url,
26 | method,
27 | headers: {
28 | "Content-Type": "application/json; charset=utf-8",
29 | },
30 | withCredentials: false, // 请求是否带cookie(跨域的请求一般是不能带cookie的)
31 | params, // GET请求的参数赋给params字段
32 | data: body, // POST请求的参数赋给data字段
33 | });
34 | };
35 |
--------------------------------------------------------------------------------
/src/util/tools.js:
--------------------------------------------------------------------------------
1 | /** 这个文件中封装了一些常用的工具函数 **/
2 |
3 | const tools = {
4 | /**
5 | 保留N位小数
6 | @param {Number|String} str 待处理数字
7 | @param {Number} x 保留几位小数点
8 | @return {Number|String} 处理成功返回字符串,处理失败返回原值
9 | */
10 | pointX(str, x = 0) {
11 | if (!str && str !== 0) {
12 | return str;
13 | }
14 | const temp = Number(str);
15 | if (temp === 0) {
16 | return temp.toFixed(x);
17 | }
18 | return temp ? temp.toFixed(x) : str;
19 | },
20 |
21 | /**
22 | 去掉字符串两端空格
23 | @param {String} str 待处理字符串
24 | @return {String} 处理后的字符串
25 | */
26 | trim(str) {
27 | const reg = /^\s*|\s*$/g;
28 | return str.replace(reg, "");
29 | },
30 |
31 | /**
32 | 给字符串打马赛克
33 | 如:将123456转换为1****6,最多将字符串中间6个字符变成*
34 | 如果字符串长度小于等于2,将不会有效果
35 | @param {String} str 待处理字符串
36 | @return {String} 处理后的字符串
37 | */
38 | addMosaic(str) {
39 | const s = String(str);
40 | const lenth = s.length;
41 | const howmuch = (() => {
42 | if (s.length <= 2) {
43 | return s.length;
44 | }
45 | const l = s.length - 2;
46 | if (l <= 6) {
47 | return l;
48 | }
49 | return 6;
50 | })();
51 | const start = Math.floor((lenth - howmuch) / 2);
52 | const ret = s.split("").map((v, i) => {
53 | if (i >= start && i < start + howmuch) {
54 | return "*";
55 | }
56 | return v;
57 | });
58 | return ret.join("");
59 | },
60 |
61 | /**
62 | 字符串加密 简单的加密方法
63 | @param {String} code 待处理字符串
64 | @param {String} 加密后的字符串
65 | */
66 | compile(code) {
67 | let c = String.fromCharCode(code.charCodeAt(0) + code.length);
68 | for (let i = 1; i < code.length; i++) {
69 | c += String.fromCharCode(code.charCodeAt(i) + code.charCodeAt(i - 1));
70 | }
71 | return c;
72 | },
73 |
74 | /**
75 | 字符串解谜 对应上面的字符串加密方法
76 | @param {String} code 加密的字符串
77 | @param {String} 解密后的字符串
78 | */
79 | uncompile(code) {
80 | let c = String.fromCharCode(code.charCodeAt(0) - code.length);
81 | for (let i = 1; i < code.length; i++) {
82 | c += String.fromCharCode(code.charCodeAt(i) - c.charCodeAt(i - 1));
83 | }
84 | return c;
85 | },
86 | };
87 |
88 | export default tools;
89 |
--------------------------------------------------------------------------------
/webpack.dev.config.js:
--------------------------------------------------------------------------------
1 | /** 这是用于开发环境的webpack配置文件 **/
2 |
3 | const path = require("path"); // 获取绝对路径用
4 | const webpack = require("webpack"); // webpack核心
5 | const HtmlWebpackPlugin = require("html-webpack-plugin"); // 动态生成html插件
6 | const AntdDayjsWebpackPlugin = require("antd-dayjs-webpack-plugin");
7 | const CopyPlugin = require("copy-webpack-plugin"); // 用于直接复制public中的文件到打包的最终文件夹中
8 | // const HappyPack = require("happypack"); // 多线程编译
9 | const webpackbar = require("webpackbar");
10 | const ESLintPlugin = require("eslint-webpack-plugin"); // eslint插件,代替原来的eslint-loader
11 | const PUBLIC_PATH = "/"; // 基础路径
12 |
13 | module.exports = {
14 | mode: "development",
15 | entry: [
16 | "webpack-hot-middleware/client?reload=true&path=/__webpack_hmr", // webpack热更新插件,就这么写
17 | "./src/index.js", // 项目入口
18 | ],
19 | output: {
20 | path: __dirname + "/", // 将打包好的文件放在此路径下,dev模式中,只会在内存中存在,不会真正的打包到此路径
21 | publicPath: PUBLIC_PATH, // 文件解析路径,index.html中引用的路径会被设置为相对于此路径
22 | filename: "bundle-[contenthash].js", // 编译后的文件名字
23 | assetModuleFilename: "assets/[name].[hash:4][ext]",
24 | },
25 | devtool: "eval-source-map", // 报错的时候在控制台输出哪一行报错
26 | optimization: {
27 | splitChunks: {
28 | chunks: "all",
29 | },
30 | },
31 | module: {
32 | rules: [
33 | {
34 | // .js .jsx用babel解析
35 | test: /\.js?$/,
36 | use: ["babel-loader"],
37 | include: path.resolve(__dirname, "src"),
38 | },
39 | {
40 | // .css 解析
41 | test: /\.css$/,
42 | use: ["style-loader", "css-loader", "postcss-loader"],
43 | },
44 | {
45 | // .less 解析
46 | test: /\.less$/,
47 | use: [
48 | "style-loader",
49 | "css-loader",
50 | "postcss-loader",
51 | {
52 | loader: "less-loader",
53 | options: { lessOptions: { javascriptEnabled: true } },
54 | },
55 | ],
56 | },
57 | {
58 | // 文件解析
59 | test: /\.(eot|woff|otf|svg|ttf|woff2|appcache|mp3|mp4|pdf)(\?|$)/,
60 | include: path.resolve(__dirname, "src"),
61 | type: "asset/resource",
62 | },
63 | {
64 | // 图片解析
65 | test: /\.(png|jpg|jpeg|gif)(\?|$)/i,
66 | include: path.resolve(__dirname, "src"),
67 | type: "asset",
68 | },
69 | {
70 | // wasm文件解析
71 | test: /\.wasm$/,
72 | include: path.resolve(__dirname, "src"),
73 | type: "webassembly/experimental",
74 | },
75 | {
76 | // xml文件解析
77 | test: /\.xml$/,
78 | include: path.resolve(__dirname, "src"),
79 | use: ["xml-loader"],
80 | },
81 | ],
82 | },
83 | plugins: [
84 | new webpackbar(),
85 | new ESLintPlugin({
86 | context: path.resolve(__dirname, "src"),
87 | }),
88 | new webpack.HotModuleReplacementPlugin(), // 热更新插件
89 | new AntdDayjsWebpackPlugin(), // dayjs 替代 momentjs
90 | new webpack.DefinePlugin({
91 | "process.env": "dev",
92 | }),
93 | // new HappyPack({
94 | // loaders: ["babel-loader"],
95 | // }),
96 | new HtmlWebpackPlugin({
97 | // 根据模板插入css/js等生成最终HTML
98 | filename: "index.html", //生成的html存放路径,相对于 output.path
99 | favicon: "./public/favicon.png", // 自动把根目录下的favicon.ico图片加入html
100 | template: "./public/index.html", //html模板路径
101 | inject: true, // 是否将js放在body的末尾
102 | }),
103 | // 拷贝public中的文件到最终打包文件夹里
104 | new CopyPlugin({
105 | patterns: [
106 | {
107 | from: "./public/**/*",
108 | to: "./",
109 | globOptions: {
110 | ignore: ["**/favicon.png", "**/index.html"],
111 | },
112 | noErrorOnMissing: true,
113 | },
114 | ],
115 | }),
116 | ],
117 | resolve: {
118 | extensions: [".js", ".jsx", ".less", ".css", ".wasm"], //后缀名自动补全
119 | alias: {
120 | "@": path.resolve(__dirname, "src"),
121 | },
122 | },
123 | };
124 |
--------------------------------------------------------------------------------
/webpack.production.config.js:
--------------------------------------------------------------------------------
1 | /** 这是用于生产环境的webpack配置文件 **/
2 |
3 | const path = require("path");
4 | const webpack = require("webpack"); // webpack核心
5 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 将CSS提取出来,而不是和js混在一起
6 | const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // 对CSS进行压缩
7 | const HtmlWebpackPlugin = require("html-webpack-plugin"); // 生成html
8 | const AntdDayjsWebpackPlugin = require("antd-dayjs-webpack-plugin");
9 | const { CleanWebpackPlugin } = require("clean-webpack-plugin"); // 每次打包前清除旧的build文件夹
10 | const WorkboxPlugin = require("workbox-webpack-plugin");
11 | const CopyPlugin = require("copy-webpack-plugin"); // 用于直接复制public中的文件到打包的最终文件夹中
12 | const FaviconsWebpackPlugin = require("favicons-webpack-plugin"); // 自动生成各尺寸的favicon图标 webpack5 wating up
13 | const TerserPlugin = require("terser-webpack-plugin"); // 对js进行压缩
14 | const webpackbar = require("webpackbar"); // 进度条
15 | const ESLintPlugin = require("eslint-webpack-plugin"); // eslint插件,代替原来的eslint-loader
16 | // const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin; // 分析打包后各个包的大小
17 | /**
18 | * 基础路径
19 | * 比如我上传到自己的服务器填写的是:"/work/pwa/",最终访问为"https://isluo.com/work/pwa/"
20 | * 根据你自己的需求填写
21 | * "/" 就是根路径,假如最终项目上线的地址为:https://isluo.com/, 那就不用改
22 | * **/
23 | const PUBLIC_PATH = "/";
24 |
25 | module.exports = {
26 | mode: "production",
27 | entry: path.resolve(__dirname, "src", "index"),
28 | output: {
29 | path: path.resolve(__dirname, "build"), // 将文件打包到此目录下
30 | publicPath: PUBLIC_PATH, // 在生成的html中,文件的引入路径会相对于此地址,生成的css中,以及各类图片的URL都会相对于此地址
31 | filename: "dist/[name].[chunkhash:8].js",
32 | chunkFilename: "dist/[name].[chunkhash:8].chunk.js",
33 | },
34 | stats: {
35 | children: false, // 不输出子模块的打包信息
36 | },
37 | optimization: {
38 | minimize: true,
39 | minimizer: [
40 | new TerserPlugin({
41 | parallel: true, // 多线程并行构建
42 | terserOptions: {
43 | // https://github.com/terser/terser#minify-options
44 | compress: {
45 | warnings: false, // 删除无用代码时是否给出警告
46 | drop_debugger: true, // 删除所有的debugger
47 | // drop_console: true, // 删除所有的console.*
48 | pure_funcs: ["console.log"], // 删除所有的console.log
49 | },
50 | },
51 | }),
52 | new CssMinimizerPlugin(),
53 | ],
54 | splitChunks: {
55 | chunks: "all",
56 | },
57 | },
58 | module: {
59 | rules: [
60 | {
61 | // .js .jsx用babel解析
62 | test: /\.js?$/,
63 | include: path.resolve(__dirname, "src"),
64 | use: ["babel-loader"],
65 | },
66 | {
67 | // .css 解析
68 | test: /\.css$/,
69 | use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
70 | },
71 | {
72 | // .less 解析
73 | test: /\.less$/,
74 | use: [
75 | MiniCssExtractPlugin.loader,
76 | "css-loader",
77 | "postcss-loader",
78 | {
79 | loader: "less-loader",
80 | options: { lessOptions: { javascriptEnabled: true } },
81 | },
82 | ],
83 | },
84 | {
85 | // 文件解析
86 | test: /\.(eot|woff|otf|svg|ttf|woff2|appcache|mp3|mp4|pdf)(\?|$)/,
87 | include: path.resolve(__dirname, "src"),
88 | type: "asset/resource",
89 | },
90 | {
91 | // 图片解析
92 | test: /\.(png|jpg|jpeg|gif)(\?|$)/i,
93 | include: path.resolve(__dirname, "src"),
94 | type: "asset",
95 | },
96 | {
97 | // wasm文件解析
98 | test: /\.wasm$/,
99 | include: path.resolve(__dirname, "src"),
100 | type: "webassembly/experimental",
101 | },
102 | {
103 | // xml文件解析
104 | test: /\.xml$/,
105 | include: path.resolve(__dirname, "src"),
106 | use: ["xml-loader"],
107 | },
108 | ],
109 | },
110 | plugins: [
111 | /**
112 | * 打包前删除上一次打包留下的旧代码(根据output.path)
113 | * https://github.com/johnagan/clean-webpack-plugin
114 | * **/
115 | new CleanWebpackPlugin(),
116 | new webpackbar(), // 打包时美化进度条
117 | new ESLintPlugin({
118 | context: path.resolve(__dirname, "src"),
119 | }),
120 | new AntdDayjsWebpackPlugin(), // dayjs 替代 momentjs
121 | /**
122 | * 在window环境中注入全局变量,虽然暂时没用上,不过在真实开发中应该会用到
123 | * **/
124 | new webpack.DefinePlugin({
125 | "process.env": "prod",
126 | }),
127 | /**
128 | * 提取CSS等样式生成单独的CSS文件,不然最终文件只有js; css全部包含在js中
129 | * https://github.com/webpack-contrib/mini-css-extract-plugin
130 | * **/
131 | new MiniCssExtractPlugin({
132 | filename: "dist/[name].[chunkhash:8].css", // 生成的文件名
133 | }),
134 | /**
135 | * 自动生成HTML,并注入各参数
136 | * https://github.com/jantimon/html-webpack-plugin
137 | * **/
138 | new HtmlWebpackPlugin({
139 | filename: "index.html", // 生成的html存放路径,相对于 output.path
140 | template: "./public/index.html", // html模板路径
141 | hash: false, // 防止缓存,在引入的文件后面加hash (PWA就是要缓存,这里设置为false)
142 | inject: true, // 是否将js放在body的末尾
143 | // 正式环境,把注册service-worker的代码加入到index.html中
144 | registerServiceWorker: ``,
151 | }),
152 | /**
153 | * 拷贝public中的文件到最终打包文件夹里
154 | * https://github.com/webpack-contrib/copy-webpack-plugin
155 | * */
156 | new CopyPlugin({
157 | patterns: [
158 | {
159 | from: "./public/**/*",
160 | to: "./",
161 | globOptions: {
162 | ignore: ["**/favicon.png", "**/index.html"],
163 | },
164 | noErrorOnMissing: true,
165 | },
166 | ],
167 | }),
168 | /**
169 | * 自动生成各种类型的favicon图标 webpack5 wating up
170 | * 自动生成manifest.json文件
171 | * 这么做是为了各种设备上的扩展功能,PWA桌面图标/应用启动图标等,主题等
172 | * https://github.com/itgalaxy/favicons#usage
173 | * **/
174 | new FaviconsWebpackPlugin({
175 | logo: "./public/favicon.png", // 原始图片路径
176 | // prefix: "", // 自定义目录,把生成的文件存在此目录下
177 | favicons: {
178 | appName: "ReactPWA", // 你的APP全称
179 | appShortName: "React", // 你的APP简称,手机某些地方会显示,比如切换多个APP时显示的标题
180 | appDescription: "ReactPWA Demo", // 你的APP简介
181 | background: "#222222", // APP启动页的背景色
182 | theme_color: "#222222", // APP的主题色
183 | appleStatusBarStyle: "black-translucent", // 苹果手机状态栏样式
184 | display: "standalone", // 是否显示搜索框,PWA就别显示了
185 | start_url: PUBLIC_PATH, // 起始页,‘.’会自动到主页,比'/'好,尤其是网站没有部署到根域名时
186 | logging: false, // 是否输出日志
187 | pixel_art: false, // 是否自动锐化一下图标,仅离线模式可用
188 | loadManifestWithCredentials: false, // 浏览器在获取manifest.json时默认不会代cookie。如果需要请设置true
189 | icons: {
190 | // 生成哪些平台需要的图标
191 | android: true, // 安卓
192 | appleIcon: false, // 苹果
193 | appleStartup: false, // 苹果启动页
194 | coast: false, // opera
195 | favicons: true, // web小图标
196 | firefox: false, // 火狐
197 | windows: false, // windows8 桌面应用
198 | yandex: false, // Yandex浏览器
199 | },
200 | },
201 | }),
202 |
203 | /**
204 | * PWA - 自动生成server-worker.js
205 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-webpack-plugin.GenerateSW?hl=en
206 | * */
207 | new WorkboxPlugin.GenerateSW({
208 | skipWaiting: true, // service-worker如果有更新的话,跳过等待直接更新
209 | }),
210 |
211 | // new BundleAnalyzerPlugin(),
212 | ],
213 | resolve: {
214 | extensions: [".js", ".jsx", ".less", ".css", ".wasm"], // 后缀名自动补全
215 | alias: {
216 | "@": path.resolve(__dirname, "src"),
217 | },
218 | },
219 | };
220 |
--------------------------------------------------------------------------------
/依赖清单.md:
--------------------------------------------------------------------------------
1 | ## 这个文件用来记录用到的第 3 方包和一些配置文件的作用
2 |
3 | > package.json
4 |
5 | ```javascript
6 | {
7 | "name": "react_luo", // 项目名字
8 | "version": "1.0.0", // 项目版本
9 | "description": "react脚手架,最新技术", // 项目简介
10 | "main": "index.js", // 项目入口,没用,因为配置了server.js
11 | "scripts": { // 自定义的脚本
12 | "start": "node server.js", // 启动开发环境
13 | "build": "webpack -p --config webpack.production.config.js --progress --profile --colors --display errors-only", // 正式打包
14 | "dist": "set NODE_ENV=production&& node server.js", // 运行打包后的build文件夹下的代码
15 | "prettier": "prettier --write \"{src,mock}/**/*.{js,css,scss,less}\"", // 一键格式化src目录,mock目录下的代码
16 | "cover": "./node_modules/.bin/istanbul cover _mocha", // 第3方测试库,不用管
17 | "coveralls": "npm run cover -- --report lcovonly && cat ./coverage/lcov.info | ./node_modules/.bin/coveralls" // 第3方测试库
18 | },
19 | "author": "luo", // 作者名字
20 | "license": "ISC", // 开源协议
21 | "private": true, // 是否私有,没用,因为不是发布npm包
22 | "repository": { // 源代码信息
23 | "type": "git", // 这是一个git存储的项目
24 | "url": "git+https://github.com/javaLuo/react-luo.git" // 源代码地址
25 | },
26 | "dependencies": { // 项目依赖包
27 | "@rematch/core": "^1.1.0", // redux中间件,按model划分,类似dva或vuex,但比dva轻量
28 | "antd": "^3.4.1", // 蚂蚁金服UI组件库
29 | "axios": "^0.18.0", // 封装了fetch的异步请求库
30 | "core-js": "^3.1.4", // 代替babel-polyfill,使浏览器支持ES6+新功能
31 | "lodash": "^4.17.5", // 常用工具库(深拷贝等)
32 | "moment": "^2.22.1", // 时间对象,antd用的这个,实际开发中可能会用到
33 | "react": "^16.3.2", // react核心
34 | "react-dom": "^16.3.2", // react Dom操作工具库(render函数等)
35 | "react-loadable": "^5.3.1", // 代码分割按需加载插件
36 | "react-redux": "^5.0.7", // react与redux连接的桥梁,挂载组件,同步状态
37 | "react-router-dom": "^4.2.2", // react前端路由(现在的版本不再需要react-router)
38 | "redux": "^4.0.0", // redux核心 状态管理
39 | },
40 | "devDependencies": { // 开发依赖包
41 | "@babel/core": "^7.1.2", // babel核心,编译ES6+新语法
42 | "@babel/plugin-proposal-class-properties": "^7.1.0", // Babel插件 - 用于让class类中支持定义箭头函数的语法
43 | "@babel/plugin-proposal-decorators": "^7.1.2", // Babel插件 - 支持修饰器语法 Decorator
44 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0", // Babel插件 - 支持对象的扩展运算符
45 | "@babel/plugin-syntax-dynamic-import": "^7.0.0", // Babel插件 - 支持异步import语法,代码分割需要
46 | "@babel/plugin-transform-runtime": "^7.1.0", // Babel插件 - 所有的垫片函数将引用babel-runtime中的,避免重复编译
47 | "@babel/preset-env": "^7.1.0", // Babel根据浏览器和运行时环境自动识别运用哪些垫片库来兼容ES6+语法
48 | "@babel/preset-react": "^7.0.0", // Babel支持react语法
49 | "@babel/runtime": "^7.1.2", // Babel运行时垫片库,提供了各种ES6的垫片,最终会自动编译为ES5
50 |
51 | "autoprefixer": "^8.3.0", // postCSS插件,自动添加CSS前缀等
52 | "babel-eslint": "^8.x", // 适配babel ES6+的eslint规范插件
53 | "babel-loader": "^8.0.4", // Webpack解析器 - 解析JS ES6+ 新语法
54 | "babel-plugin-import": "^1.10.0", // Babel插件 - 按需加载,用于antd
55 | "body-parser": "^1.18.3", // server.js有用,解析post请求的body数据
56 | "clean-webpack-plugin": "^0.1.19", // Webpack插件 - 每次打包时自动删除上一次打包留下的旧代码
57 | "copy-webpack-plugin": "^4.5.4", // 打包时将public中的文件直接拷贝到最终文件夹
58 | "css-loader": "^0.28.11", // Webpack解析器 - 用于解析js中import的css,和css中url()的路径
59 | "eslint": "^4.19.1", // Eslint 代码规范检测器
60 | "eslint-loader": "^2.0.0", // Webpack解析器 - 打包时检测代码规范时用
61 | "eslint-plugin-prettier": "^2.6.0", // Eslint插件 - prettier风格的代码格式规范,配置eslint用
62 | "eslint-plugin-react": "^7.7.0", // Eslint插件 - 让Eslint支持检测JSX(.eslintrc.json中有配置)
63 | "eslint-plugin-react-hooks": "^1.0.1", // Eslint插件 - 让Eslint支持检测Hooks语法
64 | "express": "^4.16.3", // Node.js框架 - 用于server.js中提供开发环境的服务
65 | "favicons-webpack-plugin": "^0.0.9", // 自动生成适配各终端得ico图标,pwa会用到部分
66 | "file-loader": "^1.1.11", // Webpack解析器 - 解析各类文件时有用,图片音频等,处理它们的相对路径
67 | "happypack": "^5.0.0-beta.3", // Webpack插件 - 多线程编译,速度更快,开发环境用
68 | "html-webpack-plugin": "^3.2.0", // Webpack插件 - 最终打包时自动生成index.html,并配置其类容
69 | "less": "3.x", // Less核心
70 | "less-loader": "^4.1.0", // Webpack解析器 - 解析Less,主要是解析antd的样式文件
71 | "mini-css-extract-plugin": "^0.8.0", // Webpack插件 - 打包时单独提取所有CSS
72 | "mockjs": "^1.0.1-beta3", // Mock 模拟生成随机数据用于开发测试
73 | "optimize-css-assets-webpack-plugin": "^5.0.3", // Webpack插件 - 打包时压缩提取出的CSS
74 | "postcss-loader": "^2.1.4", // Webpack解析器 - 用于进一步解析CSS,比如自动添加-webkit-前缀等
75 | "prettier": "1.12.1", // 代码自动格式化插工具
76 | "style-loader": "^0.20.3", // Webpack解析器 - 用于提取重复的css代码加入到