webpack源码之webpack函数

前言

本篇紧接着上篇初始化参数,上篇文章中主要查看bin/webpack.js文件中合并相关命令行参数的逻辑得知下一个逻辑入口即webpack函数执行以及相关Compiler对象方法的执行。
本篇主要梳理webpack函数执行过程中的相关的逻辑。

流程逻辑

流程分析的实例webpack配置:

const path = require('path');

module.exports = {
  // JS 执行入口文件
  entry: './src/main.js',
  output: {
    // 把所有依赖的模块合并输出到一个 bundle.js 文件
    filename: 'bundle.js',
    // 输出文件都放到 dist 目录下
    path: path.resolve(__dirname, '../dist'),
  }
};

首先查看lib/webpack.js文件,该文件主要导出webpack函数以及该函数的相关方法,同时还有webpack的所有内置插件,例如:

exports = webapck;
exportPlugins(exports, {
	// 环境变量定义插件
	"DefinePlugin": () => require("./DefinePlugin")
});
exportPlugins(exports.optimize = {}, {
	// 代码分割插件
	"CommonsChunkPlugin": () => require("./optimize/CommonsChunkPlugin"),
	// 压缩插件
	"UglifyJsPlugin": () => require("./optimize/UglifyJsPlugin")
});

从上面可以得知输出webpack.*和webpack.optimize.*两类的插件。
回归到webpack函数这边,函数主要逻辑如下:
在这里插入图片描述

执行validateSchema函数

该函数的作用主要就是判断用户配置的webpack配置文件是否存在非法的格式或语法。

判断options是否是数组

webpack支持多种配置类型,其中就有配置多个配置对象即存在多个option,这里的逻辑就是判断是否是多个配置option从而调用不同的逻辑。
实际上多个配置option只是递归调用webpack函数而已,代码如下:

compiler = new MultiCompiler(options.map(options => webpack(options)));

所以主要关注还是单个option的具体处理逻辑,这部分代码比较少,就直接粘贴出来,如下:

new WebpackOptionsDefaulter().process(options);
compiler = new Compiler();
compiler.context = options.context;
compiler.options = options;
new NodeEnvironmentPlugin().apply(compiler);
if(options.plugins && Array.isArray(options.plugins)) {
	compiler.apply.apply(compiler, options.plugins);
}
compiler.applyPlugins("environment");
compiler.applyPlugins("after-environment");
compiler.options = new WebpackOptionsApply().process(options, compiler);
WebpackOptionsDefaulter对象构建

WebpackOptionsDefaulter来自于同目录下/lib/WebpackOptionsDefaulter.js,该文件中定义了WebpackOptionsDefaulter类。

class WebpackOptionsDefaulter extends OptionsDefaulter {}

WebpackOptionsDefaulter继承自OptionsDefaulter,父类定义了process方法和相关的set方法,同时定义两个实例属性对象config和defaults,而WebpackOptionsDefaulter类初始化的工作有两点:

  • 将一些默认项写入defaults中
  • 标记一些特殊的配置项,同时写入config实例对象中

之后调用process方法做相关处理:

process(options) {
	for(let name in this.defaults) {
		switch(this.config[name]) {
			case undefined:
				if(getProperty(options, name) === undefined)
					setProperty(options, name, this.defaults[name]);
				break;
		}
	}
}

需要注意的是,options是用户自己配置webpack项对应的配置对象。从上面的逻辑基本可知就是将一些默认项写入options对象中,得到webpack构建过程中必须的配置项即默认化参数处理。

Compiler相关

webpack函数中会调用Compiler类创建一个实例对象,相关Compiler初始化工作没有涉及到相关配置对象中内容的处理,这里就不具体展开得到具体调用相关方法时再有针对性的说明。
这里需要提及的一点是:Compiler继承自Tapable

Webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,Tapable提供相应的钩子函数,webpack插件实现相应的功能就是依赖于此

当Compiler实例化一个compiler对象后,会对其context和options实例属性进行赋值:

compiler.context = options.context;
// options即写入了相关默认参数的配置对象
compiler.options = options;

之后的逻辑跟插件相关,主要的逻辑是:

new NodeEnvironmentPlugin().apply(compiler);
compiler.applyPlugins("environment");
compiler.applyPlugins("after-environment");
NodeEnvironmentPlugin对象

NodeEnvironmentPlugin类只有一个apply方法,主要是执行apply方法,该方法的逻辑主要是对compiler对象中相关实例属性进行赋值:

  • inputFileSystem
  • outputFileSystem
  • watchFileSystem
  • 注册before-run事件

