likes
comments
collection
share

【揭秘Webpack】【1】构建中的Module、Chunk

作者站长头像
站长
· 阅读数 54

写在前面

想要深入掌握 Webpack 构建机制,首先需要对其核心中间产物ModuleChunk以及由它们形成的依赖图有一个清晰的认识。这些构建块不仅代表了 Webpack 工作流的心脏,也是理解其精妙构建逻辑的基础。通过了解这些中间产物及其相互关系,我们才能明白构建过程中各种行为背后的原因。

Module 和 Chunk 是什么?

在Webpack世界里,Module 是代码的最基本单元,代表着一个个独立的文件或模块,JS文件、CSS文件或者其他任何类型的资源。Webpack 通过分析代码中的 importrequire 语句来识别这些 Module,并构建出一个 Module Graph,明确哪些模块彼此依赖。

Chunk 则是在Webpack的打包过程中形成的,它是一组有共同依赖关系的 Module 的集合。通常一个Chunk 对应了最终输出的一个 bundle 文件,这个文件中可能包含了一个或多个模块。

总而言之,Module 负责描述单个代码块和资源的细节,而 Chunk 则确定如何有效地加载和组织这些代码和资源。

为了方便理解,下图作为简化的示意图,实际的构建过程会更为复杂

【揭秘Webpack】【1】构建中的Module、Chunk

调试小技巧: 可以通过 compilation.modulescompilation.chunks或者compilation 上的 hooks 回调函数中的参数来读取对应的数据。然后通过不同生命周期的hooks,可以观察到modulechunk在不同阶段的变化。示例如下:

class MyPlugin {
    // module 和 chunk 在编译过程中会不断被加工,不同阶段下的输出可能会存在差异
    apply(compiler) {
        compiler.hooks.compilation.tap("MyPlugin", compilation => {
            compilation.hooks.succeedModule.tap("MyPlugin", (module) => {
                console.log('succeedModule hooks module', module);
            });
            compilation.hooks.optimizeChunks.tap('ChunkListPlugin', chunks => {
                chunks.forEach(chunk => {
                  console.log('optimizeChunks hooks chunk', chunk);
                });
            });
        });
        compiler.hooks.afterCompile.tap("MyPlugin", compilation => {
            compilation.modules.forEach(module => {
                console.log('compilation.modules', module);
            });
            compilation.chunks.forEach(chunk => {
                console.log('compilation.chunks', chunk);
            });
        });
    }
}

Module 解析

在Webpack中,模块是指是对项目中构建块(单个文件或资源)的抽象。由于需要针对不同类型的资源和处理逻辑,Webpack 因此设计了多个模块类型来专门处理这些差异,但这些模块类型都派生自一个共同的基类——ModuleModule 定义了所有模块共有的属性和方法,确保所有模块在核心功能上保持统一。

Module 基类

职责

  • 存储模块的基本数据(例如,模块ID、依赖关系等)。
  • 描述模块对其它模块的依赖方式。
  • 为构建过程中模块的加载、打包和更新提供接口。
  • 提供序列化和反序列化的方法,以支持缓存和异步加载。

核心属性

下面列举一些核心属性的作用。便于后续的分析理解

属性作用
debugId内部使用的模块标识符,主要用于调试,帮助开发者跟踪模块在构建中的处理过程。通常从1000开始累加,也可通过该值判断module创建的顺序
dependencies包含模块的同步依赖列表,表示文件间直接的关联和需要立即加载的资源。比如代码中直接的import,初始化时立即加载
block与dependencies类似,但主要用于处理如异步import()生成的代码分割和按需加载的依赖,在运行时按需加载,不会立即初始化
issuer指向引入当前模块的那个模块,通常会是第一个import该模块的那个模块
identifier()返回表示模块唯一性的字符串,用于模块缓存和追踪。
size()返回模块的大小,通常是指编译后的源码大小
build()负责编译模块,处理依赖关系,并生成最终的可执行代码
source()返回模块的原始或转换后的源代码,作为构建输出的基础
addChunk(chunk)、removeChunk(chunk)、isInChunk(chunk)、getChunks()、...chunk交互的增删改查方法
serialize() / deserialize()模块的序列化和反序列化,以便持久化或网络传输

Module 子类

作为开发者,在日常的开发中大多数时候是和与Module的子类打交道。Module 的子类是根据不同类型的文件和资源来进行差异化的处理。

