webpack源码深入--- webpack的编译主流程

news2024/12/25 9:08:15

webpack5的编译主流程

根据watch选项调用compiler.watch或者是compiler.run()方法

try {
    const { compiler, watch, watchOptions } = create();
    if (watch) {
        compiler.watch(watchOptions, callback);
    } else {
        compiler.run((err, stats) => {
            compiler.close(err2 => {
                callback(
                    err || err2,
                    /** @type {options extends WebpackOptions ? Stats : MultiStats} */
                    (stats)
                );
            });
        });
    }
    return compiler;
} catch (err) {
    process.nextTick(() => callback(/** @type {Error} */ (err)));
    return null;
}

compiler.run方法

来自于Compiler.prototype.run方法webpack/lib/Compiler.js

// 代码精简过
class Compiler {
    constructor () {}
    
    run(callback) {   
        // 判断compiler.running状态,防止重复执行相同的编译任务,若处于执行状态,则抛出异常终止本次调用
        if (this.running) {
            return callback(new ConcurrentCompilationError());
        }
        // finalCallback是编译流程的最终回调,持久化缓存的写入信号就是在这里释放的。
        const finalCallback = (err, stats) => {};
        // 设置compiler.running方法为true
        this.running = true;
        // 声明onCompiled内部方法,用于处理编译过程中的事件回调,根据编译的状态和钩子函数的返回值执行不同的操作
        const onCompiled = (err, compilation) => { };
        // 声明内部的run方法。
        const run = () => {};
        // 判断当前的空闲状态。该标识符在持久化缓存写入的时候为true,根据状态不同有不同的处理,true则需要等待缓存处理结束的会调里调用run方法启动编译。this.idle为false,则直接调用run方法
        if (this.idle) {
            this.cache.endIdle(err => {
                this.idle = false;
                run();
            });
        } else {
            run();
        }
    }
}

run方法

const run = () => {
    this.hooks.beforeRun.callAsync(this, err => {
        if (err) return finalCallback(err);

        this.hooks.run.callAsync(this, err => {
            if (err) return finalCallback(err);

            this.readRecords(err => {
                if (err) return finalCallback(err);

                this.compile(onCompiled);
            });
        });
    });
};

compiler.hooks.beforeRun.call

触发compiler.hooks.beforeRun钩子,传入compiler实例和回调。订阅该钩子的插件有

  1. NodeEnvironmentPlugin
class NodeEnvironmentPlugin {  
constructor(options) {  
    this.options = options;  
}  
apply(compiler) {  
    compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {  
        if (compiler.inputFileSystem === inputFileSystem) {  
            compiler.fsStartTime = Date.now();  
            inputFileSystem.purge();   // 出清文件系统
        }  
    });  
    }  
}

如果当前的文件系统是给定的inputFileSystem,则记录当前的时间,重新编译,此前的文件系统中的内容没用了
2. ProgressPlugin

class ProgressPlugin {
// 简化后的
    constructor () {
        apply(compiler) {
            interceptHook(compiler.hooks.beforeRun, 0.01, "setup", "before run")
        }
    }
}

输出webpack构建进度的

compiler.hooks.run.call

在compiler.hooks.beforeRun的回调中触发hooks.run钩子,webpack内部暂时无法插件订阅该钩子

compiler.readRecords方法

//代码简化
class Compiler {
    readRecords(callback) {
        if (this.hooks.readRecords.isUsed()) {
                if (this.recordsInputPath) {
                        asyncLib.parallel([
                                cb => this.hooks.readRecords.callAsync(cb),
                                this._readRecords.bind(this)
                        ]);
                } else {
                        this.records = {};
                        this.hooks.readRecords.callAsync(callback);
                }
        } else {
                if (this.recordsInputPath) {
                        this._readRecords(callback);
                } else {
                        this.records = {};
                        callback();
                }
        }
    }

}

首先判断是否注册了compiler.hooks.readRecords钩子,有则判断是否有recordsInputPath配置,有则触发this.hooks.readRecords,会触发使用records的相关插件执行。然后触发this._readRecords.bind(this)
this._readRecords

class Compielr {
    //....
    _readRecords(callback) {
        //路径不存在
        if (!this.recordsInputPath) {
            this.records = {};
            return callback();
        }
        // 路径存在
        this.inputFileSystem.stat(this.recordsInputPath, err => {
            this.inputFileSystem.readFile(this.recordsInputPath, (err, content) => {
                this.records = parseJson(content.toString("utf-8"));
                return callback();
            });
        });
    }
}

