入口初始化
入口文件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