知海

资源模块

webpackjsorg-main指南-教程

资源模块

资源模块(Asset Modules)允许你在无需配置额外 loader 的情况下使用资源文件(字体、图标等)。

在 webpack 5 之前,通常需要使用:

资源模块类型通过引入 5 种新的模块类型,取代了所有这些 loader:

  • asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
  • asset/inline 导出资源的 data URI。之前通过使用 url-loader 实现。
  • asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。
  • asset/bytes 导出资源的 Uint8Array 视图。
  • asset 在导出 data URI 和发送单独文件之间自动选择。之前通过使用带有资源大小限制的 url-loader 实现。

当在 webpack 5 中将旧的资源 loader(例如 file-loader/url-loader/raw-loader)与资源模块一起使用时,你可能希望阻止资源模块再次处理你的资源,因为这会导致资源重复。这可以通过将资源的模块类型设置为 'javascript/auto' 来实现。

webpack.config.js

diff 复制代码
export default {
  module: {
   rules: [
      {
        test: /\.(png|jpg|gif)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192,
            }
          },
        ],
+       type: 'javascript/auto'
      },
   ]
  },
}

要从资源 loader 中排除来自新 URL 调用的资源,请将 dependency: { not: ['url'] } 添加到 loader 配置中。

webpack.config.js

diff 复制代码
export default {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/i,
+       dependency: { not: ['url'] },
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192,
            },
          },
        ],
      },
    ],
  }
}

公共路径(Public Path)

默认情况下,在底层,asset 类型会执行 __webpack_public_path__ + import.meta。这意味着在你的配置中设置 output.publicPath 将允许你覆盖 asset 加载的 URL。

运行时覆盖

如果你在代码中设置 __webpack_public_path__,为了不影响 asset 加载逻辑,你需要确保它作为应用中的第一个代码运行,并且不要使用函数来设置。一个示例是创建一个名为 publicPath.js 的文件,内容如下:

js 复制代码
__webpack_public_path__ = "https://cdn.url.com";

然后在你的 webpack.config.js 中更新 entry 字段,使其看起来像这样:

js 复制代码
export default {
  entry: ["./publicPath.js", "./App.js"],
};

或者,你可以在你的 App.js 中执行以下操作,而无需修改 webpack 配置。唯一的缺点是你必须在此强制执行顺序,这可能会与某些 lint 工具冲突。

js 复制代码
import "./publicPath.js";

资源类型(Resource type)

webpack.config.js

diff 复制代码
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
+ module: {
+   rules: [
+     {
+       test: /\.png/,
+       type: 'asset/resource',
+     },
+   ],
+ },
};

src/index.js

js 复制代码
import mainImage from "./images/main.png";

img.src = mainImage; // '/dist/151cfcfa1bd74779aadb.png'

所有 .png 文件都将被发送到输出目录,并且它们的路径将被注入到打包产物中,此外,你还可以为它们自定义 outputPathpublicPath

自定义输出文件名

默认情况下,asset/resource 模块使用 [hash][ext][query] 文件名发送到输出目录。

你可以通过在 webpack 配置中设置 output.assetModuleFilename 来修改此模板:

webpack.config.js

diff 复制代码
import path from "node:path";
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
+   assetModuleFilename: 'images/[hash][ext][query]',
  },
  module: {
    rules: [
      {
        test: /\.png/,
        type: 'asset/resource',
      },
    ],
  },
};

自定义输出文件名的另一个场景是将某些类型的资源发送到指定目录:

diff 复制代码
import path from "node:path";
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
+   assetModuleFilename: 'images/[hash][ext][query]',
  },
  module: {
    rules: [
+     {
+       test: /\.html/,
+       type: 'asset/resource',
+       generator: {
+         filename: 'static/[hash][ext][query]',
+       },
+     },
    ],
  },
};

使用此配置,所有 html 文件将被发送到输出目录内的 static 目录中。

Rule.generator.filenameoutput.assetModuleFilename 相同,并且仅适用于 assetasset/resource 模块类型。

内联资源(Inlining assets)

webpack.config.js

diff 复制代码
import path from "node:path";
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
+     {
+       test: /\.svg/,
+       type: 'asset/inline',
+     },
    ],
  },
};

src/index.js

js 复制代码
import metroMap from "./images/metro.svg";

block.style.background = `url(${metroMap})`; // url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo...vc3ZnPgo=)

所有 .svg 文件都将作为 data URI 注入到打包产物中。

自定义 data URI 生成器

默认情况下,webpack 生成的 data URI 表示使用 Base64 算法编码的文件内容。

如果你想使用自定义的编码算法,你可以指定一个自定义函数来编码文件内容:

webpack.config.js

diff 复制代码
import path from "node:path";
import { fileURLToPath } from 'node:url';
+ import svgToMiniDataURI from "mini-svg-data-uri";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);


export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.svg/,
        type: 'asset/inline',
+       generator: {
+         dataUrl: content => {
+           content = content.toString();
+           return svgToMiniDataURI(content);
+         },
+       },
      },
    ],
  },
};

现在,所有 .svg 文件都将通过 mini-svg-data-uri 包进行编码。

源代码类型(Source type)

webpack.config.js

diff 复制代码
import path from "node:path";
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
+     {
+       test: /\.txt/,
+       type: 'asset/source',
+     },
    ],
  },
};

T> 当使用 import text from './file.txt' with { type: "text" }; 语法时,你不需要添加规则。

src/example.txt

text 复制代码
Hello world

src/index.js

js 复制代码
import exampleText from "./example.txt";

block.textContent = exampleText; // 'Hello world';

其他用法:

src/index.js

js 复制代码
import exampleText from "./example.txt" with { type: "text" };

