本文讲述项目基建的创建,我将采用CRACO 管理webpack配置相关,好处:无需eject,且方便修改webpack、babel、postcss等配置。
环境
node 18.20.3 环境
react18版本
webpack5 + craco构建
一、使用craco管理项目
1、安装:
yarn add @craco/craco -D
2、package.json 配置如下
{
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test"
}
}
3、项目中创建craco.config.ts
const path = require('path');
const CracoLessPlugin = require('craco-less');
module.exports = {
webpack: {
alias: {
// 配置tsconfig.json 实现路径别名
'@': path.resolve(__dirname, 'src')
},
configure: (webpackConfig, { env, paths }) => {
// 自定义 webpack 配置
return webpackConfig;
}
},
// 配置 less
plugins: [],
// 配置代理
devServer: {}
};
4、拆分 craco.config.ts
我将此文件拆分,更有利于分别配置。 将webpack和style区分
/**
* Craco 重写 CRA 配置
* - GitHub:https://github.com/gsoft-inc/craco
* - 配置参数:https://github.com/gsoft-inc/craco/blob/master/packages/craco/README.md#configuration-overview
* - 快速指南:https://blog.eleven.net.cn/2020/09/11/cra/craco/
*
* Tips:
* 1、区分 node 运行环境 —— NODE_ENV
* - whenDev ☞ process.env.NODE_ENV === 'development'
* - whenTest ☞ process.env.NODE_ENV === 'test'
* - whenProd ☞ process.env.NODE_ENV === 'production'
* 2、NODE_ENV 可以区分 node 运行环境,仅支持 development、test、production,
* 自定义的 REACT_APP_BUILD_ENV 用于区分编译环境,支持 development、test、uat、production。
* 3、craco 有提供一些好用的 plugin(https://github.com/gsoft-inc/craco#community-maintained-plugins),推荐优先考虑使用现成的 craco plugin 去解决问题。
* 4、CRA 脚手架相关的配置覆盖,优先使用 craco 提供的快捷方式去配置。解决不了的问题,在 configure 函数中配置。
* 推荐 configure 配置使用函数形式,而非对象形式。虽然,函数形式更复杂了一点,但是二者是互斥的,只能选择其中一种。
*/
const path = require('path');
// const CracoLessPlugin = require('craco-less');
module.exports = {
babel: require('./craco/babel'),
style: require('./craco/style'),
webpack: require('./craco/webpack'),
// 如果要新增插件,可直接在这里新增。 当然,你在 configure 里添加也可以,但不推荐。
// 如果你想修改内置的某些 webpack-plugin 参数,必须到 configure 里去修改
plugins: [],
// 开发环境配置代理
devServer: {
port: 3999,
historyApiFallback: true,
liveReload: false,
//系统代理
proxy: {
'/xxx': {
target: 'xxx',
//路径重写-现在,对 /xxx/users 的请求会将请求代理到 http://localhost:3999/api/users。
pathRewrite: { '^/xxx': '' },
//默认情况下,代理时会保留主机头的来源,可以将 changeOrigin 设置为 true 以覆盖此行为。
changeOrigin: true,
//接受在 HTTPS 上运行且证书无效的后端服务器
secure: false,
//对于浏览器请求,想要提供 HTML 页面,但是对于 API 请求,想要代理它。 可以执行以下操作:
bypass: function (req, res, proxyOptions) {
if (req.headers.accept.indexOf('html') !== -1) {
console.log('Skipping proxy for browser request.');
return '/index.html';
}
},
}
},
}
};
5、使用环境工具env-cmd
安装
yarn add env-cmd -D
配置package.json
"scripts": {
"start": "env-cmd -e development craco start",
"build-prod": "env-cmd -e production craco build",
"build-test": "env-cmd -e test craco build",
},
使用
创建根.env-cmdrc.js,这样就可以创建任意环境了!
/**
* env-cmd 注入 node 运行环境变量
* - env-cmd 支持定义任意多的环境,自动注入定义的环境变量,仅需在 package.json scripts 启动时,通过 -e 指定运行时对应的环境;
* - https://github.com/toddbluhm/env-cmd
*
* Tips:
* 1、自定义的环境变量:
* - 如果你期望在 js 代码中读取,「必须以 REACT_APP_ 打头」,否则会被 CRA 脚手架经过内部处理忽略掉;
* - 如果仅仅在构建阶段使用,可以不使用 REACT_APP_ 前缀;
* 2、CRA 内置的环境变量列表:https://create-react-app.dev/docs/advanced-configuration
*/
const package = require('./package.json');
module.exports = {
development: {
REACT_APP_BUILD_ENV: 'development', // 编译环境
PUBLIC_URL: '', // 配置静态资源 url,最终影响 output下的 publicPath(开发环境不需要配置)
ENABLE_VCONSOLE: false, // 是否开启 vconsole( production 环境即使设置为 true 也不会开启)
DANGEROUSLY_DISABLE_HOST_CHECK: true, // 允许代理 host 通过 IP 地址访问,
},
test: {
REACT_APP_BUILD_ENV: 'test',
// PUBLIC_URL: `https://static2.test.ximalaya.com/yx/${projectName}/last/${outputDir}/`,
BUILD_PATH: 'dist', // 自定义打包输出目录
INLINE_RUNTIME_CHUNK: false,
GENERATE_SOURCEMAP: true,
REMOVE_FILENAME_HASH: false, // 是否移除编译产物中 js/css 文件名的 hash 值
ENABLE_VCONSOLE: false,
},
uat: {
REACT_APP_BUILD_ENV: 'uat',
// PUBLIC_URL: `https://s1.uat.xmcdn.com/yx/${projectName}/last/${outputDir}/`,
BUILD_PATH: 'dist',
INLINE_RUNTIME_CHUNK: false,
GENERATE_SOURCEMAP: true,
REMOVE_FILENAME_HASH: false,
ENABLE_VCONSOLE: false,
},
production: {
REACT_APP_BUILD_ENV: 'production',
// PUBLIC_URL: `https://s1.xmcdn.com/yx/${projectName}/last/${outputDir}/`, // 尽量使用 https,避免运营商劫持资源
BUILD_PATH: 'dist',
INLINE_RUNTIME_CHUNK: false, // runtime 代码是否内嵌到 html 中
GENERATE_SOURCEMAP: true, // 是否开启 sourcemap
// REACT_APP_SOURCE_MAPPING_URL: `http://sourcemap.ximalaya.com/${projectName}/${outputDir}/`, // 暂时仅支持 http 协议
REMOVE_FILENAME_HASH: false,
SHOULD_DROP_DEBUGGER: true, // 打包时是否移除 debugger
SHOULD_DROP_CONSOLE: true, // 打包时是否移除 console
},
// 任意多的环境
};
6、不实用工具
可以定义.env | .env.development | .env.production | .env.test
使用方式:添加自定义环境变量 | Create React App 中文网
大概就是process.env.REACT_APP_xxx 打头即可!也可以再html中使用定义的变量!这好像是大部分项目采用的,不过我还是推荐统一用env-cmd处理,方便一点。
二、按需加载&webpack配置
1、antd按需加载无需配置
antd 4.x | antd 5.x 都有按需加载效果,无需配置。如下图:
antd3.x及以下配置按需加载
- 在 bebel 配置中新增
babel-plugin-import
,搞定 AntDesign 按需加载
module.exports = {
plugins: [
//AntDesign 按需加载 (antd4版本以上 支持自动按需加载,无需配置此插件)
[
'babel-plugin-import', // yarn add babel-plugin-import -D
{
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
},
'antd',
],
]
}
2、babel-lodash等其他常用库按需加载
是否可以用 这个插件呢?vant、antd-mobile等等这些
lodash 按需引入,如下
官网babel-plugin-lodash
https://github.com/lodash/babel-plugin-lodash
预期效果如下:
//原来
import _ from 'lodash' 这里全量引入了,需要优化
import { add } from 'lodash/fp'
const addOne = add(1)
_.map([1, 2, 3], addOne)
//变成
import _add from 'lodash/fp/add'
import _map from 'lodash/map'
const addOne = _add(1)
_map([1, 2, 3], addOne)
安装
npm i --save lodash
npm i --save-dev babel-plugin-lodash @babel/cli @babel/preset-env
…
官网有不奏
https://blog.youkuaiyun.com/gitblog_00091/article/details/138746055 也可以看这篇
效果
打包之后 减少24.27kb
3、webpack配置
以webpack5为例(建议如果是5以下版本,先升级5!)
webpack配置项见配置
3.1 定制devServer
主要是自定义端口号、配置开发代理服务、热更新相关!
devServer: {
port: 9876,
client: {
logging: 'error', // 日志级别
// progress: true, // 进度条
},
proxy: {
'/xxx': {
target: 'xxx',
//路径重写-现在,对 /xxx/users 的请求会将请求代理到 http://localhost:3999/api/users。
pathRewrite: { '^/xxx': '' },
//默认情况下,代理时会保留主机头的来源,可以将 changeOrigin 设置为 true 以覆盖此行为。
changeOrigin: true,
//接受在 HTTPS 上运行且证书无效的后端服务器
secure: false,
//对于浏览器请求,想要提供 HTML 页面,但是对于 API 请求,想要代理它。 可以执行以下操作:
bypass: function (req, res, proxyOptions) {
if (req.headers.accept.indexOf('html') !== -1) {
console.log('Skipping proxy for browser request.');
return '/index.html';
}
},
}
},
}
3.2 配置@映射
第一步:
resolve: {
//craco 无需resolve一层
alias: {
'@': path.resolve(__dirname, '../src'),
Templates: path.resolve(__dirname, 'src/templates/'),
// xyz$: path.resolve(__dirname, 'path/to/file.js'), //配置精准解析
'ignored-module': false,//将 resolve.alias 设置为 false 将告知 webpack 忽略模块。
},
},
第二步:
"compilerOptions":{
"paths": {
"@/*": ["./src/*"]
}
}
3.2.1 Preact
注:一些简单的项目,H5等可以借此优化项目
// 如果需要优化到 preact,开启该选项。一般一些移动端项目,涉及到的包体积小可以优化
// 'react: 'preact/compat',
// 'react-dom': 'preact/compat',
// 'react-dom/test-utils': 'preact/compat',
3.3 通用常用plugin
这里我列举一些通用的插件,特殊定制插件根据具体情况看!插件使用方式根据具体情况判定!
const path = require('path');
const { whenDev, whenProd, when } = require('@craco/craco');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const CircularDependencyPlugin = require('circular-dependency-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CompressionWebpackPlugin = require('compression-webpack-plugin')
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const WebpackBar = require('webpackbar');
plugins: {
// 添加新插件
add: [
/**
* 1.使用DllPlugin打包第三方库 2.使用DLLReferencePlugin引用manifest.json,去关联第1步中已经打好的包
* https://webpack.docschina.org/plugins/dll-plugin/ 可以减少打包时间,第三方库打包一次就不参与打包了
* https://juejin.cn/post/7154351787246354462?searchId=2024111918133865459BEC291608012FA3#heading-23 看这篇文章
* 比较麻烦,手动维护困难,慎用~
*/
// [new DllReferencePlugin({ manifest })],
/**
* 编译进度条
* - https://www.npmjs.com/package/webpackbar
*/
new WebpackBar(),
/**
* 模块间循环依赖检测插件(监测到循环依赖时,会报错)
* - https://juejin.im/post/6844904017848434702
*/
...whenProd(() => [new CircularDependencyPlugin(
{
exclude: /node_modules/,
include: /src/,
failOnError: true,
allowAsyncCycles: false,
cwd: process.cwd(),
}),
], []),
/**
* 编译产物分析
* - https://www.npmjs.com/package/webpack-bundle-analyzer
*/
...when(isBuildAnalyzer, () => [new BundleAnalyzerPlugin()], []),
/**
* js,css文件过大进行gzip压缩文件
* - https://github.com/webpack-contrib/compression-webpack-plugin
*
* # nginx配置如下 识别gzip
* gzip on; # 开启gzip压缩
* gzip_min_length 10k; # 小于4k的文件不会被压缩,大于4k的文件才会去压缩
* gzip_buffers 16 8k; # 处理请求压缩的缓冲区数量和大小,比如8k为单位申请16倍内存空间;使用默认即可,不用修改
* gzip_http_version 1.1; # 早期版本http不支持,指定默认兼容,不用修改
* gzip_comp_level 2; # gzip 压缩级别,1-9,理论上数字越大压缩的越好,也越占用CPU时间。实际上超过2的再压缩,只能压缩一点点了,但是cpu确是有点浪费。因为2就够用了
* # 压缩的文件类型 MIME类型,百度一下,一大把 # css # xml # 识别php # 图片
* gzip_types text/plain application/x-javascript application/javascript text/javascript text/css application/xml application/x-httpd-php image/jpeg image/gif image/png application/vnd.ms-fontobject font/x-woff font/ttf;
* gzip_vary on; # 是否在http header中添加Vary: Accept-Encoding,一般情况下建议开启
* https://blog.youkuaiyun.com/niuniu2878499107/article/details/141706191
* */
...whenProd(() => [new CompressionWebpackPlugin({ //压缩插件
algorithm: 'gzip', //压缩方式
test: /\.js$|\.css$/, //匹配文件名
threshold: 50240, //阈值 代表大于设置值生成压缩文件
filename: '[path].gz[query]',//使得多个.gz文件合并成一个文件,这种方式压缩后的文件少,建议使用
/**
* 是否删除原有静态资源文件,即只保留压缩后的.gz文件,
* 建议这个置为false,还保留源文件。以防gzip无法访问
*/
deleteOriginalAssets: false
})], []),
/**
* 动态链接库
* 内网打包,将一些库例如axios,echarts等。默认到vendor目录 defer引入
* 这个要考虑下,是否要这样做,会不会影响加载,慎用
* 因为:有时我们只使用第三方库中的一小部分功能,用script标签全量引入不太合理
* */
...whenProd(() => [new HtmlWebpackExternalsPlugin({
externals: [
{
module: 'axios',
entry: {
path: 'axios.min.js',
},
global: 'axios',
},
{
module: 'echarts',
entry: {
path: 'echarts.min.js',
attributes: {
async: '',
},
},
global: 'echarts',
}
]
})], []),
...whenProd(() => [
// 使用 terser 来压缩 JavaScript。
// - https://webpack.docschina.org/plugins/terser-webpack-plugin/
// 本来应该写在minimizer中的但是在craco中不生效
new TerserPlugin({
parallel: true, // 并行运行以提高构建速度
terserOptions: {
compress: {
drop_debugger: true, // 删除 debugger 认默认值:true
drop_console: ['log', 'info'], // 删除 console 默认值:false
},
},
extractComments: false, // 移除注释
})
], []),
// ModuleFederationPlugin 用到再说
// - https://blog.youkuaiyun.com/m0_37890289/article/details/144411901 这篇做了简单分享,可以参考
],
remove: [] // 删除插件,慎用
},
const webpack = require('webpack');
const path = require('path');
module.exports = {
context: __dirname,
entry: {
// 这里添加所有需要的库
vender: ['react', 'react-dom', 'axios'],
},
mode: 'production',
output: {
path: path.join(__dirname, 'dll'),
filename: '[name].[contenthash:8].js',
library: '[name]',
},
plugins: [
new webpack.DllPlugin({
// 输出 manifest.json 文件,业务的打包需要这个索引文件
path: path.join(__dirname, 'vendor', '[name]-manifest.json'),
name: '[name]',
}),
],
};
//DllReferencePlugin 引入,如上一篇代码
总结:
const { whenDev, whenProd, when } = require(‘@craco/craco’); | 环境判断 |
---|---|
const { merge } = require(‘webpack-merge’); | 合并配置 |
const WebpackBar = require(‘webpackbar’); | 进度条 |
const CircularDependencyPlugin = require(‘circular-dependency-plugin’); | 监测循环依赖 :a -> b b -> a |
const { BundleAnalyzerPlugin } = require(‘webpack-bundle-analyzer’); | 性能分析:可提供资源体积分析图 |
const CompressionWebpackPlugin = require(‘compression-webpack-plugin’) | 压缩:对于一些较大的js,可进行压缩成gzip等格式,需nginx做压缩包识别配置 |
const HtmlWebpackExternalsPlugin = require(‘html-webpack-externals-plugin’) | 动态链接库:将一些常用的包如axios 等提取成为单独js 通过script引入。注意:这是全量引入,慎用 |
const TerserPlugin = require(‘terser-webpack-plugin’); | 代码简化: 清除生产的debugger console等。配和optimization.minimize使用。 |
DllReferencePlugin | DllPlugin | DllPlugin 打包第三方库 DllReferencePlugin引入使用。慎用,这样会导致项目难以理解 |
ModuleFederationPlugin | 见下一节 |
也可以自定义插件:
参考文章:https://zhuanlan.zhihu.com/p/375802216
3.4 ModuleFederationPlugin模块联邦示范
gitee地址:xxx
让我们先来看看如何在多个应用直接实现模块共享,原来是?
1、发布npm组件
容易陷入: <font style="color:rgb(255, 80, 44);background-color:rgb(255, 245, 245);">npm 发布 ——> app 升级 npm 包 -> app 上线</font>
这样的轮回之中。所以iframe是另一种方案。
2、iframe
将 chat应用 做一个 <font style="color:rgb(255, 80, 44);background-color:rgb(255, 245, 245);">iframe</font>
嵌入到各个应用中。这样支需要升级chat一个应用,其他应用不用改动。
但也有缺点:
* 使用 iframe 每次打开组件,DOM 树都会重建,所以打开速度较慢
*通信方面: iframe 跨应用通信使用 <font style="color:rgb(255, 80, 44);background-color:rgb(255, 245, 245);">window.postMessage</font>
的方式,若应用部署在不同的域名下,使用 <font style="color:rgb(255, 80, 44);background-color:rgb(255, 245, 245);">postMessage</font>
需要控制好 <font style="color:rgb(255, 80, 44);background-color:rgb(255, 245, 245);">origin</font>
和 <font style="color:rgb(255, 80, 44);background-color:rgb(255, 245, 245);">source</font>
属性验证发件人的身份,不然可能会存在跨站点脚本漏洞。非常危险和麻烦!切换iframe,资源都会重新加载。
3、ModuleFederationPlugin模块联邦
而 MF 很好地解决了多应用模块复用的问题,相比上面的这 2 中解决方案,它的解决方式更加优雅和灵活。
但也有缺点:
- CSS 样式污染问题,建议避免在 component 中使用全局样式。
- 模块联邦并未提供沙箱能力,可能会导致 JS 变量污染
- 在 vite 中, React 项目还无法将 webpack 打包的模块公用模块
4、微前端qiankun
解决了css样式污染问题,但无法保证依赖的第三方库的全局样式可以做到应用之间的隔离。所以还是轻易不要写全局样式的好!
这里重点讨论模块联邦,乾坤暂时不深入研究。
3.5 代码optimization分割
webpack提供了optimization ,可自定义代码打包逻辑。配合HtmlWebpackPlugin使用非常nice!
optimization: {
//告知 webpack 使用
//TerserPlugin CssMinimizerPlugin(告知开发环境也启用)
//或其它在 optimization.minimizer定义的插件压缩 bundle。
minimize: true,
minimizer: [// 在这里写 TerserPlugin 插件不生效,在plugins中写生效
],
/**
* 代码分割
* https://webpack.docschina.org/plugins/split-chunks-plugin/
*/
splitChunks: {
//这表明将选择哪些 chunk 进行优化。当提供一个字符串,有效值为 all,async 和 initial。
//设置为 all 可能特别强大,
//因为这意味着 chunk 可以在异步和非异步 chunk 之间共享。
chunks: 'async', // 设置为all 本地项目无法启动
name: false,
...when(
!isDev,
() => ({
chunks: 'all',
minSize: 30000, // 生成 chunk 的最小体积(约30KB)
maxSize: 244000, // chunk 的最大体积(约244KB)
cacheGroups: {
antd: {
name: 'chunk-antd',
test: /[\\/]node_modules[\\/](antd|@ant-design)[\\/]/,
priority: 0,
chunks: 'all',
reuseExistingChunk: true,
},
vendor: {
test: /[\\/]node_modules[\\/](react|@hot-loader\/react-dom)[\\/]/,
priority: -10,
chunks: 'all',
reuseExistingChunk: true,
},
// 常用第三方库单独打包
library: {
test: /[\\/]node_modules[\\/](@xmly|moment|html2canvas)[\\/]/, // 常用第三方库单独打包 可以在此继续添加
name: 'library',
chunks: 'all',
priority: -10,
reuseExistingChunk: true,
},
common: {
name: 'common',
minChunks: 2, // 至少被引用 2 次才会被分割
priority: -20,
chunks: 'all',
reuseExistingChunk: true,
},
},
}),
{}
),
},
/**
* 运行时 chunk
* https://webpack.docschina.org/plugins/split-chunks-plugin/#runtime-chunk
*/
// runtimeChunk: {
// name: entrypoint => `runtime-${entrypoint.name}`
// }
},
3.6 图片文件处理
用loader 处理图片文件
1、MiniCssExtractPlugin 将CSS 提取到单独的文件中,支持按需加载
{
test: /\.css$/i,
/**
* MiniCssExtractPlugin
* 本插件会将 CSS 提取到单独的文件中,
* 为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
* 本插件基于 webpack v5 的新特性构建,并且需要 webpack 5 才能正常工作。
*/
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
2、将小文件图片,转换成base64
{
test: /\.(svg|jpg|jpeg|png|gif)$/i,
// 使用 asset 模块处理,Webpack 5 的 asset 模块来处理文件。
// 这是 Webpack 5 新增的功能,替代了之前的 url-loader 和 file-loader 和 raw-loader
// https://webpack.docschina.org/guides/asset-modules#inlining-assets
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 20 * 1024, // 20kb以下转为base64
},
}
},
3、处理svg可以像组件一样导入
module: {
rules: [
{
{
test: /\.svg$/, // 匹配所有 .svg 文件
use: ["@svgr/webpack"], // 使用 @svgr/webpack 加载器处理 SVG
}
},
],
},
使用;
import { ReactComponent as Report } from '@/assets/images/report.svg';
//自定义svg 颜色
<Dashboard style={{
fill: selectedKeys[0] === '/dashboard' ? '#D00D1B' : '#353D49'
}} />
4、处理图片,还可以将大文件转换成webp(注:一般大图片会走CDN,这里只是备选方案!)
{
test: /\.(png|jpe?g|gif|svg)$/i,
use: [
{
loader: 'file-loader', // 或 url-loader
options: {
name: '[name].[ext]',
outputPath: 'images/'
}
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
},
webp: {
quality: 75
}
}
}
]
}
3.7 总结
以上列举的常用的 配置项等,后续有最新的、实用的配置,我再补充!
webpack优化一般从打包体积与加载和构建速度两个方向着手,我着力于打包体积优化,关于构建速度方面,另一个打包构建vite对其有很好的支持,因为支持了esm!同时优化打包产物,能更好的体现产出!当然项目太大,也可以将项目用模块联邦拆分!
加载优化即常用的chunk lazy懒加载将项目模块包裹即可!
关于引入页面的script标签,对于稍后可以加载的script,可以采用defer=“defer” 异步加载策略!如下
<script defer="defer" src="/vendor/axios/dist/axios.min.js"></script>
关于vite,后续一定会再补充!
三、代码提交规范
ESLint、Pretttier、Husky、CommitLint
总体如下:
- ESLint + Prettier 配置
- 代码提交前的自动检查 (使用 lint-staged)
- Git Commit 规范 (使用 commitlint + husky)
相关依赖
总的依赖基本上都齐了,可以一次性安装,也可以根据代码以下代码自行安装,注意node版本号问题。(新版本有可能不兼容老版本node,导致出错)
ESLint 相关
:::info
yarn add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-import eslint-plugin-jsx-a11y prettier eslint-config-prettier eslint-plugin-prettier
:::
Commit 规范相关
:::info
yarn add -D @commitlint/cli @commitlint/config-conventional husky lint-staged
:::
背景
团队开发的成员多了之后,项目由多人维护,为了保证代码书写风格相同,有一个规范及其重要。当然你架不住一些个性生僻的代码带来的隐患,这里暂时先讨论提交规范。
以React为例,在项目中配置 eslint + pretttier + husky + commitLint 代码提交规范,是我的标准化选择。
加入你通过npx或这其他,创建了一个create-react-appp项目
1、Eslint
安装ESlint
yarn add eslint -D
生成配置文件 npx eslint --init 或这自己在根目录新建.eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint:recommended', // ESLint 推荐规则
// 'plugin:@typescript-eslint/recommended', // TypeScript 推荐规则
'plugin:react/recommended',// React 推荐规则
// 'plugin:react-hooks/recommended',// React Hooks 推荐规则
// 'plugin:prettier/recommended',// Prettier 格式化规则
],
parser: '@typescript-eslint/parser',
ignorePatterns: ['public/*', 'node_modules/*', 'dist/*', 'build/*'],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['@typescript-eslint', 'react'],
settings: {
react: {
version: 'detect',
},
},
rules: {
},
};
在 <font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">packages.json</font>
中的 <font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">script</font>
配置命令
"scripts": {
"lint": "eslint --fix \"./src/**/*.{js,jsx,ts,tsx}\""
}
Ok,现在你就可以测试了! 运行yarn run lint 试试吧~
我项目中用到craco管理webpack,进一步可以将eslint转到:
module.exports = {
/**
* 是否启用eslint 默认ture
*/
enable: true,
/**
* extends ☞ 提供的配置将扩展 CRA 设置 --- 默认
* file ☞ 告诉 craco 使用配置文件
*/
mode: 'file' /* (default value) */,
/**
* 任何 ESLint 配置选项:
* https: //eslint.org/docs/latest/user-guide/configuring/
*/
configure: {},
configure: (eslintConfig, { env, paths }) => {
/* ... */
return eslintConfig;
},
/**
* 任何 ESLint 插件配置选项:
* https ://github.com/webpack-contrib/eslint-webpack-plugin#options
*/
pluginOptions: { /* ... */ },
pluginOptions: (eslintPluginOptions, { env, paths }) => {
/* ... */
return eslintPluginOptions;
},
}
可以设置忽略文件.eslintignore
dist/*
node_modules/*
*.json
public
或者 在ignorePatterns中配置,见上!
2、prettier格式化配置文件 · Prettier 中文网
安装
yarn add prettier -D
没有装插件的,安装prettier-code formatter 插件
配置.prettierrc.js文件,配置好后,重启vscode生效。
module.exports = {
// 一行的字符数,如果超过会进行换行,默认为80
printWidth: 100,
// 行位是否使用分号,默认为true
semi: true,
// 字符串是否使用单引号,默认为false,使用双引号
singleQuote: true,
// 一个tab代表几个空格数,默认为2
tabWidth: 2,
// 是否使用尾逗号,有三个可选值"<none|es5|all>"
trailingComma: "none",
};
在 <font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">packages.json</font>
中的 <font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">script</font>
配置命令
"scripts": {
"format": "prettier --write \"src/**/*.+(js|ts|jsx|tsx)\"",
}
运行此命令,会讲我们项目中的文件都格式化一遍,也可以添加其他文件
当然也可以设置 <font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">.prettierignore</font>
忽略文件。但我推荐!
node_modules/**
dist/**
public/**
doc/**
3、解决ESlint 和Prettier冲突
eslint主要用于发现代码中的问题,潜在错误。
prettier主要用于自动格式化代码,保证代码风格的一致性。
- 冲突通常发生在两个工具对某些代码风格规则有不同的处理方式。例如,ESLint 可能要求使用单引号,而 Prettier 默认使用双引号。
- 当这两个工具同时运行时,它们可能会对代码的同一部分提出不同的修改建议,从而造成冲突。
如何解决呢?答:安装依赖包
yarn add -D eslint-config-prettier eslint-plugin-prettier
eslint-config-prettier 基于 prettier 代码风格的 eslint 规则,即eslint使用pretter规则来格式化代码。
eslint-plugin-prettier 禁用所有与格式相关的 eslint 规则,解决 prettier 与 eslint 规则冲突,确保将其放在 extends 队列最后
之后在.eslintrc.js中
extends: [
'eslint:recommended', // ESLint 推荐规则
...
'plugin:prettier/recommended',// Prettier 格式化规则
],
现在,当您运行 ESLint 时,它会同时检查代码质量和代码格式。如果代码不符合 Prettier 的格式要求,ESLint 会报告一个错误,提示您使用 Prettier 来格式化代码。
4、vscode自动保存格式化
新政./.vscode/settings.json 自动完成格式化
{
// 保存的时候自动格式化
"editor.formatOnSave": true,
// 默认格式化工具选择prettier
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
配置完成后,command + s 保存代码试试吧~
5、配置husky(提交校验)Husky
5.1 安装初始化husky
yarn add husky -D
注意:husky安装8版本的保险一点,太高版本和其他插件兼容性问题。
npx husky-init
然后项目中就会出现:
5.2 安装配置lint-staged
yarn add -D lint-staged
用来检查git add 到暂存区的代码
配置lint-staged。 可在package.json中新建
"lint-staged": {
"src/**/*.{js,jsx,ts,tsx}": "yarn run lint",
"src/**/*.{js,jsx,tsx,ts,less,md,json}": [
"prettier --write",
"eslint --fix"
]
},
或者新建根文件.lintstagedrc
{
"**/*.{js,jsx,json,css,scss,less,html,md}": ["prettier --write"],
"src/**/*.{ts,tsx}": ["prettier --parser=typescript --write"],
"src/**/*.{ts,tsx,js,jsx}": ["eslint --fix --max-warnings 0"]
}
5.3 开始配置 husky
修改<font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">.husky/pre-commit</font>
文件,使commit提交时能执行<font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">lint-staged</font>
钩子
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# 无需运行测试
# npm test
# 格式化代码 校验暂存区代码
npx lint-staged --allow-empty
6、配置commit-msg
6.1 安装
yarn add -D @commitlint/config-conventional @commitlint/cli
6.2 添加根文件 commitlint.config.js 配置文件
module.exports = {
extends: ["@commitlint/config-conventional"],
rules: {
// type 类型定义
"type-enum": [
2,
"always",
[
"feat", // 新功能 feature
"fix", // 修复 bug
"docs", // 文档注释
"style", // 代码格式(不影响代码运行的变动)
"refactor", // 重构(既不增加新功能,也不是修复bug)
"perf", // 性能优化
"test", // 增加测试
"chore", // 构建过程或辅助工具的变动
"revert", // 回退
"build", // 打包
],
],
// subject 大小写不做校验
// 自动部署的BUILD ROBOT的commit信息大写,以作区别
"subject-case": [0],
},
};
@commitlint/config-conventional 是一个规范标准,标识采用什么规范来执行消息校验,默认是Angular的提交规范。
6.3 在husky中添加commitlint钩子
首先在package.jso中添加
"scripts": {
"commitlint": "commitlint --config commitlint.config.js -e -V"
},
然后执行
npx <font style="color:rgb(0, 0, 0);background-color:rgb(250, 250, 250);">husky add .husky/commit-msg "npm run commitlint"</font>
这样.husky中就多了 commit-msg 文件了
现在快来试试提交你的代码吧~
7、自定义提交规则
例如:每个提交关联到 对应的产品文档(个人觉得不是太好,影响开发效率,产品文档应该每个迭代周期就独一份,但提交是很多的,包括代码、bug等等一些)
凑个数,来吧!
为了关联产品文档,配置如下:主要在呢宫颈癌plugin 和三个rule规则~
module.exports = {
extends: ["@commitlint/config-conventional"],
rules: {
...
"subject-case": [0],
...
//新增rule
"must-add-document-url": [2, "always"], // 加入自定义规则
'body-max-line-length': [2, 'always', 200], // 加入自定义规则
'header-max-length': [2, 'always', 200], // 加入自定义规则
},
plugins: [
{
rules: {
"must-add-document-url": ({ type, body }) => {
const ALIYUN_DOCUMENT_PREFIX = "https://devops.aliyun.com";
// 排除的类型
const excludeTypes = ["chore", "refactor", "style", "test"];
if (excludeTypes.includes(type)) {
return [true];
}
//这里校验 true 提交规则, 当然也可以自定义提交规则,举一反三!
return [
body && body.includes(ALIYUN_DOCUMENT_PREFIX),
`提交的内容中必须包含云效相关文档地址`,
];
},
},
},
],
};
然后可以测试下:
git commit -m “feat: 测试自定义提交规则”,然后就提示需要云效的文档链接
正确提交
feat: 测试自定义提交规则 | https://devops.aliyun.com/xxxxxx
到此完毕!
参考文章!
https://juejin.cn/post/7353208973880344626#heading-6
https://blog.youkuaiyun.com/u012570307/article/details/135119353
https://cloud.tencent.com/developer/article/1840688
四、关于样式
1、重置CSS文件
解决兼容性问题
yarn add normalize.css
import 'normalize.css/normalize.css'
特定css样式清除
这样即可!
2、sass模块化
安装使用sass
采用sass模块化 Sass: Documentation
安装:yarn add sass sass-loader -D
之后就可以使用啦!亲测,webpack5 无需额外配置sass-loader加载器,过多配置会报错!
一般我将sass公共文件写在src/assets/styles下面!
语法如下:
https://juejin.cn/post/7207410405855985725?searchId=20241119180950DB0C22909D0DC4EA36FE#heading-3
支持多种配置。常用scss组合如下:
/**
* 超出文本省略(不传参数时为单行文本省略)
*/
@mixin text-ellipsis($lineNumber: null) {
overflow: hidden;
@if $lineNumber {
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: $lineNumber;
-webkit-box-orient: vertical;
} @else {
text-overflow: ellipsis;
white-space: nowrap;
}
}
/**
* 媒体查询
*/
@mixin media-query($breakpoint) {
@if $breakpoint == 'small' {
@media (max-width: 576px) {
@content;
}
} @else if $breakpoint == 'medium' {
@media (max-width: 768px) {
@content;
}
} @else if $breakpoint == 'large' {
@media (max-width: 992px) {
@content;
}
} @else if $breakpoint == 'xlarge' {
@media (max-width: 1200px) {
@content;
}
}
}
//简单写了下,后续有用到的再补充...
使用方式:
@include xxxx()
为什么不用antd-style?
注:antd-style库 是Antd5首先推荐的模块化方案,但无法进行样式嵌套,所以放弃此方案!如果要用到全局token,可以单独写这个!