知海

Loader 接口

webpackjsorg-mainAPI 参考

Loader 接口

Loader 是一个导出函数的 JavaScript 模块。loader runner 会调用该函数,并将上一个 loader 的结果或资源文件传入其中。函数的 this 上下文由 webpack 和 loader runner 填充,包含一些有用的方法,允许 loader(除其他功能外)将其调用方式改为异步,或获取查询参数。

第一个 loader 会接收到一个参数:资源文件的内容。编译器期望最后一个 loader 返回结果。结果应为 StringBuffer(会被转换为字符串),表示模块的 JavaScript 源码。也可以传递一个可选的 SourceMap 结果(JSON 对象)。

单个结果可以在同步模式下返回。对于多个结果,必须调用 this.callback(),且 loader 必须返回 undefined

异步模式下,你可以从 async function 中返回单个结果。或者,你可以调用 this.async() 来指示 loader runner 等待异步结果。它会返回 this.callback()。在这种情况下,loader 必须返回 undefined 并调用该回调。这是返回多个结果的唯一方式。

js 复制代码
/**
 *
 * @param {string|Buffer} content 资源文件的内容
 * @param {object} [map] 可被 https://github.com/mozilla/source-map 消费的 SourceMap 数据
 * @param {any} [meta] 元数据,可以是任何内容
 */
function webpackLoader(content, map, meta) {
  // 你的 webpack loader 代码
}

示例

以下部分提供了一些不同类型 loader 的基本示例。请注意,mapmeta 参数是可选的,参见下文 this.callback

同步 Loaders

可以使用 returnthis.callback 来同步返回转换后的 content

sync-loader.js

js 复制代码
export default function syncLoader(content, map, meta) {
  return someSyncOperation(content);
}

this.callback 方法更灵活,因为你可以传递多个参数,而不只是 content

sync-loader-with-multiple-results.js

{/* eslint-disable no-useless-return */}

js 复制代码
export default function syncLoaderWithMultipleResults(content, map, meta) {
  this.callback(null, someSyncOperation(content), map, meta);
  return; // 调用 callback() 时始终返回 undefined
}

异步 Loaders

对于异步 loader,你可以从 async function 中返回转换后的 content

async-loader.js

js 复制代码
export default async function asyncLoader(content, map, meta) {
  const result = await someAsyncOperation(content);
  return result;
}

或者你可以使用 this.async 来获取 callback 函数:

async-loader-with-callback.js

js 复制代码
export default function asyncLoaderWithCallback(content, map, meta) {
  const callback = this.async();
  someAsyncOperation(content, (err, result) => {
    if (err) return callback(err);
    callback(null, result, map, meta);
  });
}

async-loader-with-multiple-results.js

js 复制代码
export default function asyncLoaderWithMultipleResults(content, map, meta) {
  const callback = this.async();
  someAsyncOperation(content, (err, result, sourceMaps, meta) => {
    if (err) return callback(err);
    callback(null, result, sourceMaps, meta);
  });
}

T> Loaders 最初被设计为既可工作在同步 loader 管道中(如 Node.js,使用 enhanced-require),也可工作在异步管道中(如 webpack)。然而,在像 Node.js 这样的单线程环境中,昂贵的同步计算是个坏主意,因此我们建议尽可能将 loader 设为异步。如果计算量很小,使用同步 loader 也没问题。

"Raw" Loader

默认情况下,资源文件会被转换为 UTF-8 字符串并传递给 loader。通过将 raw 标志设置为 true,loader 将接收到原始的 Buffer。每个 loader 都可以将结果作为 StringBuffer 传递。编译器会在 loader 之间进行转换。

raw-loader.js

js 复制代码
export default function rawLoader(content) {
  assert(content instanceof Buffer);
  return someSyncOperation(content);
  // 返回值也可以是 `Buffer`
  // 即使 loader 不是 "raw" 类型也允许
}
export const raw = true;

Pitching Loader

