Webpack 学习[四]:模块和模块热替换

23 阅读5分钟

一、模块

模块化编程中,开发者将程序分解为功能离散的 chunk,并称之为 模块

每个模块都拥有小于完整程序的体积,使得验证、调试及测试变得轻而易举。 精心编写的 模块 提供了可靠的抽象和封装界限,使得应用程序中每个模块都具备了条理清晰的设计和明确的目的。

模块化编程是一种软件设计技术,强调将程序的功能分离为独立的、可互换的模块,这样每个模块都包含仅执行所需功能的一个方面所需的一切

webpack 模块

webpack 模块能以各种方式表达它们的依赖关系。例如:

  • ES2015 import语句
  • CommonJS require() 语句
  • AMD] define 和 require 语句
  • css/sass/less 文件中的 @import 语句
  • stylesheet url(...) 或者 HTML <img src=...> 文件中的图片链接。

webpack 支持的模块类型

Webpack 天生支持如下模块类型:

  • ECMAScript 模块
  • CommonJS 模块
  • AMD 模块
  • Assets
  • WebAssembly 模块

通过 loader 可以使 webpack 支持多种语言和预处理器语法编写的模块。loader 向 webpack 描述了如何处理非原生 模块,并将相关依赖引入到你的 bundles中。 webpack 社区已经为各种流行的语言和预处理器创建了 loader,其中包括:

二、模块解析

当打包模块时,webpack 使用 enhanced-resolve 来解析文件路径。

webpack 能解析如下三种文件路径:

  1. 绝对路径
import '/home/me/file';

import 'C:\Users\me\file';
  1. 相对路径
import '../src/file1';
import './file2';

在这种情况下,使用 import 或 require 的资源文件所处的目录,被认为是上下文目录。在 import/require 中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径。

  1. 模块路径
import 'module';
import 'module/lib/file';

可以通过配置 resolve.modules 告诉webpack 解析模块时应该搜索的目录。

也可以通过配置别名resolve.alias的方式来替换初始模块路径。

  • 如果 package 中包含 package.json 文件,那么在 resolve.exportsFields 配置选项中指定的字段会被依次查找,package.json 中的第一个字段会根据 package 导出指南确定 package 中可用的 export。

一旦根据上述规则解析路径后,resolver 将会检查路径是指向文件还是文件夹。如果路径指向文件:

  • 如果文件具有扩展名,则直接将文件打包。
  • 否则,将使用 resolve.extensions 选项作为文件扩展名来解析,此选项会告诉解析器在解析中能够接受那些扩展名(例如 .js.jsx)。

如果路径指向一个文件夹,则进行如下步骤寻找具有正确扩展名的文件:

  • 如果文件夹中包含 package.json 文件,则会根据 resolve.mainFields 配置中的字段顺序查找,并根据 package.json 中的符合配置要求的第一个字段来确定文件路径。
  • 如果不存在 package.json 文件或 resolve.mainFields 没有返回有效路径,则会根据 resolve.mainFiles 配置选项中指定的文件名顺序查找,看是否能在 import/require 的目录下匹配到一个存在的文件名。
  • 然后使用 resolve.extensions 选项,以类似的方式解析文件扩展名

缓存

每次文件系统访问文件都会被缓存,以便于更快触发对同一文件的多个并行或串行请求。在 watch 模式 下,只有修改过的文件会被从缓存中移出。如果关闭 watch 模式,则会在每次编译前清理缓存。

三、runtime 和 manifest

3.1 runtime

runtime 是指为了使模块化应用程序在浏览器环境中正确运行所需的一段特定代码。它包含:

  • 在模块交互时,连接模块所需的加载和解析逻辑。
  • 已经加载到浏览器中的连接模块逻辑,以及尚未加载模块的延迟加载逻辑。

3.2 manifest

在Webpack中,manifest 是指构建过程中生成的一种内部数据结构,它记录了构建时模块及其导出信息之间的映射关系。

当 compiler 开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 "manifest",当完成打包并发送到浏览器时,runtime 会通过 manifest 来解析和加载模块。

无论你选择哪种 模块语法,那些 import 或 require 语句现在都已经转换为 __webpack_require__ 方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够检索这些标识符,找出每个标识符背后对应的模块。

Webpack 的 manifest 主要作用在于:

  1. 模块映射:Webpack 在编译期间创建了一个详细的模块依赖图谱,这个图谱记录了每个模块的位置、ID、导出内容以及其他必要的加载信息。manifest 就是这个图谱的内部表现形式,它让Webpack知道如何在运行时精确地定位和执行模块。
  2. 运行时加载逻辑:runtime 部分正是基于 manifest 中的信息去动态加载和执行模块,尤其是在处理异步加载、按需加载场景时。
  3. 长期缓存策略:在实际项目中,通过一些插件如webpack-manifest-plugin,可以将manifest数据生成到一个外部的JSON文件中,这份清单文件包含了各个产出文件(bundle)与其原始资源之间的映射关系,便于服务器端根据清单来准确地为客户端提供最新的或者缓存有效的资源。

四、模块热替换

模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,替换、添加或删除 模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:

  • 保留在完全重新加载页面期间丢失的应用程序状态。
  • 只更新变更内容,以节省宝贵的开发时间。
  • 在源代码中 CSS/JS 产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。

有关模块热替换的原理,个人推荐这篇推文