【Webpack构建原理与设计理念-浅显理解】

Webpack设计原理

要分析Webpack的设计原理,我们首先需要了解像Webpack这样子的打包构建构建工具在构建项目时分析,编译项目代码的流程。

构建流程

  • 获取配置(webpack.config.js)
  • 配置事件驱动
  • 开始构建 (run方法)
  • 编译模块(_build)
  • 借助plugin,loader编译模块(less,图片,ts,es6编译,HtmlWebpackPlugin插件自动引入输出文件等)
  • 构建优化(代码分割,Tree Shaking, css文件压缩,js文件压缩)

获取配置

const webpack = require('webpack')

const compiler = webpack({
	// 同默认webpack.config.js通过module.exports导出的配置参数
})
// webpack源码文件路径:lib\webpack.js

webpack获取配置后会返回一个compiler。用来开启后续的构建流程

配置事件驱动

compiler中的hooks属性借助tapable包含了大量的事件驱动。用来挂载plugin等,以按照webpack正确的执行时机来触发对应的发布事件

例如:在第一步中,webpack会对config中的配置通过事件驱动的方式进行挂载

compiler.hooks.entryOption.tap("EntryOptionPlugin", () => {}) // 源码位置 lib\EntryOptionPlugin.js 21-24

compiler.hooks.entryOption.call() // 源码位置 lib\WebpackOptionsApply.js 392-396

//如对入口文件处理逻辑的事件挂载
compiler.hooks.make.tapAsync(PLUGIN_NAME, (compilation, callback) => {
	compilation.addEntry(context, dep, options, err => {
		callback(err);
	});
}); // lib\EntryPlugin.js 47-52 (该挂起事件的执行位于开始构建过程中,可以看下一部分)

开始构建

通过配置设置获取的返回值compiler,调用run方法开始进行构建。run方法接收一个回调函数用来返回错误信息等

compiler.run((err, stats) => {}) // webpack源码文件路径:lib\Compiler.js

构建中会调用配置事件驱动过程中挂起的事件

this.hooks.make.callAsync(compilation, err => {
	//...其它逻辑		
}); // lib\Compiler.js 1322--1353

构建过程函数调用的树形图

EntryPlugin.apply (lib\EntryPlugin.js)
└── addEntry
    └── _addEntryItem
        └── addModuleTree
            └── handleModuleCreation
                ├── addModule
                └── _handleModuleBuildAndDependencies (传入addModule中的callback)
                    ├── buildModule
                    │   └── buildQueue.add (注入任务队列) (addModuleQueue实例化属性 processor: this._buildModule.bind(this))
                    │       └── this._buildModule (任务队列调用-构建模块)
                    └── processModuleDependencies (传入buildModule中的callback)
                        └── processDependenciesQueue.add (注入任务队列) (processDependenciesQueue实例化属性 processor: this._processModuleDependencies.bind(this))
                            └── this._processModuleDependencies (任务队列调用-模块依赖)

buildQueue.add与processDependenciesQueue.add的任务队列逻辑进行了抽象设计,将processor这个构建函数作为参数传入,达到对任务队列功能的封装。

// 创建依赖分析任务队列 lib\Compilation.js 1064-1068
this.processDependenciesQueue = new AsyncQueue({
	name: "processDependencies",
	parallelism: options.parallelism || 100,
	processor: this._processModuleDependencies.bind(this) // 队列的执行逻辑
});
// 创建构建模块任务队列 lib\Compilation.js 1083-1087
this.buildQueue = new AsyncQueue({
	name: "build",
	parent: this.factorizeQueue,
	processor: this._buildModule.bind(this) // 队列的执行逻辑
});

编译模块

每一个module都会根据各自的文件类型,调用不同的Module子类的构建实例,以下先简单以入口js文件作为例子
NormalModule子类build方法
开始构建中_buildModule的后续调用逻辑

Compilation (lib\Compilation.js)
└── _buildModule
    └── module.build (module是各种Module类的子类的实例)
        └── _doBuild
            └── runLoaders (使用loader-runner从左到右,从上到下执行loader链编译文件)

借助plugin,loader编译模块(less,图片,ts,es6编译,HtmlWebpackPlugin插件自动引入输出文件等)

上步骤提到的loader-runner库提供的runLoaders,是webpack当中用来通过config中配置的loader执行loader链式编译的方法

plugin的挂载和执行,下面简单以html-webpack-plugin进行举例

// plugin挂载的逻辑
if (Array.isArray(options.plugins)) {
	for (const plugin of options.plugins) {
		if (typeof plugin === "function") {
			/** @type {WebpackPluginFunction} */
			(plugin).call(compiler, compiler);
		} else if (plugin) {
			plugin.apply(compiler);
		}
	}
} // lib\webpack.js 75-84

// html-webpack-plugin 实例后的apply方法
apply () {
	//...其它逻辑
	compilation.hooks.processAssets.tapAsync({}, (...args) => {
		this.generateHTML(...args)
	})
} // html-webpack-plugin\index.js 281-307

// html-webpack-plugin 实例挂载执行函数调用
Compiler (lib\Compiler.js)
└── compile
    └── compilation.seal
        └── this.hooks.processAssets.callAsync

构建优化(代码分割,Tree Shaking, css文件压缩,js文件压缩)

以下简单以代码分割作为例子。在第一步获取配置生成compiler的逻辑中会调用
createCompiler方法。其中的new WebpackOptionsApply().process()方法的调用会挂载代码分割的逻辑等待时机调用

const createCompiler = () => {
	// ...其它逻辑
	new WebpackOptionsApply().process(options, compiler); // lib\webpack.js 94
}

// process方法中有代码分割相关调用用来发布事件
process() {
	// ...其它逻辑
	if (options.optimization.splitChunks) {
		const SplitChunksPlugin = require("./optimize/SplitChunksPlugin");
		new SplitChunksPlugin(options.optimization.splitChunks).apply(compiler); // 这里的apply内部的逻辑与上一步中提到的同理,亦是通过事件的发布订阅在正确的时机调用代码分割逻辑
	}
} // lib\WebpackOptionsApply.js 501-504

设计理念

到目前为止,可以看出webpack在构建过程中利用事件的发布订阅,将整个构建过程串联起来。在不同的周期内,订阅不同的事件,借助发布的事件(可以是webpack自己的,也可以是第三方库在配置阶段发布的)对模块进行打包处理,最终达到编译代码的效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值