总体
- 简单来说:
Webpack 通过内部的 事件流机制 ,保证了插件的有序性
Webpack 底层利用了 发布订阅模式,在运行过程中会广播事件
Webpack 插件只需要监听它所关心的事件,在特定的时机对资源做处理 - 站在代码逻辑的角度:
Webpack 在编译过代码程中,会触发一系列 Tapable 钩子事件
插件需要找到相应的钩子,在上面添加自己的任务(注册事件)
当 Webpack 构建时,插件注册的事件,会随着钩子的触发而执行
打包过程
- 版本一:
- Webpack 的编译流程分为以下几个阶段
初始化阶段(initialize):在这个阶段,Webpack 会读取配置文件,并初始化 Compiler 和 Compilation 对象。
编译阶段(compile):在这个阶段,Webpack 会从入口文件开始,递归地分析模块之间的依赖关系,生成模块图。
构建阶段(make):在这个阶段,Webpack 会根据模块图,将每个模块转换成一个或多个 Chunk,并生成对应的代码块。
输出阶段(emit):在这个阶段,Webpack 会将生成的 Chunk 和代码块输出到文件系统中。
完成阶段(done):在这个阶段,Webpack 编译完成,输出信息到控制台。
- 版本二:
- 一次完整的 Webpack 打包,大致是这样的过程:
将 cli 命令行参数与 Webpack 配置文件合并、解析得到参数对象 options,用于激活 webpack 的loader和插件
把参数对象 options 传给 Webpack, 会创建生成 Compiler 对象,并初始化基础插件
执行 Compiler 的 run 方法开始编译。每次执行 run 编译都会生成一个 Compilation 对象,也就是说,Compilation 对象负责一次编译过程
触发 Compiler 的 make 方法分析入口文件,调用 compilation 的 buildModule 方法创建主模块对象
生成入口文件 AST(抽象语法树),通过 AST 分析和递归加载依赖模块
所有模块分析完成后,执行 compilation 的 seal 方法对每个 chunk 进行整理、优化、封装
最后执行 Compiler 的 emitAssets 方法,把生成的文件输出到 output 的目录中
- 版本三:
初始化
webpack会将cli参数、配置文件、默认配置进行融合,形成一个最终的配置对象
对配置的处理过程是依托yargs的三方包去合并配置
可以简单的理解为,初始化阶段主要用于产生一个最终的配置
构建
chunk从入口开始分析依赖关系(形成一个chunk)
为什么在读取内容之后要做语法分析?因为要知道该模块依赖谁,会转换成AST,对树形结构的遍历,分析里面的依赖关系,然后会把依赖记录记录在一个数组中,叫dependencies。
产生chunk assets
在经过第二步完成后,chunk中会产生一个模块列表,列表中包含了模块id和模块转换后的代码。
接下来webpack会根据配置,为chunk生成一个资源列表,即chunk assets。资源列表可以理解为生成到最终文件的文件名和文件夹。
chunk hash是根据所有chunk assets的内容生成的一个hash字符串
hash是一种算法,具体有很多分类,特点是将一个任意长度的字符串转化为一个固定长度的字符串,而且可以保证原始内容不变,产生的hash串不变。
合并chunk assets
将多个chunk的assets合并在一起,并产生一个总的hash
输出
emit阶段
webpack通过node中的fs模块,创建相应的文件,根据编译产生的总的assets,生成相应的文件。
总过程
术语
module:模块,分割的代码但愿,webpack中的模块可以是任何内容的文件,不限于js
chunk:webpack内部构件模块的块,一个chunk中包含多个模块,这些模块是从入口模块通过依赖分析得来的。
bundle:chunk构建好模块后会生成chunk的资源清单,清单中的每一项就是一个bundle,可以认为bundle就是最终生成的文件
hash:最终的资源清单所有内容联合生成的hash值
chunkhash:chunk生成的资源清单内容联合生成的hash值
chunkname:chunk的名称,默认main
id:通常指chunk的唯一编号,如果在开发环境下构建,和chunkname相同;如果是生产环境下构建,则使用一个从0开始的数字进行编号。
Hash
var hash = crypto.createHash('sha256')
// Use update to add data
.update('I love GeeksForGeeks')
// Use digest to get the hash value
.digest('hex');
.createHash创建一个hash实例,参数是使用何种加密算法
.update添加加密内容
.digest获得hash值,参数返回输出的类型。
如何提升 webpack 编译时期计算 hash 的速度
- 使用缓存:可以使用缓存来避免重复计算,提高编译速度。可以使用 Webpack 自带的缓存,也可以使用第三方插件如 hard-source-webpack-plugin。
- 减少文件数量:可以通过减少需要编译的文件数量来提高编译速度。可以通过忽略不必要的文件或使用 include 和 exclude 配置选项来实现。
- 使用更快的 hash 算法:可以使用更快的 hash 算法来提高计算速度。可以在 Webpack 配置中设置 output.hashFunction 和 output.hashDigest 来选择不同的 hash 算法和输出格式。
- 升级 Webpack 版本:新版本的 Webpack 可能会有更好的性能和优化。
- 使用多线程编译:可以使用多线程编译来提高编译速度。可以使用 thread-loader 或 happypack 来实现。
是否可以将版本号放在文件名中?
const package = require('./package.json')
const config = {
output: {
filename: `${package.version}.{hash}.js`
}
}
不可以,因为每次版本号的改变,这将「导致所有缓存都失效」,而每次版本升级时,并不一定所有资源内容都会进行变更。
hash 是如何生成的
基于模块内容以及一系列元信息生成摘要信息
_initBuildHash(compilation) {
const hash = createHash(compilation.outputOptions.hashFunction);
if (this._source) {
hash.update("source");
this._source.updateHash(hash);
}
hash.update("meta");
hash.update(JSON.stringify(this.buildMeta));
this.buildInfo.hash = /** @type {string} */ (hash.digest("hex"));
}
SplitChunksPlugin和CommonChunksPlugin
- SplitChunksPlugin 插件是用来提取公共模块的,它的原理是将多个入口 chunk 中相同的模块提取出来,形成一个单独的 chunk。这样可以避免代码重复,减小打包后的文件体积,同时也可以利用浏览器的缓存机制,提高页面加载速度。
- SplitChunksPlugin 插件只能处理异步模块(即通过 import() 或动态导入语法加载的模块)。如果需要处理同步模块,可以使用另一个插件:CommonsChunkPlugin。
- 在 Webpack 4 中,CommonsChunkPlugin 插件已经被废弃,取而代之的是 optimization.splitChunks 配置选项。该选项使用了更先进的算法来提取公共模块
常见的插件
tree-shaking
tree-shaking 是通过静态代码分析来实现的。它的原理是基于 ES6 模块化规范中的静态特性,即模块的导入和导出关系是确定的,可以在编译时确定。
当 Webpack 进行打包时,会从入口模块开始,递归地分析模块之间的依赖关系,生成模块图。在这个过程中,Webpack 会标记每个模块中导出的变量和函数,并且会记录它们被哪些模块导入和使用。
在生成代码时,Webpack 会根据这些信息,将没有被使用的变量和函数从打包结果中删除。这个过程就是 tree-shaking。
需要注意的是,tree-shaking 只能删除没有被使用的代码,而不能删除被使用的但没有被导出的代码。
- tree-shaking生效前提:
使用 ES6 模块化规范进行开发。
在配置文件中开启 optimization.usedExports 选项。
在生产环境中使用 UglifyJS 等工具进行代码压缩。
只能处理静态的 import 和 export 语句,无法处理动态的导入语句(例如 import())。
Webpack 编译速度优化
- 使用持久化缓存:Webpack 4 引入了持久化缓存,可以将缓存数据保存到磁盘上,从而避免在每次重新启动 Webpack 时都需要重新生成缓存。可以通过在 webpack.config.js 中配置 cache.type 和 cache.cacheDirectory 来启用持久化缓存。
- 使用多进程并行编译:Webpack 5 引入了多进程并行编译的功能,可以在多个进程中同时编译不同的模块,从而提高编译速度。可以通过在 webpack.config.js 中配置 parallelism 和 minimizer.parallelism 来启用多进程并行编译。
- 启用模块热替换(HMR):模块热替换是一种开发时的优化技巧,可以在代码修改后只重新编译修改的模块,从而提高开发效率。可以通过在 webpack.config.js 中配置 devServer.hot 和 plugins 来启用模块热替换。
- 使用缓存插件:Webpack 有一些插件可以帮助优化缓存,例如 hard-source-webpack-plugin 和 cache-loader。hard-source-webpack-plugin 可以在编译过程中使用硬盘缓存来加速编译,而 cache-loader 可以在每个 loader 上使用缓存来避免重复编译。
- 避免使用绝对路径:在 Webpack 中,使用绝对路径会导致缓存失效,因为不同的机器上的绝对路径可能不同。可以使用相对路径来避免这个问题。
- 避免使用动态导入语句:在 Webpack 中,使用动态导入语句会导致缓存失效,因为无法确定导入的模块和变量。可以使用静态导入语句来避免这个问题。