这里主要关注注册before-run事件,tapable的_plugins是事件仓库,所有事件都注册在这里。

applyPlugins方法

这个方法是compiler继承自Tapable,该方法的作用就是执行注册在plugins中的回调函数。

Tapable.prototype.applyPlugins = function applyPlugins(name) {
	// 判断是否注册了相应事件
	if(!this._plugins[name]) return;
	var args = Array.prototype.slice.call(arguments, 1);
	var plugins = this._plugins[name];
	for(var i = 0; i < plugins.length; i++)
		plugins[i].apply(this, args);
};

这里的environment、after-environment事件在目前的逻辑流程中并没有被注册,所以并不会执行。

WebpackOptionsApply对象构建

WebpackOptionsApply类主要的作用就是依据options中相关字段做处理,调用相应插件的apply方法,做进一步的处理。

process(options, compiler) {
	// 主要的逻辑就是对options中相应参数调用compiler.apply方法
	compiler.apply(
		// 相关插件对象
		new LoaderTargetPlugin(options.target)
	);
}

compiler.apply方法的逻辑就是调用对应插件的apply方法,即执行插件的逻辑。而这些插件的逻辑基本都是注册相应的事件到compiler的_plugins中,这里梳理了下上面实例webpack配置项下的WebpackOptionsApply对象的process方法注册的事件有:

  • this-compilation
  • compilation
  • after-resolvers
  • entry-option
  • make
  • after-emit
  • watch-run
  • run
  • after-compile

上面的事件的含义可以查看webpack文档Compiler钩子相关的,可以具体去了解,这里暂时先不一一展开。
上面逻辑整个过程涉及到的插件数量比较多,弄清楚每个创建的细节实非易事,相对需要关注的点是调用compiler.applyPluginsBailResult方法的动作:

// 触发entry-option事件
compiler.applyPluginsBailResult("entry-option", options.context, options.entry);

Tapable.prototype.applyPluginsBailResult = funnction(name) {
	// 调用_plugins中对应插件的apply方法
}

实际上这里就是执行之前entry-option注册的所有事件的callback。这里就需要关注下entry-option被注册时时哪个插件作用时的逻辑,通过源码可知是EntryOptionPlugin。
而EntryOptionPlugin类的主要作用就是:

判断entry的类型从而执行不同插件的逻辑

webpack的Entry配置支持三种类型:

  • String,例如:entry: ‘./src/main.js’
  • Object,例如:entry: { main: ‘./src/main.js’ }
  • function即动态入口,例如:entry: () => ‘./src/main.js’

不同类型的entry由不同的插件来处理,这里涉及到:

  • SingleEntryPlugin:单入口,处理String类型的entry
  • MultiEntryPlugin:处理多入口的entry
  • DynamicEntryPlugin:处理动态入口,即function类型的entry

这里以实例webpack配置来看:

entry: ‘./src/main.js’ 会调用SingleEntryPlugin插件的appy方法

SingleEntryPlugin

该对象的逻辑主要逻辑有两点:

  • 调用compiler.plugin注册compilation 和 make事件
  • 创建SingleEntryDependency对象即依赖

翻看相关webpack文档,可以看到类型这样的描述:

在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块

compiler.plugin("make", (compilation, callback) => {
	const dep = SingleEntryPlugin.createDependency(this.entry, this.name);
	compilation.addEntry(this.context, dep, this.name, callback);
});

createDependency就是创建依赖对象的,从目前看来一个入口文件会对应一个dep对象,而这个dep对象可以表示是一个树结构来管理所有依赖的关系。
具体的逻辑还需要看看Compilation对象的addEntry内部涉及的逻辑,即make事件被触发。

MultiEntryPlugin的逻辑类似只是批量处理。

总结

webpack函数执行的主要逻辑是:

  • 检查配置文件中是否存在非法或格式错误等的配置
  • 创建Compiler对象
  • 加载所有配置(自定义 + 默认额皮质)相关逻辑的插件执行对应的apply方法(主要是注册相关事件)
  • 赋值Compiler对象的相关属性
  • 返回初步被处理Compiler对象

webpack文档中对于Compiler的介绍:

Compiler 模块是 webpack 的支柱引擎,它通过 CLI 或 Node API 传递的所有选项,创建出一个 compilation 实例。它extend自 Tapable 类,以便注册和调用插件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值