下面将简要介绍一些常用的 Module 子类,主要想表达不同子类在作用上的差异。后续文章会对NormalModule进行深入解析,其余子类的深入可自行探索

  • NormalModule: 用于处理各种类型的文件,通过 loaders 对文件内容进行转换,并生成最终的模块代码。是最常规的模块
  • ContextModule: 处理动态请求(如 require.context)生成的依赖关系,允许你引入一个目录下的所有模块。通常用在不确定具体需要哪个模块的场景。
  • MultiModule: 当你使用 require.ensure() 或动态 import() 来加载多个依赖时使用。它将多个子模块合并为一个模块,以支持对其统一管理。
  • ExternalModule: 用于引入外部的全局依赖,这些依赖在运行时从环境中获取而不是包含在 webpack 的输出 bundle 中。提高了构建性能,因为不需要打包和处理指定为外部的库。
  • CssModule: 专门用于处理 CSS 文件,将 CSS 文件转换为 JavaScript 模块。 与 CSS 相关的 loader (如 css-loaderstyle-loader)一起工作,允许将CSS应用于DOM或导出为独立文件。
  • RuntimeModule: 包含 webpack 运行时代码的模块,为模块的加载和解析提供额外的逻辑。通常专用于 webpack 的运行时加载器和模块解析逻辑。

Chunk 解析

chunk 是编译过程的核心单元,以多个模块module的聚合体形式存在,通常会对应生成的一个或多个输出文件。chunk通常源自入口点配置(由entry定义)或者由动态导入(如import())实现的代码分割。此外,它们也可以由splitChunks插件进行更细致的划分。chunk的设计思路与module不同,主要是module的集合而不是派生种类,因此它们通过一组属性来识别类型和作用,而非子类。

职责

  • 模块聚合: 为若干逻辑相关的module的集合。在构建过程中处理module的一个更高级别的抽象。
  • 代码拆分: 通过Chunks实现应用的按需加载或分割,加快首次加载速度,改善体验。
  • 生成输出文件: 构建最终产物是输出文件,每个chunk负责输出一组文件。
  • 运行时管理: 包括了启动应用程序或代码拆分块所需的运行时代码,确保模块正确加载和解析依赖。
  • 依赖优化: 运用Tree Shaking等技术优化去除多余代码,减少输出体积。
  • 代码复用: 识别并抽离共享模块,创建独立的Chunk来减少重复代码。
  • 利用缓存: 利用文件哈希值命名,实现更高效的浏览器缓存和更新机制。
  • 模块热替换: 实现HMR,即在不刷新网页的情况下替换、添加或删除模块。

核心属性

下面列举一些核心属性的作用。便于后续的分析理解

属性作用
debugId内部使用的模块标识符,主要用于调试。通常从1000开始累加,也可通过该值判断chunk创建的顺序
id唯一标识符,经常用于引用和跟踪chunk
ids在模块热替换(HMR)等特定场景下,一个chunk可能需要多个id,这就是ids数组出现的意义,它包含了那个chunk所有的id。在普通构建过程中,ids往往只有一个,等于id
modulesSize()通常表示chunk中所有模块原始代码的合计大小,不包括Webpack运行时的代码或者其他引导模板代码。
size()整个chunk构建好后的总大小,包含了modulesSize的大小。该属性对优化决策很重要
getModules()、modulesIterable获取它所包含的模块的信息,getModules()的是数组,modulesIterable为可迭代对象
integrate(otherChunk)合并两个chunks
canBeInitial()判断一个chunk是否可以作为起始加载点。返回 true ,这表明chunk可以作为初始块来使用;返回 false,表示不适合作为入口点使用,可能是异步加载的代码块,或者是通过代码分割分离出来的,只能被其他 chunk 引用或按需加载。
moveModule(module, otherChunk)、addModule(module)、removeModule(module)、containsModule(module)与 module交互的增删改查方法

总结与下期预告

本文浅谈了modulechunk:它们是什么,长什么样。希望大家对这两个概念有个大概了解。关于它们如何转换和互相影响,以及能怎么对它们进行优化,这些内容会留在了下篇文章。下次我们将深度解析moduleGraphchunkGraph,带大家更深入地掌握这些概念。

敬请关注,感兴趣的同学可订阅文章结尾处专栏!

转载自:https://juejin.cn/post/7373505141489958923
评论
请登录