Webpack 核心流程

news/2024/11/17 13:54:28/文章来源:https://www.cnblogs.com/dtux/p/18374385

我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。

本文作者:霜序

三个阶段

初始化阶段

  1. 初始化参数:从配置文件、配置对象、shell 参数中读取,与默认的配置参数结合得出最后的参数。
  2. 创建编译器对象:通过上一步得到的参数创建 Compiler 对象。
  3. 初始化编译器环境:注入内置插件、各种模块工厂、加载配置等。
  4. 开始编译:执行 compiler 对象的 run 方法。
  5. 确定入口:根据配置中的 entry 找到对应的入口文件,使用compilition.addEntry将入口文件转换为 dependence 对象。

构建阶段

  1. 编译模块(make):根据 entry 对应的 dependence 创建 module 对象,调用 loader 将模块转移成为标准的 JS 内容,在将其转成 AST 对象,从中找出该模块依赖的模块,再递归至所有的入口文件都经历了该步骤。
  2. 完成模块编译:上一步处理完成之后,得到每一个模块被转译之后的内容以及对应的依赖关系图。

生成阶段

  1. 输出资源(seal):根据入口文件和模块之间的依赖关系,组装成一个个包含多个模块的 chunk,再把每一个 chunk 转换成为单独的一个文件放到输出列表,这是最后一次可以修改输出内容的机会。
  2. 写入文件系统(emitAssets):确定好了输出内容,根据输出路径和文件名,把文件写入到文件系统。

初始化阶段

file

new webpack(config, callback)

webpack 支持两个参数,config 是 webpack.config.js 中的配置,callback 是回调函数。
webpack 引用于 webpack/lib/webpack.js
file
上图是 webpack() 的流程图,定义了 create 函数
create 函数主要完成

  1. 定义相关的参数
  2. 通过 createCompiler 创建 compiler 对象
  3. 返回 compiler 和其他参数

并会根据 callback 回调执行不同的操作:

  • 如果传入了 callback 参数,会通过 create 方法拿到对应的 compiler 对象,并执行 compiler.run 方法,返回 compiler 对象
    • 在这其中会判断是否配置 watch 参数,如果有会监听文件改变,重新编译
  • 如果没有传入 callback 参数,也会通过 create 方法拿到对应的 compiler 对象,直接返回

因此调用 webpack() 方法有两种方式:

// webpack 函数有传回调函数
const compiler = webpack(config, (err, stats) => {if (err) {console.log(err)}
})// 执行 webpack 函数没有传回调函数,手动调用一下 compiler.run
const compiler = webpack(config)
compiler.run((err, stats) => {if (err) {console.log(err)}
})

createCompiler

file
在上一步中,调用了 create 方法,compiler 对象实则是通过 createCompiler 函数返回的。
主要逻辑都是在 WebpackOptionsApply.process 中,该方法是将 config 中配置的属性转成 plugin 注入到 webpack 中。
通过 Compiler 类创建了 compiler 对象,通过 constructor 初始化一些内容

  • 使用 tapable 初始化一系列的 hooks。
  • 初始化一些参数。

compiler.run

在第一步的时候,调用 webpack 之后,最后都会调用 compiler.run 方法。
从代码中可以看出来,compiler.run 方法主要做了:

  1. 定义错误处理函数 finalCallback
  2. 定义 onCompiled 作为 this.compile 的回调
  3. 定义 run 方法,执行 run 方法

简单来说,compiler.run 其实最后调用的是 compiler.compile 方法

compiler.compile

compiler.compile 该方法中才开始做 make 处理
从代码中可以看出来,compiler.compile 方法主要做了:

  1. 初始化 compilation 参数,调用new Compilation创建 compilation 对象
  2. 执行 make hook,调用compilation.addEntry方法,进入构建阶段
  3. compilation.seal,执行 seal,对 make 阶段处理过的 module 代码进行封装, chunk 输出最终产物
  4. afterCompile hook,执行收尾逻辑

调用compile函数触发make钩子后,初始化阶段就算是结束了,流程逻辑开始进入「构建阶段

构建阶段

构建阶段主要使用的 compilation 对象,它和 compiler 是有区别的:

compiler:webpack 刚构建时就会创建 compiler 对象,存在于 webpack 整个生命周期 。
compilation:在准备编译某一个模块的时候才会创建,主要存在于 compile 到 make 这一段生命周期里面。
开启 wacth 对文件进行监听时,文件发生改变需要重新编译时,只需要重新创建一个 compilation 对象即可,不需要重新创建 compiler 对象做很多第一步初始化操作。如果改变了 config,则需要重新执行 dev/build 命令,创建新的 compiler 对象。

