likes
comments
collection
share

细说webpack中的4种文件系统

作者站长头像
站长
· 阅读数 15

前言

大家好这里是阳九,一个文科转码的野路子码农,热衷于研究和手写前端工具.

我的宗旨就是 万物皆可手写

最近也是在继续研究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暗示)

lzy-watchPack (github.com)

封装其他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) => {...}