- Published on
深入浅出webpack-webpack源码解析
- Authors
- Name
- noodles
- 每个人的花期不同,不必在乎别人比你提前拥有
在深入浅出webpack-Tapable源码解析中,我们了解到webpack是基于Tapable来实现打包的任务调度,本文基于webpack的源码梳理打包的实现流程.在源码分析中会主要关注以下几点:
- webpack启动过程(compiler compilation的生成过程)
- 入口文件的解析过程
- loader调用过程
- plugin的调用过程(webpack实现任务调度拆分的方式)
- 文件的输出过程
前置知识
Tapable
webpack基于tapable来实现任务调度和代码逻辑的拆分.深入浅出webpack-Tapable
webpack基础
webpack是前端的打包工具,通过loader实现了文件转化能力、plugin实现了打包阶段的介入能力(编译能力增强,任务拆分).
compiler在webpack启动编译后生成的对象,它负责把控整个webpack的打包构建
compilation对象是每一次构建的上下文对象包含当次构建的所有信息.
源码解读

// 启动文件 通过compiler.run开启编译
const webpack = require('../lib/index.js')
const config = require('./webpack.config')
const compiler = webpack(config)
compiler.run((err, stats) => {
console.log(stats)
})
// webpack.config.js 配置文件
// 添加入口文件 配置了解析的loader
const path = require('path')
module.exports = {
context: __dirname,
mode: 'development',
// 入口文件
entry: './src/index.js',
devtool: 'source-map',
output: {
path: path.join(__dirname, './dist'),
},
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
exclude: /node_modules/,
},
],
},
}
// 入口文件
import is from 'object.is'
console.log(is(1, 1))
在上面的代码中指定了配置文件和编译的目标文件,通过node --inspect-brk start.js结合chrome的inspect就可以对webpack的源码进行调试了.
webpack的启动过程
在webpack.js构造函数中会根据入参来创建compiler.

在createCompiler中主要做了:
- 入参处理
- 创建Compiler
- 订阅Plugin
- 根据入参加载不同的能力和应用webpack内置的插件体系(WebpackOptionsApply)
创建Compiler&&订阅plugin

加载webpack内置的插件体系
在WebpackOptionsApply我们先只关注一个插件的处理逻辑-EntryOptionPlugin.EntryOptionPlugin对不同类型的entry加载了不同的处理逻辑,在处理非函数entry的时候加载了EntryPlugin.

在EntryPlugin中订阅了make的hook,在触发make钩子的时候触发compilation的编译逻辑.

打包过程
通过webpack(config)获取到创建的Compiler,通过调用Compiler的run方法打包过程. 在run方法中主要:
- 定义了三个阶段的函数 run(开始打包) onCompiled(文件输出) finalCallback(编译后处理) 串联起调用逻辑 run => onCompiled => finalCallback
- 调用run函数发起打包
compilation创建
调起打包逻辑
深入打包过程

在调用了compilation的addEntry方法后触发了如下的函数调用链路: addEntry => _addEntryItem => addModuleTree => handleModuleCreation => factorizeModule => addModule => buildModule => moulde.needBuild => module.build => processModuleDependencies
在这一串调用逻辑中完成了:
- 入口文件的依赖解析和打包
- 入口文件依赖模块的解析打包过程
创建模块
在处理入口文件过程中,在调用factorizeModule的时候通过SyncQueue发起_factorizeModule的调用,在_factorizeModule主要是根据当前模块的工厂函数创建模块对象,在EntryPlugin设置的moduleFactory函数是NormalModuleFactory.NormalModuleFactory继承NormalModule,在NormalModule中封装了模块的build方法等供后续的调用.
在创建完模块之后,通过调用addModule将创建的模块加入到ModuleGraph中进行存储.
模块解析

- 在doBuild中调用runLoaders调用设置的loader解析文件
- 通过parse方法解析生成的ast 生成依赖模块的信息
在当前Entry模块解析完毕后触发回调回到上次发起processModuleDependencies的调用开启依赖模块的打包.
打包文件生成
上面梳理完了模块的打包过程,通过loader的转化能力和plugin的劫持能力已经将文件转化成需要的内容,它存在Compilation中.通过make 钩子触发编译逻辑之后通过调用compilation的seal方法生成文件.
下面是seal中的一些关键方法的调用梳理: compilation.seal => EntryPoint => buildChunkGraph => _runCodeGenerationJobs => createChunkAssets => getRenderManifest => fileManifest.render
上面的一系列调用主要做了:
- 根据入口创建EntryPoint 他是一个chunk group.负责维护与入口相关的依赖.
- buildChunkGraph 生成模块的依赖依赖结构 相关的模块会保存到一个chunk group里面
- _runCodeGenerationJobs调用module的生成代码逻辑,生成代码
- 最后通过getRenderManifest fileManifest.render进行最后输出文件的拼接. 比如在入口函数中会通过this.renderMain的方式拼接代码.这样就添加了打包出文件的Bootstrap逻辑.
以上从源码的角度大致梳理了webpack整个构建的流程.通过源码的阅读可以看到webpack在设计上一些可以借鉴的点:
- webpack通过tapable实现代码的构建流程这样在实现上业务代码的职责更加单一和清晰,但是一定程度上也引入了callback的处理逻辑