onCompiled函数

上面的函数处理的是beforeCompile到afterCompile钩子,onCompiled函数则处理后续的编译产物和records的写入工作。

const onCompiled = (err, compilation) => {
    // err对象,编译失败,调用finalCallback这个函数终止编译
    if (err) return finalCallback(err);
    // 触发this.hooks.shouldEmit.call这个钩子。
    // shouldEmit标识是否输出编译产物,如果不想让产物输出,可以订阅这个钩子,并在这个钩子最后返回false,这样可以防止产物写入本地文件系统。
    if (this.hooks.shouldEmit.call(compilation) === false) {
       // 阻止文件写入
        compilation.startTime = startTime;
        compilation.endTime = Date.now();
        const stats = new Stats(compilation);
        this.hooks.done.callAsync(stats, err => {
            if (err) return finalCallback(err);
            return finalCallback(null, stats);
        });
        return;
    }
    process.nextTick(() => {
        // 写入文件
        this.emitAssets(compilation, err => {
            if (compilation.hooks.needAdditionalPass.call()) {
            // 暂时忽略
            }
            // 完成写入工作
            this.emitRecords(err => {
                compilation.startTime = startTime;
                compilation.endTime = Date.now();
                const stats = new Stats(compilation);
                this.hooks.done.callAsync(stats, err => {
                    this.cache.storeBuildDependencies(
                        compilation.buildDependencies,
                        err => {
                            // records完成后调用finalCallback函数完成最终的收尾工作
                            return finalCallback(null, stats);
                        }
                    );
                });
            });
        });
    });
};

finalCallback函数
用于处理compiler.run方法的收尾工作

const finalCallback = (err, stats) => {
   // idle是处理持久化缓存的标识
    this.idle = true;
    this.cache.beginIdle();
    this.idle = true;
    // 关闭本编译器
    this.running = false;
    if (err) {
            this.hooks.failed.call(err);
    }
    // callback是webpack-cli里面启动编译器传入的,这里算是webpack编译器和webpack-cli在通信。
    if (callback !== undefined) callback(err, stats);
    // 触发compiler.hooks.afterDone钩子,告知订阅插件做收编译工作的总结。
    this.hooks.afterDone.call(stats);
};

调用compiler.cache.benginIdle()方法, 标识编译器空闲,可以进行缓存写入工作。后续在IdleFileCachePlugin和PackFileCacheStrategy完成

webpack流程

webpack的运行是一个串行的过程

  1. 初始化: 启动构建,读取以及合并参数,加载plugin,实例化compiler
  2. 编译,从entry发出,针对module串行调用对应的loader去翻译文件内容,然后找到module依赖的module,递归的进行编译处理
  3. 输出: 对编译后的module组合称为chunk,把chunk转换为文件,输出到文件系统
    未开启监听模式不会有文件发生变化的那个箭头,开启监听模式为
    在这里插入图片描述

初始化阶段

合并shell和配置文件的参数形成实例化complier对象 => 加载插件 => 处理入口

  • 初始化参数:从配置文件和shell语句中读取合并参数,得到最终的参数。还会执行配置文件中的插件实例化语句new plugin()
  • 实例化compiler: 从上一步的参数初始化compiler实例,compiler实例负责整个文件的监听和启动编译,compiler中包含了完整的webpack配置。全局中只有一个compiler实例对象
  • 加载插件: 依次调用插件的apply方法,让插件可以监听后续的所有事件节点,并且传入compiler实例的引用,方便插件可以通过compiler调用webpack提供的api
  • enviroment:开始应用node.js风格的文件系统到compiler对象,以方便后续的文件寻找和读取
  • entry-option:读取配置的entry是,为每个entry实例化一个对应的entryplugin,为后面的entry的递归解析做准备。
  • after-plugin: 调用完所有的内置和配置的插件的apply方法
  • after-resolvers: 根据配置初始化弯沉resolver,resolver负责在文件系统中寻找指定路径的文件

