diff --git a/.eslintrc.json b/.eslintrc.json index bd28ac5f..e48cda8f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,7 +5,7 @@ "es6": true, "mocha": true }, - "parser": "babel-eslint", + "parser": "@babel/eslint-parser", "parserOptions": { "ecmaVersion": 9, "ecmaFeatures": { @@ -28,6 +28,7 @@ "no-catch-shadow": "error", "react/no-unescaped-entities": "off", "react-hooks/rules-of-hooks": "error", - "prettier/prettier": "warn" + "prettier/prettier": "warn", + "no-unused-expressions": "warn" } } diff --git a/.gitignore b/.gitignore index 5479b62d..37cd911d 100644 --- a/.gitignore +++ b/.gitignore @@ -38,7 +38,9 @@ jspm_packages build .happypack yarn.lock +.vscode/* dll +pnpm-lock.yaml # Optional npm cache directory .npm diff --git a/.travis.yml b/.travis.yml index c4e153a9..f5a7d5d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: node_js node_js: - - '10' - - '8' + - "14" + - "10" script: - - yarn dll - yarn build diff --git a/README.md b/README.md index 2216bba0..5553a1b2 100644 --- a/README.md +++ b/README.md @@ -6,42 +6,25 @@ ## what is this? -react automaticaly
+react automatically
这是一个 React 脚手架,没有使用 create-react-app
标准的 React+Redux 分层结构
经过了多个项目的实践,不停的更新和优化出来的。目前自己做项目也在用。 -- PWA、Hooks、代码分割、热替换、dllPlugin 静态资源预编译、HappyPack 多线程构建、ES6+语法 - -## 注意的地方 - -- antd icon 打包体积过大:https://github.com/ant-design/ant-design/issues/12011,开了 gzip 之后还行 +- PWA、Hooks、代码分割、热替换、HappyPack 多线程构建、ES6+语法 ## 构建 Start ``` yarn install # 安装依赖模块 -``` - -``` -yarn dll # 静态资源预编译 -``` - -``` -yarn start # 运行开发环境,默认监听8888端口 -``` -``` +yarn start # 运行开发环境: http://localhost:8888 yarn build # 正式打包,用于生产环境 -``` -``` -yarn prettier # 自动格式化src、mock目录下的所有.js/.css/.scss/.less文件 -``` +yarn dist # 运行正式打包后的最终文件(build目录下的文件): http://localhost:8889 +yarn distmac # MAC下运行最终文件:http://localhost:8889 -``` -yarn dist # 运行正式打包后的最终文件(build目录下的文件),默认监听8888端口 -yarn distmac # MAC下运行最终文件 +yarn prettier # 自动格式化src、mock目录下的所有.js/.css/.scss/.less文件 ``` ## 更新日志 Update log @@ -59,7 +42,6 @@ yarn distmac # MAC下运行最终文件 │   ├── index.html # 编译后的主页html │   ├── manifest.json # PWA配置文件,配置了桌面图标,以APP方式启动时的启动页面相关参数 │   └── service-worker.js # PWA核心worker, 用于离线访问,缓存不变的资源文件 -├── dll # 静态资源预编译插件生成的dll文件 ├── mock # mock测试数据 ├── public # 静态文件,index.html等 ├── src                                 # 项目代码目录 @@ -78,13 +60,12 @@ yarn distmac # MAC下运行最终文件 │   └── index.html                     # 主页html文件,开发环境和生产打包共用 ├── server.js # 用于开发环境的服务部署 ├── webpack.dev.config.js # 用于开发环境的webpack配置 -├── webpack.dll.config.js # 静态资源预编译所需webpack配置 └── webpack.production.config.js # 用于生产环境正式打包的webpack配置 ``` ## 预览地址 Demo -https://isluo.com/work/pwa/ (线上没有 mock 环境) +https://isluo.com/work/pwa/ ## 参阅资料 diff --git a/mock/mock-data.js b/mock/mock-data.js index e6a0cabf..57731942 100644 --- a/mock/mock-data.js +++ b/mock/mock-data.js @@ -8,9 +8,9 @@ const ajaxTest = { "data|1-10": [ { "id|+1": 1, - email: "@EMAIL" - } - ] + email: "@EMAIL", + }, + ], }; exports.mockApi = (url, params) => { diff --git a/package.json b/package.json index 8e90aebb..eccb91af 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,15 @@ "description": "react脚手架,最新技术", "main": "index.js", "scripts": { - "dll": "webpack -p --config webpack.dll.config.js --progress --profile --colors", "start": "node server.js --max_old_space_size=4096", - "build": "webpack -p --config webpack.production.config.js", + "build": "webpack --config webpack.production.config.js", "dist": "set NODE_ENV=production&& node server.js", "distmac": "export NODE_ENV=production&& node server.js", "prettier": "prettier --write \"{src,mock}/**/*.{js,css,less}\"", "cover": "./node_modules/.bin/istanbul cover _mocha", "coveralls": "npm run cover -- --report lcovonly && cat ./coverage/lcov.info | ./node_modules/.bin/coveralls" }, - "author": "luo", + "author": "admin", "license": "ISC", "private": true, "repository": { @@ -24,65 +23,67 @@ "pnp": false }, "dependencies": { - "@rematch/core": "^1.2.0", - "antd": "^3.25.0", - "axios": "^0.19.0", - "core-js": "^3.3.6", - "history": "^4.10.1", - "lodash": "^4.17.15", - "moment": "^2.24.0", - "react": "^16.11.0", - "react-dom": "^16.11.0", + "@ant-design/icons": "^4.7.0", + "@rematch/core": "^2.2.0", + "antd": "^4.24.2", + "axios": "^1.1.3", + "core-js": "^3.26.1", + "favicons": "6.2.2", + "lodash": "^4.17.21", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-loadable": "^5.5.0", - "react-redux": "^7.1.3", - "react-router-dom": "^5.1.2", - "redux": "^4.0.4" + "react-redux": "^8.0.5", + "react-router-dom": "^6.4.3", + "redux": "^4.2.0" }, "devDependencies": { - "@babel/core": "^7.7.0", - "@babel/plugin-proposal-class-properties": "^7.7.0", - "@babel/plugin-proposal-decorators": "^7.7.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4", - "@babel/plugin-proposal-object-rest-spread": "^7.6.2", - "@babel/plugin-proposal-optional-chaining": "^7.6.0", - "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/plugin-transform-runtime": "^7.6.2", - "@babel/preset-env": "^7.7.1", - "@babel/preset-react": "^7.7.0", - "@babel/runtime": "^7.7.1", - "autoprefixer": "^9.7.1", - "babel-eslint": "^10.0.3", - "babel-loader": "^8.0.6", - "babel-plugin-import": "^1.12.2", - "body-parser": "^1.19.0", - "clean-webpack-plugin": "^3.0.0", - "css-loader": "^3.2.0", - "eslint": "^6.6.0", - "eslint-loader": "^3.0.2", - "eslint-plugin-prettier": "^3.1.1", - "eslint-plugin-react": "^7.16.0", - "eslint-plugin-react-hooks": "^2.2.0", - "express": "^4.17.1", - "favicons-webpack-plugin": "^1.0.2", - "file-loader": "^4.2.0", + "@babel/core": "^7.20.2", + "@babel/eslint-parser": "^7.19.1", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-decorators": "^7.20.2", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.2", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.19.6", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/runtime": "^7.20.1", + "antd-dayjs-webpack-plugin": "^1.0.6", + "autoprefixer": "^10.4.13", + "babel-loader": "^9.1.0", + "babel-plugin-import": "^1.13.5", + "clean-webpack-plugin": "^4.0.0", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.7.2", + "css-minimizer-webpack-plugin": "^4.2.2", + "dayjs": "^1.11.6", + "eslint": "^8.27.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react": "^7.31.10", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-webpack-plugin": "^3.2.0", + "express": "^4.18.2", + "favicons-webpack-plugin": "^5.0.2", "happypack": "^5.0.1", - "html-webpack-plugin": "^3.2.0", - "less": "^3.10.3", - "less-loader": "^5.0.0", - "mini-css-extract-plugin": "^0.8.0", + "html-webpack-plugin": "^5.5.0", + "less": "^4.1.3", + "less-loader": "^11.1.0", + "mini-css-extract-plugin": "^2.6.1", "mockjs": "^1.1.0", - "optimize-css-assets-webpack-plugin": "^5.0.3", - "postcss-loader": "^3.0.0", - "prettier": "1.18.2", - "style-loader": "^1.0.0", - "sw-precache-webpack-plugin": "^0.11.5", - "terser-webpack-plugin": "^2.2.1", - "url-loader": "^2.2.0", - "webpack": "^4.41.2", - "webpack-cli": "^3.3.10", - "webpack-dev-middleware": "^3.7.2", - "webpack-hot-middleware": "^2.25.0", - "webpackbar": "^4.0.0", + "postcss": "^8.4.19", + "postcss-loader": "^7.0.1", + "prettier": "^2.7.1", + "style-loader": "^3.3.1", + "terser-webpack-plugin": "^5.3.6", + "webpack": "^5.75.0", + "webpack-bundle-analyzer": "^4.7.0", + "webpack-cli": "^4.10.0", + "webpack-dev-middleware": "^5.3.3", + "webpack-hot-middleware": "^2.25.3", + "webpackbar": "^5.0.2", + "workbox-webpack-plugin": "^6.5.4", "xml-loader": "^1.2.1" }, "browserslist": [ @@ -92,4 +93,4 @@ "not dead", "not op_mini all" ] -} +} \ No newline at end of file diff --git a/postcss.config.js b/postcss.config.js index 796abe22..6ebf6265 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,4 +1,6 @@ /** postcss-loader 解析器所需的配置文件 **/ module.exports = { - plugins: [require("autoprefixer")()] + plugins: [ + require('autoprefixer') + ] }; diff --git a/public/index.ejs b/public/index.html similarity index 86% rename from public/index.ejs rename to public/index.html index 72382db5..1d7b6e3c 100644 --- a/public/index.ejs +++ b/public/index.html @@ -5,9 +5,9 @@ React - <%= dll %>
+ <%= htmlWebpackPlugin.options.registerServiceWorker %> diff --git a/server.js b/server.js index 9ff59022..84aa6508 100644 --- a/server.js +++ b/server.js @@ -1,7 +1,6 @@ /** 用于开发环境的服务启动 **/ const path = require("path"); // 获取绝对路径有用 const express = require("express"); // express服务器端框架 -const bodyParser = require("body-parser"); const env = process.env.NODE_ENV; // 模式(dev开发环境,production生产环境) const webpack = require("webpack"); // webpack核心 const webpackDevMiddleware = require("webpack-dev-middleware"); // webpack服务器 @@ -12,15 +11,26 @@ const mock = require("./mock/mock-data"); // mock模拟数据,模拟后台业 const app = express(); // 实例化express服务 const DIST_DIR = webpackConfig.output.path; // webpack配置中设置的文件输出路径,所有文件存放在内存中 -const PORT = 8888; // 服务启动端口号 +let PORT = 8888; // 服务启动端口号 -app.use(bodyParser.urlencoded({ extended: false })); -app.use(bodyParser.json()); +app.use(express.urlencoded({ extended: false })); +app.use(express.json()); + +/** 监听POST请求,返回MOCK模拟数据 **/ +app.post(/\/api.*/, (req, res, next) => { + const result = mock.mockApi({ url: req.originalUrl, body: req.body }); + res.send(result); +}); +app.get(/\/api.*/, (req, res, next) => { + const result = mock.mockApi({ url: req.originalUrl, body: req.body }); + res.send(result); +}); if (env === "production") { // 如果是生产环境,则运行build文件夹中的代码 + PORT = 8889; app.use(express.static("build")); - app.get("*", function(req, res) { + app.get("*", (req, res) => { res.sendFile(path.join(__dirname, "build", "index.html")); }); } else { @@ -28,17 +38,11 @@ if (env === "production") { app.use(express.static("dll")); app.use( webpackDevMiddleware(compiler, { - // 挂载webpack小型服务器 publicPath: webpackConfig.output.publicPath, // 对应webpack配置中的publicPath - quiet: true, // 是否不输出启动时的相关信息 - stats: { - colors: true, // 不同信息不同颜色 - timings: true // 输出各步骤消耗的时间 - } - }) + }), ); - // 挂载HMR热更新中间件 - app.use(webpackHotMiddleware(compiler)); + + app.use(webpackHotMiddleware(compiler)); // 挂载HMR热更新中间件 // 所有请求都返回index.html app.get("*", (req, res, next) => { const filename = path.join(DIST_DIR, "index.html"); @@ -55,12 +59,6 @@ if (env === "production") { }); } -/** 监听POST请求,返回MOCK模拟数据 **/ -app.post("*", (req, res, next) => { - const result = mock.mockApi(req.originalUrl, req.body); - res.send(result); -}); - /** 启动服务 **/ app.listen(PORT, () => { console.log("本地服务启动地址: http://localhost:%s", PORT); diff --git a/src/component/footer/index.js b/src/component/footer/index.js index 1fa99160..aeddf5ee 100644 --- a/src/component/footer/index.js +++ b/src/component/footer/index.js @@ -2,16 +2,16 @@ import React from "react"; import "./index.less"; -export default function Footer(props) { +export default function Footer() { return (
- © 2018-2019{" "} + © 2018-{new Date().getFullYear()}{" "} - isluo.com + blog.isluo.com , Inc.
diff --git a/src/component/menu/index.js b/src/component/menu/index.js index a47a5009..b79fc166 100644 --- a/src/component/menu/index.js +++ b/src/component/menu/index.js @@ -2,31 +2,34 @@ import React from "react"; import { Link } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import "./index.less"; export default function Menu() { + const navigate = useNavigate(); + + function goToTest() { + navigate("/test?a=123&b=abc", { state: { c: 456, d: "ABC" } }); + } + return (
首页|构建与特性| - - 测试(hooks) + + 测试:Link跳转 | - goToTest()} to={{ - pathname: "/testclass", + pathname: "/test", search: "?a=123&b=abc", - state: { c: "456", d: "ABC" } }} + state={{ c: 456, d: "ABC" }} > - 测试(class) - + 测试:api跳转 + |

构建与特性

@@ -16,10 +15,6 @@ function FeaturesPageContainer(props) {

安装依赖文件

npm install

-
-

静态资源预编译

-

npm run dll

-

启动开发环境

npm run start

@@ -33,13 +28,14 @@ function FeaturesPageContainer(props) {

运行生产环境的代码

-

npm run dist

+

npm run dist (windows系统)

+

npm run distmac (Mac/Linux系统)

运行build文件夹下生成好的最终代码

代码自动格式化

npm run prettier

-
自动美化js/css/less等文件
+
自动美化所有js/css/less等文件

HMR局部热更新

@@ -53,17 +49,10 @@ function FeaturesPageContainer(props) {
src/container/root/index.js中能查看例子
-

webpack4.x

+

webpack5.x

使用了最新版本的webpack,编译速度更快
); } - -export default connect( - state => ({}), - dispatch => ({ - actions: {} - }) -)(FeaturesPageContainer); diff --git a/src/container/home/index.js b/src/container/home/index.js index 43867cbb..c362bbce 100644 --- a/src/container/home/index.js +++ b/src/container/home/index.js @@ -2,31 +2,25 @@ /** 所需的各种插件 **/ import React from "react"; -import { connect } from "react-redux"; +import { useStore } from "react-redux"; /** 所需的各种资源 **/ import "./index.less"; import ImgLogo from "../../assets/react-logo.jpg"; -function HomePageContainer(props) { +export default function HomePageContainer(props) { + const store = useStore(); + console.log("store:", store); + console.log("what props:", props); return (
React-Luo
- react16、redux4、router5、webpack4、eslint、babel7、antd3 + react17、redux4、router6、webpack5、eslint、babel7、antd4
); } - -export default connect( - state => { - return {}; - }, - dispatch => ({ - actions: {} - }) -)(HomePageContainer); diff --git a/src/container/notfound/index.js b/src/container/notfound/index.js index aebf1024..21cc519f 100644 --- a/src/container/notfound/index.js +++ b/src/container/notfound/index.js @@ -2,22 +2,14 @@ /** 所需的各种插件 **/ import React from "react"; -import { connect } from "react-redux"; /** 所需的所有资源 **/ import "./index.less"; -function NotFoundPageContainer() { +export default function NotFoundPageContainer() { return (
404 not found
); } - -export default connect( - state => ({}), - dispatch => ({ - actions: {} - }) -)(NotFoundPageContainer); diff --git a/src/container/routers/index.js b/src/container/routers/index.js index 2b8f173a..e15485a2 100644 --- a/src/container/routers/index.js +++ b/src/container/routers/index.js @@ -2,15 +2,22 @@ /** 所需的各种插件 **/ import React, { useEffect } from "react"; -import { connect } from "react-redux"; -import { Router, Route, Switch, Redirect } from "react-router-dom"; + +// import { Router, Route, Switch, Redirect } from "react-router-dom"; +import { + HashRouter as Router, + Routes, + Route, + Navigate, +} from "react-router-dom"; // antd的多语言 -import { LocaleProvider } from "antd"; +import { ConfigProvider } from "antd"; import zhCN from "antd/lib/locale-provider/zh_CN"; -// import {createBrowserHistory as createHistory} from "history/"; // URL模式的history -import { createHashHistory as createHistory } from "history"; // 锚点模式的history +// import { createBrowserHistory as createHistory } from "history/"; // URL模式的history +// import { createHashHistory as createHistory } from "history"; // 锚点模式的history + import Loadable from "react-loadable"; // 用于代码分割时动态加载模块 /** 普通组件 **/ @@ -24,92 +31,81 @@ import "./index.less"; const Home = Loadable({ loader: () => import(/* webpackChunkName:'home' */ "../home"), loading: Loading, // 自定义的Loading动画组件 - timeout: 10000 // 可以设置一个超时时间(s)来应对网络慢的情况(在Loading动画组件中可以配置error信息) + timeout: 10000, // 可以设置一个超时时间(s)来应对网络慢的情况(在Loading动画组件中可以配置error信息) }); const Test = Loadable({ loader: () => import(/* webpackChunkName:'test' */ "../test"), - loading: Loading + loading: Loading, +}); +const Page1 = Loadable({ + loader: () => + import(/* webpackChunkName:'testclass' */ "../test/container/page1"), + loading: Loading, }); -const TestClass = Loadable({ - loader: () => import(/* webpackChunkName:'testclass' */ "../testclass"), - loading: Loading +const Page2 = Loadable({ + loader: () => + import(/* webpackChunkName:'testclass' */ "../test/container/page2"), + loading: Loading, +}); +const Page3 = Loadable({ + loader: () => + import(/* webpackChunkName:'testclass' */ "../test/container/page3"), + loading: Loading, }); const Features = Loadable({ loader: () => import(/* webpackChunkName:'features' */ "../features"), - loading: Loading + loading: Loading, }); const NotFound = Loadable({ loader: () => import(/* webpackChunkName:'notfound' */ "../notfound"), - loading: Loading + loading: Loading, }); -const history = createHistory(); // 实例化history对象 +// const history = createHistory(); // 实例化history对象 /** 组件 **/ -function RootRouterContainer(props) { +export default function RootRouterContainer(props) { // 在组件加载完毕后触发 useEffect(() => { // 可以手动在此预加载指定的模块: - //Features.preload(); // 预加载Features页面 - //Test.preload(); // 预加载Test页面 + // Features.preload(); // 预加载Features页面 + // Test.preload(); // 预加载Test页面 // 也可以直接预加载所有的异步模块 // Loadable.preloadAll(); }, []); - /** 简单权限控制 **/ - function onEnter(Component, props) { + /** 简单权限控制 路由守卫 **/ + function onEnter(Component) { // 例子:如果没有登录,直接跳转至login页 // if (sessionStorage.getItem('userInfo')) { - // return ; + // return Component; // } else { // return ; // } - return ; + return Component; } return ( - + <> - - { - return ( -
- - - onEnter(Home, props)} - /> - onEnter(Features, props)} - /> - onEnter(Test, props)} - /> - onEnter(TestClass, props)} - /> - - - -
- ); - }} - /> + +
+ + } /> + )} /> + )} /> + )}> + )} /> + )} /> + )} /> + + } /> + + +