
【前端-工程化】React项目工程化记录-内置项目活文档(老项目升级优化-集成Hosky/ESLint/Prettier-升级Webpack/Babel/NodeSass/React)
文章已收录至https://lichong.work,转载请注明原文链接。 ps:欢迎关注公众号“Fun肆编程”或添加我的私人微信交流经验🤝 👍文章中优化的项目已整理为模板工程提交至GitHub,欢迎star😉
背景
最近接到一个项目,说是一个老的React项目,希望做个优化升级,我刚clone下来代码人就傻了(钱要少了),先看一眼原始项目吧:
问题点
- 项目有依赖冲突混乱问题,所以
npm install
装不上,只能用cnpm或者--force强制安装,一些node版本下依然会出现问题 - 项目依赖版本过低不易维护,最好升级下版本(貌似是用的 NodeJS 4 !!!)
- 项目结构混乱,即引入了webpack-dev-server,还引入了express;express完全没有发挥优势反而增加项目复杂度
- 这个项目是基于一个叫UEditor的百度开源原生js项目做的二次开发及扩展,原生js部分直接写死在了HTML里,每次修改源码后都手动压缩混淆js为min文件再改HTML,究其原因还是开发者对webpack不熟悉,并且使用错误配置方式,将ouput输出到了public,但public同时还是资源目录(脑阔疼!!!)
- 打包时index.html中引入js文件时还需要手动指定min的压缩文件,没有充分发挥webpack的作用
- React编码错误,导致开发环境控制台有warning,会降低效率、增加后期维护成本(生命周期使用错误)
- 没有引入ESLint/Prettier/StyleLint等开发组件,导致多人开发时代码风格不一致,甚至存在部分漏洞
- 项目活文档不完善,只有一个默认的README.md,对新人十分不友好
优化过程
- 去掉express 项目最终是部署在nginx的,实在不理解引进express的目的是什么。可能当时比较火吧,大家都想玩玩,删掉express的配置js,再删掉package.json里的依赖。
npm uninstall -D express
- 尝试升级相关项目组件到最新版解决依赖冲突 其中最明显的就是node-sass,相信接触过老项目的应该都被这个坑过,这个组件Sass官方现在也已经不维护了,直接重构了叫sass,但是可以兼容之前的语法,果断弃旧纳新
npm uninstall -D node-sass sass-loader @babel/core @babel/plugin-proposal-class-properties @babel/plugin-syntax-dynamic-import @babel/plugin-transform-runtime @babel/preset-env @babel/preset-react classnames
npm install -D sass sass-loader @babel/core @babel/plugin-proposal-class-properties @babel/plugin-syntax-dynamic-import @babel/plugin-transform-runtime @babel/preset-env @babel/preset-react classnames
npm uninstall -S babel-polyfill prop-types react react-dom
npm install -S core-js prop-types react react-dom
- 引入webpack-merge用于区分开发环境和生产环境配置
npm install -D webpack-merge
- 升级webpack至最新版,开发环境下改用webpack-dev-server,引入copy-webpack-plugin解决问题4
- 先安装开发组件:
npm install -D webpack webpack-cli webpack-dev-server css-loader html-webpack-plugin copy-webpack-plugin workbox-webpack-plugin babel-loader style-loader source-map-loader
- 因为低版本的webpack配置和高版本的不太兼容,直接创建新的webpack配置文件吧: --------------build/webpack.common.config.js
const path = require('node:path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');
const config = {
entry: {
index: './src/index.js',
},
output: {
path: path.join(__dirname, '../dist'),
filename: '[name].bundle.js',
clean: true,
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
favicon: './public/favicon.ico',
}),
new ESLintPlugin({
emitError: true,
emitWarning: true,
failOnError: true,
failOnWarning: true,
}),
new CopyWebpackPlugin({
patterns: [
{
from: path.join(__dirname, '../', 'public', 'core'),
to: path.join(__dirname, '../', 'dist', 'public', 'core'),
toType: 'dir',
force: true,
},
{
from: path.join(__dirname, '../', 'public', 'DatePicker'),
to: path.join(__dirname, '../', 'dist', 'public', 'DatePicker'),
toType: 'dir',
force: true,
},
],
options: {
concurrency: 100,
},
}),
],
module: {
rules: [
{
test: /\.[jt]sx?$/,
include: path.join(__dirname, '../', 'src'),
use: {
loader: 'babel-loader',
},
},
{
test: /\.s[ac]ss$/i,
include: path.join(__dirname, '../', 'src', 'styles'),
use: [
'style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true,
},
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
},
},
],
},
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif?)(\?[a-z0-9]+)?$/,
type: 'asset/resource',
},
],
},
performance: {
hints: false,
maxEntrypointSize: 1024000,
maxAssetSize: 1024000,
},
ignoreWarnings: [/Failed to parse source map/],
};
module.exports = config;
-------------- build/webpack.dev.config.js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const { merge } = require('webpack-merge');
const path = require('node:path');
const common = require('./webpack.common.config.js');
const port = process.env.PORT || 8080;
module.exports = merge(common, {
mode: 'development',
devtool: 'eval-source-map',
devServer: {
hot: true,
host: 'localhost',
port,
historyApiFallback: true,
open: true,
},
module: {
rules: [
{
test: /\.[jt]sx?$/,
include: path.join(__dirname, '../', 'src'),
use: [
{
loader: require.resolve('babel-loader'),
options: {
plugins: [
require.resolve('react-refresh/babel'),
],
},
},
],
},
{
test: /\.js$/,
enforce: 'pre',
use: ['source-map-loader'],
},
],
},
plugins: [
new ReactRefreshWebpackPlugin(),
],
});
--------------build/webpack.prod.config.js
const { merge } = require('webpack-merge');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const common = require('./webpack.common.config.js');
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
plugins: [
new WorkboxWebpackPlugin.GenerateSW(),
],
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
});
- 引入ESLint
npm install -D eslint eslint-config-airbnb eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks eslint-webpack-plugin @babel/eslint-parser
编辑配置文件: --------------eslintrc.js
module.exports = {
env: {
browser: true,
node: true,
es2021: true,
},
extends: ['plugin:react/recommended', 'plugin:react-hooks/recommended', 'airbnb', 'prettier'],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: '2021',
sourceType: 'module',
},
plugins: ['react', 'react-hooks'],
settings: {
react: {
version: '17.0.2',
},
},
// 声明全局变量
globals: {
baidu: true,
UE: true,
command: true,
$: true,
},
parser: '@babel/eslint-parser',
rules: {
eqeqeq: 2, // 必须使用 === 和 !==
'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }],
'no-empty-function': 2, // 禁止空函数
'no-multi-spaces': 2, // 禁止使用多个空格
'no-trailing-spaces': 2, // 禁止禁用行尾空格
'space-infix-ops': 2, // 要求操作符周围有空格
'space-in-parens': 2, // 强制在圆括号内使用一致的空格
'no-var': 2, // 要求使用 let 或 const 而不是 var,
'no-unused-vars': 2, // 禁止出现未使用过的变量
'react/prop-types': 0, // 防止在react组件定义中缺少props验证(禁用)
'react/no-array-index-key': 0, // 防止在数组中使用index作为key(禁用)
'react/no-danger': 0, // 防止使用危险的 JSX 属性 (react/no-danger)
'jsx-a11y/click-events-have-key-events': 0, // 点击事件必须绑定一个键盘事件以帮助残疾人士(禁用)
'jsx-a11y/interactive-supports-focus': 0, // 必须设置tabIndex允许元素获取焦点从而使用键盘tab就可以触发点击帮助残疾人士(禁用)
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
optionalDependencies: true,
peerDependencies: true,
packageDir: './',
},
],
},
};
--------------eslintignore
/public/
build
.vscode
.idea
node_modules
- 引入Prettier
npm install -D prettier eslint-config-prettier
编辑配置文件: --------------.prettierrc.json
{
"semi": true,
"singleQuote": true,
"jsxSingleQuote": true,
"trailingComma": "all",
"printWidth": 120,
"bracketSameLine": true,
"tabWidth": 2,
"useTabs": false,
"quoteProps": "as-needed",
"bracketSpacing": true,
"arrowParens": "always",
"requirePragma": false,
"insertPragma": false,
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "ignore",
"vueIndentScriptAndStyle": false,
"endOfLine": "lf",
"embeddedLanguageFormatting": "auto"
}
- 引入Husky,增加提交前的git hook
- 编辑项目简易活文档,方便新手上手。
优化后
- 编辑 README.md 提供项目完整介绍文档(包括项目简介、开发规范、项目管理、环境搭建、构建部署、团队协作等)等项目活文档;
- 修改 public 作为静态公用资源文件夹而非 output 输出目录,避免混淆源代码和编译后的文件,更规范;
- 升级所有组件,引用新版稳定组件代替已弃用组件;
- 升级至 React17,可以使用 Hook 方式了;
- 升级至 Webpack5,最大化简化配置,同时提供自动压缩 js 能力,不需要手动编译为 min 文件了;
- 重构 webpack 配置文件,区别开发环境和生产环境,开发环境提供 source-map 文件控制台可以打印源码行数,方便排查问题,生产环境提供最优化的打包;
- 细化规范 package.json,支持 ie10 及以上浏览器,可以控制台输入
browserslist
查看支持浏览器; - 引入 eslint,规范化开发,非必要最好不要修改规范,每一条规范都是有理由的;
- 引入 prettier,保证多人开发不同开发工具场景下格式统一;
- 修改升级后的 eslint 问题和代码格式化问题;
- 修改控制台部分 undefined 报错问题;
- 去掉 html 中重复引入的 js;
- 集成husky,提供提交代码前自动 eslint --fix 和 prettier 格式化功能,并校验提交信息是否符合规范;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~往期精选🪶~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~文章已收录至https://lichong.work,转载请注明原文链接。 ps:欢迎关注公众号“Fun肆编程”或添加我的私人微信交流经验🤝 👍文章中优化的项目已整理为模板工程提交至GitHub,欢迎star😉
【Docker】入门教程-基本概念解读 【前端-开发环境】使用NVM实现不同nodejs版本的自由切换(NVM完整安装使用手册) 【前端-NPM私服】内网使用verdaccio搭建私有npm服务器 【前端-IE兼容】Win10和Win11使用Edge调试前端兼容IE6、IE7、IE8、IE9、IE10、IE11问题 【工具-TWRP-frp-Termux】旧手机暴改成免费云服务器-MIUI刷TWRP安装magisk获取root 【工具-Shell脚本】java程序产品包模板-linux和windows通用shell启动停止脚本(无需系统安装Java运行环境) 【工具-Nginx】从入门安装到高可用集群搭建 【工具-Nginx】Nginx高性能通用配置文件-注释版-支持防刷限流、可控高并发、HTTP2、防XSS、Gzip、OCSP Stapling、负载、SSL 【工具-WireShark】网络HTTP抓包使用教程 【后端-maven打包】通过profile标签解决同时打jar包 war包需求 【架构-DDD】使用领域驱动设计-互联网未来架构设计之道(一) 【后端-SpringCache】基于Spring Cache封装一个能够批量操作的Redis缓存记录下踩坑历程(pipeline或mget封装) 【后端-SkyWalking】SkyWalking前后端开发环境搭建详细教程步骤-6.x/7.x/8.x版本通用-插件二次开发利器(一) 【后端-Quartz】Springboot整合Quartz支持集群环境-设计业务与框架分离及实现定时任务调度
✨欢迎为耿直少年点赞、关注、收藏!!!
👇👇👇