前端项目工程化配置指南
Webpack5 + React18 + Ts 开发和打包环境
初始化项目
初始化package.json
初始化目录结构
1 | ├── build |
安装依赖
webpack依赖
1 | npm i webpack webpack-cli -D |
react依赖
1 | npm i react react-dom -S |
react类型依赖
1 | npm i @types/react @types/react-dom -D |
添加网页根html模版
1 |
|
配置tsconfig.json文件
1 | { |
初始化src/App.tsx和src/index.tsx内容
src/index.tsx是入口文件
1 | import React from 'react' |
src/App.tsx是根组件
1 | import React from 'react'; |
配置基础版React+ts环境
webpack公共环境
- 配置入口文件 entry
- 配置出口文件 output
- 配置loader解析ts和jsx,需要安装依赖:
- babel-loader
- @babel/core
- @babel/preset-react jsx解析器
- @babel/preset-typescript ts解析器
1 | npm i babel-loader @babel/core @babel/preset-react @babel/preset-typescript -D |
- 配置文件后缀 resolve
- 将静态资源引入html文件,需要安装依赖html-webpack-plugin
1 | npm i html-webpack-plugin -D |
1 | const path = require('path') |
webpack开发环境配置
配置开发环境需要额外安装2个库:
- webpack-dev-server 开发环境中启动服务器
- webpack-merge 合并webpack.base.js基本配置
1 | npm i webpack-dev-server webpack-merge -D |
修改webpack.dev.js文件:
1 | const path = require('path'); |
在package.json中添加dev脚本:
1 | { |
控制台执行dev启动命令:
1 | npm run dev |
webpack打包环境配置
修改webpack.prod.js配置文件:
1 | const {merge} = require('webpack-merge') |
添加build脚本:
1 | { |
执行打包命令
1 | npm run build |
可以通过serve启动打包好的项目:
1 | npm i serve -g |
基础功能配置
配置环境变量
- 按照模式
- 开发环境
- 生产环境
- 区分方式:process.env.NODE_ENV
- 按照项目业务环境
- 开发环境
- 测试环境
- 预测环境
- 正式环境
设置一个环境变量 process.env.BASE_ENV
设置方式:
- cross-env 兼容各系统设置环境变量的包
- webpack.DefinePlugin
- webpack内置,为业务代码注入环境变量
安装cross-env:
1 | npm i cross-env -D |
修改package.json里的脚本:
1 | { |
在执行脚本时,会将用于判断环境的参数传入webpack的配置文件中,
webpack可以根据参数进行不同的配置:
1 | process.env.NODE_ENV // 模式参数 |
直到这一步,传入的参数只能在webpack配置文件中获取到,
要想在业务代码中用到参数,需要使用webpack.DefinePlugin,
将参数注入到业务代码中去:
1 | const webpack = require('webpack'); |
css/less配置
对css的解析需要用到loader:
- style-loader 把解析后的css代码抽离放到头部的style标签
- css-loader 解析css文件
1 | npm i style-loader css-loader -D |
将loader配置到webpack.base.js中:
1 | module.exports = { |
注意,loader会从右向左引入,这里的顺序不能改变,
因为需要先编译css代码,再将css部分嵌入到style里
需要用到2个包:
- less-loader 把less编译为css
- less 核心
1 | npm i less less-loader -D |
把less-loader加入到之前的css配置部分:
1 | module.exports = { |
处理css3前缀兼容性
在css3还没有普及的时候,
各家浏览器通过“实验属性”的方式对特性进行实现,
并在自家实现的属性前挂上浏览器的标识,
导致为了实现同一个属性,要写好几个版本
postcss就是为了解决这个问题而实现的,
虽然现在大部分的主流浏览器都没有这个问题,
但还是存在需要兼容低版本浏览器的情况存在,
这里就可以使用postcss-loader,
需要引入2个库:
- postcss-loader 处理css的前缀
- autoprefixer 决定需要兼容哪些浏览器
1 | npm i postcss-loader autoprefixer -D |
可以将postcss单独放到根目录里配置:
在根目录中添加postcss.config.js文件
1 | module.exports = { |
再添加autoprefixer配置文件.browserslistrc
1 | IE 9 # 兼容IE 9 |
然后将postcss-loader配置到webpack中
1 | module.exports = { |
babel处理js兼容
babel可以用来转换js的版本,也能将不标准的js转换为标准的js,
需要安装下面的库:
- babel-loader
- 使用babel加载最新js并转换为ES5
- @babel/core
- babel编译的核心包
- @babel/preset-env
- 编译预设,转换目前最新的js标准语法
- core-js
- 使用低版本js语法模拟高版本的库,垫片
1 | npm i babel-loader @babel/core @babel/preset-env core-js -D |
在根目录添加babel.config.js,进行配置:
1 | module.exports = { |
将babel添加到webpack配置中:
1 | module.exports = { |
复制public文件夹
public文件夹下一般放一些静态资源,比如图片、全局css等,
在dev模式下,可以通过托管配置,直接通过绝对路径访问到public文件下的资源:
1 | module.exports = merge(baseConfig,{ |
但在build模式下,需要将public文件内容直接复制到构建出口文件夹中
需要用到copy-webpack-plugin插件:
1 | npm i copy-webpack-plugin -D |
在webpack.prod.js中进行文件源、复制目标、过滤器等配置:
1 | const path = require('path'); |
处理图片文件
使用asset-module进行处理,
在webpack.base.js中进行配置:
1 | module.exports = { |
字体和媒体文件
字体和媒体文件与图片的配置基本相似:
1 | module.exports = { |
react热更新
使用webpack中,devServer.hot开启的热更新方式是直接刷新浏览器,
因此会导致数据状态的丢失,
要想不刷新浏览器,进行模块热更新,可以借助插件:
- @pmmmwh/react-refresh-webpack-plugin
- react-refresh
1 | npm i @pmmmwh/react-refresh-webpack-plugin react-refresh -D |
在webpack.dev.js中配置热更新插件:
1 | const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin') |
需要在babel.config.js中更新插件,
并且需要对环境进行判断,尽在开发环境下开启:
1 | const isDEV = process.env.NODE_ENV === 'development'; |
优化构建速度
构件耗时分析
需要借助到一个插件:
1 | npm i speed-measure-webpack-plugin -D |
构建分析配置文件webpack.analy.js:
1 | const prodConfig = require('./webpack.prod') |
添加package.json脚本并执行:
1 | { |
控制台输出结果:
1 | PS D:\webpack-react-ts> npm run build:analy |
开启持久化存储缓存
webpack4中文件缓存:
- 使用babel-loader缓存js解析结果
- cache-loader缓存css等资源解析结果
- 模块缓存插件hard-source-webpack-plugin
- 二次打包时会对文件做哈希对比验证文件前后是否一致
webpack5对缓存策略进行优化:
- 增加了持久化缓存
- 改进了缓存算法
在webpack.base.js中配置开启缓存策略
1 | module.exports = { |
缓存位置:node_modules/.cache/webpack
开启多线程loader
开启多线程loader可以借助多核cpu开启多线程loader解析,
需要安装依赖:
1 | npm i thread-loader -D |
使用时,需要将thread-loader放置在其他loader之前,
在它之后的loader会在一个独立的worker池中运行
开启多线程需要启动时间,适合规模较大的项目
配置alias别名
配置别名可以降低项目开发中资源引用的复杂度
webpack.base.js配置:
1 | module.exports = { |
tsconfig.json配置:
1 | { |
资源的路径调用:
1 | import logo from '@/assets/imgs/logo.png' |
缩小loader作用范围
可以通过2个属性,来对loader解析的范围进行限制:
- include 只解析改配置的模块
- exclude 不解析的模块
精确使用loader
loader会在webpack构建模块依赖关系引入新文件时执行,
通过test对文件名称进行正则匹配,
如果匹配到了,就从右向左依次执行use中的loader,
可以更精确的根据文件的后缀,区分需要执行的loader,
比如,可以将css和less拆分,
以减少css收到less-loader处理的这一步:
1 | module.exports = { |
缩小模块搜索范围
Node中存在3中模块:
- node核心模块
- node_modules模块
- 自定义文件模块
在项目中如果没有指定准确的路径,引入模块时,查找顺序如下:
- 优先查询node核心模块
- 当前目录下node_modules
- 祖辈目录的node_modules
- node全局模块
如果是追溯到了node全局模块的情况,开发环境和生产环境的node全局可能不一样,
因此要避免这种情况,将查找的范围局限在项目目录下,
在webpack.base.js中配置:
1 | module.exports = { |
devtool配置
开发调试时需要看到源代码,
生产模式下则是编译后的代码,
这就需要SourceMap进行转换,
devtool就是用来控制SourceMap的生成的:
webpack.dev.js
1 | module.exports = merge(baseConfig, { |
优化构建结果文件
webpack包分析工具
需要借助库:
1 | npm install webpack-bundle-analyzer -D |
在webpack.analy.js中加入插件相关的配置:
1 | const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer') |
抽取css样式文件
css文件在不同的环境下有不同的优化策略:
- 开发环境下,使用style标签嵌入方便热更新替换
- 生产环境下,将css文件单独打包方便缓存策略
这里需要用到插件:
1 | npm i mini-css-extract-plugin -D |
在开发模式下使用style-loader,抽取css,
webpack.base.js:
1 | const isDev = process.env.NODE_ENV === 'development' |
打包时,使用MiniCssExtractPlugin进行抽离:
1 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); |
压缩css文件
下载css压缩插件
1 | npm i css-minimizer-webpack-plugin -D |
在webpack.prod.js中进行配置:
1 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') |
压缩js文件
安装terser-webpack-plugin插件:
1 | npm i terser-webpack-plugin -D |
修改webpack.prod.js配置
1 | const TerserPlugin = require('terser-webpack-plugin') |
合理配置打包文件hash
hash是浏览器缓存策略中重要的一部分,
webpack提供三种hash:
- hash
- 全部文件共用相同的hash值
- 项目文件只要修改,项目构建的hash值就会改变
- chunkhash
- 对不同的入口文件进行依赖解析,构建对应chunk,生成相应哈希值
- 文件本身修改或者依赖文件修改,chunkhash会变化
- contenthash
- 每个文件都有自己的哈希值
配置hash值的格式如下:
1 | filename: "[name].[chunkhash:8][ext]" |
- ext 文件后缀
- name 文件名
- path 文件相对路径
- folder 文件夹
- hash 每次构建生成的唯一hash值
- chunkhash 根据chunk生成hash值
- contenthash 根据文件内容生成hash值
- 一般用于图片、css等资源文件的哈希值生成
在webpack.base.js中对生成的文件名称进行hash映射配置:
1 | module.exports = { |
在构建模式中,对被抽离出来css文件也进行hash映射:
1 | module.exports = merge(baseConfig, { |
代码分割第三方包和公共模块
node_modules中的代码变化频率一般比较小,
可以单独打包,对应的chunkhash值很少变化。
同样的,还有一些公共模块,
由于很少变化,也可以单独分割出来。
这需要在webpack.prod.js中进行配置:
1 | module.exports = merge(baseConfig, { |
tree-shaking清理未使用的js/css
webpack内置tree-shaking,在进行prod打包时将没有使用的js清除掉
对于没有用到的css的清理,则需要额外安装库:
- purgecss-webpack-plugin
- glob-all 选择要检测那些文件里面的类型、id还有标签名称
1 | npm i purgecss-webpack-plugin glob-all -D |
在webpack.prod.js中进行配置
1 | const {PurgeCSSPlugin} = require('purgecss-webpack-plugin') |
资源懒加载
react提供懒加载组件,
webpack默认支持资源懒加载,
资源在需要使用的时候进行动态加载
组件的懒加载:
1 | import React, {lazy, Suspense, useState} from 'react'; |
资源预加载
关于link标签的ref属性:
- preload
- 资源必要,浏览器一定会加载
- prefetch
- 资源可能需要,浏览器空闲时加载
webpack中支持在import时,
使用注释进行预获取/预加载配置:
1 | // 单个目标 |
打包时生成gzip文件
nginx可以通过配置gzip:on开启压缩,
但是会消耗服务器的资源,
因此可以在前端构建生产项目时,就对内容进行gzip压缩。
需要用到插件:
1 | npm i compression-webpack-plugin -D |
在webpack.prod.js中添加配置:
1 | const CompressionPlugin = require("compression-webpack-plugin"); |
React + Ts 项目开发代码规范
editorconfig统一编辑器配置
在VSCode编辑器里,添加editorconfig需要安装EditorConfig for VSCode插件
在项目根目录里添加.editorconfig配置文件:
1 | root = true # 控制配置文件 .editorconfig 是否生效的字段 |
editorconfig一般用于规范项目中的缩进风格
Prettier
在IDE插件中安装Prettier
在项目根目录中添加.prettierrc.js配置文件,
用于对代码进行格式化处理
1 | module.exports = { |
在webstorm中,需要首先安装prettier包:
1 | npm install --save-dev --save-exact prettier |
在设置/prettier中,将prettier指向本地npm包里的prettier,开启run on save,
这样在保存项目时就会自动执行prettier格式化代码
eslint + lint-staged监测代码
项目安装eslint依赖
1 | npm i eslint -D |
配置.eslintrc.js文件,使用快捷命令:
1 | npm init @eslint/config |
根据控制台问题的回答情况,最终会生成一个eslint.config.js文件:
1 | import globals from "globals"; |