webpack5 源码看构建过程

初始化阶段

webpack.js

  • 归一化 options,将部分配置转换成 webpack 需要的格式
  • 创建context上下文,取的是process.cwd()
  • 创建compiler实例
  • 初始化流插件
  • 初始化用户配置的插件,注册插件钩子
  • 进一步优化options,给一些配置赋上默认值
  • 初始化webpack内部插件,例如js解析器、缓存插件、添加入口的插件等。
const createCompiler = rawOptions => {
 
    // 归一化 options,将部分配置转换成 webpack 需要的格式
    const options = getNormalizedWebpackOptions(rawOptions);
    // 创建context上下文,取的是process.cwd()
    applyWebpackOptionsBaseDefaults(options);
 
    // 创建 complier 的实例
    const compiler = new Compiler(options.context, options);
 
    // 初始化流插件
    new NodeEnvironmentPlugin({
        infrastructureLogging: options.infrastructureLogging
    }).apply(compiler);
 
    // 初始化用户配置的插件,注册插件钩子
    if (Array.isArray(options.plugins)) {
        for (const plugin of options.plugins) {
            if (typeof plugin === "function") {
                plugin.call(compiler, compiler);
            } else {
                plugin.apply(compiler);
            }
        }
    }
 
    // 进一步优化options,给一些配置赋上默认值
    applyWebpackOptionsDefaults(options);
 
    compiler.hooks.environment.call();
    compiler.hooks.afterEnvironment.call();
 
    // 初始化webpack内部插件,例如js解析器、缓存插件、添加入口的插件等。
    new WebpackOptionsApply().process(options, compiler);
 
    compiler.hooks.initialize.call();
    return compiler;
};

Complier.js

hooks 调用了tapable这个库!这个库是webpack团队为了webpack专门写的一个事件/钩子库。

this.hooks = Object.freeze({
    /** @type {SyncHook<[]>} */
    initialize: new SyncHook([]),
})

webpack是一个事件驱动的框架,webpack负责搭架子,安排好每个流程,再由事件触发插件去做具体的事情。

webpack的流程至少有以下这几个事件钩子:

  • env
  • init
  • run
  • beforeCompile
  • compile
  • compilation
  • make:
  • finishMake
  • afterCompile
  • emit

核心事件

1、make,读取入口文件,分析和收集依赖

EntryPlugin.js

compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
        // 入口插件,创建编译入口
        compilation.addEntry(context, dep, options, err => {
            callback(err);
        });
    });

2、make - finishMake 之间

EntryPlugin 的 addEntry 函数就是 make 阶段最重要的事情之一

Complition.js

// 存入 this.entryies, 后续构建 chunk 遍历的是该 map 对象
    this._addEntryItem(context, entry, "dependencies", options, callback);
 
    // 开始创建模块实例
        this.handleModuleCreation(
            {
                factory: moduleFactory,
                dependencies: [dependency],
                originModule: null,
                contextInfo,
                context
            },
            (err, result) => {
                if (err && this.bail) {
                    callback(err);
                    this.buildQueue.stop();
                    this.rebuildQueue.stop();
                    this.processDependenciesQueue.stop();
                    this.factorizeQueue.stop();
                } else if (!err && result) {
                    callback(null, result);
                } else {
                    callback();
                }
            }
        );
 
// 异步的任务队列, 有任务就会自动执行
Compilation.prototype.factorizeModule = /** @type {{
    (options: FactorizeModuleOptions & { factoryResult?: false }, callback: ModuleCallback): void;
    (options: FactorizeModuleOptions & { factoryResult: true }, callback: ModuleFactoryResultCallback): void;
}} */ (
    function (options, callback) {
        // factorizeQueue 是一个异步的任务队列,任务队列发现有任务就会自动执行
        this.factorizeQueue.add(options, callback);
    }
);
 
buildModule(module, callback) {
        // 创建loader上下文
        // runLoaders,通过enhanced-resolve解析得到的模块和loader的路径获取函数,执行loader
        // 调用JavascriptParser.js将loader执行完的源码解析成ast(使用了acorn工具),这步会生成当前模块的以来集合
        // 生成模块的hash
        // 缓存解析完的module至_modulesCache,此时已经有_source(解析后的源码)
 
        this.buildQueue.add(module, callback);
    }

构建过程

1、初始化阶段:根据配置中的 entry 找出所有的入口文件,调用 compilition.addEntry 将入口文件转换为 dependence 对象

2、构建阶段:根据 entry 对应的 dependence 创建 module 对象,调用 loader 将模块转译为标准 JS 内容,调用 JS 解释器将内容转换为 AST 对象,从中找出该模块依赖的模块,再 递归 本步骤直到所有入口依赖的文件都经过了本步骤的处理「完成模块编译」:上一步递归处理所有能触达到的模块后,得到了每个模块被翻译后的内容以及它们之间的 「依赖关系图」

