Webpack 核心流程
我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。
本文作者:霜序
三个阶段
初始化阶段
- 初始化参数:从配置文件、配置对象、shell 参数中读取,与默认的配置参数结合得出最后的参数。
- 创建编译器对象:通过上一步得到的参数创建 Compiler 对象。
- 初始化编译器环境:注入内置插件、各种模块工厂、加载配置等。
- 开始编译:执行 compiler 对象的 run 方法。
- 确定入口:根据配置中的 entry 找到对应的入口文件,使用
compilition.addEntry将入口文件转换为 dependence 对象。
构建阶段
- 编译模块(make):根据 entry 对应的 dependence 创建 module 对象,调用 loader 将模块转移成为标准的 JS 内容,在将其转成 AST 对象,从中找出该模块依赖的模块,再递归至所有的入口文件都经历了该步骤。
- 完成模块编译:上一步处理完成之后,得到每一个模块被转译之后的内容以及对应的依赖关系图。
生成阶段
- 输出资源(seal):根据入口文件和模块之间的依赖关系,组装成一个个包含多个模块的 chunk,再把每一个 chunk 转换成为单独的一个文件放到输出列表,这是最后一次可以修改输出内容的机会。
- 写入文件系统(emitAssets):确定好了输出内容,根据输出路径和文件名,把文件写入到文件系统。
初始化阶段

new webpack(config, callback)
webpack 支持两个参数,config 是 webpack.config.js 中的配置,callback 是回调函数。
webpack 引用于 webpack/lib/webpack.js

上图是 webpack() 的流程图,定义了 create 函数
create 函数主要完成
- 定义相关的参数
- 通过 createCompiler 创建 compiler 对象
- 返回 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

在上一步中,调用了 create 方法,compiler 对象实则是通过 createCompiler 函数返回的。
主要逻辑都是在 WebpackOptionsApply.process 中,该方法是将 config 中配置的属性转成 plugin 注入到 webpack 中。
通过 Compiler 类创建了 compiler 对象,通过 constructor 初始化一些内容
- 使用 tapable 初始化一系列的 hooks。
- 初始化一些参数。
compiler.run
在第一步的时候,调用 webpack 之后,最后都会调用 compiler.run 方法。
从代码中可以看出来,compiler.run 方法主要做了:
- 定义错误处理函数 finalCallback
- 定义 onCompiled 作为 this.compile 的回调
- 定义 run 方法,执行 run 方法
简单来说,compiler.run 其实最后调用的是 compiler.compile 方法
compiler.compile
compiler.compile 该方法中才开始做 make 处理
从代码中可以看出来,compiler.compile 方法主要做了:
- 初始化 compilation 参数,调用
new Compilation创建 compilation 对象 - 执行 make hook,调用
compilation.addEntry方法,进入构建阶段 - compilation.seal,执行 seal,对 make 阶段处理过的 module 代码进行封装, chunk 输出最终产物
- afterCompile hook,执行收尾逻辑
调用compile函数触发make钩子后,初始化阶段就算是结束了,流程逻辑开始进入「构建阶段」
构建阶段
构建阶段主要使用的 compilation 对象,它和 compiler 是有区别的:
compiler:webpack 刚构建时就会创建 compiler 对象,存在于 webpack 整个生命周期 。
compilation:在准备编译某一个模块的时候才会创建,主要存在于 compile 到 make 这一段生命周期里面。
开启 wacth 对文件进行监听时,文件发生改变需要重新编译时,只需要重新创建一个 compilation 对象即可,不需要重新创建 compiler 对象做很多第一步初始化操作。如果改变了 config,则需要重新执行 dev/build 命令,创建新的 compiler 对象。

- 当执行初始化阶段的时候
WebpackOptionsApply.process的时候会去初始化 EntryPlugin 调用compiler.hooks.make.tapAsync注册 compiler 的 make 钩子,用来开启编译。 - 当初始化完成之后调用
compiler.compile方法时,会执行this.hooks.make.callAsync,从而开始执行compilation.addEntry添加入口文件。 - 调用
handleModuleCreation方法,根据文件类型创建不同的 module。 - 调用
module.build开始构建,通过 loader-runner 转译 module 内容,将各种资源转为 webpack 可以理解的 JavaScript 文本。 - 调用 acorn 的
parse方法将 JS 代码解析成为 AST 结构。 - 通过 JavaScriptParser 类中遍历 AST,触发各种 hooks 。
- 遇到 import 语句时,触发
hooks.exportImportSpecifier。 - 该 hook 在 HarmonyExportDependencyParserPlugin 插件中被注册,会将依赖资源添加成为 Dependency 对象。
- 调用
module.addDependency将依赖对象加入到 module 依赖列表中。
- 遇到 import 语句时,触发
- AST 遍历完毕后,调用
module.handleParseResult处理模块依赖。 - 对于 module 新增的依赖,调用
handleModuleCreate,控制流回到第一步。 - 所有依赖都解析完毕后,构建阶段结束。
在整个过程中数据流 module ⇒ AST ⇒ dependency ⇒ module 的转变,将源码转为 AST 主要是为了分析模块的 import 语句收集相关依赖数组,最后遍历 dependences 数组将 Dependency 转换为 Module 对象,之后递归处理这些新的 Module,直到所有项目文件处理完毕。
总结来说就是,从入口文件开始收集其依赖模块,并对依赖模块再进行相同的模块处理。
构建过程

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

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

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

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