file

  1. 当执行初始化阶段的时候WebpackOptionsApply.process的时候会去初始化 EntryPlugin 调用compiler.hooks.make.tapAsync注册 compiler 的 make 钩子,用来开启编译。
  2. 当初始化完成之后调用compiler.compile方法时,会执行this.hooks.make.callAsync,从而开始执行compilation.addEntry添加入口文件。
  3. 调用handleModuleCreation方法,根据文件类型创建不同的 module。
  4. 调用module.build开始构建,通过 loader-runner 转译 module 内容,将各种资源转为 webpack 可以理解的 JavaScript 文本。
  5. 调用 acorn 的parse方法将 JS 代码解析成为 AST 结构。
  6. 通过 JavaScriptParser 类中遍历 AST,触发各种 hooks 。
    • 遇到 import 语句时,触发hooks.exportImportSpecifier
    • 该 hook 在 HarmonyExportDependencyParserPlugin 插件中被注册,会将依赖资源添加成为 Dependency 对象。
    • 调用module.addDependency将依赖对象加入到 module 依赖列表中。
  7. AST 遍历完毕后,调用module.handleParseResult处理模块依赖。
  8. 对于 module 新增的依赖,调用handleModuleCreate,控制流回到第一步。
  9. 所有依赖都解析完毕后,构建阶段结束。

在整个过程中数据流 module ⇒ AST ⇒ dependency ⇒ module 的转变,将源码转为 AST 主要是为了分析模块的 import 语句收集相关依赖数组,最后遍历 dependences 数组将 Dependency 转换为 Module 对象,之后递归处理这些新的 Module,直到所有项目文件处理完毕。

总结来说就是,从入口文件开始收集其依赖模块,并对依赖模块再进行相同的模块处理。

构建过程
file

例如上图,entry 文件为 index.js,分别依赖 a.js/b.js,其中 a.js 又依赖 c.js/d.js 。
第一步
根据 webpack 初始化之后,能够确定入口文件 index.js,并调用compilation.addEntry函数将之添加为 Module 对象。
file

第二步
通过 acorn 解析 index 文件,分析 AST 得到 index 有两个依赖。
file

第三步
得到了两个 dependence 之后,调用 module[index] 的 handleParserResult 方法处理 a/b 两个依赖对象。
file

第四步
又触发 module[a/b] 的 handleModuleCreation 方法,从 a 模块中又解析到 c/d 两个新依赖,于是再继续调用 module[a] 的 handleParseResult,递归上述流程。
file

第五步
最终得到 a/b/c/d 四个 Module 以及其对应的 dependence。
file
所有的模块构建完毕,没有新的依赖可以继续,由此进入生成阶段。

生成阶段

在构建阶段 make 结束之后,就会进入生成阶段,调用compilation.seal表明正式进入生成阶段。
在 seal 阶段主要是是将构建阶段生成的 module 拆分组合到 chunk 对象中,再转译成为目标环境的产物,并写出为产物文件,解决的是资源输出问题。
file

  1. 构建本次编译的ChunkGraph对象
  2. 通过hooks.optimizeDependencies优化模块依赖关系
  3. 循环compilation.entries入口文件创建 chunks,调用 addChunk 为每一个入口添加 chunk 对象,并且遍历当前入口的 dependency 对象找到对应 module 对象关联到该 chunk
  4. 触发optimizeModules/optimizeChunks等钩子,对 chunk 和 module 进行一系列的优化操作,这些优化操作都是有插件去完成的,例如 SplitChunksPlugin
  5. 调用codeGeneration方法生成 chunk 代码,会根据不同的 module 类型生成 template 代码
  6. 调用createChunkAssets方法为每一个 chunk 生成资产文件
  7. compilation.emitAsset 将产物提交到 compilation.assets 中,还尚未写入磁盘
  8. 最后执行 callback 回调回到 compile 的控制流中,执行 onCompiled 方法中的 compiler.emitAsset 输出资产文件

流转过程

在 webpack 执行的三个阶段,对应着资源形态扭转,每一个阶段操作的对象都是不一样的
file

  • compication.make
    • 以 entry 文件为入口,作为 dependency 放入 compilcation 的依赖列表
    • 根据 dependences 创建 module 对象,之后读入 module 对应的文件内容,调用 loader-runner 对内容做转化,转化结果若有其它依赖则继续读入依赖资源,重复此过程直到所有依赖均被转化为 module
  • compication.seal
    • 遍历所有的 module,根据 entry 的配置以及 module 的类型,分配到不同的 chunk
    • 将 chunk 构建成为 chunkGraph
    • 遍历 chunkGraph 调用 complication.emitAssets 方法标记 chunk 的输出规则,即转化为 assets 集合。
  • compiler.emitAssets
    • 将 assets 输出到文件系统

总结

  • 初始化阶段:负责构建环境,初始化工厂类,注入内置插件
  • 构建阶段:读入并分析 entry 文件,查找其模块依赖,再一次处理模块依赖的依赖,直到所有的依赖都被处理完毕,该过程解决资源输入问题
  • 生成阶段:根据 entry 的配置将模块封装称为不同的 chunk,经过一系列的优化再将模块代码编译成为最终的形态,按 chunk 合并成最后的产物,该过程解决资源输出问题

最后

欢迎关注【袋鼠云数栈UED团队】~
袋鼠云数栈 UED 团队持续为广大开发者分享技术成果,相继参与开源了欢迎 star

  • 大数据分布式任务调度系统——Taier
  • 轻量级的 Web IDE UI 框架——Molecule
  • 针对大数据领域的 SQL Parser 项目——dt-sql-parser
  • 袋鼠云数栈前端团队代码评审工程实践文档——code-review-practices
  • 一个速度更快、配置更灵活、使用更简单的模块打包器——ko
  • 一个针对 antd 的组件测试工具库——ant-design-testing

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法与数据结构——基本数据类型与编码

