浅学webpack
webpack原理(一)
1.啥是webpack捏❔
本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容
2.webpack主要流程
感觉webpack可以分成三部分
- 构建的核心流程
- loader 的作用
- plugin 架构与常用套路
3.核心流程解析 😆
首先得知道这玩意最核心的功能是什么的吧
就像他官网说的一样
1 |
|
也就是将各种类型的资源,包括图片、css、js等,转译、组合、拼接、生成 JS 格式的 bundler 文件
这个过程核心完成了 [内容转换+资源合并] 两种功能
实际上包含三个阶段
-
初始化阶段:
- 初始化参数:从配置文件、 配置对象、Shell 参数中读取,与默认配置结合得出最终的参数
- 创建编译器对象:用上一步得到的参数创建
Compiler
对象 - 初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化 RuleSet 集合、加载配置的插件等
- 开始编译:执行
compiler
对象的run
方法 - 确定入口:根据配置中的
entry
找出所有的入口文件,调用compilition.addEntry
将入口文件转换为dependence
对象
-
构建阶段:
- 编译模块(make):根据
entry
对应的dependence
创建module
对象,调用loader
将模块转译为标准 JS 内容,调用 JS 解释器将内容转换为 AST 对象,从中找出该模块依赖的模块,再 递归 本步骤直到所有入口依赖的文件都经过了本步骤的处理 - 完成模块编译:上一步递归处理所有能触达到的模块后,得到了每个模块被翻译后的内容以及它们之间的 依赖关系图
-
生成阶段:
- 输出资源(seal):根据入口和模块之间的依赖关系,组装成一个个包含多个模块的
Chunk
,再把每个Chunk
转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会 - 写入文件系统(emitAssets):在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
接下来说说细节部分吧 对上面一些听起来很高级的名词解释一手 :happy:
-
Entry
:编译入口,webpack 编译的起点 -
Compiler
:编译管理器,webpack 启动后会创建compiler
对象,该对象一直存活知道结束退出这玩意记录了webpack运行环境的所有信息 插件可以通过它获取到webpack配置信息,像
entry output module
等配置 -
Compilation
:单次编辑过程的管理器,比如watch = true
时,运行过程中只有一个compiler
但每次文件变更触发重新编译时,都会创建一个新的compilation
对象它储存了当前的模块资源、编译生成的资源、变化的文件、以及被跟踪依赖的状态信息
-
Dependence
:依赖对象,webpack 基于该类型记录模块间依赖关系 -
Module
:webpack 内部所有资源都会以“module”对象形式存在,所有关于资源的操作、转译、合并都是以 “module” 为基本单位进行的 -
Chunk
:编译完成准备输出时,webpack 会将module
按特定的规则组织成一个一个的chunk
,这些chunk
某种程度上跟最终输出一一对应 -
Loader
:资源内容转换器,其实就是实现从内容 A 转换 B 的转换器 -
Plugin
:webpack构建过程中,会在特定的时机广播对应的事件,插件监听这些事件,在特定时间点介入编译过程
4.Plugin
解析
看插件之前 首先想三个问题
什么是插件
什么时间点会有什么钩子被触发
在钩子回调中,如何影响编译状态
What: 什么是插件🚘
先从形态上来看 插件是个带有apply
函数的类
1 |
|
apply
函数运行时会得到参数 compiler
,以此为起点可以调用 hook
对象注册各种钩子回调,例如: compiler.hooks.make.tapAsync
,这里面 make
是钩子名称,tapAsync
定义了钩子的调用方式,webpack 的插件架构基于这种模式构建而成,插件开发者可以使用这种模式在钩子回调中,插入特定代码。webpack 各种内置对象都带有 hooks
属性,比如 compilation
对象:
1 |
|
When: 什么时候会触发钩子 📦
官网对钩子的说明都比较简短
直接看几个例子吧
compiler.hooks.compilation
:
- 时机:启动编译创建出 compilation 对象后触发
- 参数:当前编译的 compilation 对象
- 示例:很多插件基于此事件获取 compilation 实例
compiler.hooks.make
:
- 时机:正式开始编译时触发
- 参数:同样是当前编译的
compilation
对象 - 示例:webpack 内置的
EntryPlugin
基于此钩子实现entry
模块的初始化
compilation.hooks.optimizeChunks
:
- 时机:
seal
函数中,chunk
集合构建完毕后触发 - 参数:
chunks
集合与chunkGroups
集合 - 示例:
SplitChunksPlugin
插件基于此钩子实现chunk
拆分优化
compiler.hooks.done
:
- 时机:编译完成后触发
- 参数:
stats
对象,包含编译过程中的各类统计信息 - 示例:
webpack-bundle-analyzer
插件基于此钩子实现打包分析
看这个钩子时候我直接看 触发时机 传递参数 示例代码
首先看触发时机勒
触发时机是与webpack工作过程紧密相关的,大体上从启动到结束,compiler
对象逐次触发以下钩子
而compilation
对象逐次触发:
就都是嘟嘟嘟嘟嘟嘟长串 😓
我自己的理解是 这个时机就是几个情况
- 创建出这个对象触发
- 正式开始编译就触发
- 函数构建完毕后触发
传递参数
我在他的配置里没怎么找到 感觉无非传点modules之类的东西
示例代码
webpack这钩子实话实说
抽象程度是小大的 我感觉最好的学习办法 就是查询其他插件中如何使用这些钩子 不然你就看吧 一看一个不吱声😭
How 如何影响编译状态捏
webpack的插件体系跟平常看到的不太一样 hooks回调由webpack决定何时 以何种方式 而在hooks回调内部可以直接修改状态 调用上下文api等方式webpack产生 一些影响
举个🏮子
EntryPlugin
插件
1 |
|
在这里面 webpack会将上下文信息以参数或this
形式传递给钩子回调用
在回调中可以调用上下文对象的方法或者直接修改上下文对象属性的方式,对原定的流程产生 side effect。所以想纯熟地编写插件,除了要理解调用时机,还需要了解我们可以用哪一些api,例如:
compilation.addModule
:添加模块,可以在原有的module
构建规则之外,添加自定义模块compilation.emitAsset
:直译是“提交资产”,功能可以理解将内容写入到一个特定路径compilation.addEntry
:添加入口,功能上与直接定义entry
配置相同module.addError
:添加编译错误信息
5.Loader介绍
看一下loader
在编译流程中的生效位置
在这个流程图里,runloaders
会调用用户所配置的loader集合读取,转移资源,前面的内容可以千奇百怪👽
但是转译之后一般都是输出Javascript文本,webpack才可以继续处理模块依赖
理解了这个基本逻辑,loader职责基本上也知道了 就是把内容A =>内容B