Bootstrap

webpack2源码架构设计与构建流程

入口初始化

入口文件lib/webpack.js

function webpack(options, callback) {
	let compiler;
	if(Array.isArray(options)) {
		compiler = new MultiCompiler(options.map(options => webpack(options)));
	} else if(typeof options === "object") {
                //创建默认options
		new WebpackOptionsDefaulter().process(options);

                //创建compiler对象
		compiler = new Compiler();
		compiler.context = options.context;
		compiler.options = options;

		/* 遍历用户的插件并注册 */
		if(options.plugins && Array.isArray(options.plugins)) {
			compiler.apply.apply(compiler, options.plugins);
		}
		
                 //注册内置插件、注册resolverFactory相关的钩子
		compiler.options = new WebpackOptionsApply().process(options, compiler);
	}
	if(callback) {
                //开始构建
		compiler.run(callback);
	}
	return compiler;
}
//WebpackOptionsApply.js
process(options, compiler) {
       //...
	compiler.apply(new EntryOptionPlugin());
	compiler.applyPluginsBailResult("entry-option", options.context, options.entry);
}

appply()进行插件注册,随即触发entry-option事件。

 
"use strict";

const SingleEntryPlugin = require("./SingleEntryPlugin");
const MultiEntryPlugin = require("./MultiEntryPlugin");
const DynamicEntryPlugin = require("./DynamicEntryPlugin");

module.exports = class EntryOptionPlugin {
	apply(compiler) {
		compiler.plugin("entry-option", (context, entry) => {
			function itemToPlugin(item, name) {
				if(Array.isArray(item)) {
					return new MultiEntryPlugin(context, item, name);
				} else {
					return new SingleEntryPlugin(context, item, name);
				}
			}
			if(typeof entry === "string" || Array.isArray(entry)) {
				compiler.apply(itemToPlugin(entry, "main"));
			} else if(typeof entry === "object") {
				Object.keys(entry).forEach(name => compiler.apply(itemToPlugin(entry[name], name)));
			} else if(typeof entry === "function") {
				compiler.apply(new DynamicEntryPlugin(context, entry));
			}
			return true;
		});
	}
};
    //SingleEntryPlugin.js
     apply(compiler) {
		compiler.plugin("compilation", (compilation, params) => {
			const normalModuleFactory = params.normalModuleFactory;

			compilation.dependencyFactories.set(SingleEntryDependency, normalModuleFactory);
		});

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

 

dependency

build模块时遍历AST收集dependency,dependency分为3种类型:dependencies、blocks、variables。 Dependency子类继承结构如下:

blocks即AsyncDependenciesBlock类型,用于分离异步模块,其本身同Module一样都继承自DependenciesBlock。

从options.entry开始到其依赖链上的解析是由dependency驱动,先有dependency再有模块,比如在这里构建从SingleEntryDependency开始的,然后创建该依赖关联的模块,创建依赖后再解析该模块的依赖,再从依赖到模块,因此是先有依赖再有模块。构建module流程如下:

架构设计

大家之所以认为 Webpack 复杂,很大程度上是因为它依附着一套庞大的生态系统。其实 Webpack 的核心流程远没有我们想象中那么复杂,甚至只需百来行代码就能完整复刻出来。 打包过程中也有一些特定的时机需要处理,比如:

  • 在打包前需要校验用户传过来的参数,判断格式是否符合要求
  • 在打包过程中,需要知道哪些模块可以忽略编译,直接引用 cdn 链接
  • 在编译完成后,需要将输出的内容插入到 html 文件中
  • 在输出到硬盘前,需要先清空 dist 文件夹
  • ...

这个时候需要一个可插拔的设计,方便给社区提供可扩展的接口 —— Plugin 系统

Plugin 系统 本质上就是一种事件流的机制,到了固定的时间节点就广播特定的事件,用户可以在事件内执行特定的逻辑,类似于生命周期:

我们需要建立一套事件流的机制来管控整个打包过程,大致可以分为三个阶段:

  • 打包开始前的准备工作
  • 打包过程中(也就是编译阶段)
  • 打包结束后(包含打包成功和打包失败)

这其中又以编译阶段最为复杂,另外还考虑到一个场景:watch mode]当文件变化时,将重新进行编译),因此这里最好将编译阶段(也就是compilation)单独解耦出来。compilation编译阶段分离出来,是为了解耦和复用。 在 Webpack 源码中,compiler 就像是一个大管家,它就代表上面说的三个阶段,在它上面挂载着各种生命周期函数,而 compilation 就像专管伙食的厨师,专门负责编译相关的工作,也就是打包过程中这个阶段。画个图帮助大家理解:

大致架构定下后,那现在应该如何实现这套事件流呢? 这时候就需要借助 Tapable的库,专注于自定义事件的触发和处理。通过 Tapable 我们可以注册自定义事件,然后在适当的时机去执行自定义事件。 类比到 Vue 和 React 框架中的生命周期函数,它们就是到了固定的时间节点就执行对应的生命周期,tapable 做的事情就和这个差不多,我们可以通过它先注册一系列的生命周期函数,然后在合适的时间点执行。 代码中的类:Compiler类,Compilation类,NormalModuleFactory类,NormalModule类,Dependency类,Chunk类,Parser类,MainTemplate类等。其中Compiler类和Compilation类这两个类确定了主要框架。Compiler类是webpack的最上层的管理整个生命周期的类,作用于webpack的整个生命流程。通过发布订阅者模式消息通信。Compilation类在compiler事件make触发后使用生成compilaition对象,用于存储打包编译的模块数据和处理编译模块。NormalModule类,Dependency类,Chunk类为数据类,Parser类,MainTemplate类为工具类。

构建流程

原文:https://juejin.cn/post/7392066728438136882

;