简单的 webpack 源码解析
-
初始化
webpack-cli 解析配置文件和命令行参数,生成一份配置信息(options),经由lib/webpack.js接收options并实例化一个compiler控制整个构建流程,随后通过WebpackOptionsApply初始化一些内部插件如调用node进行读写的插件等
-
compile
经由compile进入编译流程。整个构建过程中webpack提供了tapable这个自带的基于事件流的任务处理库,在Compiler上挂载各个生命周期的事件钩子,以便在处理到某个阶段时触发回调函数。Compilation 是由 compiler 创建的单次构建过程的实例对象,Compiler 是控制从输入命令到完成输出的这整个生命周期,Compilation是把所有处理成 modules 的过程,Compiler周期内会反复触发 Compilation 实例.
run(callback) { // 调用 run 方法后进入编译,并挂上回调函数 this.compile(onCompiled); // ... } compile(callback) { //初始化compilation const compilation = this.newCompilation(params); // ... } -
make
Compiler 在 make hook 被触发时,开始进入编译构建。以 entry 为入口,生成一个module对象,然后通过ast工具生成语法树对象,通过分析节点,递归生成module,并形成依赖关系。
compiler.plugin("make", (compilation, callback) => { // 调用 compilation.addEntry 进入 compilation.addEntry(this.context, dep, this.name, callback) }) moduleFactory.create({},(err, module) => { //处理源码 this.buildModule() //处理module建的依赖关系 this.processModuleDependencies() }) -
buildModule
buildModule 是 make过程的核心,此过程主要完成3个任务:
- 通过runLoaders匹配loader规则和处理方法
runLoaders({ resource: this.resource, loaders: this.loaders, context: loaderContext, readResource: fs.readFile.bind(fs) }- 通过ast工具,把源码解析成AST树
// 根据js代码获取ast语法树对象 ast = acorn.parse(code, parserOptions);- 分析语法树节点递归生成新module,并产出一个树结构的module
// 根据ast加载模块的依赖 this.prewalkStatements(ast.body); this.walkStatements(ast.body); -
seal & createChunkAssets
build完成, 通过调用 Compilation.seal 方法,开始组装编译后的内容。在这个阶段,开始将modules组成chunk,并执行一些挂载在此阶段钩子上的插件,比如tree-shaking等优化代码大小的插件。封装完成chunk后调用Compilation.createChunkAssets,将源码和模板代码生成待打包的最终代码内容
createModuleAssets(){ for (let i = 0; i < this.modules.length; i++) { const module = this.modules[i]; if (module.buildInfo.assets) { for (const assetName of Object.keys(module.buildInfo.assets)) { const fileName = this.getPath(assetName); this.assets[fileName] = module.buildInfo.assets[assetName]; this.hooks.moduleAsset.call(module, fileName); } } } } -
emitAssets
Compilation.emitAssets 方法触发 Compiler 的 emit hook。通过自带注册的文件读写插件,最终将之前生成的内容作为文件输出到配置信息中的目录,自此完成整个构建的流程。
// 获取资源输出的路径 outputPath = compilation.getPath(this.outputPath); // 递归创建输出目录并输出资源 this.outputFileSystem.mkdirp(outputPath, emitFiles);