主体架构
借用大佬的一张图,这张图就很明了,从开始到结束,什么时候执行什么,干什么事情。什么时候执行的 loader,而plugin 则是贯穿了整个 webpack 的生命周期。
核心流程
webpack 最核心的流程:
At its core, webpack is a static module bundler for modern JavaScript applications.
也就是将各种类型的资源,包括图片、css、js 等,转译、组合、拼接、生成 JS 格式的 bundler 文件。官网首页的动画也很形象表达了这一点。
这个过程完成了 内容转换 + 资源合并 两种功能,实现上包含三个阶段:
1、初始化阶段:
- 初始化参数:从配置文件、配置对象、Shell 参数中读取,与默认配置结合得出最终的参数;
- 创建编译器对象:用上一 步得到的参数创建 Compiler 对象;
- 初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化 RuleSet 集合、加载配置的插件等;
- 开始编译:执行 compiler 对象的 run 方法;
- 确定入口:根据配置中的 entry 找出所有的入口文件,调用 compilition.addEntry将入口文件转换为 depenence 对象;
2、构建阶段:
- 编译模块(make):根据 entry 对应的 dependence 创建 module 对象,调用 loader 将模块转译标准JS 内容,调用 JS 解释器将内容转换为 AST 对象,从中找出该模块,再递归本步骤直到所有入口的文件都经过了本步骤的处理;
- 完成模块编译:上一步递归处理所有能触达到的模块后,得到了每个模块被翻译后的内容以及他们的依赖关系图;
3、生成阶段:
- 输出资源(seal):根据入口模块之间的依赖关系,组装成一个个包含多个模块的 chunk,再把每个 chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
- 写入文件系统(emitAssets):在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
单次构建过程自上而下按顺序执行,下面会展开聊聊细节,在此之前,对上述提及的各类技术名词不太熟悉的同学,可以先看看简介:
- Entry:编译入口,webpack 编译的起点;
- Complier:编译管理器,webpack 启动后会创建 compiler 对象,该对象一直存活直到结束退出;
- Compilation:单次编辑过程的管理器,比如watch=true 时,运行过程中只有一个 compiler,但每次文件变更触发重新编译时,都会创建一个新的 compilation 对象;
- Dependence:依赖对象,webpack 基于该类型记录模块间依赖关系;
- Module:webpack 内部所有资源都会以"module"对象形式存在,所有关于资源的操作、转译、合并都是以"module"为基本单位进行的;
- Chunk:编译完成准备输出时,webpack 会将 module 按特定的规则组织成一个个的 chunk,这些 chunk 某种程度上跟最终输出一一对应;
- Loader:资源内容转换器,其实就是实现从内容 A 转换 B 的转换器;
- Plugin:webpack 构建过程中,会在特定的时间广播对应的事件,插件监听这些时事件,在特定时间点接入编译过程;
关于 module 、chunk、bundle 的理解
看这个图就很明白了:
- 对于一份同逻辑的代码,当我们手写下一个一个的文件,它们无论是 ESM 还是 commonJS 或是 AMD,他们都是 module ;
- 当我们写的 module 源文件传到 webpack 进行打包时,webpack 会根据文件引用关系生成 chunk 文件,webpack 会对这个 chunk 文件进行一些操作;
- webpack 处理好 chunk 文件后,最后会输出 bundle 文件,这个 bundle 文件包含了经过加载和编译的最终源文件,所以它可以直接在浏览器中运行。
一般来说一个 chunk 对应一个 bundle,比如上图中的 utils.js -> chunks 1 -> utils.bundle.js;但也有例外,比如说上图中,我就用 MiniCssExtractPlugin 从 chunks 0 中抽离出了 index.bundle.css 文件。
module,chunk 和 bundle 其实就是同一份逻辑代码在不同转换场景下的取了三个名字:
我们直接写出来的是 module,webpack 处理时是 chunk,最后生成浏览器可以直接运行的 bundle。
动态导入的情况
举个例子 : 10个js文件 打包运行时先生成 3个chunk 最后3个chunk合并为一个bundle 。chunk是在运行时起作用的,按需加载的原理又是另一部分。按需是把本应该在bundle里的部分 单独拿出来, 放到顶层的promise队列里面 ,用函数异步import 达到按需的效果。所以 按需的chunk并没有合并到bundle里 。