首页>国内 > 正文

环球今热点:从0到1使用Webpack5 + React + TS构建标准化应用

2022-09-02 09:51:43来源:阿里开发者

作者 | 刘皇逊(恪语

前言

本篇文章主要讲解如何从一个空目录开始,建立起一个基于webpack + react + typescript的标准化前端应用。


(资料图片)

技术栈: webpack5 + React18 + TS工程化: eslint + prettier + husky + git hooks支持图片、less、sass、fonts、数据资源(JSON、csv、tsv等)、Antd按需加载以及主题支持热更新、资源压缩、代码分离(动态导入、懒加载等)、缓存、devServer背景

在项目开发中,我们可以使用create-react-app或者飞冰等脚手架工具,那么,为什么我们要自己来搭建一个标准化项目?

原因当我们使用优秀的脚手架工具开发项目时,当然会提升很多便利,他们的功能更全面、性能更强大,但是在这些值得学习的榜样面前,我们需要从零开始,动手去实现每一个细节和功能,看的再多都不如自己动手实现一个demo更有效果。并且动手实践也可以帮助我们理解项目打包和编译的原理,进而提升自己的技术熟练度,扩展我们的知识面。Webpack 实现工程化方方面面的功能,自然不是 all in one code实现的。从 Webpack 的设计理念和实现原理中,我们能接触到工程化方面的知识:架构扩展、插件化、缓存机制。学习Webpack也代表着学习前端的发展趋势:例如在webpack的竟对Vite上,我们可以学到bundleless的理念,跳过了传统的打包这个概念,并且其他先进理念都是我们需要去学习的地方。开发中,我们发现使用def、aone等生成一个成熟的前端项目模版,不难会发现,项目中的babel、weback、prettier、loader等配置文件缺失,而且难以修改现成的脚手架配置,可扩展能力较弱。导致在性能优化方面能做的工作有限,使得开发受到限制。项目结构目录
├── 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 democd demogit initnpm init
React和Babel引入

对于一个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
@babel/core: babel转码的核心引擎babel-preset-env: 添加对ES5、ES6的支持babel-preset-react: 添加对JSX的支持@babel/plugin-proposal-class-properties: 对React中class的支持Webpack引入
tnpm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin
webpack: weback插件的核心依赖webpack-cli: 为插件提供命令行工具webpack-dev-server: 帮助启动live serverhtml-webpack-plugin: 帮助创建HTML模版Babel配置

.babelrc中添加基本配置:

{  "presets": ["@babel/react", "@babel/env"],  "plugins": ["@babel/plugin-proposal-class-properties"]}
Babel Plugin

Babel是代码转换器,借助Babel,我们可以使用最流行的js写法,而plugin就是实现Babel功能的核心。

这里的配置是为了支持react中class的写法。

Babel Preset

Babel的Plugin一般拆成尽可能小的粒度,开发者可以按需引进,例如ES6到ES5的功能,官方提供了20+插件,这样可以提高性能和扩展性,但是很多时候逐个引入就很让人头大,而Babel Preset就是为此而生,可以视为Presets是相关Plugins的集合。

@babel/react: 支持了React所有的转码需求@babel/env: 不夸张滴讲,仅需要它自己内部的配置项,就可以完成现代JS工程几乎所有的转码需求Webpack基本配置

新建一个webpack.config.js文件。

//webpack.config.jsconst 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")       })    ]}
entry: 入口,开始打包的起点output: 打包文件的地址devServer: live server配置test: 使用loader的文件类型loader: 将要使用的loaderPackage.json基本配置
"start": "webpack serve --mode development --open --hot","build": "webpack --mode production"
mode: process.env.NODE_ENV --> development, 为modules和chunks启用有意义的名称open: 告诉server在服务启动后打开默认浏览器hot: 开启热更新写一个React Demo

目前的项目结构如下图所示:

js和html文件如下图所示:

/_index.js_import React from "react";import ReactDOM from "react-dom";const App = () => {  return (    

Hello!!

Welcome to your First React App..!

);};ReactDOM.render(, document.getElementById("root"));
//_index.html_          React Web        
<script src = "bundle.js"></script>

最后,只要start一下,项目就会启动在8080端口。

TypeScript配置
tnpm install -D typescript ts-loader @types/node @types/react @types/react-dom
typescript: TypeScript的主要引擎ts-loader: 转义.ts --> .js 并打包@types/node @types/react @types/react-dom: 对node、react、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的支持。

添加ts-loader:
//_webpack.config.js_...{  test: /\.tsx?$/,  exclude: /node_modules/,  loader: "ts-loader"}...
设置resolve属性,来指定文件如何被解析:
//_webpack.config.js_...resolve: {   extensions: [ ".tsx", ".ts", ".js" ],}...
rename入口:
//_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呢?

ts-loader 在内部是调用了 TypeScript 的官方编译器 -- tsc。所以,ts-loader 和 tsc 是共享 tsconfig.json,所以会提供完整的报错信息,ts-loader也与 vscode 提供的语法校验表现一致而@babel/preset-typescript有的时候会无法提供完整的报错信息和类型提示管理资源

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效的模块中。loader中,test属性可以识别出哪些文件会被转换;use属性可以定义出转换时,应该是用哪个loader。

CSS、Less、Sass

安装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,            },          },        ],      }, ...
图片、JSON资源

