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

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.mfbz.cn/a/757354.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用鸿蒙HarmonyOs NEXT 开发 快速开发 简单的购物车页面

目录 资源准备:需要准备三张照片:商品图、向下图标、金钱图标 1.显示效果: 2.源码: 资源准备:需要准备三张照片:商品图、向下图标、金钱图标 1.显示效果: 定义了一个购物车页面的布局&#x…

[方法] Unity 3D模型与骨骼动画

1. 在软件中导出3D模型 1.1 3dsmax 2014 1.1.1 TGA转PNG 3dsmax的贴图格式为tga,我们需要在在线格式转换中将其转换为Unity可识别的png格式。 1.1.2 模型导出 导出文件格式为fbx。在导出设置中,要勾选三角算法,取消勾选摄像机和灯光&#…

mysql解压版本安装5.7

1. 官网下载好解压版本 我这边5.7版本 https://dev.mysql.com/downloads/file/?id523570 mysql官网 创建 my.ini文件 内容如下 [client] #客户端设置,即客户端默认的连接参数# socket /data/mysqldata/3306/mysql.sock #用于本地连接的socket套接字 # 默…

运维锅总详解HAProxy

本文尝试从HAProxy简介、HAProxy工作流程及其与Nginx的对比对其进行详细分析;在本文最后,给出了为什么Nginx比HAProxy更受欢迎的原因。希望对您有所帮助! HAProxy简介 HAProxy(High Availability Proxy)是一款广泛使…

【知识学习】阐述Unity3D中Profile和性能的概念及使用方法示例

在Unity3D中,"Profile"和"性能"是两个相关但不同的概念,它们在游戏开发中扮演着重要的角色。 Profile(配置文件) "Profile"在Unity中通常指的是一种配置文件,它包含了一系列的设置和参…

JAVA医院绩效考核管理系统源码:系统优势、系统目的、系统原则 (自主研发 功能完善 可直接上项目)

JAVA医院绩效考核管理系统源码:系统优势、系统目的、系统原则 (自主研发 功能完善 可直接上项目) 医院绩效考核系统优势 1.实现科室负责人单独考核 对科室负责人可以进行单独考核、奖金发放。 2. 科室奖金支持发放到个人 支持奖金二次分配&…

Numpy array和Pytorch tensor的区别

1.Numpy array和Pytorch tensor的区别 笔记来源: 1.Comparison between Pytorch Tensor and Numpy Array 2.numpy.array 4.Tensors for Neural Networks, Clearly Explained!!! 5.What is a Tensor in Machine Learning? 1.1 Numpy Array Numpy array can only h…

已解决问题 | 该扩展程序未列在 Chrome 网上应用店中,并可能是在您不知情的情况下添加的

在Chrome浏览器中,如果你看到“该扩展程序未列在 Chrome 网上应用店中,并可能是在您不知情的情况下添加的”这样的提示,通常是因为该扩展程序没有通过Chrome网上应用店进行安装。以下是解决这个问题的步骤: 解决办法:…

Bridging nonnull in Objective-C to Swift: Is It Safe?

Bridging nonnull in Objective-C to Swift: Is It Safe? In the world of iOS development, bridging between Objective-C and Swift is a common practice, especially for legacy codebases (遗留代码库) or when integrating (集成) third-party libraries. One importa…

uniapp+vue3开发微信小程序踩坑集

本文主要记录使用uniappvue3开发微信小程序遇见的各种常见问题及注意点。(持续更新) 问题: 自定义组件为什么有些样式加不上去 给自定义组件增加class的时候,有时候不生效有时候生效,一度让我怀疑自己记忆错乱。后来…

揭秘Etched AI:三个哈佛辍学00后挑战英伟达,推出Transformer专用ASIC芯片sohu

人工智能领域最近掀起了一股新的热潮,三位哈佛辍学的00后本科生创建了Etched AI,并成功推出了一款超强AI芯片sohu,直指英伟达的AI芯片帝国。这款芯片被誉为比英伟达H100快20倍,吸引了众多科技界的关注。本文将深入探讨Etched AI及…

css 布局出现无法去除的空白

案件介绍&#xff1a;在没有设置任何的css样式的情况下 文字顶部出现无法去除的空白 源代码 <div click"onClick" ><div class"tableTextButton--container"></div><Icon v-if"loading || thisLoading" type"ios-lo…

springboot的特点是什么?

Spring Boot是一个基于Spring框架的开源项目&#xff0c;它旨在简化Spring应用的初始搭建和开发过程。以下是Spring Boot的一些主要特点&#xff1a; 快速开发&#xff1a; Spring Boot提供了许多默认配置&#xff0c;使得开发者可以更快地开始开发应用程序&#xff0c;而无需…

Linux Doxygen快速生成文档

此前写过一篇编写Doxygen格式的注释以用于生成文档,点击以查阅, Doxygen常用语法与字段记录,但是当时用的windows桌面版的doxygen,最近使用ubuntu编写代码想直接使用doxygen生成,故写下此博客 Doxygen Doxygen是一个用于生成软件文档的工具&#xff0c;它可以从代码中提取注释…

工业自动化控制中心

目录 一 设计原型 二 后台源码 一 设计原型 二 后台源码 using System; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms;namespace 工业自动化控制中心 {public partial class Form1 : Form{public Form1(){InitializeComponent();}pri…

【智能算法】目标检测算法

目录 一、目标检测算法分类 二、 常见目标检测算法及matlab代码实现 2.1 R-CNN 2.1.1 定义 2.1.2 matlab代码实现 2.2 Fast R-CNN 2.2.1 定义 2.2.2 matlab代码实现 2.3 Faster R-CNN 2.3.1 定义 2.3.2 matlab代码实现 2.4 YOLO 2.4.1 定义 2.4.2 matlab代码实现…

boost asio异步服务器(4)处理粘包

粘包的产生 当客户端发送多个数据包给服务器时&#xff0c;服务器底层的tcp接收缓冲区收到的数据为粘连在一起的。这种情况的产生通常是服务器端处理数据的速率不如客户端的发送速率的情况。比如&#xff1a;客户端1s内连续发送了两个hello world&#xff01;,服务器过了2s才接…

昇思25天学习打卡营第11天|SSD目标检测

1. 学习内容复盘 模型简介 SSD&#xff0c;全称Single Shot MultiBox Detector&#xff0c;是Wei Liu在ECCV 2016上提出的一种目标检测算法。使用Nvidia Titan X在VOC 2007测试集上&#xff0c;SSD对于输入尺寸300x300的网络&#xff0c;达到74.3%mAP(mean Average Precision)…

amis源码 更新组件数据域的几种方法

更新组件数据域的几种方法&#xff1a; 默认都是合并数据&#xff0c;非覆盖(指定replace为true的才是覆盖)&#xff1a; const comp amisScoped.getComponentById(id);//或者getComponentByName(name) 1.comp.setData(values, replace); //更新多个值values&#xff0c; r…

开启网络监控新纪元:免费可视化工具助力网络信息链路拓扑监控大屏

在数字化浪潮汹涌的今天&#xff0c;网络已成为我们生活、工作的不可或缺的一部分。然而&#xff0c;你是否曾经想过&#xff0c;在这个庞大的网络世界中&#xff0c;是谁在默默守护着每一条信息的传输&#xff0c;确保我们的数据安全、稳定地抵达目的地&#xff1f; 网络信息链…