CC 4.0 协议

本节内容派生于以下链接指向的内容 ,并遵守 CC BY 4.0 许可证的规定。

以下内容如果没有特殊声明,可以认为都是基于原内容的修改和删减后的结果。

Loader 类型

Rspack 支持多种 loader 类型,包括同步 loader、异步 loader、ESM loader、Raw loader 和 Pitching loader 等。

以下部分提供了不同类型 loader 的一些基本示例。

同步 Loader

同步 loader 是最基本的 loader 类型,它可以通过 return 语句或 this.callback 方法同步返回转换后的内容:

sync-loader.js
module.exports = function (content, map, meta) {
  return someSyncOperation(content);
};

相比于 return 语句,this.callback 方法更加灵活,它允许传递多个参数,包括错误信息、source map 和 meta 数据:

sync-loader-with-callback.js
module.exports = function (content, map, meta) {
  this.callback(null, someSyncOperation(content), map, meta);

  // 调用 callback() 时需要返回 undefined,避免返回值冲突
  return;
};
INFO
  • mapmeta 参数是可选的,查看 this.callback 了解更多。
  • 出于技术和性能方面的考虑,Rspack 会在内部将 loader 转换为异步,无论它是否为同步 loader。

异步 Loader

当需要执行异步操作(如文件读写、网络请求等)时,应该使用异步 loader。通过调用 this.async() 方法获取 callback 函数,告知 Rspack 该 loader 需要异步处理。

异步 loader 的 callback 也可以传递多个参数,包括转换后的内容、source maps 和 meta 数据:

async-loader.js
module.exports = function (content, map, meta) {
  // 获取异步回调函数
  const callback = this.async();

  // 执行异步操作
  someAsyncOperation(content, function (err, result) {
    // 处理错误情况
    if (err) return callback(err);

    // 成功时返回处理结果
    callback(null, result, map, meta);
  });
};

ESM loader

Rspack 支持 ESM 格式的 loader,你可以使用 ESM 语法编写 loader,通过 export default 导出 loader 函数。

在编写 ESM Loader 时,文件名需要以 .mjs 结尾,或者在最近的 package.json 中将 type 设置为 module

my-loader.mjs
export default function loader(content, map, meta) {
  // ...
}

如果需要导出 rawpitch 等选项,可以使用具名导出:

my-loader.mjs
export default function loader(content) {
  // ...
}

// 设置 raw loader
export const raw = true;

// 添加 pitch 函数
export function pitch(remainingRequest, precedingRequest, data) {
  // ...
}
TIP

ESM loader 和 CommonJS loader 的功能完全相同,只是使用了不同的模块语法。你可以根据项目需求选择使用哪种格式。

使用 TypeScript 编写

如果你使用 TypeScript 来编写 Rspack loader,可以引入 LoaderContext 来为 loader 添加类型:

my-loader.ts
import type { LoaderContext } from '@rspack/core';

// 声明 loader 选项的类型
type MyLoaderOptions = {
  foo: string;
};

export default function myLoader(
  this: LoaderContext<MyLoaderOptions>,
  source: string,
) {
  const options = this.getOptions();
  console.log(options); // { foo: 'bar' }
  return source;
}

Raw Loader

默认情况下,Rspack 会将文件内容转换为 UTF-8 字符串,然后传递给 loader 进行处理。然而,在处理二进制文件(如图片、音频或字体文件)时,我们需要直接操作原始二进制数据,而不是字符串形式。

通过在 loader 文件中导出 raw: true 属性,loader 可以接收原始的 Buffer 对象作为输入,而不是字符串。

  • CJS:
raw-loader.js
module.exports = function (content) {
  // 对二进制内容进行处理
  // 此时 content 是 Buffer 实例
  const processed = someBufferOperation(content);

  // 返回处理后的结果
  return processed;
};

// 标记为 Raw Loader
module.exports.raw = true;
  • ESM:
raw-loader.mjs
export default function loader(content) {
  // ...
}

export const raw = true;

当多个 loader 串联使用时,每个 loader 都可以选择以字符串或 Buffer 的形式接收和传递处理结果。Rspack 会在不同 loader 之间自动进行 Buffer 和字符串的相互转换,确保数据能够正确传递给下一个 loader。

Raw Loader 在处理图片压缩、二进制资源转换、文件编码等场景中特别有用。例如,当开发处理图片的 loader 时,通常需要直接操作二进制数据以便正确处理图像格式。

Pitching loader

在 Rspack 的 loader 执行过程中,默认导出的 loader 函数总是从右向左被调用(称为 normal 阶段)。但有时,loader 可能只关注文件的元数据,而非前一个 loader 的处理结果。为了解决这类需求,Rspack 提供了 "pitching" 阶段 —— 在 loader 的常规执行前,每个 loader 可以定义的特殊阶段。

与常规执行相反,loader 文件里导出的 pitch 方法会从左向右被调用,先于任何 loader 的默认函数执行。这种双向处理机制为开发者提供了更灵活的资源处理方式。

例如,对于以下配置:

rspack.config.mjs
export default {
  //...
  module: {
    rules: [
      {
        //...
        use: ['a-loader', 'b-loader', 'c-loader'],
      },
    ],
  },
};

会得到这些步骤:

|-a-loader `pitch |- b-loader `pitch `. |-c-loader `pitch` |- 所请求的模块被作为依赖收集起来 |- c-loader正常执行 |-b-loader正常执行 |- a-loader 正常执行

通常情况下,如果 loader 足够简单以至于只导出了 normal 阶段的钩子:

module.exports = function (source) {};

那么其 pitching 阶段将被跳过。

那么,"pitching" 阶段对于 loader 来说有哪些优势呢?

首先,传递给 pitch 方法的数据在执行阶段也暴露在 this.data 下,可以用来捕获和共享 loader 生命周期中早期的信息。

module.exports = function (content) {
  return someSyncOperation(content, this.data.value);
};

module.exports.pitch = function (remainRequest, precedingRequest, data) {
  data.value = 42;
};

第二,如果一个 loader 在 pitch 方法中提供了一个结果,整个 loader 链路就会翻转过来,跳过其余的 normal 阶段的 loader。

在我们上面的例子中,如果 b-loaders 的 pitch 方法返回了一些内容:

module.exports = function (content) {
  return someSyncOperation(content);
};

module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  if (someCondition()) {
    return (
      'module.exports = require(' +
      JSON.stringify('-!' + remainingRequest) +
      ');'
    );
  }
};

上面的步骤将被缩短为:

|- a-loader `pitch` |- b-loader `pitch`返回一个模块 |- a-loader 正常执行

一个实际应用的例子是 style-loader,它利用了第二个优势来处理请求的调度。 请访问 style-loader 了解详情。