编译阶段

  • before-run: 清除缓存
  • run: 启动一次新的编译
  • watch-run: 和run类型,实在监视模式下启动的编译,这个事件中可以获得到哪些文件发生了变化导致重新启动一次新的编译
  • compile: 告诉插件一次新的编译要启动,会给插件带上compiler对象
  • compilation: 当webpack以开发模式运行的时候,当检测到文件变化的时候,一次新的compilation将会被创建,一个compilation对象包含了当前的模块资源,编译生成资源,变化的文件等,compilation对象也提供了很多事件回调供插件做拓展
  • make: 一个新的compilation创建完毕,就会从entry开始读取文件,根据文件类型和配置loader对文件进行编译,编译完成之后再找出对该文件依赖的文件,递归的进行编译和解析
  • after-compile: 一次compilation执行完成,这里会根据编译结果,合并出我们最终生成的文件名和文件内容
  • invalid: 当遇到文件不存在,文件编译错误等的异常情况下会触发这种事件,该事件不会导致webpack退出。
    compilation实际上就是调用相应的loader处理文件生成chunks并对这些chunks做优化的过程
  • build-module:使用对应的loader去转换一个模块
  • normal-module-loader: 使用loader对一个模块进行转换完之后,使用acorn转换后面的内容,输出对应的抽象语法树,方便以后webpack对后面的代码进行分析
  • program: 从配置的入口模块开始,分析其ast,当遇到require等导入其他模块的语句之后,将其加到依赖的模块列表中,同时找到新找出的依赖模块进行递归分析,最后搞清楚所有模块的依赖关系
  • seal: 所有模块及其依赖通过loader进行转换完成之后,根据依赖开始生成chunk

输出阶段

  • should-emit: 所有的输出文件已经生成好,询问插件那些文件需要输出,那些不需要
  • emit: 确定好那些文件后,执行文件输出,可以再这里获取和修改输出内容
  • after-emit: 文件输出完毕
  • done: 成功完成一次完成编译和输出流程
  • failed: 如果再编译和输出过程中遇到异常或者是导致webpack退出的时候,就会跳到此步骤,插件可以再本事件中获取到具体的错误原因。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1877756.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux 生产消费者模型

💓博主CSDN主页:麻辣韭菜💓   ⏩专栏分类:Linux初窥门径⏪   🚚代码仓库:Linux代码练习🚚   🌹关注我🫵带你学习更多Linux知识   🔝 前言 1. 生产消费者模型 1.1 什么是生产消…

FastAPI-Cookie

fastapi-learning-notes/codes/ch01/main.py at master Relph1119/fastapi-learning-notes GitHub 1、Cookie的作用 Cookie可以充当用户认证的令牌,使得用户在首次登录后无需每次手动输入用户名和密码,即可访问受限资源,直到Cookie过期或…

Hi3861 OpenHarmony嵌入式应用入门--LiteOS Semaphore做同步使用

信号量作为同步使用 创建一个Semaphore对象,并指定一个初始的计数值(通常称为“许可”或“令牌”的数量)。这个计数值表示当前可用的资源数量或可以同时访问共享资源的线程数。当一个线程需要访问共享资源时,它会尝试从Semaphore…

c++用什么软件编程?都有哪些?

c用什么软件编程?都有哪些? C 作为一种高效、面向对象的编程语言,广泛应用于软件开发、游戏开发、嵌入式系统等领域。那么在进行 C 编程时,我们通常会使用哪些软件呢?下面就来具体分析。 1. Visual Studio Visual Stu…

python selenium 打开网页

selenium工具类 - 文件名 seleniumkit.py 代码如下 # -*- coding:utf-8 _*-from selenium import webdriverimport os import timefrom selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from seleniu…

【计算机网络】期末复习(2)

目录 第一章:概述 第二章:物理层 第三章:数据链路层 第四章:网络层 第五章:传输层 第一章:概述 三大类网络 (1)电信网络 (2)有线电视网络 &#xff0…

Eclipse + GDB + J-Link 的单片机程序调试实践

Eclipse GDB J-Link 的调试实践 本文介绍如何创建Eclipse的调试配置,如何控制调试过程,如何查看修改各种变量。 对 Eclipse 的要求 所用 Eclipse 应当安装了 Eclipse Embedded CDT 插件。从 https://www.eclipse.org/downloads/packages/ 下载 Ecli…

快手正式推出Vision Pro版本,引领虚拟现实社交新潮流

6月28日,快手正式推出其专为Apple Vision Pro打造的版本——快手vp版app,成为国内首批登陆Apple Vision Pro的短视频平台。 借助先进的虚拟现实技术,用户可以在快手上体验更真实生动的视频内容,无论是观看趣味短视频内容&#xf…

怎样查看自己的Windows电脑最近弄了哪些内容

