1、Compiler
Compiler 模块是 webpack 的支柱引擎,它通过 CLI 或 Node API 传递的所有选项,创建出一个 compilation 实例。它扩展(extend)自 Tapable 类,以便注册和调用插件。大多数面向用户的插件首会先在 Compiler 上注册。
其中NodeEnvironmentPlugin是Compiler中比较重要的一个模块,主要负责文件I/O还有监听文件内容改变
以下是一张简易的编译流程图,概括了NodeEnvironmentPlugin的关键作用
1.2、NodeEnvironmentPlugin
NodeEnvironmentPlugin由四个基本的文件系统组成,分别是inputFileSystem与outputFileSystem处理文件i/o,watchFileSystem监听文件改动,intermediateFileSystem则处理所有不被看做是输入或输出文件系统操作,比如写记录,缓存或者分析输出。 outputFileSystem与intermediateFileSystem就是普通的fs,inputFileSystem与watchFileSystem是经过优化后的fs。
1.2、inputFileSystem
compiler.inputFileSystem = new CachedInputFileSystem(fs, 60000);
CachedInputFileSystem对node的文件(信息)内容读取方法做了一层封装,添加了缓存机制,缓存主要原理就是将文件路径与文件信息内容以kv的存储方式出入Map,把过期时间以500ms为一个单元拆成n个Set单元,再用定时器每间隔500ms使Set中的所有文件路径对应的内容删除实现内容过期。
1.3、watchFileSystem
watchFileSystem内容比较多,有兴趣的朋友可以看一下watchpack
watchpack中实现文件变动监听的方式分为两种polledWatching与fs.watch,可以理解为polledWatching为文件扫描(主动),fs.watch即是系统回调(被动)。
1.2.1、polledWatching
可以通过配置options.poll(number)来选择使用轮询扫描的方式,轮询扫描会开启一个定时器然后在固定周期(即options.poll参数数值)扫描文件,使用fs.lstat获取文件的状态,如下:
Stats {
dev: 2114,
ino: 48064969,
mode: 33188,
nlink: 1,
uid: 85,
gid: 100,
rdev: 0,
size: 527,
blksize: 4096,
blocks: 8,
atimeMs: 1318289051000.1,
mtimeMs: 1318289051000.1,
ctimeMs: 1318289051000.1,
birthtimeMs: 1318289051000.1,
atime: Mon, 10 Oct 2011 23:24:11 GMT,
mtime: Mon, 10 Oct 2011 23:24:11 GMT,
ctime: Mon, 10 Oct 2011 23:24:11 GMT,
birthtime: Mon, 10 Oct 2011 23:24:11 GMT }
- actime - active time 访问时间
- mtime - modify time 修改时间
- ctime - change time 文件的元数据发生变化。比如权限,所有者等
原理也比较简单,通过当前文件状态与上次的文件状态的mtime,如果不相同则代表文件发生了修改。
1.2.2、fs.watch
watchpack抽象出两种类型的watcher,分别是DirectWatcher与RecursiveWatcher,本质都是fs.watch,两者的区别是后者的recursive为true。
出于电脑性能问题,watcher的数量是有限的,watchpack中默认mac为2000、windows为10000。
const watcherLimit = +process.env.WATCHPACK_WATCHER_LIMIT || (IS_OSX ? 2000 : 10000);
当watcher超出限制的时候,watchpack只会创建DirectWatcher。
// Fast case when we are not reaching the limit
if (!SUPPORTS_RECURSIVE_WATCHING || watcherLimit - watcherCount >= map.size) {
// Create watchers for all entries in the map
for (const [filePath, entry] of map) {
const w = createDirectWatcher(filePath);
if (Array.isArray(entry)) {
for (const item of entry) w.add(item);
} else {
w.add(entry);
}
}
return;
}
未超出限制的情况下,watchpack会判断当前目录是否有多个文件被监听或者子目录有文件被监听,判断是的话优先使用RecursiveWatcher,毕竟DirectWatcher与RecursiveWatcher构建都只算一个watcher,而RecursiveWatcher可以监听的文件可以比DirectWatcher多很多。
// Merge map entries to keep watcher limit
// Create a 10% buffer to be able to enter fast case more often
const plan = reducePlan(map, watcherLimit * 0.9);
// Update watchers for all entries in the map
for (const [filePath, entry] of plan) {
if (entry.size === 1) {
for (const [watcher, filePath] of entry) {
const w = createDirectWatcher(filePath);
const old = underlyingWatcher.get(watcher);
if (old === w) continue;
w.add(watcher);
if (old !== undefined) old.remove(watcher);
}
} else {
const filePaths = new Set(entry.values());
if (filePaths.size > 1) {
const w = createRecursiveWatcher(filePath);
for (const [watcher, watcherPath] of entry) {
const old = underlyingWatcher.get(watcher);
if (old === w) continue;
w.add(watcherPath, watcher);
if (old !== undefined) old.remove(watcher);
}
} else {
for (const filePath of filePaths) {
const w = createDirectWatcher(filePath);
for (const watcher of entry.keys()) {
const old = underlyingWatcher.get(watcher);
if (old === w) continue;
w.add(watcher);
if (old !== undefined) old.remove(watcher);
}
}
}
}
}