webpack编译流程

40 阅读4分钟

概念

webpack编译过程中一个比较重要的概念compilercompilation

  • Compiler类: webpack 的主要引擎,compiler对象是一个全局单例,在compiler对象记录了完整的webpack环境信息,在webpack从启动到结束,compiler只会生成一次。
  • Compilation类:代表每一次构建的上下文对象。每次热更新和重新构建,compiler都会重新生成一个新的compilation对象,负责此次更新的构建过程。一个compilation对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。

涉及术语

  • module:模块,分割的代码单元,webpack中的模块可以是任何内容的文件,不仅限于JS
  • chunk:webpack内部构建模块的块,一个chunk中包含多个模块,这些模块是从入口模块通过依赖分析得来的
  • bundle:chunk构建好模块后会生成chunk的资源清单,清单中的每一项就是一个bundle,可以认为bundle就是最终生成的文件
  • hash:最终的资源清单所有内容联合生成的hash值
  • chunkhash:chunk生成的资源清单内容联合生成的hash值
  • chunkname:chunk的名称,如果没有配置则使用main

运行流程

Webpack的运行流程是一个串行的过程,从启动到结束依次执行以下流程:

  1. 初始化,启动构建,读取与合并配置参数,实例化 Compiler,加载 Plugin。
  2. 编译,从入口文件出发,对每个模块文件调用对应的Loader进行转换,生成AST并记录依赖模块,根据依赖模块递归加载。
  3. 输出,对编译后的模块组合成 Chunk,把 Chunk 转换成文件,输出到文件系统。

初始化阶段

  1. 读取webpack的配置参数,将CLI参数、配置文件、默认配置进行融合,形成一个最终的配置对象。
  2. 启动webpack,创建Compiler对象;
  3. 注册所有的自定义插件
  4. 调用environment钩子 、afterEnvironment钩子
  5. 注册所有webpack内置的插件

编译阶段

  1. 创建新的Compilation对象
  2. 从入口文件(entry)开始解析,读取cache中是否已经有了相同hash的资源,如果有,则直接返回内容,否则才会继续执行模块生成的逻辑,并存入cache中。
  3. 对不同文件类型的依赖模块文件使用对应的Loader进行编译,最终转为Javascript文件;
  4. 通过acorn库生成AST语法树,找到其导入的依赖模块,并记录在module.dependencies数组。
  5. 保存转换后的代码并记录在compilation.assetscompilation.chunks

image-20230214141057803.png

image-20230214141137968.png

输出阶段

  1. 触发shouldEmit钩子,询问插件哪些文件需要输出,哪些不需要
  2. 触发emit钩子,输出 asset到 output 目录之前执行。插件在这里可以修改输出内容。
  3. 触发after-emit钩子,代表文件输出完成。
  4. 触发done钩子,成功完成一次完整的编译和输出流程。

整个过程中webpack会通过发布订阅模式,向外抛出一些hookswebpack的插件即可通过监听这些关键的事件节点,执行插件任务进而达到干预输出结果的目的。

webpack编译结果分析

最终Webpack打包出来的bundle文件是一个IIFE的执行函数。

// webpack 5 打包的bundle文件内容
​
(() => { // webpackBootstrap
    var __webpack_modules__ = ({
        'file-A-path': ((modules) => { // ... })
        'index-file-path': ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { // ... })
    })
    
    // The module cache
    var __webpack_module_cache__ = {};
    
    // The require function
    function __webpack_require__(moduleId) {
        // Check if module is in cache
        var cachedModule = __webpack_module_cache__[moduleId];
        if (cachedModule !== undefined) {
                return cachedModule.exports;
        }
        // Create a new module (and put it into the cache)
        var module = __webpack_module_cache__[moduleId] = {
                // no module.id needed
                // no module.loaded needed
                exports: {}
        };
​
        // Execute the module function
        __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
​
        // Return the exports of the module
        return module.exports;
    }
    
    // startup
    // Load entry module and return exports
    // This entry module can't be inlined because the eval devtool is used.
    var __webpack_exports__ = __webpack_require__("./src/index.js");
})

在上面的打包demo中,整个立即执行函数里边只有三个变量和一个函数方法,__webpack_modules__存放了编译后的各个文件模块的JS内容,__webpack_module_cache__用来做模块缓存,__webpack_require__Webpack内部实现的一套依赖引入函数。最后一句则是代码运行的起点,从入口文件开始,启动整个项目。

总流程

image-20230214142933930.png

参考

webpack编译流程

Webpack源码解读:理清编译主流程