Loader 总是从右到左被调用。在某些情况下,loader 只关心请求背后的元数据,并且可以忽略前一个 loader 的结果。loader 上的 pitch 方法在 loader 实际执行(从右到左)之前从左到右被调用。

T> Loaders 可以被内联添加到请求中,也可以通过内联前缀禁用,这会影响它们被 "pitched" 和执行的顺序。更多细节请参阅 Rule.enforce

对于以下 use 配置:

js 复制代码
export default {
  // ...
  module: {
    rules: [
      {
        // ...
        use: ["a-loader", "b-loader", "c-loader"],
      },
    ],
  },
};

将发生以下步骤:

diff 复制代码
|- a-loader `pitch`
  |- b-loader `pitch`
    |- c-loader `pitch`
      |- 请求的模块被作为依赖拾取
    |- c-loader 正常执行
  |- b-loader 正常执行
|- a-loader 正常执行

那么,为什么 loader 可能会利用 "pitching" 阶段呢?

首先,传递给 pitch 方法的 data 在执行阶段也会以 this.data 的形式暴露出来,这对于捕获和共享周期早期阶段的信息很有用。

js 复制代码
export default function myLoaderName(content) {
  return someSyncOperation(content, this.data.value);
}

export function pitch(remainingRequest, precedingRequest, data) {
  data.value = 42;
}

其次,如果 loader 在 pitch 方法中返回了结果,流程将掉头并跳过剩余的 loader。在我们上面的例子中,如果 b-loaderpitch 方法返回了一些内容:

js 复制代码
export default function myLoaderName(content) {
  return someSyncOperation(content);
}

export function pitch(remainingRequest, precedingRequest, data) {
  if (someCondition()) {
    return `import _from_loader from "${JSON.stringify(`-!${remainingRequest}`)}"; export default _from_loader;`;
  }
}

上面的步骤将被缩短为:

diff 复制代码
|- a-loader `pitch`
  |- b-loader `pitch` 返回一个模块
|- a-loader 正常执行

Loader 上下文

Loader 上下文表示在 loader 内可通过 this 属性访问的可用属性。

Loader 上下文示例

给定以下示例,使用了这个 require 调用:

/abc/file.js 中:

js 复制代码
import "./loader1?xyz!loader2!./resource?rrr";

this.addContextDependency

ts 复制代码
addContextDependency(directory: string)

将目录添加为 loader 结果的依赖。

this.addDependency

ts 复制代码
addDependency(file: string)
dependency(file: string) // 快捷方式

将现有文件添加为 loader 结果的依赖,以便使其可被监听。例如,sass-loaderless-loader 使用它来在任何导入的 css 文件发生变化时重新编译。

this.addMissingDependency

ts 复制代码
addMissingDependency(file: string)

将不存在的文件添加为 loader 结果的依赖,以便使其可被监听。与 addDependency 类似,但处理了在编译期间创建文件而监听器尚未正确附加的情况。

this.async

告知 loader-runner,该 loader 打算异步回调。返回 this.callback

this.cacheable

一个设置缓存标志的函数:

ts 复制代码
cacheable(flag = true: boolean)

默认情况下,loader 的结果被标记为可缓存。调用此方法并传入 false 可使 loader 的结果不可缓存。

一个可缓存的 loader 必须在输入和依赖未改变时产生确定性的结果。这意味着 loader 不应有除了通过 this.addDependency 指定的依赖之外的其它依赖。

this.callback

一个可以同步或异步调用以返回多个结果的函数。预期的参数是:

ts 复制代码
this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: SourceMap,
  meta?: any
);
  1. 第一个参数必须是 Errornull
  2. 第二个参数是 stringBuffer
  3. 可选:第三个参数必须是可由此模块解析的 source map。
  4. 可选:第四个参数会被 webpack 忽略,可以是任何内容(例如某些元数据)。

T> 将抽象语法树(AST),如 ESTree,作为第四个参数(meta)传递可能会很有用,如果你想在 loader 之间共享公共 AST 以加快构建时间。

如果调用了此函数,你应该返回 undefined 以避免产生歧义的 loader 结果。

