性能分析工具
1. 编译进度条
通过 progress-bar-webpack-plugin 插件查看编译进度,方便我们掌握编译情况。
npm i -D progress-bar-webpack-plugin
webpack.common.js
配置方式如下:
const chalk = require("chalk");
const ProgressBarPlugin = require("progress-bar-webpack-plugin");
module.exports = {
plugins: [
// 进度条
new ProgressBarPlugin({
format: `:msg [:bar] ${chalk.green.bold(":percent")} (:elapsed s)`,
}),
],
};
2. 编译速度分析
通过 speed-measure-webpack-plugin 插件进行构建速度分析,可以看到各个 loader、plugin 的构建时长,后续可针对耗时 loader、plugin 进行优化。
npm i -D speed-measure-webpack-plugin
webpack.dev.js
配置方式如下:
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap({
// ...webpack config...
});
3. 打包体积分析
使用 webpack-bundle-analyzer 查看打包后生成的 bundle 体积分析,将 bundle 内容展示为一个便捷的、交互式、可缩放的树状图形式。帮助我们分析输出结果来检查模块在何处结束。
webpack.prod.js
配置方式如下:
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
module.exports = {
plugins: [
// 打包体积分析
new BundleAnalyzerPlugin(),
],
};
优化开发体验
webpack.dev.js
配置方式如下:
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
module.exports = {
devServer: {
contentBase: "./dist",
hot: true,
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new ReactRefreshWebpackPlugin(),
],
};
加快构建速度
1. cache
通过配置 webpack 持久化缓存 cache: filesystem,来缓存生成的 webpack 模块和 chunk,改善构建速度。
webpack.common.js
配置方式如下:
module.exports = {
cache: {
type: "filesystem", // 使用文件缓存
},
};
每个的 loader、plugin 都有其启动时间。尽量少地使用工具,将非必须的 loader、plugins 删除。
3. 指定include、exclude
const path = require('path');
module.exports = {
rules: [
{
test: /\.(js|ts|jsx|tsx)$/,
exclude: /node_modules/,
include: [
path.resolve(__dirname, 'src')
],
use: [
{
loader: "esbuild-loader",
options: {
loader: "tsx",
target: "es2015",
},
},
],
},
],
};
4. 管理资源
使用 webpack 资源模块 (asset module) 代替旧的 assets loader(如 file-loader/url-loader/raw-loader 等),减少 loader 配置数量。
const path = require('path');
module.exports = {
rules: [
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
include: [
path.resolve(__dirname, 'src')
],
type: "asset/resource",
},
],
};
5. 优化 resolve 配置
resolve 用来配置 webpack 如何解析模块,可通过优化 resolve 配置来覆盖默认配置项,减少解析范围。
const path = require('path');
module.exports = {
// 指定目录可缩小 webpack 解析范围,加快构建速度。
modules: ["node_modules", path.resolve(__dirname, 'src')],
module: {
//不需要解析依赖的第三方大型类库等,可以通过这个字段进行配置,以提高构建速度
//使用 noParse 进行忽略的模块文件中不会解析 import、require 等语法
noParse: /jquery|lodash/,
rules: []
},
resolve: {
alias: {
"@": path.resolve(__dirname, 'src'), // @ 代表 src 路径
},
extensions: [".tsx", ".js"], // 项目用到的文件类型。
symlinks: false, //如果项目不使用symlinks(例如 npm link 或者 yarn link),可以设置resolve.symlinks: false,减少解析工作量。
},
// 从输出的bundle中排除,从CDN引入
externals: {
react: 'react',
moment: 'moment',
antd: 'antd',
lodash: 'lodash',
}
};
6. 多进程
通过 thread-loader 将耗时的 loader 放在一个独立的 worker 池中运行,加快 loader 构建速度。
const path = require('path');
module.exports = {
rules: [
{
test: /\.module\.(scss|sass)$/,
include: path.resolve(__dirname, 'src'),
use: [
"style-loader",
{
loader: "css-loader",
options: {
modules: true,
importLoaders: 2,
},
},
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [["postcss-preset-env"]],
},
},
},
{
loader: "thread-loader",
options: {
workerParallelJobs: 2,
},
},
"sass-loader",
].filter(Boolean),
},
],
};
减小打包体积
1. JS 压缩
使用 TerserWebpackPlugin 来压缩JavaScript。
webpack5 自带最新的 terser-webpack-plugin,无需手动安装。
terser-webpack-plugin 默认开启了 parallel: true 配置,并发运行的默认数量: os.cpus().length - 1 ,本文配置的 parallel 数量为 4,使用多进程并发运行压缩以提高构建速度。
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: 4,
// extractComments:默认值为true,表示会将注释抽取到一个单独的文件中;
// 在开发中,我们不希望保留这个注释时,可以设置为false;
extractComments: false,
terserOptions: {
parse: {
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
comparisons: false,
inline: 2,
},
mangle: {
safari10: true,
},
output: {
ecma: 5,
comments: false,
ascii_only: true,
},
},
}),
],
},
};
2. CSS 压缩
使用 CssMinimizerWebpackPlugin 压缩 CSS 文件。
和 optimize-css-assets-webpack-plugin 相比,css-minimizer-webpack-plugin 在 source maps 和 assets 中使用查询字符串会更加准确,而且支持缓存和并发模式下运行。
CssMinimizerWebpackPlugin 将在 Webpack 构建期间搜索 CSS 文件,优化、压缩 CSS。
webpack.prod.js
配置方式如下:
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
optimization: {
minimizer: [
new CssMinimizerPlugin({
parallel: 4,
}),
],
},
};
3. 代码分离
3.1 抽离重复代码
SplitChunksPlugin 插件开箱即用,可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。
webpack 将根据以下条件自动拆分 chunks:
- 新的 chunk 可以被共享,或者模块来自于 node_modules 文件夹;
- 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积);
- 当按需加载 chunks 时,并行请求的最大数量小于或等于 30;
- 当加载初始化页面时,并发请求的最大数量小于或等于 30; 通过 splitChunks 把 react 等公共库抽离出来,不重复引入占用体积。
注意:切记不要为 cacheGroups 定义固定的 name,因为 cacheGroups.name 指定字符串或始终返回相同字符串的函数时,会将所有常见模块和 vendor 合并为一个 chunk。这会导致更大的初始下载量并减慢页面加载速度。
module.exports = {
splitChunks: {
// include all types of chunks
chunks: "all",
// 重复打包问题
cacheGroups: {
vendors: {
// node_modules里的代码
test: /[\\/]node_modules[\\/]/,
chunks: "all",
// name: 'vendors', 一定不要定义固定的name
priority: 10, // 优先级
enforce: true,
},
},
},
};
3.2 CSS文件分离
MiniCssExtractPlugin 插件将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const isEnvProduction = process.env.NODE_ENV === 'production';
module.exports = {
plugins: [new MiniCssExtractPlugin()],
module: {
rules: [
{
test: /\.module\.(scss|sass)$/,
include: [path.resolve(__dirname, 'src')],
use: [
"style-loader",
isEnvProduction && MiniCssExtractPlugin.loader, // 仅生产环境
{
loader: "css-loader",
options: {
modules: true,
importLoaders: 2,
},
},
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [["postcss-preset-env"]],
},
},
},
{
loader: "thread-loader",
options: {
workerParallelJobs: 2,
},
},
"sass-loader",
].filter(Boolean),
},
],
},
};
4. 组件库按需引用
babel.config.js
配置:
module.exports = {
plugins: [
['import', { libraryName: 'antd' }],
"lodash"
]
}
5. CDN
将大的静态资源上传至 CDN:
- 字体:压缩并上传至 CDN;
- 图片:压缩并上传至 CDN。
6. 按需加载
通过 webpack 提供的 import()语法 动态导入功能进行代码分离,通过按需加载,大大提升网页加载速度。
懒加载 JS 脚本的方式
- CommonJS:require.ensure
- ES6:动态import (目前还没有原生支持,需要babel转换)
如何使用动态import
- 安装babel插件
npm i @babel/plugin-syntax-dynamic-import --save-dev
- 引入在.babelrc文件的根目录下
{
"plugins" : [
"@babel/plugin-syntax-dynamic-import"
]
}
function loadComponent() {
import('./text.js').then((Text) => {
this.setState({
Text: Text.default
})
})
}