阅读源码先导
webpack源码很庞大,并且配置很丰富,阅读起来十分困难。有很多重要的功能模块,概念,独立于webpack外的包等。在阅读源码之前最好先把它们单独整理出来进行简单的介绍,更利于后面的阅读。
Compiler
webpack在运行的时候,会传入配置信息,返回实例化的Compiler对象。它在运行环境里是唯一的,所有主流程都由它来控制,比如开始编译,从入口开始处理模块,对于不同模块根据配置进行封装在一个组里,最后生成文件等等。当然,它只是操作流程,流程内的事情都是托付给其他模块处理。
Compilation
它能够调用许多处理模块的方法,并保存相应的构建的信息,比如modules,assets,moduleGraph, chunkGraph等。每次资源的重构建都会产生新的Compilation。
Module
在webpack里,万物都是模块,js文件,图片,视频等。普通的模块都会生成NormalModule对象,它可以调用方法解析模块,可以调用parser解析成AST(抽象语法树)等。NormalModule对象包含着Loader处理过后的代码,被哪个模块解析引入,模块解析的路径,引入其他模块的依赖等信息。而且在构建模块的时候,会生成moduleGraph来记录模块之间引用信息,可以保存导出信息,用于分析导出是否有被使用。
Chunk
在构建完之后,如果不进行相关配置,所有的module都会被打包在一个Chunk里,但也可以自行配置,用最优的方法来进行代码分块,优化网页的加载速度。
Loader
webpack能够处理js,并且webpack5还添加了许多能处理其他资源的能力。但是还有很多文件webpack本身不能处理,需要引入其他Loader转换成js,才能够被webpack操作。如果你想写一个Loader处理相关资源,可以阅读文档
Plugin
插件能够帮助webpack扩展功能,包括内置的很多功能也是通过插件的方式引入的,比如热模块替换,external,splitChunk等。它们统统扩展于Tapable,这样的好处在于能够很好的扩展webpack的功能并且和webpack解耦。如果你想写一个Plugin扩展功能,可以阅读这里
Parser
Parser用于解析资源,最常用到的parser就是JavascriptParser。它用acorn将js解析成AST(抽象语法树),并且对所有的声明或表达式进行遍历,找出相关模块的导入导出信息,添加至依赖里。也能收集导出导入情况,为后续的tree-shaking做准备。也能解析注释,实现webpack magic comment功能。
除了JavascriptParser,还有webassemblyParser,会调用@webassemblyjs/ast
进行解析。还有cssParser , jsonParser等
独立库
除了webpack核心的代码,它还将很多功能独立成一个库,这些库不仅用于webpack,也能用于vanillaJs。
Tapable
这是webpack贯穿全文的库,基本重要的功能模块都有它的身影,功能模块与plugin的通信都需要靠它。在webpack里它叫做hooks,其实就是一个高级的EventEmitter,我也写了一篇详细的文章去介绍它。
如果你想写好一个插件,最好了解webpack主要模块的hooks都有哪些:
enhanse-resolve
enhanse-resolve是一个高度可配置的resolve库,它在webpack用来解析文件和loader的路径信息,也可以支持许多配置,比如我们最常用的配置就是resolve.alias,通过别名更方便导入资源,还有resolve.extensions增加文件扩展名等,webpack resolve options都是用于配置这个库。
loader-runner
loader-runner用于执行webpack loader。
webpack-sources
webpack-sources可以生成源码的sourceMap,hash等
运行环境
webpack v5.70.0
webpack.config.js
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
mode: "development",
devtool: "source-map",
entry: "./index.js",
output: {
path: path.join(__dirname, "./dist"),
filename: "[fullhash].[name].bundle.js"
},
optimization: {
usedExports: true
},
plugins: [new CleanWebpackPlugin()]
};
index.js
import { c_var } from "./module_c"
import(/* webpackExports: ["a"] */ "./module_a").then(({ a }) => {
console.log(a)
})
var log_var = c_var;
console.log(log_var)
module_c.js
const c_var = "module_c";
const b_var = "unused";
export { c_var, b_var };
module_a.js
export const a = "moudle_a";
export const b = "unused";
export { a, b };
入口为./index.js
全篇执行目录context为c:\Users\Administrator\Desktop\webpack
流程
webpack的流程可以分为四个大块来讲
run
初始化,做好开始编译的准备工作。处理options,rules,注册插件,实例化compiler所需要的其他对象等。
make
通过入口文件开始构建模块,构建模块会分为几个步骤,解析路径,通过loader转换模块,进行paser收集依赖,递归处理所有外部依赖等。
seal
seal是属于Compilation的hooks,不像run,make,emit都属于Compiler,单独拿出来因为这个阶段也做了很多事,并且也是在make之后执行的。seal阶段不再接收模块,会根据ChunkGroup进行封装成一个或多个chunk,之后会做一些代码优化工作,生成文件Hash name等。
emit
输出文件阶段,webpack会根据不同的template来生成代码,一些代码会被替换,比如import,export会被替换成__webpack_require__,__webpack_exports__等。