this.clearDependencies

ts 复制代码
clearDependencies();

移除 loader 结果的所有依赖,包括初始依赖和其他 loader 的依赖。请考虑使用 pitch

this.context

模块所在的目录。 可以作为解析其他内容的上下文。

示例中为:/abc,因为 resource.js 位于此目录中。

this.data

在 pitch 阶段和正常阶段之间共享的数据对象。

this.emitError

ts 复制代码
emitError(error: Error)

发出一个错误,该错误也会显示在输出中。

bash 复制代码
ERROR in ./src/lib.js (./src/loader.js!./src/lib.js)
Module Error (from ./src/loader.js):
Here is an Error!
 @ ./src/index.js 1:0-25

T> 与直接抛出 Error 不同,它不会中断当前模块的编译过程。

this.emitFile

ts 复制代码
emitFile(name: string, content: Buffer|string, sourceMap: {...})

发出一个文件。这是 webpack 特有的。

this.emitWarning

ts 复制代码
emitWarning(warning: Error)

发出一个警告,该警告将像下面这样显示在输出中:

bash 复制代码
WARNING in ./src/lib.js (./src/loader.js!./src/lib.js)
Module Warning (from ./src/loader.js):
Here is a Warning!
 @ ./src/index.js 1:0-25

T> 请注意,如果 stats.warnings 被设置为 false,或者对 stats 使用了其他省略设置,如 noneerrors-only,则警告将不会显示。请参阅 stats 预设配置

this.environment

检查生成的运行时代码中可以使用哪种 ES 特性。

例如:

json 复制代码
{
  // 环境支持箭头函数 ('() => { ... }')。
  "arrowFunction": true,
  // 环境支持 BigInt 字面量 (123n)。
  "bigIntLiteral": false,
  // 环境支持使用 const 和 let 进行变量声明。
  "const": true,
  // 环境支持解构 ('{ a, b } = obj')。
  "destructuring": true,
  // 环境支持异步 import() 函数来导入 EcmaScript 模块。
  "dynamicImport": false,
  // 环境支持在创建 worker 时使用异步 import(),目前仅适用于 web 目标。
  "dynamicImportInWorker": false,
  // 环境支持 'for of' 迭代 ('for (const x of array) { ... }')。
  "forOf": true,
  // 环境支持 'globalThis'。
  "globalThis": true,
  // 环境支持使用 ECMAScript Module 语法导入 ECMAScript 模块 (import ... from '...')。
  "module": false,
  // 环境支持可选链 ('obj?.a' 或 'obj?.()')。
  "optionalChaining": true,
  // 环境支持模板字面量。
  "templateLiteral": true
}

this.fs

访问 compilationinputFileSystem 属性。

this.getOptions(schema)

提取给定的 loader 选项。可以选择接受 JSON schema 作为参数。

T> 从 webpack 5 开始,this.getOptions 在 loader 上下文中可用。它取代了 loader-utils 中的 getOptions 方法。

this.getResolve

ts 复制代码
getResolve(options: ResolveOptions): resolve

resolve(context: string, request: string, callback: function(err, result: string))
resolve(context: string, request: string): Promise<string>

创建一个类似于 this.resolve 的解析函数。

webpack resolve options 下的任何选项都是可能的。它们会与配置的 resolve 选项合并。请注意,可以在数组中使用 "..." 来扩展 resolve 选项中的值,例如 { extensions: [".sass", "..."] }

options.dependencyType 是一个额外的选项。它允许我们指定依赖的类型,用于从 resolve 选项中解析 byDependency

解析操作的所有依赖都会自动添加到当前模块的依赖中。

this.hot

关于 loader 的 HMR 信息。

js 复制代码
export default function (source) {
  console.log(this.hot); // 如果通过 --hot 标志或 webpack 配置启用了 HMR,则为 true
  return source;
}

this.hashDigest

string

生成哈希时使用的编码。参见 output.hashDigest

this.hashDigestLength

number

要使用的哈希摘要前缀长度。参见 output.hashDigestLength