对于图片和字体,我们可以使用内置的Assets Modules来轻松地把这些内容加到我们的系统中,对于类型,我们可以选择:

asset/resource 发送一个单独的文件并导出 URL。asset/inline 导出一个资源的 data URI。asset/source 导出资源的源代码。asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。
//_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做一次拆分:分成

webpack.prod.js 生产环境打包配置webpack.dev.js 开发环境打包配置

里面的区别主要在于打包后的文件名称、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-shakingimport { add } from "./math"console.log(add(5, 6))
// 导入但没有赋值给 JavaScript 对象,也没有在代码里用到// 这会被当做“死”代码,会被 tree-shakingimport { 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.tsxconsole.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,      },  },...

minSize:形成一个新代码块最小的体积

cacheGroups:这里开始设置缓存的 chunks

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["0"]) => {  const isMounted = useMounted();    if (isMounted) {    return ;  }  return <>{props.fallback};} ...return (  }>      );
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.jsHash: 36308e3786425ccd2e9dVersion: webpack 4.41.0Time: 2482msBuilt at: 12/20/2019 4:52:43 PM     Asset       Size  Chunks             Chunk Names    app.js  932 bytes       0  [emitted]  mainindex.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.jsHash: e5a133a9510259e1f027Version: webpack 4.41.0Time: 726msBuilt at: 12/20/2019 4:54:20 PMAsset       Size  Chunks             Chunk Namesapp.js  932 bytes       0  [emitted]  mainindex.html  338 bytes          [emitted]Entrypoint main = app.js[0] ./src/index.ts 14 bytes {0} [built]Child html-webpack-plugin for "index.html":1 assetEntrypoint 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统一编辑器规范

在根目录新建.editorconfig即可,注意不要与已有的lint规则冲突:

// __.editorconfig__# http://editorconfig.orgroot = 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 = tabindent_size = 4
Antd配置

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配置

ESlint主要功能包含代码格式和代码质量的校验,并且可以配置pre-commit来规范代码的提交。

tnpm install -D eslint eslint-webpack-plugin @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react
eslint: eslint主要引擎eslint-webpack-plugin: webpack loader@typescript-eslint/parser: 帮助ESlint lint ts代码@typescript-eslint/eslint-plugin: 包含TS扩展规则的插件eslint-plugin-react: 包含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    },  },};
Prettier配置

虽然 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
pretty-quick: 配合git-hooks进行代码检测,并且fixhusky: 可以通过配置的方式来使用git-hooks,避免手动修改

package.json设置;

"pretty": "./node_modules/.bin/pretty-quick --staged"..."husky": {    "hooks": {      "pre-commit": "tnpm run pretty"    }  },
Webpack完整配置

最后贴一下完整的配置,因为Aone发布自动更新版本号,所以不用拆分config文件来根据环境设置缓存,并且配置已经尽可能简化,拆分反而会增加维护成本。

//_webpack.config.js_//webpack.config.jsconst 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 2015-2020   三好网  版权所有