第五步
最终得到 a/b/c/d 四个 Module 以及其对应的 dependence。

所有的模块构建完毕,没有新的依赖可以继续,由此进入生成阶段。
生成阶段
在构建阶段 make 结束之后,就会进入生成阶段,调用compilation.seal表明正式进入生成阶段。
在 seal 阶段主要是是将构建阶段生成的 module 拆分组合到 chunk 对象中,再转译成为目标环境的产物,并写出为产物文件,解决的是资源输出问题。

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

- 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
Webpack 核心流程的更多相关文章
- 细说 webpack 之流程篇
摘自: http://taobaofed.org/blog/2016/09/09/webpack-flow/ 引言 目前,几乎所有业务的开发构建都会用到 webpack .的确,作为模块加载和打包神器 ...
- 细说webpack之流程篇
引言 目前,几乎所有业务的开发构建都会用到 webpack .的确,作为模块加载和打包神器,只需配置几个文件,加载各种 loader 就可以享受无痛流程化开发.但对于 webpack 这样一个复杂度较 ...
- 手写webpack核心原理,再也不怕面试官问我webpack原理
手写webpack核心原理 目录 手写webpack核心原理 一.核心打包原理 1.1 打包的主要流程如下 1.2 具体细节 二.基本准备工作 三.获取模块内容 四.分析模块 五.收集依赖 六.ES6 ...
- paip.刮刮卡砸金蛋抽奖概率算法跟核心流程.
paip.刮刮卡砸金蛋抽奖概率算法跟核心流程. #---抽奖算法需要满足的需求如下: 1 #---抽奖核心流程 1 #---问题???更好的算法 2 #---实际使用的扩展抽奖算法(带奖品送完判断和每 ...
- 实例模拟struts核心流程
Struts,经典框架之一,每个java web 开发人员都应该晓得它的大名.这里,我就用一个简单实例来模拟一下struts的核心流程.具体实例如下: 主界面: 点击提交后,程序根据具体的actio ...
- webpack学习--创建一个webpack打包流程
创建一个webpack打包流程 首先安装webpack插件 mkdir webpack-demo && cd webpack-demo npm init -y npm install ...
- ibatis源码学习1_整体设计和核心流程
背景介绍ibatis实现之前,先来看一段jdbc代码: Class.forName("com.mysql.jdbc.Driver"); String url = "jdb ...
- dubbo核心流程一览
整体设计 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI. Serv ...
- KVM Run Process之KVM核心流程
在"KVM Run Process之Qemu核心流程"一文中讲到Qemu通过KVM_RUN调用KVM提供的API发起KVM的启动,从这里进入到了内核空间执行,本文主要讲述内核中KV ...
- 2017.3.31 spring mvc教程(二)核心流程及配置详解
学习的博客:http://elf8848.iteye.com/blog/875830/ 我项目中所用的版本:4.2.0.博客的时间比较早,11年的,学习的是Spring3 MVC.不知道版本上有没有变 ...
随机推荐
- 执行insmod提示 invalid module format
内核版本和驱动版本不匹配: 1.假如内核版本是2018.3,驱动使用了另外一个版本,可能会出现这样的问题 2.内核和驱动版本一致,但内核进行了一些配置,导致驱动装不上,此时应该: make clean ...
- Markdown 文章 跳转
背景 在查阅一些文档的时候,一些比较优秀博客在文章中是带有目录的,点击就会跳转到指定的锚点. 在本人的某些文章中,也想尝试这样的效果. 做法 实现这样的效果有2种做法(不同之处在于 超链接的写法不同) ...
- xlookup与vlookup的区别
区别还是很大的,vlookup暂时扔不了.
- aiohttp 子线程启动/中止服务
import time, threading from aiohttp import web import asyncio async def handler(request): return web ...
- manage.py“Couldn't import Django”报错的问题解决
问题分析: 在pyharm中项目可以正常运行但是在终端 终端输入python manage.py runserver首次测试项目时,出现了无法引用Django的错误. Traceback (most ...
- Servlet3.0+SpringBoot2.X注解Listener常用监听器
监听器:应用启动监听器,会话监听器,请求监听器 作用: ServletContextListener 应用启动监听 HttpSessionLisener 会话监听 ServletRequestList ...
- 使用urllib3实现http请求
Urllib3是一个功能强大,条理清晰,用于HTTP客户端的Python库,许多Python的原生系统已经开始使用urllib3. 1.发送请求 import urllib3 # 创建实例 http ...
- 深度解读昇腾CANN模型下沉技术,提升模型调度性能
本文分享自华为云社区<深度解读昇腾CANN模型下沉技术,提升模型调度性能>,作者:昇腾CANN. AI模型的运行通常情况下需要CPU和NPU(昇腾AI处理器)等AI专用处理器协同工作,CP ...
- tensorflow学习率指数衰减ExponentialDecay的参数介绍与使用方法
本文介绍在tensorflow库中,用于动态调整神经网络的学习率的一种方法--指数衰减ExponentialDecay()策略的参数含义及其具体用法. 在进行神经网络训练时,我们经常需要用到动 ...
- PHP 程序员为什么依然是外包公司的香饽饽?
大家好,我是码农先森. PHP 唯一的爽点就是开发起来「哇真快」这刚好和外包公司的需求相契合,在 Web 领域的芒荒年代 PHP 以王者姿态傲视群雄.如果 PHP 敢说第二,就没有哪门子语言敢称第一, ...