一、需求说明 有时候我们的电脑别人需要使用,你不给他使用又不行,且你也不在电脑身边,你只能告诉他自己的电脑密码让他操作,此时你并不不知道他操作了哪些内容。 还有一个种情况是自己不在电脑旁边,且电脑没有锁屏&…

鳗鱼-石斑鱼优化算法(EGO)-鲸鱼算法作者又提出新算法!公式原理详解与性能测评 Matlab代码免费获取

声明:文章是从本人公众号中复制而来,因此,想最新最快了解各类智能优化算法及其改进的朋友,可关注我的公众号:强盛机器学习,不定期会有很多免费代码分享~ 目录 原理简介 一、石斑鱼追踪猎物(勘探阶段) 二…

QGroundControl@Jetson Orin Nano - 从代码编译安装

QGroundControlJetson Orin Nano - Build from Source 1. 源由2. 步骤2.1 QT 编译2.1.1 下载2.1.2 版本2.1.3 初始化2.1.4 配置2.1.5 编译2.1.6 安装 2.2 QGC 编译2.2.1 下载2.2.2 版本2.2.3 初始化2.2.4 配置2.2.5 编译2.2.6 安装2.2.7 QT5命令备注 3. 可行方案4. 总结5. 补充…

如何用GPT开发一个基于 GPT 的应用?

原文发自博客:GPT应用开发小记 如何开发一个基于 GPT 的应用?答案就在问题里,那就是用 GPT 来开发基于 GPT 的应用。本文以笔者的一个开源项目 myGPTReader 为例,分享我是如何基于 GPT 去开发这个系统的,这个系统的功能…

Typora failed to export as pdf. undefined

变换版本并没有用,调整图片大小没有用 我看到一个博客后尝试出方案 我的方法 解决:从上图中的A4,变为其他,然后变回A4 然后到处成功,Amazing! 参考: Typora 导出PDF 报错 failed to export…

Tesseract Python 图片文字识别入门

1、安装tesseract Index of /tesseract https://digi.bib.uni-mannheim.de/tesseract/tesseract-ocr-w64-setup-v5.3.0.20221214.exe 2、安装中文语言包 https://digi.bib.uni-mannheim.de/tesseract/tessdata_fast/ 拷贝到C:\Program Files\Tesseract-OCR\tessdata 3、注…

《重构》读书笔记【第1章 重构,第一个示例,第2章 重构原则】

文章目录 第1章 重构,第一个示例1.1 重构前1.2 重构后 第2章 重构原则2.1 何谓重构2.2 两顶帽子2.3 为何重构2.4 何时重构2.5 重构和开发过程 第1章 重构,第一个示例 我这里使用的IDE是IntelliJ IDEA 1.1 重构前 plays.js export const plays {&quo…

springcloud第4季 springcloud-alibaba之nacos+openfegin+gateway+sentinel熔断限流【经典案例】

一 说明 1.1 架构说明 本案例实现原理: 采用alibaba的nacos,openfegin,sentinel,gateway等组件实现熔断限流。 主要理解sentinel的ResouceSentinel和fallback的区别联系。 ResourceSentinel 主要是页面配置熔断限流规则&#…

海康+libtorch的血泪教训

一、LibTorch使用, 详见: /INCLUDE:?warp_sizecudaatYAHXZ 二、海康二次开发, 目前选4.31,只能c14。 三、做dll注意:

实用的vueuseHooks,提高编码效率

文章目录 写在前面vueuse 官网安装HooksuseStorage [地址](https://vueuse.org/core/useStorage/)传统方法数据持久化 举例子传统持久化的弊端useStorage 数据持久化 举例子使用useStorage 更改存储数据使用useStorage 删除存储数据 useScriptTag [地址](https://vueuse.org/co…

FinalShell:功能强大的 SSH 工具软件,Mac 和 Win 系统的得力助手

在当今数字化的时代,SSH 工具软件成为了许多开发者、运维人员以及技术爱好者不可或缺的工具。而 FinalShell 作为一款出色的中文 SSH 工具软件,无论是在 Mac 系统还是 Windows 系统上,都展现出了卓越的性能和便捷的使用体验。 FinalShell 拥…

go语言DAY7 字典Map 指针 结构体 函数

Go中Map底层原理剖析_go map底层实现-CSDN博客 目录 Map 键值对key,value 注意: map唯一确定的key值通过哈希运算得出哈希值 一、 map的声明及初始化: 二、 map的增删改查操作: 三、 map的赋值操作与切片对比: 四、 通用所有…