block.textContent = exampleText; // 'Hello world';

所有 .txt 文件都将作为 UTF-8 字符串注入到打包产物中。

URL 资源

当使用 new URL('./path/to/asset', import.meta.url) 时,webpack 也会创建一个资源模块。

T> 当使用 const file = new URL('./file.ext', import.meta.url); 语法时,你不需要添加规则。

src/index.js

js 复制代码
const logo = new URL("./logo.svg", import.meta.url);

根据配置中的 target,webpack 会将上述代码编译为不同的结果:

js 复制代码
// target: web
new URL(
  `${__webpack_public_path__}logo.svg`,
  document.baseURI || self.location.href,
);

// target: webworker
new URL(`${__webpack_public_path__}logo.svg`, self.location);

// target: node, node-webkit, nwjs, electron-main, electron-renderer, electron-preload, async-node
new URL(
  `${__webpack_public_path__}logo.svg`,
  require("node:url").pathToFileUrl(__filename),
);
js 复制代码
// 启用 ECMA 模块输出时的任何目标
new URL(`${__webpack_public_path__}logo.svg`, import.meta.url);

自 webpack 5.38.0 起,new URL() 也支持 Data URL

src/index.js

js 复制代码
const url = new URL("data:,", import.meta.url);
console.log(url.href === "data:,");
console.log(url.protocol === "data:");
console.log(url.pathname === ",");

通用资源类型(Asset type)

webpack.config.js

diff 复制代码
import path from "node:path";
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
+     {
+       test: /\.txt/,
+       type: 'asset',
+     },
    ],
  },
};

现在,webpack 将根据默认条件自动在 resourceinline 之间进行选择:小于 8kb 的文件将被视为 inline 模块类型,否则为 resource 模块类型。

你可以通过在 webpack 配置的模块规则级别上设置 Rule.parser.dataUrlCondition.maxSize 选项来更改此条件:

webpack.config.js

diff 复制代码
import path from "node:path";
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.txt/,
        type: 'asset',
+       parser: {
+         dataUrlCondition: {
+           maxSize: 4 * 1024, // 4kb
+         },
+       },
      },
    ],
  },
};

你还可以 指定一个函数 来决定是否内联一个模块。

字节类型(Bytes type)

webpack.config.js

diff 复制代码
import path from "node:path";
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
+     {
+       test: /\.txt/,
+       type: 'asset/bytes',
+     },
    ],
  },
};

T> 当使用 import data from './file.ext' with { type: "bytes" }; 语法时,你不需要添加规则。

src/example.txt

text 复制代码
Hello world

src/index.js

js 复制代码
import exampleText from "./example.txt";

const decoder = new TextDecoder("utf-8");
const textString = decoder.decode(exampleText);

block.textContent = textString; // 'Hello world';

其他用法:

src/index.js

js 复制代码
import exampleText from "./example.txt" with { type: "bytes" };

const decoder = new TextDecoder("utf-8");
const textString = decoder.decode(exampleText);

block.textContent = textString; // 'Hello world';

所有 .txt 文件都将作为原始字节(Uint8Array)注入到打包产物中,不进行任何文本编码或转换。

替换内联 loader 语法

在资源模块和 webpack 5 之前,可以使用内联语法配合上述遗留 loader。

现在建议移除所有内联 loader 语法,并使用 resourceQuery 条件来模拟内联语法的功能。

例如,在将 raw-loader 替换为 asset/source 类型的情况下:

diff 复制代码
- import myModule from 'raw-loader!my-module';
+ import myModule from 'my-module?raw';

并在 webpack 配置中:

diff 复制代码
module: {
    rules: [
    // ...
+     {
+       resourceQuery: /raw/,
+       type: 'asset/source',
+     }
    ]
  },

如果你想将原始资源排除在其他 loader 的处理之外,可以使用否定条件:

diff 复制代码
module: {
    rules: [
    // ...
+     {
+       test: /\.m?js$/,
+       resourceQuery: { not: [/raw/] },
+       use: [ ... ]
+     },
      {
        resourceQuery: /raw/,
        type: 'asset/source',
      }
    ]
  },

或者使用 oneOf 规则列表。这里只有第一个匹配的规则会被应用:

diff 复制代码
module: {
    rules: [
    // ...
+     { oneOf: [
        {
          resourceQuery: /raw/,
          type: 'asset/source',
        },
+       {
+         test: /\.m?js$/,
+         use: [ ... ]
+       },
+     ] }
    ]
  },

内置查询后缀

当启用了 experiments.futureDefaults 时,webpack 会为你注册上述规则:对于任何导入,?raw?url?inline?no-inline 查询后缀都可以直接使用,类似于 Vite 的资源查询。

js 复制代码
import source from "./file.txt?raw"; // asset/source - 原始文件内容作为字符串
import dataUri from "./icon.svg?inline"; // asset/inline - 一个 data: URI
import url from "./image.png?url"; // asset/resource - 发送的文件的 URL
import fileUrl from "./small.png?no-inline"; // asset/resource - 即使规则会内联它,也绝不内联

这些后缀会在查询字符串的任何位置进行匹配(?foo&raw 也可以),并以 oneOf 列表的形式工作,因此只有第一个匹配的后缀生效。由于它们是普通的默认规则,你自己的 module.rules 具有优先权,可以覆盖它们。

禁用资源生成

对于像服务端渲染这样的用例,你可能希望禁用资源的生成,这可以通过 Rule.generator 下的 emit 选项来实现:

js 复制代码
export default {
  // …
  module: {
    rules: [
      {
        test: /\.png$/i,
        type: "asset/resource",
        generator: {
          emit: false,
        },
      },
    ],
  },
};

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。