浅学webpack

webpack原理(一)

1.啥是webpack捏❔

本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容

2.webpack主要流程

感觉webpack可以分成三部分

  • 构建的核心流程
  • loader 的作用
  • plugin 架构与常用套路

3.核心流程解析 😆

首先得知道这玩意最核心的功能是什么的吧

就像他官网说的一样

1
At its core, webpack is a static module bundler for modern JavaScript applications.

也就是将各种类型的资源,包括图片、css、js等,转译、组合、拼接、生成 JS 格式的 bundler 文件

屏幕截图 2024-03-23 012454.png

这个过程核心完成了 [内容转换+资源合并] 两种功能

实际上包含三个阶段

  • 初始化阶段:

  1. 初始化参数:从配置文件、 配置对象、Shell 参数中读取,与默认配置结合得出最终的参数
  2. 创建编译器对象:用上一步得到的参数创建 Compiler 对象
  3. 初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化 RuleSet 集合、加载配置的插件等
  4. 开始编译:执行 compiler 对象的 run 方法
  5. 确定入口:根据配置中的 entry 找出所有的入口文件,调用 compilition.addEntry 将入口文件转换为 dependence 对象
  • 构建阶段:

  1. 编译模块(make):根据 entry 对应的 dependence 创建 module 对象,调用 loader 将模块转译为标准 JS 内容,调用 JS 解释器将内容转换为 AST 对象,从中找出该模块依赖的模块,再 递归 本步骤直到所有入口依赖的文件都经过了本步骤的处理
  2. 完成模块编译:上一步递归处理所有能触达到的模块后,得到了每个模块被翻译后的内容以及它们之间的 依赖关系图
  • 生成阶段:

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

屏幕截图 2024-03-23 012530.png

接下来说说细节部分吧 对上面一些听起来很高级的名词解释一手 :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
2
3
4
class SomePlugin {
apply(compiler){
}
}

apply 函数运行时会得到参数 compiler ,以此为起点可以调用 hook 对象注册各种钩子回调,例如: compiler.hooks.make.tapAsync ,这里面 make 是钩子名称,tapAsync 定义了钩子的调用方式,webpack 的插件架构基于这种模式构建而成,插件开发者可以使用这种模式在钩子回调中,插入特定代码。webpack 各种内置对象都带有 hooks 属性,比如 compilation 对象:

1
2
3
4
5
6
7
class SomePlugin {
apply(compiler) {
compiler.hooks.thisCompilation.tap('SomePlugin', (compilation) => {
compilation.hooks.optimizeChunkAssets.tapAsync('SomePlugin', ()=>{});
})
}
}

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对象逐次触发以下钩子

屏幕截图 2024-03-23 012707.png

compilation对象逐次触发:

image (1).png

就都是嘟嘟嘟嘟嘟嘟长串 😓

我自己的理解是 这个时机就是几个情况

  • 创建出这个对象触发
  • 正式开始编译就触发
  • 函数构建完毕后触发

传递参数

我在他的配置里没怎么找到 感觉无非传点modules之类的东西

示例代码

webpack这钩子实话实说

抽象程度是小大的 我感觉最好的学习办法 就是查询其他插件中如何使用这些钩子 不然你就看吧 一看一个不吱声😭

How 如何影响编译状态捏

webpack的插件体系跟平常看到的不太一样 hooks回调由webpack决定何时 以何种方式 而在hooks回调内部可以直接修改状态 调用上下文api等方式webpack产生 一些影响

举个🏮子

EntryPlugin插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class EntryPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(
"EntryPlugin",
(compilation, { normalModuleFactory }) => {
compilation.dependencyFactories.set(
EntryDependency,
normalModuleFactory
);
}
);

compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
const { entry, options, context } = this;

const dep = EntryPlugin.createDependency(entry, options);
compilation.addEntry(context, dep, options, (err) => {
callback(err);
});
});
}
}

在这里面 webpack会将上下文信息以参数或this形式传递给钩子回调用

在回调中可以调用上下文对象的方法或者直接修改上下文对象属性的方式,对原定的流程产生 side effect。所以想纯熟地编写插件,除了要理解调用时机,还需要了解我们可以用哪一些api,例如:

  • compilation.addModule:添加模块,可以在原有的 module 构建规则之外,添加自定义模块
  • compilation.emitAsset:直译是“提交资产”,功能可以理解将内容写入到一个特定路径
  • compilation.addEntry:添加入口,功能上与直接定义 entry 配置相同
  • module.addError:添加编译错误信息

5.Loader介绍

看一下loader在编译流程中的生效位置

屏幕截图 2024-03-23 012734.png

在这个流程图里,runloaders会调用用户所配置的loader集合读取,转移资源,前面的内容可以千奇百怪👽

但是转译之后一般都是输出Javascript文本,webpack才可以继续处理模块依赖

理解了这个基本逻辑,loader职责基本上也知道了 就是把内容A =>内容B


浅学webpack
http://example.com/2024/03/23/dzѧwebpack/
Author
Shanyujia
Posted on
March 23, 2024
Licensed under