赞
踩
本篇文章主要讲解如何从一个空目录开始,建立起一个基于webpack + react + typescript的标准化前端应用。
在项目开发中,我们可以使用create-react-app或者飞冰等脚手架工具,那么,为什么我们要自己来搭建一个标准化项目?
- ├── dist // 默认的 build 输出目录
- ├── .husky // pre-commit hook
- ├── webpack.config.js // 全局配置文件及webpack配置文件
- ├── test // 测试目录
- └── src // 源码目录
- ├── assets // 公共的文件(如image、css、font等)
- ├── components // 项目组件
- ├── constants // 常量/接口地址等
- ├── routes // 路由
- ├── utils // 工具库
- ├── pages // 页面模块
- ├── Home // Home模块,建议组件统一大写开头
- ├── ...
- ├── App.tsx // react顶层文件
- ├── typing // ts类型文件
- ├── .editorconfig // IDE格式规范
- ├── .eslintignore // eslint忽略
- ├── .eslintrc // eslint配置文件
- ├── .gitignore // git忽略
- ├── .prettierrc // prettierc配置文件
- ├── .babelrc // babel配置文件
- ├── LICENSE.md // LICENSE
- ├── package.json // package
- ├── README.md // README
- ├── tsconfig.json // typescript配置文件
- "dependencies": {
- "antd": "^4.22.4", // 懂得都懂
- "react": "^18.2.0", // 懂得都懂
- "react-dom": "^18.2.0" // 懂得都懂
- },
- "devDependencies": {
- // babel全家桶
- "@babel/core": "^7.18.10",
- "@babel/plugin-proposal-class-properties": "^7.18.6", // React class支持
- "@babel/plugin-transform-runtime": "^7.18.10", // 抽离提取 Babel的注入代码,防止重复加载,减小体积
- "@babel/preset-env": "^7.18.10", // 提供的预设,允许我们使用最新的JavaScript
- "@babel/preset-react": "^7.18.6", // react支持
-
- // ts类型检查
- "@types/node": "^18.6.4",
- "@types/react": "^18.0.15",
- "@types/react-dom": "^18.0.6",
- // @types 开头的是对应包的 TypeScript 类型声明
- "@typescript-eslint/eslint-plugin": "^5.33.0",
- "@typescript-eslint/parser": "^5.33.0",
-
- // webpack loader:解析对应文件
- "csv-loader": "^3.0.5",
- "sass-loader": "^13.0.2",
- "xml-loader": "^1.2.1",
- "ts-loader": "^9.3.1",
- "less-loader": "^11.0.0",
-
- // eslint全家桶
- "eslint": "^8.21.0",
- "eslint-config-ali": "^14.0.1", // ali前端规约
- "eslint-config-prettier": "^8.5.0", // 关闭所有不必要或可能与[Prettier]冲突的规则
- "eslint-import-resolver-typescript": "^3.4.0", // 添加 ts 语法支持 eslint-plugin-import
- "eslint-plugin-import": "^2.26.0", // ES6+ import/export 语法支持
- "eslint-plugin-prettier": "^4.2.1", // prettier语法支持
- "eslint-plugin-react": "^7.30.1", // react语法支持
- "eslint-plugin-react-hooks": "^4.6.0", // hooks语法支持
- "eslint-webpack-plugin": "^3.2.0",
-
- // webpack plugin
- "fork-ts-checker-webpack-plugin": "^7.2.13", // 避免webpack中检测ts类型
- "html-webpack-plugin": "^5.5.0", // 简化HTML文件的创建 ,配合webpack包含hash的bundle使用
- "mini-css-extract-plugin": "^2.6.1", // css拆分
- "optimize-css-assets-webpack-plugin": "^6.0.1", // css压缩
- "terser-webpack-plugin": "^5.3.3", // 使用 terser 压缩 js (terser 是一个管理和压缩 ES6+ 的工具)
- "webpack-bundle-analyzer": "^4.5.0", // webpack打包体积可视化分析
- "webpack-cli": "^4.10.0", // 提供脚手架命令
- "webpack": "^5.74.0", // webpack引擎
- "webpack-dev-server": "^4.9.3", // 开发环境的live server
-
- // 工具
- "husky": "^8.0.1", // 自动配置 Git hooks 钩子
- "less": "^4.1.3", // css类型
- "sass": "^1.54.3", // css类型
- "typescript": "^4.7.4", // ts
- "lint-staged": "^13.0.3", // 对暂存的git文件运行linter
-
- // prettier 格式化
- "prettier": "^2.7.1",
- "pretty-quick": "^3.1.3", // 在更改的文件上运行 prettier
- }
首先从一个空目录开始,对项目初始化:
- mkdir demo
- cd demo
- git init
- npm init
对于一个React项目,我们首先要安装React,写一个Hello World!
安装我们主要的项目依赖:
tnpm i -S react react-dom
由于我们的浏览器不支持最新的ECMAScript语法,所以我们需要Babel来转义为ES5或者ES6。
安装我们的Babel来提高兼容性:
tnpm i -D @babel/core babel-preset-env babel-preset-react @babel/plugin-proposal-class-properties
tnpm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin
.babelrc中添加基本配置:
- {
- "presets": ["@babel/react", "@babel/env"],
- "plugins": ["@babel/plugin-proposal-class-properties"]
- }
Babel是代码转换器,借助Babel,我们可以使用最流行的js写法,而plugin就是实现Babel功能的核心。
这里的配置是为了支持react中class的写法。
Babel的Plugin一般拆成尽可能小的粒度,开发者可以按需引进,例如ES6到ES5的功能,官方提供了20+插件,这样可以提高性能和扩展性,但是很多时候逐个引入就很让人头大,而Babel Preset就是为此而生,可以视为Presets是相关Plugins的集合。
新建一个webpack.config.js文件。
- //webpack.config.js
- const path = require('path');
- const HtmlWebpackPlugin = require('html-webpack-plugin');
- module.exports = {
- entry: './src/index.js',
- output: {
- path: path.join(__dirname, '/dist'),
- filename: 'bundle.js'
- },
- devServer: {
- port: 8080
- },
- module: {
- rules: [
- {
- test: /\.jsx?$/,
- exclude: /node_modules/,
- loader: 'babel-loader',
- },
- {
- test: /\.css$/,
- use: [ 'style-loader', 'css-loader' ]
- }
- ]
- },
- plugins:[
- new HtmlWebpackPlugin({
- template: path.join(__dirname,'/src/index.html')
- })
- ]
- }
- "start": "webpack serve --mode development --open --hot",
- "build": "webpack --mode production"
目前的项目结构如下图所示:
js和html文件如下图所示:
最后,只要start一下,项目就会启动在8080端口。
tnpm install -D typescript ts-loader @types/node @types/react @types/react-dom
同时在根目录加入tsconfig.json来对ts编译进行配置:
- //_tsconfig.json_
-
- {
- "compilerOptions": {
- "outDir": "./dist/",
- "noImplicitAny": true,
- "module": "es6",
- "target": "es5",
- "jsx": "react",
- "allowJs": true,
- "allowSyntheticDefaultImports": true,
- "moduleResolution": "Node"
- }
- }
最后在webpack中添加对ts的支持。
- //_webpack.config.js_
- ...
- {
- test: /\.tsx?$/,
- exclude: /node_modules/,
- loader: 'ts-loader'
- }
- ...
- //_webpack.config.js_
- ...
- resolve:
- {
- extensions: [ '.tsx', '.ts', '.js' ],
- }
- ...
- //_webpack.config.js_
- ...
- entry: "./src/index.tsx",
- ...
最后启动一下server来看一下ts配置是否正确。
上述我们的配置其实相当于执行了一次:
npx create-react-app my-app --template typescript
在这种流程下很是麻烦,将 *.ts 提供给 TypeScript,然后将运行的结果提供给 Babel,而且还要借助很多loader。
那么我们能不能简化一下这样的流程,因为Babel7中提供的babel-loader就可以完美进行编译ts,答案是可以的,这种方式直接简化了过程。
- module: {
- rules: [
- {
- test: /\.tsx?$/,
- exclude: /node_modules/,
- loader: ['babel-loader']
- }
- ]
- },
并且在.babelrc中也只多了一行@babel/preset-typescript,这种配置更简单,而且打包速度更快一点,逻辑更加清晰。
那么为什么还要在项目中使用ts-loader呢?
webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效的模块中。
loader中,test属性可以识别出哪些文件会被转换;use属性可以定义出转换时,应该是用哪个loader。
安装loader:
tnpm i -D less less-loader style-loader css-loader sass sass-loader
webpack配置:
- //_webpack.config.js_
- ...
- rules: [
- {
- test: /\.jsx?$/,
- exclude: /node_modules/,
- loader: 'babel-loader',
- },
- {
- test: /\.css$/,
- use: ['style-loader', 'css-loader'],
- },
- {
- test: /\.tsx?$/,
- exclude: /node_modules/,
- loader: 'ts-loader',
- },
- {
- test: /\.(less|css)$/,
- exclude: /\.module\.less$/,
- use: [
- {
- loader: 'css-loader',
- options: {
- importLoaders: 2,
- sourceMap: !!DEV,
- },
- },
- {
- loader: 'less-loader',
- options: {
- sourceMap: !!DEV,
- },
- },
- ],
- },
- {
- test: /\.(sass|scss)$/,
- use: [
- {
- loader: 'css-loader',
- options: {
- importLoaders: 2,
- sourceMap: !!DEV,
- },
- },
- {
- loader: 'sass-loader',
- options: {
- sourceMap: !!DEV,
- },
- },
- ],
- },
- ...
对于图片和字体,我们可以使用内置的Assets Modules来轻松地把这些内容加到我们的系统中,对于类型,我们可以选择:
- //_webpack.config.js_
- ...
- module: {
- rules: [{
- test: /\.png/,
- type: 'asset/resource'
- }]
- },
- ...
对于其他类型资源,我们需要安装csv-loader、xml-loader等:
- //_webpack.config.js_
- ...
- {
- test: /\.(csv|tsv)$/i,
- use: ['csv-loader'],
- },
- {
- test: /\.xml$/i,
- use: ['xml-loader'],
- },
- ...
目前,我们的应用已经可以正常运行tsx文件,并且在本地进行调试和开发,那么我们来看看如何设置一个开发环境,来使开发变得更加轻松。
- //_webpack.config.js_
- ...
- const { DEV, DEBUG } = process.env;
- process.env.BABEL_ENV = DEV ? 'development' : 'production';
- process.env.NODE_ENV = DEV ? 'development' : 'production';
- ...
- mode: DEV ? 'development' : 'production',
- devtool: DEV && 'source-map',
- ...
我们可以从process.env中获取环境变量来区分开发环境和生产环境。
当webpack在本地打包代码时,我们可以使用inline-source-map,可以将编译后的代码映射回原始源代码,这样在报错的时候,错误就会被定为到确切的文件和行数。当然,在生产环境中,为了保护隐私,最好把这个设置动态关掉。
在开发环境中,webpack-dev-server会为你提供一个基本的web server,并且具有实时重新加载功能。
我们希望每次打包都把上次的打包文件删除,可以使用CleanWebpackPlugin:
- //_webpack.config.js_
- ...
- const { CleanWebpackPlugin } = require('clean-webpack-plugin');
-
- module.exports = {
- plugins: [
- new CleanWebpackPlugin(),
- ]
- }
- ...
并且,在我们生产环境,我们希望改动后的新版本可以丢弃缓存,并且没有改动的版本可以保留缓存;但是在开发环境,我们不希望有缓存,而是每次都是拿到最新的资源。所以,需要对webpack config做一次拆分:分成
里面的区别主要在于打包后的文件名称、sourceMap等。
contenthash:只有模块的内容改变,才会改变hash值:
- output: {
- filename: 'js/[name].[contenthash:8].js', // contenthash:只有模块的内容改变,才会改变hash值
- },
- output: {
- filename: 'js/[name].[hash:8].js',
- }
可以使用webpack-bundle-analyzer来分析我们打包资源的大小:
- const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
- plugins: [
- DEBUG && new BundleAnalyzerPlugin(),
- ]
同时设置package.json的启动项
OptimizeCSSAssetsPlugin主要用来优化css文件的输出,包括摈弃重复的样式定义、砍掉样式规则中多余的参数、移除不需要的浏览器前缀等。
TerserPlugin主要用来优化js体积,包括重命名变量,甚至是删除整个的访问不到的代码块。
- //_webpack.config.js_
- ...
- const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
- const TerserPlugin = require('terser-webpack-plugin');
- ...
- optimization: {
- minimizer: [
- new TerserPlugin({
- parallel: false,
- terserOptions: {
- output: {
- comments: false,
- },
- },
- }),
- new OptimizeCSSAssetsPlugin({}),
- ],
- minimize: !DEV,
- splitChunks: {
- minSize: 500000,
- cacheGroups: {
- vendors: false,
- },
- },
- },
- ...
资源分离
1)多入口
webpack内置的特性能够把代码分离到不同的bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
- //_webpack.config.js_
- ...
- entry: {
- index: './src/index.js',
- another: './src/another-module.js',
- },
- output: {
- filename: '[name].bundle.js'
- ...
2)Tree Shaking
Webpack5在生产环境已经集成了Tree Shaking功能,不用的代码会被shaking掉:
- // _webpack.config.js_
- module.exports = {
- // ...
- mode: 'production',
- };
但是在开发环境中需要手动配置(Not Recommend):
- // _webpack.config.js_
- module.exports = {
- // ...
- mode: 'development',
- optimization: {
- usedExports: true,
- }
- };
处于好奇,webpack是如何完美的避开没有使用的代码的呢?
很简单:就是 Webpack 没看到你使用的代码。Webpack 跟踪整个应用程序的import/export 语句,因此,如果它看到导入的东西最终没有被使用,它会认为那是未引用代码(或叫做“死代码”—— dead-code ),并会对其进行 tree-shaking 。死代码并不总是那么明确的。下面是一些例子:
- // _test.js_
- // 这会被看作“活”代码,不会做 tree-shaking
- import { add } from './math'
- console.log(add(5, 6))
- // 导入但没有赋值给 JavaScript 对象,也没有在代码里用到
- // 这会被当做“死”代码,会被 tree-shaking
- import { add, minus } from './math'
- console.log('hello webpack')
- // 导入整个库,但是没有赋值给 JavaScript 对象,也没有在代码里用到
- // 非常奇怪,这竟然被当做“活”代码,因为 Webpack 对库的导入和本地代码导入的处理方式不同。
- import { add, minus } from './math' // 死的
- import 'lodash' // 活的
- console.log('hello webpack')
- 所以对于这种三方库我们可以使用下面的Shimming方法。
注意 Webpack 不能百分百安全地进行 tree-shaking。有些模块导入,只要被引入,就会对应用程序产生重要影响。一个很好的例子就是全局样式表,或者设置全局配置的JavaScript 文件。Webpack 认为这样的文件有“副作用”。具有副作用的文件不应该做 tree-shaking,因为这将破坏整个应用。比较好的告诉Webpack你的代码有副作用的方法就是在package.json里面设置sideEffects。
- {
- "name": "your-project",
- "sideEffects": ["./src/some-side-effectful-file.js", "*.css"]
- }
3)Shimming预置依赖
对于上面的lodash库无法被shaking,我们可以使用细粒度shimming预置的方法来优化,首先引入ProvidePlugin插件,把应用程序中的模块依赖,改为一个全局变量依赖,让我们先移除 lodash 的 import语句,改为通过插件提供它,并且提取出join方法来全局使用它:
- // _src/index.tsx
- console.log(join(['hello', 'webpack'], ' '))
- // _webpack.config.js_
- plugins: [
- new webpack.ProvidePlugin({
- //_: 'lodash'
- // 如果没注释的话,需要这样引用console.log(_.join(['hello', 'webpack'], ' '))
- join: ['lodash', 'join'],
- })
- ]
细粒度Shimming
一些遗留的模块依赖的this指向的window对象,我们可以使用import-loaders,它对依赖 window 对象下的全局变量(比如 $ 或 this )的第三方模块非常有用。
CommonJS 上下文中,这将会变成一个问题,也就是说此时的 this指向的是 module.exports。在这种情况下,你可以通过使用 imports-loader覆盖 this 指向:
- // _webpack.config.js_
- module: {
- rules: [
- {
- test: require.resolve('./src/index.js'),
- use: 'imports-loader?wrapper=window',
- },
- ]
- },
4)公共部分提取
防止重复可以使用splitChunk,提取出代码中的公共部分:
- //_webpack.config.js_
- ...
- minimize: !DEV,
- splitChunks: {
- minSize: 500000,
- cacheGroups: {
- vendors: false,
- },
- },
- ...
5)按需分离
在React项目中,代码按需分离可以使用如下方法,webpack 把 import() 作为一个分离点(split-point),并把引入的模块作为一个单独的 chunk。import() 将模块名字作为参数并返回一个 Promoise 对象,即 import(name) -> Promise。
- //_index.tsx_
- ...
- const WdAndDxEntry = lazy(() => import(/* webpackChunkName: "wd-and-dx" */ '../../old-code/component/wd-and-dx/entry'));
- const WdAndDxFallback = () => ()
- const SSRCompatibleSuspense = (props: Parameters< typeof Suspense>['0']) => {
- const isMounted = useMounted();
-
- if (isMounted) {
- return < Suspense {...props} />;
- }
- return < >{props.fallback}< />;
- }
- ...
- return (
- < SSRCompatibleSuspense fallback={< WdAndDxFallback />}>
- < WdAndDxEntry
- className=""
- data={data}
- style={{
- height: 150,
- }}
- />
- < /SSRCompatibleSuspense>
- );
6)分离三方库
配置 dependOn option 选项,这样可以在多个 chunk 之间共享模块:
- //_webpack.config.js_
- ...
- module.exports = {
- entry: {
- index: {
- import: './src/index.js',
- dependOn: 'shared',
- },
- another: {
- import: './src/another-module.js',
- dependOn: 'shared',
- },
- shared: 'lodash',
- }
- }
- ...
CSS分离
该插件MiniCssExtractPlugin将CSS提取到单独的文件中。它为每个包含CSS的JS文件创建一个CSS文件。
- //_webpack.config.js_
- ...
- const MiniCssExtractPlugin = require('mini-css-extract-plugin');
- ...
- {
- test: /\.(sass|scss)$/,
- use: [
- {
- loader: MiniCssExtractPlugin.loader,
- },
- {
- loader: 'css-loader',
- options: {
- importLoaders: 2,
- sourceMap: !!DEV,
- },
- },
- {
- loader: 'sass-loader',
- options: {
- sourceMap: !!DEV,
- },
- },
- ],
- },
- ...
- DEBUG && new BundleAnalyzerPlugin(),
- new MiniCssExtractPlugin({
- filename: '[name].css',
- chunkFilename: '[name].css',
- }),
- ...
当项目体积增大时,编译时间也随之增加。其中时间大头就是ts的类型检测耗时。ts-loader 提供了一个 transpileOnly 选项,它默认为 false,我们可以把它设置为 true,这样项目编译时就不会进行类型检查,也不会输出声明文件。
- //_webpack.config.js_
- ...
- module: {
- rules: [
- {
- test: /\.tsx?$/,
- use: [
- {
- loader: 'ts-loader',
- options: {
- transpileOnly: true
- }
- }
- ]
- }
- ]
- }
- ...
可以看一下开关这个选项后的前后对比:
开启检查前
- $ webpack --mode=production --config ./build/webpack.config.js
- Hash: 36308e3786425ccd2e9d
- Version: webpack 4.41.0
- Time: 2482ms
- Built at: 12/20/2019 4:52:43 PM
- Asset Size Chunks Chunk Names
- app.js 932 bytes 0 [emitted] main
- index.html 338 bytes [emitted]
- Entrypoint main = app.js
- [0] ./src/index.ts 14 bytes {0} [built]
- Child html-webpack-plugin for "index.html":
- 1 asset
- Entrypoint undefined = index.html
- [0] ./node_modules/html-webpack-plugin/lib/loader.js!./index.html 489 bytes {0} [built]
- [2] (webpack)/buildin/global.js 472 bytes {0} [built]
- [3] (webpack)/buildin/module.js 497 bytes {0} [built]
- + 1 hidden module
- ✨ Done in 4.88s.
关闭检查后
- $ webpack --mode=production --config ./build/webpack.config.js
- Hash: e5a133a9510259e1f027
- Version: webpack 4.41.0
- Time: 726ms
- Built at: 12/20/2019 4:54:20 PM
- Asset Size Chunks Chunk Names
- app.js 932 bytes 0 [emitted] main
- index.html 338 bytes [emitted]
- Entrypoint main = app.js
- [0] ./src/index.ts 14 bytes {0} [built]
- Child html-webpack-plugin for "index.html":
- 1 asset
- Entrypoint undefined = index.html
- [0] ./node_modules/html-webpack-plugin/lib/loader.js!./index.html 489 bytes {0} [built]
- [2] (webpack)/buildin/global.js 472 bytes {0} [built]
- [3] (webpack)/buildin/module.js 497 bytes {0} [built]
- + 1 hidden module
- ✨ Done in 2.40s.
From 4.88s --> 2.4s,但是缺少了类型检查。
这里官方推荐了一个解决方案,使用fork-ts-checker-webpack-plugin,它在一个单独的进程上运行类型检查器,此插件使用 TypeScript 而不是 webpack 的模块解析,有了 TypeScript 的模块解析,我们不必等待webpack 编译。可以极大加快编译速度。
- //_webpack.config.js_
- ...
- module: {
- rules: [
- {
- test: /\.tsx?$/,
- use: [
- {
- loader: 'ts-loader',
- options: {
- transpileOnly: true
- }
- }
- ]
- }
- ]
- },
- plugins: [
- new ForkTsCheckerWebpackPlugin()
- ]
- ...
在根目录新建.editorconfig即可,注意不要与已有的lint规则冲突:
- // __.editorconfig__
- # http://editorconfig.org
- root = true
-
- [*]
- indent_style = space
- indent_size = 2
- end_of_line = lf
- charset = utf-8
- trim_trailing_whitespace = true
- insert_final_newline = true
-
- [*.md]
- trim_trailing_whitespace = false
-
- [makefile]
- indent_style = tab
- indent_size = 4
babel中配置按需加载:
- {
- "presets": ["@babel/react", "@babel/env"],
- "plugins": [
- "@babel/plugin-proposal-class-properties",
- [
- "import",
- {
- "libraryName": "antd",
- "libraryDirectory": "es",
- "style": true // or 'css'
- },
- "antd"
- ]
- ]
- }
webpack中定制主题:
- module: {
- rules: [
- // 处理 .css
- {
- test: /\.css$/,
- use: ['style-loader', 'css-loader'],
- },
- // 处理 .less
- {
- test: /\.less$/,
- use: [
- 'style-loader',
- 'css-loader',
- // less-loader
- {
- loader: 'less-loader',
- options: {
- lessOptions: {
- // 替换antd的变量,去掉 @ 符号即可
- // https://ant.design/docs/react/customize-theme-cn
- modifyVars: {
- 'primary-color': '#1DA57A',
- },
- javascriptEnabled: true, // 支持js
- },
- },
- },
- ],
- },
- ]
- }
注意样式必须加载 less 格式,一个常见的问题就是引入了多份样式,less 的样式被 css 的样式覆盖了。
ESlint主要功能包含代码格式和代码质量的校验,并且可以配置pre-commit来规范代码的提交。
tnpm install -D eslint eslint-webpack-plugin @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react
ESlint配置文件
- // _eslintrc_
- module.exports = {
- parser: '@typescript-eslint/parser', // ESlint Parser
- extends: [
- 'plugin:react/recommended', // 从@eslint-plugin-react中选择推荐的规则
- 'plugin:@typescript-eslint/recommended', // 从@typescript-eslint/eslint-plugin选择推荐的规则
- ],
- parserOptions: {
- ecmaVersion: 2018, // 帮助转化最先进的ECMAScript功能
- sourceType: 'module', // 允许imports的用法
- ecmaFeatures: {
- jsx: true, // JSX兼容
- },
- },
- rules: {
- },
- settings: {
- react: {
- version: 'detect', // 告诉eslint-plugin-react自动检测最新版本的react
- },
- },
- };
虽然 ESLint 也可以校验代码格式,但 Prettier 更擅长,所以项目中一般会搭配一起使用。为了避免二者的冲突,一般的解决思路是禁掉 ESLint 中与 Prettier 冲突的规则,然后使用 Prettier 做格式化, ESLint 做代码校验。
prettier配置文件
- {
- "arrowParens": "avoid",
- "bracketSpacing": true,
- "embeddedLanguageFormatting": "auto",
- "htmlWhitespaceSensitivity": "css",
- "insertPragma": false,
- "jsxBracketSameLine": true,
- "jsxSingleQuote": false,
- "printWidth": 100,
- "proseWrap": "preserve",
- "quoteProps": "as-needed",
- "requirePragma": false,
- "semi": true,
- "singleQuote": true,
- "tabWidth": 2,
- "trailingComma": "es5",
- "useTabs": true,
- "vueIndentScriptAndStyle": false
- }
prettier 只是保证了在通过编辑器(vs code)进行格式化代码的时候,格式化成需要的格式(当然可以通过配置 onSave 在代码保存时自动格式化),但是无法保证所有人都会主动进行。
因此进行自动格式化显得非常重要,而自动格式化的是时机选择 pre-commit 最恰当,通过 git hook ,能够在 commit 之前格式化好代码(如果已经 commit,会将暂存转为提交,生成提交记录,需要回滚才会撤销)。
tnpm i -D pretty-quick prettier husky
package.json设置
- "pretty": "./node_modules/.bin/pretty-quick --staged"
- ...
- "husky": {
- "hooks": {
- "pre-commit": "tnpm run pretty"
- }
- },
最后贴一下完整的配置,因为Aone发布自动更新版本号,所以不用拆分config文件来根据环境设置缓存,并且配置已经尽可能简化,拆分反而会增加维护成本。
- //_webpack.config.js_
- //webpack.config.js
- const path = require('path');
- const HtmlWebpackPlugin = require('html-webpack-plugin');
- const MiniCssExtractPlugin = require('mini-css-extract-plugin');
- const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
- const TerserPlugin = require('terser-webpack-plugin');
- const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
- const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
- const ESLintPlugin = require('eslint-webpack-plugin');
-
- const { DEV, DEBUG } = process.env;
-
- process.env.BABEL_ENV = DEV ? 'development' : 'production';
- process.env.NODE_ENV = DEV ? 'development' : 'production';
-
- module.exports = {
- entry: './src/index.tsx',
- output: {
- path: path.join(__dirname, '/dist'),
- filename: 'bundle.js',
- clean: true,
- },
- devServer: {
- port: 8080,
- },
- mode: DEV ? 'development' : 'production',
- devtool: DEV && 'source-map',
- module: {
- rules: [
- {
- test: /\.jsx?$/,
- exclude: /node_modules/,
- loader: 'babel-loader',
- },
- {
- test: /\.tsx?$/,
- exclude: /node_modules/,
- loader: 'ts-loader',
- },
- {
- test: /\.css$/,
- use: ['style-loader', 'css-loader'],
- },
- // 处理 .less
- {
- test: /\.less$/,
- use: [
- 'style-loader',
- 'css-loader',
- // less-loader
- {
- loader: 'less-loader',
- options: {
- lessOptions: {
- // 替换antd的变量,去掉 @ 符号即可
- // https://ant.design/docs/react/customize-theme-cn
- modifyVars: {
- 'primary-color': '#1DA57A',
- 'border-color-base': '#d9d9d9', // 边框色
- 'text-color': '#d9d9d9'
- },
- javascriptEnabled: true, // 支持js
- },
- },
- },
- ],
- },
- {
- test: /\.(sass|scss)$/,
- use: [
- {
- loader: MiniCssExtractPlugin.loader,
- },
- {
- loader: 'css-loader',
- options: {
- importLoaders: 2,
- sourceMap: !!DEV,
- },
- },
- {
- loader: 'sass-loader',
- options: {
- sourceMap: !!DEV,
- },
- },
- ],
- },
- {
- test: /\.png/,
- type: 'asset/resource',
- },
- {
- test: /\.(woff|woff2|eot|ttf|otf)$/i,
- type: 'asset/resource',
- },
- {
- test: /\.(csv|tsv)$/i,
- use: ['csv-loader'],
- },
- {
- test: /\.xml$/i,
- use: ['xml-loader'],
- },
- ],
- },
- optimization: {
- minimizer: [
- new TerserPlugin({
- parallel: false,
- terserOptions: {
- output: {
- comments: false,
- },
- },
- }),
- new OptimizeCSSAssetsPlugin({}),
- ],
- minimize: !DEV,
- splitChunks: {
- minSize: 500000,
- cacheGroups: {
- vendors: false,
- },
- },
- },
- resolve: {
- modules: ['node_modules'],
- extensions: ['.json', '.js', '.jsx', '.ts', '.tsx', '.less', 'scss'],
- },
- plugins: [
- new HtmlWebpackPlugin({
- template: path.join(__dirname, '/src/index.html'),
- filename: 'app.html',
- inject: 'body',
- }),
- DEBUG && new BundleAnalyzerPlugin(),
- new MiniCssExtractPlugin({
- filename: '[name].css',
- chunkFilename: '[name].css',
- }),
- new ESLintPlugin(),
- new ForkTsCheckerWebpackPlugin(),
- ].filter(Boolean),
- };
这篇文章主要记录了开发过程中从项目初始化开始,再到一个标准化前端项目的搭建路程。涉及相关代码规范、开发环境搭建、生产环境优化等,旨在打造出一个可快速使用的现代Webpack5.x+React18.x+Typescript+Antd4.x模板,以供在以后的实际业务场景需求中零成本使用。
推荐阅读
作者 | 刘皇逊(恪语)
本文为阿里云原创内容,未经允许不得转载
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。