细说webpack中的4种文件系统
前言
大家好这里是阳九,一个文科转码的野路子码农,热衷于研究和手写前端工具.
我的宗旨就是 万物皆可手写
最近也是在继续研究webpack,看到了内置了四个FileSystem,想看看到底是干了什么
新手创作不易,有问题欢迎指出和轻喷,谢谢
原生JS手写的webpack项目(功能持续补全中) tiny_webpack_test: 原生JS手写简易webpack+手写React测试项目 (github.com)
Compiler类中的文件系统
打开webpack的主类Compiler
我们可以看到,一共挂载了四种文件系统,
我们一一来讲解一下这四种文件系统的作用
- inputFileSystem
- outputFileSystem
- watchFileSystem
- intermediateFileSystem
class Compiler {
constructor(){
/** @type {OutputFileSystem} */
this.outputFileSystem = null;
/** @type {IntermediateFileSystem} */
this.intermediateFileSystem = null;
/** @type {InputFileSystem} */
this.inputFileSystem = null;
/** @type {WatchFileSystem} */
this.watchFileSystem = null;
...
}
...
}
通过插件注入文件系统
webpack是一个典型的可插拔架构, 其本体也是由大量的插件构成的
而它的文件系统,也是在Compiler类创建时通过NodeEnvironmentPlugin
插件注入的.
// webpack.js
const createCompiler = rawOptions => {
const compiler = new Compiler(options.context, options);
new NodeEnvironmentPlugin().apply(compiler);
}
可以看到 NodeEnvironmentPlugin
插件做了这么几件事情
// NodeEnvironmentPlugin.js 伪代码
const fs = require("graceful-fs");
class NodeEnvironmentPlugin {
constructor(options) {this.options = options;}
apply(compiler) {
// 创建并注入Logger
compiler.infrastructureLogger = createConsoleLogger({...});
// 创建并注入inputFileSystem
compiler.inputFileSystem = new CachedInputFileSystem(fs, 60000);
// 创建并注入outputFileSystem
compiler.outputFileSystem = fs;
// 创建并注入intermediateFileSystem
compiler.intermediateFileSystem = fs;
// 创建并注入watchFileSystem
compiler.watchFileSystem = new NodeWatchFileSystem(
compiler.inputFileSystem
);
...
}
}
并且,如果webpack需要允许在非Node环境下,想必也可以通过注入其他环境下的FS模块去进行适配。
InputFileSystem
compiler.inputFileSystem = new CachedInputFileSystem(fs, 60000);
也就是CachedInputFileSystem
这个东西,字面意思上看,是一个缓存文件系统,
封装了一些fs模块的常见API, lstat,stat,readdir,readFile,readJson,readlink 以及他们的Sync方法
这个模块一共有两个功能
- 允许Webpack从不同的输入源读取数据(如内存、磁盘等)
- 将通过此系统的文件缓存,下次快速读取
这里以readFile举例,创建了一个Backend(后端缓存), readFile实际执行的是CacheBackend.provide
module.exports = class CachedInputFileSystem {
...
this._readFileBackend = createBackend(
duration,
this.fileSystem.readFile,
this.fileSystem.readFileSync,
this.fileSystem
);
// readFile实际执行CacheBackend.provide
const readFile = this._readFileBackend.provide;
}
由CacheBackend提供的provide方法一共做了两件事情
- 类型检查
- 检查是否缓存过结果,有则直接返回缓存结果
// 创建CacheBackend实例
const createBackend = (duration, provider, syncProvider, providerContext) => {
if (duration > 0) {
return new CacheBackend(duration, provider, syncProvider, providerContext);
}
return new OperationMergerBackend(provider, syncProvider, providerContext);
};
class CacheBackend {
constructor(){
this._data = new Map(); // 保存结果的Map
}
...
provide(path, options, callback) {
// Check in cache
// Run the operation
}
}
outputFileSystem
可以看到 在NodeEnvironmentPlugin
中 这两个玩意直接使用了graceful-fs
,
graceful-fs 是一个对Node原生fs模块的二次封装库, 将其中的部分方法做了修复,兼容并进行了错误拦截/ 错误重试处理. 比起原生Node的fs模块功能更加强大,稳定性更好
watchFileSystem
这个系统的功能是监控文件变化,
我们devServer每次保存都会触发热更新,就是通过这个系统去监控文件变化,触发热更新回调的。
- 可以看到,在插件注入时,创建了
NodeWatchFileSystem
, 传入了我们的缓存文件系统(CachedInputFileSystem
), 给watchFileSystem添加了缓存功能。
compiler.watchFileSystem = new NodeWatchFileSystem(
compiler.inputFileSystem
);
Watchpack
NodeWatchFileSystem
的主要功能是依靠其中的watcher进行的,也就是Watchpack
Watchpack是一个由webpack团队封装的文件监视器,会监视文件的变化并执行提前注册好的回调
const Watchpack = require("watchpack");
class NodeWatchFileSystem {
constructor(){
this.watcher = new Watchpack(this.watcherOptions);
}
}
监听文件变化的原理, 实际上就是轮询+fs.statSync方法 stat方法会获取文件信息, 当我们每0.5s去检查一下文件的信息, 就可以根据返回的数据判断出文件的变化(create,delete,change)
fs.stat方法是调用了操作系统底层的stat
方法,执行速度很快,所以即便是0.5s去遍历文件列表做检查,也是可以的。
(当然由于文件数量过大和短间隔轮询,这个操作消耗依然不小,这也是为什么我们开启devServer时电脑会发热的原因= =)
具体的文件监视器的架构与实现可以看我以前的文章:
当然我自己也有实现一个文件监视器,可以拿来学习(start暗示)
封装其他Fs方法
对于webpack这种复杂工程,自带的由fs提供的文件系统固然是不能满足其需求的, 所以webpack基于传入的文件系统自行封装了其他的fs操作
// utils/fs.js
const readJson = (fs, p, callback) => {...}
const mkdirp = (fs, p, callback) => {...}
const dirname = (fs, absPath) => {...}
const relative = (fs, rootPath, targetPath) => {...}
const join = (fs, rootPath, filename) => {...}
转载自:https://juejin.cn/post/7221802192600039479