基本数据类型 基本数据类型是计算机CPU可以直接进行运算的类型,在算法中直接被使用,主要包括以下几种整数类型byte、short、int、long。 浮点数类型float、double,用于表示小数 字符类型char,用于表示各种语言的字母、标点符号甚至表情符号等。 布尔类型bool,用于表示“是…

中国电信公布2024年中期业绩!

2024年上半年,中国电信紧抓发展机遇,完整准确全面贯彻新发展理念,坚定履行建设网络强国和数字中国、维护网信安全责任,持续深入实施云改数转战略,加快推动数字信息基础设施智能化、绿色化演进升级,加大高质量产品和服务供给,加速战略新兴业务规模拓展,全面深化改革开放…

这是一款轻量存储黑科技!

近日,天翼云联合InfoQ举办了以“新存储,更轻量”为主题的线上技术分享会。天翼云存储产品线总监潘亚莲以“轻量级存储集群控制器HBlock的设计理念与场景实践”为主题,讲解了HBlock在产品理念、价值主张、应用场景等方面的内容。近日,天翼云联合InfoQ举办了以“新存储,更轻…

xlsx-js-style前端组装表格数据下载到本地

1.数据格式: 对象:key/valueconst data1 = {合同 主体信息:{\n "合同编号": "FWTsAEY-S1T-2023-01",\n "项目编号": "FTCG2023000265A",\n "项目名称": "福田区第二人民医院食堂服务采购项直",\n "总计…

Cookie,Session Filter,Listener详解

HTTP请求的无状态性HTTP的无状态性是其一个重要的特征,指的是HTTP协议本身并不保留客户端与服务器交互的历史信息,换而言之,即每次的HTTP请求都是独立的,服务器在处理每一个请求时都不会记住前一个请求的状态无状态的含义独立性:每次的HTTP请求都是独立的,不依赖于之前的请求,即…

探索风扇产品模型的3D可视化魅力

在这个科技日新月异的时代,每一个细微的创新都能为我们的生活带来前所未有的便捷与享受。今天,就让我们一起踏入一场视觉与科技的盛宴,探索风扇产品模型如何通过3D可视化技术,重新定义家居生活的舒适与美学。想象一下,在炎炎夏日,你无需亲临实体店,只需轻点鼠标或滑动指…

Tree组件的快速定位更新节点的状态,以及修改节点的数据属性等操作

当我们点击树节点的时候我们常常只能获得树的id,那么我么如何获快速定位到树节点的内容呢,除此之外,当树已经存在时,但是缺少我们想要的内容时,我们想在树节点上添加我们需要的额外的内容时该怎么办,那么就是用以下方法可以快速定位到我们需要的节点并可以快速添加内容当…

供应链管理系统(SCM) —— 企业物流的智能枢纽

SAP 供应链管理系统以打造数字化和集成化的供应链管理平台为使命,将传统的仓储管理系统、制造执行系统、产品管理系统等软件进行升级和上云管理,为企业提供面向客户、合作伙伴及员工的数字化SCM系统平台。SAP SCM系统从设计到运维,全面优化供应链,规避风险 供应链管理模式日…

Flannel IPsec 模式

Flannel IPsec 模式Flannel IPSec 模式 一、环境信息主机 IPubuntu 172.16.94.141软件 版本docker 26.1.4helm v3.15.0-rc.2kind 0.18.0clab 0.54.2kubernetes 1.23.4ubuntu os Ubuntu 20.04.6 LTSkernel 5.11.5 内核升级文档二、安装服务 kind 配置文件信息 $ cat install.sh#…

[Mysql]执行一条语句的过程

执行一条 select 语句,期间发生了什么? 学习 SQL 的时候,大家肯定第一个先学到的就是 select 查询语句了,比如下面这句查询语句: // 在 product 表中,查询 id = 1 的记录 select * from product where id = 1;但是有没有想过,MySQL 执行一条 select 查询语句,在 MySQL …

Free Online 1080P Youtube Video Downloader All In One

Free Online 1080P Youtube Video Downloader All In One 免费在线 1080P Youtube 视频下载器IELTS 雅思Free Online 1080P Youtube Video Downloader All In One免费在线 1080P Youtube 视频下载器https://yt5s.io/en93https://yt5s.biz/enxj100/ demosIELTS 雅思https://www.…

基于UltraScaleKU115的高性能PCIe3.0 x8-FMC+接口卡

基于UltraScaleKU115的高性能PCIe3.0 x8-FMC+接口卡,是一款高性能的PCIe 3.0 X8板卡。主控芯片采用Xilinx 公司Kintex UltraScale系列FPGA 家族中的XCKU115-FLVA1517(兼容XCKU085-FLVA1517)。板卡含有2 组共8GB(单组4GB)的DDR4 内存颗粒,支持JTAG 口和QSFP+光纤接口以及F…