this.hashFunction

string function

要使用的哈希算法。参见 output.hashFunction

this.hashSalt

string

可选的盐值,用于通过 Node.JS 的 hash.update 更新哈希。参见 output.hashSalt

this.importModule

this.importModule(request, options, [callback]): Promise

一个轻量级的替代方案,用于在构建时编译和执行请求,替代 child compiler。

  • request:要从中加载模块的请求字符串。
  • options
    • layer:指定该模块被放置/编译的层。
    • publicPath:用于构建模块的 public path。
  • callback:一个可选的 Node.js 风格回调,返回模块的导出或 ESM 的命名空间对象。如果未提供回调,importModule 将返回一个 Promise。

webpack.config.js

js 复制代码
export default {
  module: {
    rules: [
      {
        test: /stylesheet\.js$/i,
        use: ["./a-pitching-loader.js"],
        type: "asset/source", // 我们设置 type 为 'asset/source',因为 loader 将返回一个字符串
      },
    ],
  },
};

a-pitching-loader.js

js 复制代码
export async function pitch(remaining) {
  const result = await this.importModule(
    `${this.resourcePath}.webpack[javascript/auto]!=!${remaining}`,
  );
  return result.default || result;
}

src/stylesheet.js

js 复制代码
import { green, red } from "./colors.js";

export default `body { background: ${red}; color: ${green}; }`;

src/colors.js

js 复制代码
export const red = "#f00";
export const green = "#0f0";

src/index.js

js 复制代码
import stylesheet from "./stylesheet.js";
// stylesheet 在构建时将是一个字符串 `body { background: #f00; color: #0f0; }`

你可能注意到上面示例中的几点:

  1. 我们有一个 pitching loader
  2. 我们在该 pitching loader 中使用了 !=! 语法来为请求设置 matchResource,即,我们将使用 this.resourcePath + '.webpack[javascript/auto]' 来匹配 module.rules 而不是原始资源。
  3. .webpack[javascript/auto].webpack[type] 模式的伪扩展名,我们使用它来在未指定其他模块类型时指定默认的 module type。它通常与 !=! 语法结合使用。

请注意,以上示例是一个简化版本,你可以在 webpack 仓库中查看完整示例

this.loaderIndex

当前 loader 在 loaders 数组中的索引。

示例中:对于 loader1:0,对于 loader2:1

this.loadModule

ts 复制代码
loadModule(request: string, callback: function(err, source, sourceMap, module))

将给定的请求解析为一个模块,应用所有配置的 loader,并回调生成的 source、sourceMap 和模块实例(通常是 NormalModule 的实例)。如果你需要知道另一个模块的源代码来生成结果,请使用此函数。

loader 上下文中的 this.loadModule 默认使用 CommonJS 解析规则。在使用不同语义之前,请使用带有适当 dependencyType(例如 'esm''commonjs' 或自定义类型)的 this.getResolve

this.loaders

所有 loaders 的数组。在 pitch 阶段它是可写的。

ts 复制代码
loaders = [{request: string, path: string, query: string, module: function}]

示例中:

js 复制代码
[
  {
    request: "/abc/loader1.js?xyz",
    path: "/abc/loader1.js",
    query: "?xyz",
    module: [Function],
  },
  {
    request: "/abc/node_modules/loader2/index.js",
    path: "/abc/node_modules/loader2/index.js",
    query: "",
    module: [Function],
  },
];

this.mode

读取 webpack 正在以哪种 mode 运行。

可能的值:'production''development''none'

this.query

  1. 如果 loader 是通过 options 对象配置的,这将指向该对象。
  2. 如果 loader 没有 options,但通过查询字符串调用,这将是一个以 ? 开头的字符串。

this.request

解析后的请求字符串。

示例中:'/abc/loader1.js?xyz!/abc/node_modules/loader2/index.js!/abc/resource.js?rrr'

this.resolve

ts 复制代码
resolve(context: string, request: string, callback: function(err, result: string))

