基础篇可以参考webpack4基础
webpack作者是c#出身,很多代码是OOP模式,可以借鉴下
Tapable
webpack4重写了Tapable, 是webpack的插件组织的核心。它提供给各个插件钩子,在事件触发时执行这些挂载的方法。webapck的插件里必须有apply()
方法,当其被调用的时候webpack将钩子上的方法挂载到各个事件下面有点像nodejs
里EventEmitter
的$on
class Car {
constructor() {
this.hooks = {
accelerate: new SyncHook(["newSpeed"]),
brake: new SyncHook(),
calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
};
},
setSpeed(newSpeed) {
this.hooks.accelerate.call(newSpeed);
}
}
复制代码
如上代码所示先是在实例化的过程中注册了三个钩子函数,在实例上调用方法时触发钩子函数。 下面介绍webpack里主要的6个Tapable
的实例, 它们都继承了Tapable
,定义了一些自己的hook
Compiler
最高层的实例,初始化配置,提供全局性的钩子比如done
, compilation
。其他的Tapable
实例需要通过其访问,如
compiler.hooks.compilation.tap(
"myFirstWebpackPlugin",
(compilation, params) => {
compilation.hooks.seal.tap()
}
);
复制代码
Compilation
由Compiler
创建,整个构建就在这里完成,进行依赖图构建,优化资源,渲染出runtime时的代码等。下面的4个实例都是发生在这个阶段。
Resolver
当你请求一个模块的时候,你将模块名或者相对地址发给模块解析器,它会去解析出绝对地址去寻找那个模块,看是否存在,如果存在则返回相应的模块信息,包括上下文等。这里的请求可以类似网络请求一样携带上查询参数之类的,Resolver
将会返回额外信息。webpack4里将Resolver
这个实例抽出来单独发了一个包enhanced-resolve
, 抽象出来可以便于用户实现自己的Resolver
ModuleFactory
模块工厂就是负责构造模块的实例,介绍两种NormalModuleFactory
和ContextModuleFactory
。两者不同的地方在于后者用于解析动态import()
. 模块工厂主要是用于将Resolver
解析成功的请求里的源码从文件中拿出,在内存中创建一个模块对象(NormalModule)
Parser
Parser
主要用于将代码解析成AST抽象语法?.可以在ast查看代码转换成AST后的样子。webpack默认采用acorn
解析器,babel是babylon
。Parser
将ModuleFactory
返回的对象里的代码字符串转换成AST后进行解析,发现import
或者require
或者define
类似模块引用时会将这些引用信息也就是依赖添加到当前模块的对象里,这样每个模块对象里不但有自己模块的信息还包含它的依赖信息。webpack会在不仅仅会在模块声明处触发事件,它甚至会在解析到变量时也触发事件。如下在webpack/lib/Parser.js
里可以看到如下三个钩子函数
varDeclaration: new HookMap(() => new SyncBailHook(["declaration"])),
varDeclarationLet: new HookMap(() => new SyncBailHook(["declaration"])),
varDeclarationConst: new HookMap(() => new SyncBailHook(["declaration"])),
复制代码
Template
负责生成运行时的代码
// 源码
// index.js
var multiply = require('./multiply')
var sum = (a,b)=>{
return a+b;
}
module.exports = sum;
// multiply.js
module.exports = (a, b) => a*b
// 生成的runtime
[
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
var multiply = __webpack_require__(1)
var sum = (a,b)=>{
return a+b;
}
module.exports = sum;
/***/ }),
/* 1 */
/***/ (function(module, exports) {
module.exports = (a, b) => a*b
/***/ })
];
复制代码
如上面代码所示,里面包含三个模板,分别负责chunk, module, dependency. chunk是包含多个模块的数组,就是外面数组的形式;module就是里面用立即执行函数包围的部分;dependency就是将原先import
, require
等引用模块部分转换成 __webpack_require__
.
工作流程
目前只看到模块构建那部分,后续再补充╮(╯_╰)╭。。。心得就是利用好vscode的调试工具多打断点~~
介绍完了这六个实例,下面大致讲下webpack的工作流程,webpack做的工作非常多,这里只挑主要的讲下。括号里的是源码所在的文件位置,上下文是node_modules/webpack
. 本流程基于webpack4.30.0版本。
- 首先是将配置文件读入,webpack4有默认配置
options = new WebpackOptionsDefaulter().process(options);
会以用户的配置为先。Compiler进行创建compiler = new Compiler(options.context);
将配置里的plugin部分进行绑定调用
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
复制代码
进行其他配置设置compiler.options = new WebpackOptionsApply().process(options, compiler);
(lib/webpack.js) 2. 接着根据打包的目标(web, node, electron等)生成不同的打包模板
switch (options.target) {
case "web":
JsonpTemplatePlugin = require("./web/JsonpTemplatePlugin");
...
break;
case "webworker":
...
复制代码
因为浏览器端请求异步加载的模块会类似jsonp插入dom中<script>
标签,而比如node端是没有dom结构的。
new EntryOptionPlugin().apply(compiler);
compiler.hooks.entryOption.call(options.context, options.entry);
复制代码
这部分是将入口处配置添加调用entryOption
钩子。 (lib/WebpackOptionsApply.js) 3. 根据不同接口类型调用不同的类,webpack里到处都是类
// lib/EntryOptionPlugin.js
if (typeof entry === "string" || Array.isArray(entry)) {
itemToPlugin(context, entry, "main").apply(compiler);
} else if (typeof entry === "object") {
for (const name of Object.keys(entry)) {
itemToPlugin(context, entry[name], name).apply(compiler);
}
} else if (typeof entry === "function") {
new DynamicEntryPlugin(context, entry).apply(compiler);
}
复制代码
这里举例是单文件入口, 在compilation钩子上绑定(即Compiler
创建compilation
后调用)回调,指定当前依赖的模块生成方法。
compiler.hooks.compilation.tap(
"SingleEntryPlugin",
(compilation, { normalModuleFactory }) => {
compilation.dependencyFactories.set(
SingleEntryDependency,
normalModuleFactory
);
}
);
复制代码
(lib/SingleEntryPlugin.js) 4. 创建compilation
(lib/Compiler.js).
const compilation = this.createCompilation();
复制代码
// lib/SingleEntryPlugin.js
compiler.hooks.make.tapAsync(
"SingleEntryPlugin",
(compilation, callback) => {
const { entry, name, context } = this;
const dep = SingleEntryPlugin.createDependency(entry, name);
compilation.addEntry(context, dep, name, callback);
}
);
复制代码
上面这段是之前注册的,但是会在compilation
创建完成前调用,是个异步钩子。compilation
创建好后传入,它会将入口创建一个依赖。 5. 开始执行addEntry()
方法,在addEntry
方法里调用_addModuleChain
,将当前入口文件创建模块moduleFactory.create
,模块创建好后处理当前模块的依赖项this.processModuleDependencies
. 将依赖创建模块后再依次解析模块的依赖。(lib/Compilation.js)
参考资料
主要还是Sean的课程 webpack-plugins