webpack阅读经验
阅读历程回顾
对于webpack源码阅读,
刚开始我从 手写一个webpack源码 的视频开始,收益还是挺大的;
后期看了一个webpack 5 调试视频 说明,收益也不错;
最后以打游击战的方式,解决 Babel options 加载的问题、 module rule exclude 问题,了解了webpack ,并以实战的方式初次阅读源码解决问题,效率低了点,但收获不错。
接着是毫无目标的,比如看一个js reloader 的过程,等等,这些效率是极差的,受益也很少;
接着自己再稍微看了下源码debug调试,收益和效率继续很差;
这其中几次想过放弃阅读webpack源码,
后面转换思想,
在网络上大量搜索相关教程与视频,
大量阅读各种视频教程和文档教程;
按照之前的经验,偏好于视频教程,
奈何视频教程中 关于源码调试的部分太少,而且效果不高,
在文档教程上,关于webpack源码解读的教程还蛮多,
遇到高质量的文档教程后,阅读完 对整个调试 大有裨益,
【因此觉得 遇到复杂的 源码解读这类的 学习,可能文档教程是一个不错的选择。
虽然文档教程没有视频教程生动直观,但是它以文字记录,你copy笔记进行查询等等 非常方便】
另外也遇到一个蛮不错的视频调试经验教程,收获也蛮大;
有了自己之前一些webpack 调试经验,以及大量的文档、视频教程阅读后。
再次调试 webpack 就轻车熟路了。
知道抓住重点,放掉之前非常在意的小点。
然后遇到详细的 文档教程,对着文档教程一点点调试,也是很有收获的。
关于webpack版本的选择
webpack5相比 4,代码有了很大的增加,核心文件中的代码 增加 有30-40% 之多,
而且增加了大量的注释或编译代码,淹没了核心流程代码;
读起来成本更大;
由于核心流程基本一致,故而选择webpack4。
先快速或更容易将自己引进 webpack 源码之门,
以后有需求 再考虑 webpack 5 的阅读。
如果再次 webpack 源码阅读我会这样做:
- 学习手写一个webpack
- 阅读 webpack 调试视频的经验;
- 然后以打游击战的方式,解决 Babel options 加载的问题、 module rule exclude 问题,了解了webpack ,并以实战的方式初次阅读源码解决问题,
以此熟悉webpack 大致过程; - 阅读大量的 babel、loader、plugin 的使用教程或视频, 比如手写plugin、loader 等等,知道这些怎么用 对webpack概念更加熟悉;
- 再次阅读大量的 源码解读或调试经验视频 、文档, 这一步做到,就算不看webpack源码,也能对webpack源码做的事情了然于心,
这样的好处是,到最终自己阅读源码调试时,看到代码 就知道这个代码是用来做什么的,因为之前的视频和文档攻略。 - 跟着一个好的教程,比如这次我是跟着一个 文档教程,分模块进行阅读调试,
比如我先调试module,后调试chunk,然后分别做笔记
调试与前言
前言
本篇主要介绍 调试module的技巧与注意点或要点。
关于webpack源码中 module 部分的知识,请熟读文末的《参考文档或视频》。
webpack版本
webpack 4.46.0
说明
本次调试 以单entry SingleEntryPlugin.js 展开。
要在一无所知的情况下调试 module 其实是比较困难的,只有对整个 业务逻辑比较清楚的情况下,
再去调试会比较轻松。
另外,在调试前,先打印出最终创建的module的数据(对象) 以及 stats 对象,对此有大致了解。
有助于 了解 module;
调试技巧
调试的时候,在 lib\Compilation.js
和 lib\Compiler.js
中对各个钩子打断点的技巧
以及同时开两个 demo,一主一辅进行调试,
以 webpack直接源码 demo 调试为主
以 同样配置的 使用webpack的demo 为辅助,上面调试遇到问题时,在不中断上面调试的情况下,使用此demo断点测试猜想。
module编译过程
1 | # lib\Compiler.js |
逻辑说明
1 | addEntry(context, entry, name, callback) { |
所有create共用一个factory
无论是入口的 moduleFactory.create()
还是 factory.create
。
1 |
|
其factory都来自于 下面:
1 | // lib\Compiler.js |
1 | // lib\SingleEntryPlugin.js |
为什么所有的factory 都至于一个, 或者说factory只初始化一次,然后被用于所有的 module 创建。
其实奥妙在于 lib\NormalModuleFactory.js 的 create 方法。
不同的module创建前,都执行了 create 方法,而且入参不一样,所以有了只用同一个factory的可能。
应该是 factory -》 NormalModuleFactory 将所有共有的逻辑抽象到一起。
比如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16this.hooks = {
resolver: new SyncWaterfallHook(["resolver"]),
factory: new SyncWaterfallHook(["factory"]),
beforeResolve: new AsyncSeriesWaterfallHook(["data"]),
afterResolve: new AsyncSeriesWaterfallHook(["data"]),
createModule: new SyncBailHook(["data"]),
module: new SyncWaterfallHook(["module", "data"]),
createParser: new HookMap(() => new SyncBailHook(["parserOptions"])),
parser: new HookMap(() => new SyncHook(["parser", "parserOptions"])),
createGenerator: new HookMap(
() => new SyncBailHook(["generatorOptions"])
),
generator: new HookMap(
() => new SyncHook(["generator", "generatorOptions"])
)
};
create module 的入参
moduleFactory.create
1 | moduleFactory.create( |
factory.create
1 |
|
如何看某个文件创造的module
参考上面
待研究
被用于 factory.create 的dependencies 通过以上梳理 貌似只有一个元素,
后期可以尝试下如何出现多个元素。
还有resolve的过程;
哪里算结束
this.hooks.succeedEntry.call(entry, name, module);
webpack 解析module的过程:
以入口entry为入口,通过create解析成module;
解析完后,再看下此 module 是否有 import 或 require 即依赖 【processModuleDependencies】,
如果有 则遍历此依赖,
对每个依赖文件,进行 create 操作,解析成module 递归,
以上过程重复一次;
1 | addEntry(context, entry, name, callback) { |
当然也可以在这里打印所有编译出来的modules:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// Compilation.js 的 constructor 中
Object.keys(this.hooks).forEach(hookName => {
const hook = this.hooks[hookName];
if (hook.tap) {
hook.tap("show", () => {
const hooktype = Object.getPrototypeOf(hook).constructor.name;
console.log(
`Compilation ${hookName} 【类型:${hooktype} 入参:${hook._args} `
);
// console.log(this.modules)
if (hookName === "needAdditionalPass") {
// needAdditionalPass 执行时,webpack 的编译完成
// 可以在这里 console.log(this.modules) 打印所有编译出来的 modules
console.log(this.modules);
}
});
}
});
如何确认module标识
入口文件会经历一次 moduleFactory.create
;
之后的每个文件都会经历 一次 factory.create
;
每个文件都会创建一个module;
无论时入口文件还是其依赖的文件,创建的module 全部保存在 Compilation.js this.modules
上;
可以从这里看出是哪个文件的module
每个文件创建module之前,经历create,此文件信息在这里:1
2
3
4
5
6
7
8
9
10
11{
contextInfo: {
issuer: module.nameForCondition && module.nameForCondition(),
compiler: this.compiler.name
},
resolveOptions: module.resolveOptions,
context: module.context,
// 此文件信息在这里
// dependencies[0].request
dependencies: dependencies
}
创建好的module,参考 - 关于module对象 ,其文件标识在 resource 中。1
resource: 'D:\\workplace\\webpack\\webpack-travel\\src\\index.js',
延申
限制一次操作的次数
在创建module时,使用 lib\util\Semaphore.js 进行次数控制,超过100个时 等待处理完后再处理。1
2
3
4
5// lib\Compilation.js
const semaphore = this.semaphore;
semaphore.acquire(() => {
const factory = item.factory;
factory.create(
参考
参考文档或视频
webpack4 源码分析系列
webpack5 源码分析
webpack4 如何调试和阅读webpack源码 视频
webpack3 源码分析系列: 玩转webpack
webpack3 源码分析系列: 玩转webpack
webpack3 源码分析系列: 玩转webpack 备用地址
手摸手带你实现打包器 仅需 80 行代码理解 webpack 的核心
[万字总结] 一文吃透 Webpack 核心原理
调试demo地址
用于主调试的 webpack源码仓库: version-4.46.0-module
用于辅助的: webpack-4.46.0-module