像 require 表达式一样解析请求。

  • context 必须是目录的绝对路径。该目录用作解析的起始位置。
  • request 是要解析的请求。通常使用相对请求(如 ./relative)或模块请求(如 module/path),但绝对路径(如 /some/path)也可以作为请求。
  • callback 是一个标准的 Node.js 风格的回调函数,提供解析后的路径。

解析操作的所有依赖都会自动添加到当前模块的依赖中。

this.resource

请求的资源部分,包括查询参数。

示例中:'/abc/resource.js?rrr'

this.resourcePath

资源文件。

示例中:'/abc/resource.js'

this.resourceQuery

资源的查询参数。

示例中:'?rrr'

this.rootContext

从 webpack 4 开始,原先的 this.options.context 作为 this.rootContext 提供。

this.sourceMap

告知是否应生成 source map。由于生成 source map 可能是一项昂贵的任务,你应该检查是否真的需要 source map。

this.target

编译的目标环境。从配置选项传递。

示例值:'web''node'

this.utils

访问以下工具函数。

  • absolutify:尽可能使用绝对路径返回一个新的请求字符串。
  • contextify:尽可能避免绝对路径返回一个新的请求字符串。
  • createHash:从提供的哈希函数返回一个新的 Hash 对象。

my-sync-loader.js

js 复制代码
export default function (content) {
  this.utils.contextify(
    this.context,
    this.utils.absolutify(this.context, "./index.js"),
  );
  this.utils.absolutify(this.context, this.resourcePath);
  const mainHash = this.utils.createHash(
    this._compilation.outputOptions.hashFunction,
  );
  mainHash.update(content);
  mainHash.digest("hex");
  // …
  return content;
}

this.version

Loader API 版本。 目前为 2。这对于提供向后兼容性很有用。使用版本号,你可以针对破坏性变更指定自定义逻辑或回退方案。

this.webpack

当由 webpack 编译时,此布尔值被设置为 true

T> Loaders 最初被设计为也可以作为 Babel 转换工作。因此,如果你编写一个对两者都适用的 loader,可以使用此属性来了解是否可以访问额外的 loaderContext 和 webpack 特性。

Webpack 特有属性

Loader 接口提供了所有与模块相关的信息。然而,在极少数情况下,你可能需要访问 compiler API 本身。

W> 请注意,使用这些 webpack 特有的属性会对你的 loader 的兼容性产生负面影响。

因此,你应该仅在万不得已时使用它们。使用它们会降低 loader 的可移植性。

this._compilation

访问 webpack 当前的 Compilation 对象。

this._compiler

访问 webpack 当前的 Compiler 对象。

已废弃的上下文属性

W> 强烈不鼓励使用这些属性,因为我们计划将它们从上下文中移除。此处列出它们仅供文档参考。

this.debug

一个布尔标志。在调试模式下被设置。

this.inputValue

从上一个 loader 传递而来。如果要将输入参数作为模块执行,请考虑读取此变量作为快捷方式(为了性能)。

this.minimize

告知结果是否应被压缩。

this.value

将值传递给下一个 loader。如果你知道你的结果作为模块执行时会导出什么,请在此处设置该值(作为单元素数组)。

this._module

Hacky 地访问正在加载的 Module 对象。

错误报告

你可以通过以下方式从 loader 内部报告错误:

  • 使用 this.emitError。会报告错误,但不会中断模块的编译。
  • 使用 throw(或其他未捕获的异常)。在 loader 运行时抛出错误将导致当前模块编译失败。
  • 使用 callback(在异步模式下)。向回调传递错误也会导致模块编译失败。

例如:

./src/index.js

js 复制代码
import "./loader!./lib";

从 loader 抛出错误:

./src/loader.js

js 复制代码
export default function (source) {
  throw new Error("This is a Fatal Error!");
}

或者在异步模式下将错误传递给回调:

./src/loader.js

js 复制代码
export default function (source) {
  const callback = this.async();
  // ...
  callback(new Error("This is a Fatal Error!"), source);
}

模块将会像这样被打包:

