构建异常和中断处理
webpack 4 之前的版本构建失败不会抛出错误码(error code)
Node.js 中的 process.exit 规范
- 0 表示成功完成,回调函数中,err 为 null
- 非 0 表示执行失败,回调函数中,err 不为 null,err.code 就是传给 exit 的数字
// webpack4 写法
// webpack3 把 this.hooks.done.tap 换成 this.plugin
function() {
// this 指向的是 compiler
this.hooks.done.tap('done', (stats)=> {
if(stats.compilation.errors && stats.compilation.errors.length && process.argv.indexOf('-watch') == -1) {
console.log('build error');
process.exit(1);
}
})
}
构建配置包设计
构建配置抽离成 npm 包的意义
- 通用性
- 业务开发者无需关注构建配置
- 统一团队构建脚本
- 可维护性
- 构建配置合理拆分
- Readme 文档,Changelog 文档
- 质量
- 冒烟测试、单元测试、测试覆盖率
- 持续集成
构建配置管理的可选方案
通过多个配置文件管理不同环境的构建,webpack --config
参数进行控制
将构建配置设计成一个库,比如hjs-webpack
、Neutrino
、webpack-blocks
开发人员超过 20 人,可以考虑下面的方案
抽成一个工具进行管理,比如create-react-app
、kyt
、nwb
将所有配置放在一个文件,通过--env
参数控制分支选择
构建配置包的设计
通过多个配置文件管理不同环境的 webpack 配置
- 基础配置:webpack.base.js
- 开发环境:webpack.dev.js
- 生成环境:webpack.prod.js
- SSR 环境:webpack.ssr.js
抽离成一个 npm 包统一管理
目录结构设计
冒烟测试
冒烟测试是指对提交测试的软件再进行详细深入的测试之前而进行的预测试,这种预测试的主要目的是暴露一些导致软件需重新发布的严重问题,比如基本功能失效。
冒烟测试执行
- 构建是否成功
- 每次构建完成 build 目录是否有内容输入
- 是否有 JS、CSS 等静态资源文件
- 是否有 HTML 文件
下载包
cnpm i rimraf -D
webpack 性能分析和优化
速度分析
下载 npm i speed-measure-webpack-plugin -D
- 分析整个打包总耗时
- 每个插件和 loader 的耗时情况
// webpack.config.js
const SpeedMeasureWebpackPlugin= require('speed-measure-webpack-plugin')
const smp = new SpeedMeasureWebpackPlugin()
const webpackConfig = {
entry: {},
output: {},
module: {},
plugins: [],
mode: '',
}
module.exports = smp.wrap(webpackConfig);
体积分析
下载 npm i webpack-bundle-analyzer -D
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const webpackConfig = {
entry: {},
output: {},
module: {},
plugins: [
new BundleAnalyzerPlugin()
],
mode: '',
}
module.exports = webpackConfig;
优化:高版本的 webpack 和 node.js
webpack4 做了哪些优化?由于 webpack4 依赖于更高版本的 node >= 6.11.5
优化:多进程构建
- 可选方案
const webpackConfig = {
module: {
rules: [
{
test: /\.m?(js)$/,
exclude: /(node_modules|bower_components)/,
use: ['happypack/loader']
},
]
}
plugins: [
new HappyPack({
id: 'js',
thread: 4,
loader: ['babel-loader']
}),
new HappyPack({
id: 'styles',
thread: 2,
loader: ['style-loader','css-loader', 'less-loader']
})
]
}
使用 thread-loader
下载 cnpm i thread-loader -D
const webpackConfig = {
module: {
rules: [
{
test: /\.m?(js|ts|jsx)$/,
exclude: /(node_modules|bower_components)/,
use: [
{
loader: 'thread-loader',
options: {
workers: 3
}
},
{
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env'],
['@babel/preset-react']
],
plugins: ['@babel/plugin-transform-runtime'],
cacheDirectory: true,
}
}
]
},
]
}
}
并行压缩
- 使用 parallel-uglify-plugin 插件
使用 uglifyjs-webpack-plugin 开启 parallel 参数
这个插件不支持压缩 ES6 语法的代码
使用 terser-webpack-plugin 开启 parallel 参数
webpack4 默认使用这个。 这个插件支持压缩 ES6 语法的代码
webpack5 开箱即带有最新版本的 terser-webpack-plugin。 如果你使用的是 webpack v5 或更高版本,同时希望自定义配置,那么仍需要安装 terser-webpack-plugin。
const TerserPlugin = require("terser-webpack-plugin");
const webpackConfig = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
}),
'...'
],
}
}
优化:分包
设置 externals
思路:将 react、react-dom 基础包通过 cdn 引入,不打入 bundle 中
方法:使用 npm i html-webpack-externals-plugin -D
实际项目中,可能有业务的基础包,如果直接放入 cdn ,通过 script 标签获取,不太好
预编译资源模块
思路:将 react、react-dom、redux、react-redux 基础包和业务基础包打包成一个文件
方法:使用 DLLPlugin 进行分包,DllReferencePlugin 对 manifest.json 引用
- 先使用 DLLPlugin 进行分包 ```javascript // webpack.dll.js const webpack = require(‘webpack’) const path = require(‘path’)
const dll = { mode: ‘production’, entry: { library: [ ‘react’, ‘react-dom’, ] }, output: { filename: ‘[name].dll.js’, path: path.join(dirname, ‘../build/library’), library: ‘[name]’ }, plugins: [ new webpack.DllPlugin({ name: ‘[name]_[contentHash:8]’, path: path.join(dirname, ‘../build/library/[name].json’) }) ], module: { rules: [
],
}, }
module.exports = dll;
2. 再使用 DllReferencePlugin 引用 manifest.json
```javascript
// webpack.prod.js
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.DllReferencePlugin({
manifest: require('../build/library/library.json')
})
]
}
优化:缓存
目的:提高二次构建速度
缓存思路
- babel-loader:开启缓存
- 使用 cache-loader 或者 hard-source-webpack-plugin
babel-loader 开启缓存
const webpackConfig = {
module: {
rules: [
{
test: /\.m?(js|ts|jsx)$/,
exclude: /(node_modules|bower_components)/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env'],
['@babel/preset-react']
],
plugins: ['@babel/plugin-transform-runtime'],
cacheDirectory: true, // 开启缓存
}
}
]
},
]
}
}
使用 hard-source-webpack-plugin
下载 npm i hard-source-webpack-plugin -D
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
const webpackConfig = {
plugins: [
new HardSourceWebpackPlugin()
]
}
优化:缩小构建目标
目的:尽可能的少构建模块
比如:babel-loader 不解析 node_modules
减少文件搜索范围
- 优化 resolve.modules 配置(减少模块搜索层级)
- 优化 resolve.mainFields 配置,指定搜索文件夹时,入口文件的名称
- 优化 resolve.extensions 配置,默认
['.js','.json']
- 合理使用 alias
const webpackConfig = {
resolve: {
alias: {
'@': path.resolve(__dirname, '../src'),
},
mainFields: ['index'],
extensions: ['.js', '.jsx', '.ts'],
}
}
优化:图片压缩
要求:基于 node 库的 imagemin 或者 tinypng API
使用:配置 image-webpack-loader
使用 ImageMin
优点:
- 有很多定制选项
- 可以引入更多第三方优化插件,例如 pngquant
- 可以处理多种图片格式
压缩原理
下载npm i image-webpack-loader -D