likes
comments
collection
share

学 Webpack5 的 Compiler 实例化看这篇就够了

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

一、前文回顾

前文主要讨论了 创建 compiler 实例中的重要分支流程: new WebpackOptionsApply().process 方法,该方法主要做了以下工作

  1. 为不同类型的模块注册 Parser 和 Generator:
    • 1.1 注册 JavascriptModulesPlugin:普通 JS 模块的 Parser 和 Generator
    • 1.2 JsonModulesPlugin:JSON 模块的 Parser 和 Generator
    • 1.3 WebAssemblyModulesPlugin:WASM 的 Parser 和 Generator
  2. 为 entry 声明 EntryPlugin;
  3. 注册性能优化相关插件,当然包括 treeshaking 相关的:
    • 4.1 ModuleConcatenationPlugin:作用域提升的实现
    • 4.2 SplitChunkPlugin:拆分 chunk 的实现
  4. 处理 if (options.cache && ... 开启持久化缓存,这其中重点介绍了四个模块(或插件):
    • 5.1 AddBuildDependenciesPlugin:处理构建编译
    • 5.2 IdleFileCachePlugin:缓存读写调度器
    • 5.3 PackFileCacheStrategy:缓存读/写实现
    • 5.4 ResolverCachePlugin:路径解析相关缓存重要组成插件;

本文的将继续关注 Compiler 实例创建的过程中,即 Compiler 的构造函数

二、编译启动

虽然在更广的范围内来讲,调用 npm run build 的这一刻就算是启动编译了,但是 webpack 内部真正进入编译工作流程的标志是调用 compiler.run 方法。

我们先来看看 compiler 对象:

2.1 Compiler 概览

该对象实例化自 Compiler 类型:

  1. 文件模块:webpack/lib/Compiler.js
  2. 构造函数参数:
    • context:构建的上下文目录,即运行构建命令的工作目录(项目目录)
    • options:options 选项对象
  3. 重要方法:
    • run:常规启动 webpack 编译,一次编译,输出 bundle 后即退出进程;
    • watch:watch 模式启动编译,一次编译后不退出进程,监视到文件变化后重启 webpack 编译流程;
    • getCache:该方法是创建持久化缓存的门面对象的方法

重点关注的代码结构如下:

class Compiler {
    constructor(context, options = {}) {}
    
    getCahe () {}
}

2.2 构造函数

构造函数参数:

  • context:构建的上下文目录,即运行构建命令的工作目录(项目目录)
  • options:options 选项对象
class Compiler {
    constructor(context, options = ({})) {
        this.hooks = Object.freeze({   /* .... */   });

        this.webpack = webpack;

        this.name = undefined;

        this.parentCompilation = undefined;

        this.root = this;

        this.outputPath = "";

        this.watching = undefined;

        this.outputFileSystem = null;

        this.intermediateFileSystem = null;

        this.inputFileSystem = null;

        this.watchFileSystem = null;

        this.recordsInputPath = null;
        this.recordsOutputPath = null;
        this.records = {};

        this.resolverFactory = new ResolverFactory();
        
        this.cache = new Cache();

        this.idle = false;
    }
        
}

下面我们来看构造函数中的关键步骤:

2.2.1 compiler.hooks

创建 compiler 的生命周期钩子 this.hooks,也就是大家写插件的时候定义的 compiler.hooks 就是这里声明的; 这里为大家准挑选了一些钩子:

  1. initialize: SyncHook,同步钩子,没有参数;该钩子触发时表示 webpack 的初始化已完成;
  2. done: AsyncSeriesHook,异步串行钩子,参数 stats 对象,该钩子触发表示 webpack 构建完成;
  3. run: AsyncSeriesHook,异步串行钩子,参数 compiler 实例对象,该钩子在 run() 方法被调用启动编译时触发;
  4. emit: AsyncSeriesHook,异步串行钩子,参数 compilation 对象,该钩子在编译后期发射编译产物文件前触发;
  5. afterEmit: AsyncSeriesHook,异步串行钩子,参数 compilation 对象,该钩子在完成文件发射后触发;
  6. thisCompilation: SyncHook,同步钩子,参数 compilation, params,该钩子隶属于本次编译,在 compilation 钩子创建前触发,该钩子注册的插件不会被复制到子编译器;
  7. compilation: SyncHook 同步钩子,参数 compilation, params 对象,compilation 钩子在 compilation 对象被创建后触发,compilation 在整个编译声明周期内创建一次,有别于 thisComilation;
  8. normalModuleFactory: SyncHook,同步钩子,参数 normalModuleFactory 对象,实例化 NormalModuleFactory 后触发,normalModuleFactory 用创建常规模块;
  9. compile: SyncHook,同步钩子,params 用于创建 compilation 的 params,于编译启动时,创建 compilation 对象前触发;
  10. make: AsyncParallelHook,异步并行钩子,参数 compilation 对象,于创建 compilation 对象后触发,开启 entry 收集。EntryPlugin 就是定义在了 make 钩子上,标识开始生产了,模块的创建及构建过程主要集中在 make 阶段;make 这个单词很神奇,我不是能很准确的翻译 make,暂定生产吧;
  11. finishMake: AsyncSeriesHook,异步串行钩子,参数 compilation 对象,在 make 阶段完成后触发;
  12. readRecords: AsyncSeriesHook,异步串行钩子,无参数,读取 records 文件后触发;
  13. emitRecords: AsyncSeriesHook,异步串行钩子,无参数,向文件系统写入 records 文件后触发;
  14. watchRun: AsyncSeriesHook,异步串行钩子,参数 compiler 对象,watch 模式下启动编译后触发;
  15. shutdown: AsyncSeriesHook,异步串行钩子,参数无,编译停止后触发,这个钩子主要是持久化缓存写入工作的信号;
  16. environment: SyncHook,同步钩子,参数无,初始化用户插件并完成 webpack 内部默认配置初始化完成后,应用 webpack 默认特性插件(new WebpackOptionsApply().process)之前触发;

2.2.2 文件系统声明

webpack 构建过程中重度依赖文件系统,它没有直接引用 fs 模块,而是针对不同的功能分别设计了不同的文件系统,当然最终这些文件系统底层是依赖 fs 模块的。

注意: 启用 dev-server 会默认将文件写入内存,相当于是接管了原有的文件系统写入

class Compiler {
    constructor(context, options = ({})) {
        // ...
        this.outputFileSystem = null;

        this.intermediateFileSystem = null;

        this.inputFileSystem = null;

        this.watchFileSystem = null;
        
        // ...
      
    }
        
}

主要有四个文件系统:

  1. outputFileSystem:用于向文件系统写入的场景;
  2. intermediateFileSystem:这个属于 webpack 特有的文件系统,负责 webpack 构建过程中的中间产物,比如持久化缓存的读写工作;
  3. inputFileSystem:用于 webpack 文件的读取(输入)场景;
  4. watchFileSystem:该文件系统封装了监听文件变化的能力,用于 watch 模式下的监听文件变化的文件系统;

2.2.3 records 对象声明

Records 对象是 Webpack 提供的一种用于记录编译过程中的信息的对象。它可以用于记录编译过程中的各种信息,例如模块的依赖关系、错误信息等。通过 Records 对象,开发人员可以更加方便地分析和调试编译过程中的问题,从而提高开发效率。

class Compiler {
    constructor(context, options = ({})) {
        this.recordsInputPath = null;
        this.recordsOutputPath = null;
        this.records = {};
    }
        
}

这里主要声明三个属性:

  1. recordsInputPath:records 的输入路径,相当于是告知 webpack 从哪里读取之前的 records;
  2. recordsOutputPath:records 的输出路径,告知 webpack 把本次编译的 records 写到哪里;
  3. records:收集到的 records 内容存储;

2.2.4 resolverFactory 实例化

resolverFactory 是用于创建 Resolver (路径解析器的)工厂,比如我们在开发时写了这样一段代码:

// 模块路径 /Document/user/ss/proj/vue-components/somejs/index.js
import some from '@vue-comps/somejs'

你的源头是 '@vue-comps/somejs',但是这个玩意儿 webpack 并不认识,但是 webpack 能找到最终模块并完成编译工作,是谁把 '@vue-comps/somejs' 最终被解析成 /Document/user/ss/proj/vue-components/somejs/index.js 呢?

答案就是 Resolver,所谓路径解析器,他们是由 resolverFactory 创建,后面到了模块编译的时候我们一起学习这个玩意儿,这个是个大篇幅;

class Compiler {
    constructor(context, options = ({})) {
  
        // ...
        this.resolverFactory = new ResolverFactory();

    }
        
}

2.2.5 持久化缓存声明

webpack 内部启用持久化缓存后,整个构建流程中涉及到的缓存都由 compiler.cache 统一调度。整个调度的过程封装在 Cache 类型中。

class Compiler {
    constructor(context, options = ({})) {
        this.cache = new Cache();
    }
        
}

后续构建过程中都会通过 compiler.getCache 方法获取相应的缓存对象。Cache 类型本身不负责缓存读写,这一点在前面的 IdleFileCachePlugin 插件注册中提到过,读写的部分是由 strategy (策略)负责。

那 Cache 干啥呢?调度,只是负责 compiler.cache.hooks 的触发;name谁订阅了这些钩子呢,当然是在 IdleFileCachePlugin 了。

三、总结

本文详细讨论了 Compiler 的构造函数中的重要细节工作,包括以下几点:

  1. 调度整个构建过程的 compiler.hooks 的注册,并且挑选其中具有代表性的钩子进行了讲解,包括其类型、参数、调用时机、作用等;
  2. 介绍了 webpack 的四个文件系统包括,负责输入、输出、中间产物、watch 四种文件系统;outputFileSystem:用于向文件系统写入的场景;intermediateFileSystem:这个属于 webpack 特有的文件系统,负责 webpack 构建过程中的中间产物,比如持久化缓存的读写工作;inputFileSystem:用于 webpack 文件的读场景;
  3. watchFileSystem:该文件系统封装了监听文件变化的能力,用于 watch 模式下的监听文件变化的文件系统;
  4. records 对象的作用和相关输入和输出路径;
  5. resolverFactory 的实例化以及 resolverFactory 的作用;
  6. 持久化缓存的声明及 compiler.cache、IdleFileCachePlugin、PackFileSCachetrategy 三者间的关系;
转载自:https://juejin.cn/post/7331135154210324495
评论
请登录