text 复制代码
/***/ "./src/loader.js!./src/lib.js":
/*!************************************!*\
  !*** ./src/loader.js!./src/lib.js ***!
  \************************************/
/*! no static exports found */
/***/ (function(module, exports) {

throw new Error("Module build failed (from ./src/loader.js):\nError: This is a Fatal Error!\n    at Object.module.exports (/workspace/src/loader.js:3:9)");

/***/ })

然后构建输出也会显示错误(类似于 this.emitError):

bash 复制代码
ERROR in ./src/lib.js (./src/loader.js!./src/lib.js)
Module build failed (from ./src/loader.js):
Error: This is a Fatal Error!
    at Object.module.exports (/workspace/src/loader.js:2:9)
 @ ./src/index.js 1:0-25

如下所示,不仅包含错误消息,还包含涉及哪个 loader 和模块的详细信息:

  • 模块路径:ERROR in ./src/lib.js
  • 请求字符串:(./src/loader.js!./src/lib.js)
  • loader 路径:(from ./src/loader.js)
  • 调用者路径:@ ./src/index.js 1:0-25

W> 自 webpack 4.12 起,错误中会显示 loader 路径。

T> 所有的错误和警告都会被记录到 stats 中。请参阅 Stats Data

内联 matchResource

webpack v4 引入了一种新的内联请求语法。在请求前加上 <match-resource>!=! 将为该请求设置 matchResource

W> 不建议在应用程序代码中使用此语法。
内联请求语法用于 loader 生成的代码。
不遵循此建议将使你的代码特定于 webpack 且不符合标准。

T> 相对 matchResource 将相对于包含模块的当前上下文进行解析。

当设置了 matchResource 时,它将用于匹配 module.rules 而不是原始资源。如果应该对资源应用更多 loader,或者需要更改模块类型,这将非常有用。它也会显示在 stats 中,并用于匹配 Rule.issuersplitChunks 中的 test

示例:

file.js

js 复制代码
/* STYLE: body { background: red; } */
console.log("yep");

一个 loader 可以将文件转换为以下文件,并使用 matchResource 来应用用户指定的 CSS 处理规则:

file.js(由 loader 转换后)

js 复制代码
import "./file.js.css!=!extract-style-loader/getStyles!./file.js";

console.log("yep");

这将为 extract-style-loader/getStyles!./file.js 添加一个依赖,并将结果视为 file.js.css。因为 module.rules 中有一条匹配 /\.css$/ 的规则,它会应用于这个依赖。

loader 可能看起来像这样:

extract-style-loader/index.js

js 复制代码
import getStylesLoader from "./getStyles";

export default function (source) {
  if (STYLES_REGEXP.test(source)) {
    source = source.replace(STYLES_REGEXP, "");
    return `import ${JSON.stringify(
      this.utils.contextify(
        this.context || this.rootContext,
        `${this.resource}.css!=!${getStylesLoader}!${this.remainingRequest}`,
      ),
    )};${source}`;
  }
  return source;
}

extract-style-loader/getStyles.js

js 复制代码
export default function (source) {
  const match = source.match(STYLES_REGEXP);
  return match[0];
}

日志记录

日志记录 API 自 webpack 4.37 发布以来可用。当在 stats configuration 中启用了 logging 并且/或者启用了 infrastructure logging 时,loader 可以记录消息,这些消息将按相应的 logger 格式(stats、infrastructure)打印出来。

  • Loaders 应优先使用 this.getLogger() 进行日志记录,它是 compilation.getLogger() 的快捷方式,并带有 loader 路径和已处理文件的信息。这种日志会存储到 Stats 中并进行相应格式化。webpack 用户可以对其进行过滤和导出。
  • Loaders 可以使用 this.getLogger('name') 来获取具有子名称的独立 logger。loader 路径和已处理文件仍然会被添加。
  • Loaders 可以使用特定的回退逻辑来检测日志记录支持,例如 this.getLogger ? this.getLogger() : console,以便在使用不支持 getLogger 方法的旧版本 webpack 时提供回退。

帮助我们改进文档

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