核心流程
Webpack 最最核心的功能,一是使用适当 Loader 将任意类型文件转译成 JavaScript 代码,二是将这些经过 Loader 处理的文件资源合并、打包成向下兼容的产物文件。
为了实现这些功能,Webpack 底层的工作流程大致可以总结为这几个阶段:
-
初始化阶段:
- 初始化参数:从配置文件、配置对象、Shell 参数中读取,与默认配置结合得出最终的参数
- 创建编译器对象:用上一步得到的参数创建 Compiler 对象
- 初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化 RuleSet 集合、加载配置的插件等
- 开始编译:执行 compiler 对象的 run 方法,创建 Compilation 对象
- 确定入口:根据配置中的
entry
找出所有的入口文件,调用compilation.addEntry
将入口文件转换为dependence
对象
-
构建阶段:
- 编译模块:从
entry
文件开始,调用loader
将模块转译为标准 JS 内容,调用 JS 解析器将内容转换为 AST 对象,从中找出该模块依赖的模块,再递归处理这些依赖模块,直到所有入口依赖的文件都经过了本步骤的处理 - 完成模块编译:模块编译完成后,输出每个模块被编译后的内容以及它们之间的依赖关系图
- 编译模块:从
-
封装阶段:
- 合并:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的
Chunk
- 优化:对上述
Chunk
施加一系列优化操作,包括:tree-shaking、terser、scope-hoistring、压缩、Code Split等 - 写入文件系统:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
- 合并:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的
性能分析
有许多被反复实践、行之有效的构建性能优化手段,包括并行编译、缓存、缩小资源搜索范围等等,但在介绍这些具体的优化方法之前,有必要先聊聊:如何收集、分析 Webpack 打包过程的性能数据。
收集数据的方法很简单 ------ Webpack 内置了 stats 接口,专门用于统计模块构建耗时、模块依赖关系等信息,推荐用法:
- 添加
profile = true
配置:
java
// webpack.config.js
module.exports = {
// ...
profile: true
}
- 运行编译命令,并添加
--json
参数,参数值为最终生成的统计文件名,如:
ini
npx webpack --json=stats.json
上述命令执行完毕后,会在文件夹下生成 stats.json
文件,内容大致如下:
json
{
"hash": "2c0b66247db00e494ab8",
"version": "5.36.1",
"time": 81,
"builtAt": 1620401092814,
"publicPath": "",
"outputPath": "/Users/tecvan/learn-webpack/hello-world/dist",
"assetsByChunkName": { "main": ["index.js"] },
"assets": [
// ...
],
"chunks": [
// ...
],
"modules": [
// ...
],
"entrypoints": {
// ...
},
"namedChunkGroups": {
// ...
},
"errors": [
// ...
],
"errorsCount": 0,
"warnings": [
// ...
],
"warningsCount": 0,
"children": [
// ...
]
}
stats
对象收集了 Webpack 运行过程中许多值得关注的信息,包括:
-
modules
:本次打包处理的所有模块列表,内容包含模块的大小、所属chunk
、构建原因、依赖模块等,特别是modules.profile
属性,包含了构建该模块时,解析路径、编译、打包、子模块打包等各个环节所花费的时间,非常有用; -
chunks
:构建过程生成的chunks
列表,数组内容包含chunk
名称、大小、包含了哪些模块等; -
assets
:编译后最终输出的产物列表、文件路径、文件大小等; -
entrypoints
:entry 列表,包括动态引入所生产的 entry 项也会包含在这里面; -
children
:子 Compiler 对象的性能数据,例如extract-css-chunk-plugin
插件内部就会调用 compilation.createChildCompiler 函数创建出子 Compiler 来做 CSS 抽取的工作。 我们可以从这些数据中分析出模块之间的依赖关系、体积占比、编译构建耗时等,Webpack 社区还提供了许多优秀的分析工具,能够将这些数据转换各种风格的可视化图表,帮助我们更高效地找出性能卡点,包括: -
Webpack Analysis :Webpack 官方提供的,功能比较全面的
stats
可视化工具; -
Statoscope:主要侧重于模块与模块、模块与 chunk、chunk 与 chunk 等,实体之间的关系分析;
-
Webpack Visualizer:一个简单的模块体积分析工具,真的很简单!
-
Webpack Bundle Analyzer:应该是使用率最高的性能分析工具之一,主要实现以 Tree Map 方式展示各个模块的体积占比;
-
Webpack Dashboard:能够在编译过程实时展示编译进度、模块分布、产物信息等;
-
Unused Webpack Plugin:能够根据
stats
数据反向查找项目中未被使用的文件。
Webpack Analysis
Webpack Analysis 是 webpack 官方提供的可视化分析工具,相比于其它工具,它提供的视图更全,功能更强大,能够通过创建依赖关系图对你的包进行更彻底的检查。
使用上只需要将上一节 webpack --json=stats.json
命令生成的 stats.json
文件导入页面,就可以看到一些关键统计信息
Statoscope
Statoscope 也是一个非常强大的可视化分析工具,主要提供如下功能:
- 完整的依赖关系视图,涵盖 modules/chunks/assets/entrypoints/packages 维度;
- entrypoints/chunks/packages/module 体积分析;
- 重复包检测;
- 多份
stats
数据对比; - 等等。
有两种用法,一是将 stats
数据导入到 Statoscope 在线页面;二是使用 @statoscope/webpack-plugin
插件,用法:
- 安装依赖:
sql
yarn add -D @statoscope/webpack-plugin
- 注册插件:
ini
const StatoscopeWebpackPlugin = require('@statoscope/webpack-plugin').default;
module.exports = {
...
plugins: [new StatoscopeWebpackPlugin()],
};
之后,运行 npx webpack
命令,编译结束后默认打开分析视图,
Webpack Bundle Analyzer
Webpack-bundle-analyzer 是一个非常有名的性能分析插件,只需要一些简单配置就可以在 Webpack 构建结束后生成 Tree Map 形态的模块分布统计图,用户可以通过对比 Tree Map 内容推断各模块的体积占比,是否包含重复模块、不必要的模块等,用法:
- 安装模块依赖:
csharp
yarn add -D webpack-bundle-analyzer
- 添加插件:
ini
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
module.exports = {
...
plugins: [new BundleAnalyzerPlugin()],
};
Webpack Visualizer
Webpack Visualizer 是一个在线分析工具,可用于检测、可视化 Webpack 产物的构成模块。有两种用法,一是将 stats.json
文件上传到在线 页面;二是使用 webpack-visualizer-plugin
生成统计页面,用法:
- 安装依赖:
csharp
yarn add ---D webpack-visualizer-plugin
- 添加插件:
java
// webpack.config.js
const VisualizerPlugin = require('webpack-visualizer-plugin');
module.exports = {
// ...
plugins: [
new Visualizer({
filename: './stats.html'
})
],
}
//...
Webpack Dashboard
webpack-dashboard 是一个命令行可视化工具,能够在编译过程中实时展示编译进度、模块分布、产物信息等,用法:
- 安装依赖:
csharp
yarn add -D webpack-dashboard
- 注册插件:
ini
const DashboardPlugin = require("webpack-dashboard/plugin");
module.exports = {
// ...
plugins: [new DashboardPlugin()],
};
- 注意了,需要用
webpack-dashboard
命令启动编译:
bash
# 打包
npx webpack-dashboard -- webpack
# Dev Server
npx webpack-dashboard -- webpack-dev-server
# 运行 Node 程序
npx webpack-dashboard -- node index.js
Speed Measure Plugin
SpeedMeasureWebpackPlugin 插件能够统计出各个 Loader、插件的处理耗时,开发者可以根据这些数据分析出哪些类型的文件处理更耗时间,用法:
- 安装依赖:
csharp
yarn add -D speed-measure-webpack-plugin
- 修改配置:
ini
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const config = {
entry: "./src/index.ts",
// ...
};
// 注意,这里是用 `smp.wrap` 函数包裹住 Webpack 配置
module.exports = smp.wrap(config);
之后运行打包命令如 npx webpack
即可。
UnusedWebpackPlugin
最后分享 UnusedWebpackPlugin 插件,它能够根据 webpack 统计信息,反向查找出工程项目里哪些文件没有被用到,我日常在各种项目重构工作中都会用到,非常实用。用法也比较简单:
csharp
const UnusedWebpackPlugin = require("unused-webpack-plugin");
module.exports = {
// ...
plugins: [
new UnusedWebpackPlugin({
directories: [path.join(__dirname, "src")],
root: path.join(__dirname, "../"),
}),
],
};
示例中,directories
用于指定需要分析的文件目录;root
用于指定根路径,与输出有关。配置插件后,webpack 每次运行完毕都会输出 directories
目录中,有哪些文件没有被用到。