3、生成阶段:「输出资源(seal)」:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会「写入文件系统(emitAssets)」:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

在这里插入图片描述

构建阶段

1、调用 handleModuleCreate ,根据文件类型构建 module 子类

2、调用 loader-runner 仓库的 runLoaders 转译 module 内容,通常是从各类资源类型转译为 JavaScript 文本

3、调用 acorn 将 JS 文本解析为 AST

4、遍历 AST,触发各种钩子在 HarmonyExportDependencyParserPlugin 插件监听 exportImportSpecifier 钩子,解读 JS 文本对应的资源依赖调用 module 对象的 addDependency 将依赖对象加入到 module 依赖列表中

5、AST 遍历完毕后,调用 module.handleParseResult 处理模块依赖

6、对于 module 新增的依赖,调用 handleModuleCreate ,控制流回到第一步

7、所有依赖都解析完毕后,构建阶段结束

这个过程中数据流 module => ast => dependences => module ,先转 AST 再从 AST 找依赖。这就要求 loaders 处理完的最后结果必须是可以被 acorn 处理的标准 JavaScript 语法,比如说对于图片,需要从图像二进制转换成类似于 export default “data:image/png;base64,xxx” 这类 base64 格式或者 export default “http://xxx” 这类 url 格式。

compilation 按这个流程递归处理,逐步解析出每个模块的内容以及 module 依赖关系,后续就可以根据这些内容打包输出。
在这里插入图片描述

ast 树的生成与依赖收集过程

https://astexplorer.net/

在这里插入图片描述

在这里插入图片描述

生成阶段

生成阶段则围绕 chunks 展开。经过构建阶段之后,webpack 得到足够的模块内容与模块关系信息,接下来开始生成最终资源了

1、生成 module 和 chunks 的集合对象(有很长一段代码在处理这块的逻辑)

2、调用 createModuleHashes 生成 module hash

3、调用 codeGneration 生成代码,写入缓存

4、触发 seal 回调,控制流回到 compiler 对象

5、写入文件

这一步的关键逻辑是将 module 按规则组织成 chunks ,webpack 内置的 chunk 封装规则比较简单:

1、entry 及 entry 触达到的模块,组合成一个 chunk

2、使用动态引入语句引入的模块,各自组合成一个 chunk

chunk 是输出的基本单位,默认情况下这些 chunks 与最终输出的资源一一对应,那按上面的规则大致上可以推导出一个 entry 会对应打包出一个资源,而通过动态引入语句引入的模块,也对应会打包出相应的资源。

seal 结束之后,紧接着调用 compiler.emitAssets 函数,函数内部调用 compiler.outputFileSystem.writeFile 方法将 assets 集合写入文件系统

大致上可以梳理成这么4个步骤:

  1. 遍历 compilation.modules ,记录下模块与 chunk 关系
  2. 触发各种模块优化钩子,这一步优化的主要是模块依赖关系
  3. 遍历 module 构建 chunk 集合
  4. 触发各种优化钩子

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当运行命令`npm run build:dev`时,存在一些可能的源码漏洞。首先,Webpack是一个模块打包工具,用于将多个模块(不同的JavaScript文件、样式文件等)合并为一个或多个bundle.js文件。在构建过程中,可能会存在一些安全隐患或源码漏洞。 1. 依赖漏洞:在项目的package.json文件中,可能存在一些依赖库版本过低或存在已知的安全漏洞。运行`npm run build:dev`时,Webpack会处理这些依赖,并将它们打包到bundle.js中。一个依赖库的漏洞可能会导致整个应用程序存在安全风险。 2. 配置漏洞:Webpack的配置文件(通常为webpack.config.js)中可能存在漏洞,例如文件路径配置错误或配置了不安全的插件。这些配置漏洞可能会导致打包的代码容易受到攻击,例如注入恶意代码或泄露敏感信息。 3. 源代码引入不安全的模块:在代码编写过程中,开发人员可能不小心引入了不可信或不安全的外部模块。这些模块可能会包含恶意代码,从而导致打包生成的bundle.js文件存在安全漏洞。 为了避免这些源码漏洞,建议采取以下措施: 1. 定期更新和检查依赖库版本,确保使用的库没有已知的安全漏洞。可以通过运行`npm outdated`命令来检查过期的依赖项,并使用`npm update`来更新它们。 2. 仔细审查和调整Webpack的配置文件,确保没有不必要的插件和配置,并确保路径配置正确、安全。 3. 在引入外部模块之前,对其进行详细审查和调查,以确保其源代码的可靠性和安全性。 如果发现有任何源码漏洞,应立即采取相应的措施来修复漏洞,例如更新依赖库、修复配置文件或更换不可信的模块。此外,定期进行代码审查和安全性检查也是一个好习惯,以确保应用程序的安全性和稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值