写点什么

webpack 编译原理

用户头像
法医
关注
发布于: 3 小时前
webpack编译原理

webpack 的作用是根据入口文件将源代码编译(构建、打包)成最终代码。中间经过 webpack 打包,打包的过程就是编译



整个过程大致分为三个步骤:初始化编译(最重要)输出


初始化

在初始化这个阶段 webpack 会将CLI参数配置文件默认配置进行融合,形成一个最终的配置对象。


CLI 参数:使用命令行工具,可能会加一些参数进去,比如:


npx webpack --mode=development --config xxx
复制代码


配置文件:webpack.config.js文件里面的配置


默认配置:比如入口 entry,默认为 ./src/index.js


对配置的处理过程是依托一个第三方库 yargs 完成的,yargs 库就是融合配置的。初始化阶段相对比较简单,主要是为接下来的编译阶段做必要的准备。目前,可以简单的理解为:初始化阶段主要用于产生一个最终的配置

编译

1. 创建 chunk


chunk是 webpack 在内部构建过程中的一个概念,译为,它表示通过某个入口找到的所有依赖的统称,比方说:入口模块(./src/index.js)依赖 a 模块(./src/a.js),a 模块又依赖 b 模块(./src/b.js),通过一个入口模块分析依赖关系,可以找到三个模块,那么index.jsa.jsb.js这三个统称为一个chunk


根据入口模块(默认为./src/index.js)创建一个 chunk,每一个 chunk 是有名字的,意味着 chunk 有可能也会有多个,入口文件是可以有多个的。



默认情况下只有一个 chunk,每个 chunk 都有至少两个属性:


name:默认为 main


id:唯一编号,如果是开发环境,那么 id 和 name 相同,如果是生产环境,则是一个数字,从 0 开始。


2. 构建所有依赖模块



我们先通过下面代码来简单过一遍这个图:


代码:


//模块名:./src/index.js (未加载状态)
//模块内容:console.log("index");require("./a");require("./b");
复制代码


  • 第 1 步:根据入口模块文件(./src/index.js)进行构建,模块文件它是有一个路径的,入口模块文件路径就是./src/index.js,它会通过这个路径检查当前这个模块是否已经加载过,注意哦:它不是运行模块,而是瞅一眼,看看模块记录表(上图右边蓝色表格)中该模块是不是被加载过,首次检查表格是没有内容的,空的。

  • 第 2 步:如果说模块记录表中有记录,说明模块已经加载过了。如果没有记录,那么会继续走下一步,说明该模块需要加载


检查 ./src/index.js 模块,发现该模块并未加载过
复制代码


  • 第 3 步:读取该模块中的内容,内容其实是个字符串


//读取内容(字符串)console.log("index");require("./a");require("./b");
复制代码


  • 第 4 步:对模块的内容进行语法分析,树形结构遍历,找到所有依赖,最后生成 AST 抽象语法树


require("./a");require("./b");
复制代码


AST 在线测试工具:https://astexplorer.net/


  • 第 5 步:将分析出来的依赖记录到 dependencies 数组中


//记录依赖["./src/a.js","./src/b.js"]
复制代码


  • 第 6 步:替换依赖函数,什么意思呢?就是说把有依赖的地方变一种代码格式,将require改为_webpack_require,将依赖的模块改为模块id


console.log("index");_webpack_require("./src/a.js");_webpack_require("./src/b.js");
复制代码


  • 第 7 步:我们将替换后的代码称为转换后的模块代码,并且把它保存到模块记录表中

  • 第 8 步:index.js 模块处理完成,由于 index.js 依赖其它模块,所以递归循环保存在 dependencies 数组中的依赖,开始分析./src/a.js 模块,从头再走一遍这个流程就可以了,假设 a 模块依赖./src/b.js 模块,那么它会等 a 模块处理完成后,再处理 a 模块所依赖的 b 模块,再最后处理 index 模块所依赖的 b 模块,此时它会发现 b 模块在处理 a 模块所依赖的 b 模块已经加载过了,那么 index 模块所依赖的 b 模块是不会进行下一步处理,直接结束。


以上就是 webpack 编译过程,做这一切最终的目的就是形成一个模块记录表。下面有个简图,经过上述编译过程之后会在 chunk 中通过入口文件加载形成多个模块,每个模块记录了转换之后的代码。



3. 产生 chunk assets


在第二步完成后,chunk 中会产生一个模块列表,列表中包含了模块id模块转换后的代码。接下来,webpack 会根据配置为 chunk 生成一个资源列表,即chunk assets,资源列表可以理解为是生成到最终文件的文件名和文件内容。



chunk hash 是根据所有 chunk assets 的内容生成的一个 hash 字符串


hash:一种算法,具体有很多分类,特点是将一个任意长度的字符串转换为一个固定长度的字符串,而且可以保证原始内容不变,产生的 hash 字符串就不变。


简图:



4. 合并 chunk assets


将多个 chunk 的 assets 合并到一起,并产生一个总的 hash


输出 emit

webpack 将利用 node 中的 fs 模块(文件处理模块),根据编译产生的总的 assets,生成相应的文件。


总过程


当敲下 webpack 打包命令之后,文件开始初始化,各个参数进行融合,形成一个最终的配置对象,然后把配置对象交给编译器进行编译,通过入口模块找到互相依赖模块形成模块列表,接下来 webpack 会根据配置,为 chunk 生成一个资源列表,然后将每一个 chunk 生成的资源合并成一个完整的资源,并且生成一个完整的 hash 值,最终根据完整的资源列表输出到文件。


涉及术语


  1. module:模块,分割的代码单元,webpack 中的模块可以是任何内容的文件,不仅限于 JS

  2. chunk:webpack 内部构建模块的块,一个 chunk 中包含多个模块,这些模块是从入口模块通过依赖分析得来的

  3. bundle:chunk 构建好模块后会生成 chunk 的资源清单,清单中的每一项就是一个 bundle,可以认为 bundle 就是最终生成的文件

  4. hash:最终的资源清单所有内容联合生成的 hash 值

  5. chunkhash:chunk 生成的资源清单内容联合生成的 hash 值

  6. chunkname:chunk 的名称,如果没有配置则使用 main

  7. id:通常指 chunk 的唯一编号,如果在开发环境下构建,和 chunkname 相同;如果是生产环境下构建,则使用一个从 0 开始的数字进行编号


📌 最后


若本文对于 webpack编译原理 阅读有任何错误的地方,欢迎大家给我提意见,一定虚心听取你们的指正


发布于: 3 小时前阅读数: 2
用户头像

法医

关注

公众号@前端猎手 2020.07.17 加入

喜欢用写作记录自己技术成长过程

评论

发布
暂无评论
webpack编译原理