Webpack 5 Asset 模块类型实战:零 Loader 处理静态资源,一文读懂 asset 的四种形态与底层实现
【免费下载链接】webpackA bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand. Through "loaders", modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.项目地址: https://gitcode.com/GitHub_Trending/web/webpack
本文基于 webpack 官方示例examples/asset展开,完整讲解 webpack 5 内置的 asset module type(asset、asset/inline、asset/resource、asset/source、asset/bytes)如何在不依赖任何 loader 的前提下导入图片、文本与二进制文件:涵盖普通import、import ... with { type: "text" | "bytes" }导入属性以及new URL("./file", import.meta.url)三种开箱即用的写法,并结合lib/asset/源码解析 8096 字节内联阈值、dataUrlCondition判断逻辑与产物哈希命名的底层机制,读完即可在自己的项目中正确配置type: "asset"相关规则并理解其生成代码。
示例定位:为什么 asset 模块类型值得单独掌握
在 webpack 5 之前,处理图片、字体等静态资源必须引入file-loader(输出为独立文件)、url-loader(小文件内联为 Data URL)、raw-loader(读取为字符串)等多个第三方 loader,规则冗长且彼此耦合。webpack 5 将这一能力内置为asset module type,官方示例 examples/asset/template.md 对此的表述是:
This is a very simple example that shows the the usage of the asset module type. Files can be imported like other modules without file-loader.
也就是说,静态资源从此像普通模块一样参与打包流程,由module.rules中的type字段决定其处理方式。当前仓库(webpack 5.110.3,见 package.json)中examples/asset/目录完整给出了入口源码、构建配置与实际构建产物,是研究该特性的最佳一手材料。
三种开箱即用的资源导入方式
示例入口 examples/asset/example.js 演示了 asset 模块的三种典型用法,它们都不需要额外配置即可工作(方式 1 需要 rules 声明type: "asset"):
// There are different ways to use files: // 1. Using `import something from "./file.ext";` // return URLs or Data URL, depends on your configuration import png from "./images/file.png"; import jpg from "./images/file.jpg"; import svg from "./images/file.svg"; // 2. Using `import something from "./file.ext"; with { type: "text" }` or `import something from "./file.ext"; with { type: "bytes" }` // You don't need extra options in your configuration for these imports, they work out of the box // returns the content as text import text from "./content/file.text" with { type: "text" }; // returns the content as `Uint8Array` import bytes from "./content/bytes.svg" with { type: "bytes" }; // 3. Using `new URL("./file.ext", import.meta.url);` // You don't need extra options in your configuration for `new URL(...)` construction, they work out of the box const url = new URL("./images/url.svg", import.meta.url);三种方式对应的行为分别是:
| 写法 | 返回内容 | 对应模块类型 | 等价的历史 loader |
|---|---|---|---|
import png from "./images/file.png" | URL 或 Data URL(取决于配置与文件大小) | asset | file-loader+url-loader |
import text from "./file.text" with { type: "text" } | 文件内容字符串 | asset/source | raw-loader |
import bytes from "./file.svg" with { type: "bytes" } | Uint8Array | asset/bytes | 无直接对应(二进制读取) |
new URL("./file.svg", import.meta.url) | 资源 URL | asset/resource | file-loader |
其中with { type: ... }是 ECMAScript Import Attributes 语法:webpack 在 lib/javascript/JavascriptParser.js 中解析import声明的with/assert属性对象,将type值传递给模块工厂,从而在不写任何 rules 的情况下把文件按 source/bytes 处理。而new URL(..., import.meta.url)则由 lib/dependencies/URLDependency.js 识别,把 URL 构造表达式转成对资源模块的依赖。
示例的后半部分只是把这几类导入结果渲染到页面:createImageElement用img.src展示 URL/Data URL,createTextElement直接输出文本,createBlobElement则把Uint8Array包成Blob并用URL.createObjectURL生成对象 URL(图片加载完成后URL.revokeObjectURL释放),完整源码见 examples/asset/example.js。
构建配置:output.assetModuleFilename 与 module.rules 中的 type: "asset"
示例的构建配置 examples/asset/webpack.config.js 极为精简,只有两个关键点:
"use strict"; /** @type {import("webpack").Configuration} */ const config = { output: { assetModuleFilename: "images/[hash][ext]" }, module: { rules: [ { test: /file\.(png|jpg|svg)$/, type: "asset" } ] } }; module.exports = config;output.assetModuleFilename: "images/[hash][ext]":指定asset/resource形态(即需要落盘的资源)的输出文件名模板。[hash]是资源内容哈希(对应产物中的89a353e9c515885abd8e.png),[ext]保留原始扩展名,因此资源统一落在dist/images/目录下。该字段在类型声明中为assetModuleFilename?: AssetModuleFilename(declarations/WebpackOptions.d.ts)。module.rules[0]:仅用正则test: /file\.(png|jpg|svg)$/匹配示例图片,并声明type: "asset"。注意这里没有设置parser与generator选项——这正是要点:asset类型自带默认行为,即按文件大小自动选择内联或落盘。
type: "asset"的完整取值家族在 lib/ModuleTypeConstants.js 中有明确注释:
/** * @type {Readonly<"asset">} * This is the module type used for automatically choosing between `asset/inline`, `asset/resource` based on asset size limit (8096). */ const ASSET_MODULE_TYPE = "asset"; // asset/inline: 内联为 data URI,等价于 url-loader // asset/resource: 拷贝到输出目录,等价于 file-loader // asset/source: 以源码文本导入,等价于 raw-loader // asset/bytes: 以 Uint8Array 导入即五种类型:asset(自动切换)、asset/inline(强制内联)、asset/resource(强制落盘)、asset/source(字符串)、asset/bytes(Uint8Array)。
产物分析:同一份配置下 png 落盘、jpg/svg 内联的真相
构建产物 dist/output.js(模板中引用为js/output.js)清晰展示了type: "asset"的自动切换效果。对同一组规则命中的三个文件:
/* 1 */ module.exports = __webpack_require__.p + "images/89a353e9c515885abd8e.png"; // file.png(14.6 KiB,> 8096 字节)→ 独立文件 + publicPath 拼接 /* 2 */ module.exports = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAA...4CD/9M//Z"; // file.jpg → Data URL 内联 /* 3 */ module.exports = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo...vc3ZnPgo="; // file.svg → Data URL 内联file.png体积较大,超过默认阈值,因此被发射为images/89a353e9c515885abd8e.png,模块导出值为__webpack_require__.p(publicPath,此处为"dist/")与文件名的拼接;file.jpg、file.svg未超阈值,内容以 base64 Data URL 直接内联进 JS,运行时零额外请求;new URL("./images/url.svg", import.meta.url)产物是__webpack_require__(/* asset import */ 4),对应模块导出__webpack_require__.p + "images/afc10c70ed4ce2b33593.svg",入口中被改写为new URL(资产URL, __webpack_require__.b),其中__webpack_require__.b是基准 URI(document.baseURI || self.location.href),保证了相对路径解析与浏览器行为一致;with { type: "bytes" }导入的bytes.svg产物是__webpack_require__.tb("PHN2Zy..."):运行时内置了一个to binaryhelper(__webpack_require__.tb),用 128 项查表法把 base64 字面量解码成Uint8Array,并冻结底层ArrayBuffer使其不可变(toImmutableBytes),避免运行期意外改写共享二进制。
webpack 的构建输出(stats)也印证了这一点:
asset output.js 19.5 KiB [emitted] (name: main) asset images/89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: images/file.png] (auxiliary name: main) asset images/afc10c70ed4ce2b33593.svg 656 bytes [emitted] [immutable] [from: images/url.svg] (auxiliary name: main) chunk (runtime: main) output.js (main) 12.4 KiB (javascript) 15.2 KiB (asset) 1.53 KiB (runtime) [entry] [rendered] webpack X.X.X compiled successfully注意 png 只有file.png一个文件被真正发射(jpg/svg 已内联),资源被标记为[immutable]——因为文件名含内容哈希,适合强缓存。
源码纵深:8096 字节阈值与 dataUrlCondition 的判定链
自动切换行为的核心在 lib/asset/AssetModulesPlugin.js 与 lib/asset/AssetParser.js。
插件注册:AssetModulesPlugin.apply()通过normalModuleFactory.hooks.createModuleClass为五种 asset 类型统一挂上AssetModule类(lib/asset/AssetModule.js,它是NormalModule的子类,仅附加 asset 专属的buildInfo形状),并分别为各类型注册 parser/generator:
// lib/asset/AssetModulesPlugin.js let dataUrlCondition = parserOptions.dataUrlCondition; if (!dataUrlCondition || typeof dataUrlCondition === "object") { dataUrlCondition = { maxSize: 8096, ...dataUrlCondition }; } return new AssetParser(dataUrlCondition);这里就是关键默认值:asset类型未显式配置parser.dataUrlCondition时,maxSize默认为 8096 字节;asset/inline直接构造new AssetParser(true)(永远内联),asset/resource构造new AssetParser(false)(永远落盘)。
判定逻辑:AssetParser.parse()对文件字节长度与阈值做比较(lib/asset/AssetParser.js):
if (typeof this.dataUrlCondition === "function") { buildInfo.dataUrl = this.dataUrlCondition(source, { filename, module }); } else if (typeof this.dataUrlCondition === "boolean") { buildInfo.dataUrl = this.dataUrlCondition; } else if (this.dataUrlCondition && typeof this.dataUrlCondition === "object") { buildInfo.dataUrl = Buffer.byteLength(source) <= this.dataUrlCondition.maxSize; }支持三种形态:函数(完全自定义,可拿到源内容与模块上下文)、布尔常量、{ maxSize }对象。buildInfo.dataUrl决定了生成阶段走 Data URL 分支还是走“发射文件 + 拼接 URL”分支;生成器 lib/asset/AssetGenerator.js 负责按generator.filename/output.assetModuleFilename模板计算文件名并输出模块代码。此外generator.emit: false(lib/asset/AssetModulesPlugin.js 中generatorOptions.emit !== false)可以只生成 URL 而不落盘,配合generator.dataUrl.encoding/mimetype可控制 Data URL 的编码与 MIME(类型见 declarations/WebpackOptions.d.ts)。
发射阶段:compilation.hooks.renderManifest中,插件按 chunk 遍历ASSET_MODULE_TYPE源类型的模块,把每个资源以auxiliary: true(辅助产物)推入渲染清单,identifier形如assetModule<moduleId>——这解释了 stats 中[from: images/file.png] (auxiliary name: main)的标注方式。
小结:从示例到生产的配置心智模型
- 图片等二进制资源:
{ test: /\.(png|jpg|svg|...)$/, type: "asset" },让 webpack 按 8096 字节自动选择内联/落盘;需要控制时用parser: { dataUrlCondition: { maxSize } }(或函数)覆盖阈值。 - 强制策略:
asset/inline对应url-loader,asset/resource对应file-loader,asset/source对应raw-loader,asset/bytes产出Uint8Array。 - 文本/二进制内容用
with { type: "text" | "bytes" }导入属性,无需任何 rules;new URL(..., import.meta.url)则永远走 resource 语义。 - 输出路径统一由
output.assetModuleFilename模板控制,[hash]提供内容级强缓存能力。
完整可复现的演示位于 examples/asset 目录(入口、配置、HTML 页面与生成产物俱全),仓库内另有更细粒度的 Data URL 内联演示 examples/asset-svg-data-uri,可作为延伸阅读。
【免费下载链接】webpackA bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand. Through "loaders", modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.项目地址: https://gitcode.com/GitHub_Trending/web/webpack
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考