CC 4.0 协议
本节内容派生于以下链接指向的内容 ,并遵守 CC BY 4.0 许可证的规定。
以下内容如果没有特殊声明,可以认为都是基于原内容的修改和删减后的结果。
Loader 上下文
Loader 上下文表示 loader 内部可用的属性,这些属性在 loader 中通过 this 属性进行访问。
this.addBuildDependency()
function addBuildDependency(file: string): void;
添加一个文件作为 loader 结果的构建依赖。当构建依赖发生变化时,Rspack 会使持久化缓存失效。
它适用于会影响 loader 行为或转换结果的文件,例如 loader 的配置文件。
loader.mjs
import path from 'node:path';
export default function loader(source) {
this.addBuildDependency(
path.resolve(this.rootContext, 'custom-loader.config.js'),
);
return source;
}
Tip
this.addBuildDependency() 不会将文件添加为 watch 依赖。如果文件变化时还应在 watch 模式下触发重新构建,请同时调用 this.addDependency()。
this.addContextDependency()
function addContextDependency(directory: string): void;
添加目录作为 loader 结果的依赖,使目录中文件的任何变化可以被监听到。
例如,添加 src/static 目录作为依赖,当目录中的文件发生变化时,会触发重新构建。
loader.mjs
import path from 'node:path';
export default function loader(source) {
this.addContextDependency(path.resolve(this.rootContext, 'src/static'));
return source;
}
this.addDependency()
function addDependency(file: string): void;
添加一个文件作为 loader 结果的依赖,使它们的任何变化可以被监听到。例如,sass-loader、less-loader 就使用了这个技巧,当导入的样式文件发生变化时就会重新编译。
loader.mjs
import path from 'node:path';
export default function loader(source) {
this.addDependency(path.resolve(this.rootContext, 'src/styles/foo.scss'));
return source;
}
this.addMissingDependency()
function addMissingDependency(file: string): void;
添加一个当前不存在的文件作为 loader 结果的依赖,使它们的创建和任何变化可以被监听到。例如,当该路径下新建了文件时,会触发重新构建。
loader.mjs
import path from 'node:path';
export default function loader(source) {
this.addMissingDependency(
path.resolve(this.rootContext, 'src/dynamic-file.json'),
);
return source;
}
this.async()
- 类型:
() => LoaderContextCallback
告诉 Rspack 这个 loader 将会异步被调用。返回值为 this.callback。
查看 异步 loader 了解如何使用。
this.cacheable()
function cacheable(flag: boolean = true): void;
默认情况下,当前模块经过整条 loader 链生成的最终构建结果可以被缓存。调用 this.cacheable(false) 会将该结果标记为不可缓存。
设置后,后续 loader 即使调用 this.cacheable(true) 或 this.cacheable() 也无法重新启用缓存。只有 this.clearDependencies() 会重置这个状态。
loader.mjs
export default function loader(source) {
this.cacheable(false);
return source;
}
this.callback()
interface AdditionalData {
[index: string]: any;
}
function callback(
err?: Error | null,
content?: string | Buffer,
sourceMap?: string | Rspack.RawSourceMap,
additionalData?: AdditionalData,
): void;
调用 this.callback() 返回 loader 的处理结果,同步或异步 loader 均可使用。各参数的含义如下:
err:loader 执行失败时传入 Error;执行成功时传入 null 或 undefined。
content:转换后的模块内容,可以是 string 或 Buffer。只报告错误时可以省略。
sourceMap:可选的 source map,可以是 string 或 Rspack.RawSourceMap。
additionalData:可选的附加数据。Rspack 会将它作为第三个参数传给 loader 链中的下一个 loader。
查看 同步 loader 了解如何使用。
Warning
当这个函数被调用时,你应该返回 undefined 以避免 loader 结果的歧义。
content、sourceMap 和 additionalData 会传递给 loader 链中的下一个 loader。
this.clearDependencies()
function clearDependencies(): void;
清除 loader 链中已收集的所有文件依赖、上下文依赖和缺失依赖,但不会清除 build dependencies。它还会将 cacheable 重置为 true,因此此前任何 loader 调用 this.cacheable(false) 产生的设置都会失效。
只有在当前 loader 会为最终结果重新注册全部所需依赖时,才应调用此方法。
this.context
当前被处理的模块所在的目录路径,会随着每个被处理的模块的位置而变化。
例如,如果 loader 处理的是 /project/src/components/Button.js,那么 this.context 的值就是 /project/src/components。
loader.mjs
export default function loader(source) {
console.log(this.context); // '/project/src/components'
return source;
}
如果正在处理的模块没有资源路径,this.context 的值为 null。
this.loaderIndex
当前 loader 在 loaders 数组中的索引。
this.loaders
this.loaders 包含应用于当前模块的所有 loader。每一项都提供解析后的 request、path、query 和 options 等信息。
在 pitch 阶段可以修改这个数组,以调整 loader 链。可通过 this.loaderIndex 定位当前 loader。
loader.mjs
export function pitch() {
const currentLoader = this.loaders[this.loaderIndex];
console.log(currentLoader.request);
}
this.data
用于在 pitch 和 normal 阶段之间共享数据。
this.dependency()
function dependency(file: string): void;
this.addDependency() 的别名。
this.emitError()
function emitError(error: Error): void;
发出一个错误。
与在 loader 中 throw 和 this.callback(err) 不同,它不会将当前模块标记为编译失败,只会向 Rspack 的 Compilation 添加一个错误,并在本次编译结束后显示在命令行中。
this.emitWarning()
function emitWarning(warning: Error): void;
发出一个警告。
this.experiments.emitDiagnostic()
interface DiagnosticLocation {
/** Text for highlighting the location */
text?: string;
/** 1-based line */
line: number;
/** 0-based column in bytes */
column: number;
/** Length in bytes */
length: number;
}
interface Diagnostic {
message: string;
help?: string;
sourceCode?: string;
/**
* 源代码的位置信息
* 如果未提供 `sourceCode`,则位置信息将被省略
*/
location?: DiagnosticLocation;
/**
* 可选展示的文件名
* 如果设置,它将成为 stats 中的 `StatsError.file` 值
*/
file?: string;
severity: 'error' | 'warning';
}
function emitDiagnostic(diagnostic: Diagnostic): void;
格式化并输出错误或警告诊断信息,支持显示模块路径、源代码片段和行列号。
Info
与在 loader 中 throw 和 this.callback(err) 不同,它不会将当前模块标记为编译失败,而是根据 severity 向 Rspack 的 Compilation 添加错误或警告,并在本次编译结束后显示在命令行中。
只提供 message 和 severity 时,仅会输出基本的诊断信息。
loader.mjs
/** @type {import("@rspack/core").LoaderDefinition} */
export default function () {
this.experiments.emitDiagnostic({
message: '`React` is not defined',
severity: 'error',
});
this.experiments.emitDiagnostic({
message: '`React` is not defined',
severity: 'warning',
});
return '';
}
将会打印:
ERROR in (./loader.mjs!)
× ModuleError: `React` is not defined
WARNING in (./loader.mjs!)
⚠ ModuleWarning: `React` is not defined
loader.mjs
/** @type {import("@rspack/core").LoaderDefinition} */
export default function () {
this.experiments.emitDiagnostic({
message: '`React` is not defined',
severity: 'error',
sourceCode: `<div></div>`,
location: {
line: 1,
column: 1,
length: 3,
},
file: './some-file.js',
});
return '';
}
将会打印:
ERROR in ./some-file.js
./file.js 1:1-4
× ModuleError: `React` is not defined
╭────
1 │ <div></div>
· ───
╰────
其中 ./some-file.js 为传入的 file 字段。
this.emitFile()
function emitFile(
name: string,
content: string | Buffer,
sourceMap?: string,
assetInfo?: AssetInfo,
): void;
输出一个新文件。这个方法允许你在 loader 执行过程中创建新的文件。
loader.mjs
export default function loader(source) {
// 输出一个新文件,该文件将在产物目录中输出为 `foo.js`
this.emitFile('foo.js', 'console.log("Hello, world!");');
return source;
}
loader.mjs
export default function loader(source) {
this.emitFile(
'foo.js',
'console.log("Hello, world!");',
undefined, // no sourcemap
{
sourceFilename: this.resourcePath,
},
);
return source;
}
this.fs
访问 compilation 对象的 inputFileSystem 属性。
this.getContextDependencies()
function getContextDependencies(): string[];
返回 loader 当前作为上下文依赖监听的所有目录,其中包括通过 this.addContextDependency() 添加的目录。
loader.mjs
export default function loader(source) {
const contextDependencies = this.getContextDependencies();
console.log(contextDependencies);
return source;
}
this.getDependencies()
function getDependencies(): string[];
返回 loader 当前作为依赖监听的所有文件,其中包括通过 this.addDependency() 或 this.dependency() 添加的文件。
loader.mjs
export default function loader(source) {
const dependencies = this.getDependencies();
console.log(dependencies);
return source;
}
this.getMissingDependencies()
function getMissingDependencies(): string[];
返回 loader 当前监听的所有尚不存在的文件路径,其中包括通过 this.addMissingDependency() 添加的路径。当这些文件被创建时,可能会触发重新构建。
loader.mjs
export default function loader(source) {
const missingDependencies = this.getMissingDependencies();
console.log(missingDependencies);
return source;
}
这三个方法都会返回一个新数组。修改返回的数组不会影响 loader 已注册的依赖。如需清空这三类依赖列表,请使用 this.clearDependencies()。
this.getOptions()
function getOptions(schema?: any): OptionsType;
获取 loader 的使用者传入的选项。
例如:
rspack.config.mjs
export default {
module: {
rules: [
{
test: /\.txt$/,
use: {
loader: './my-loader.mjs',
options: {
foo: 'bar',
},
},
},
],
},
};
在 my-loader.mjs 中获取传入的选项:
my-loader.mjs
export default function myLoader(source) {
const options = this.getOptions();
console.log(options); // { foo: 'bar' }
return source;
}
Tip
当 loader 通过查询字符串配置时,例如 loader: './my-loader?s=foo+bar',this.getOptions() 会使用 Node.js 的 querystring.parse() 解析该字符串。这意味着字面量 + 会被解码为空格,因此结果会是 { s: 'foo bar' }。
如果你需要字面量加号,请将 + 编码为 %2B,或者优先在规则里使用 options 对象。若你需要读取原始查询字符串,请使用 this.query。
在 TypeScript 中,你可以通过 LoaderContext 的泛型来设置 options 的类型。
my-loader.ts
import type { LoaderContext } from '@rspack/core';
type MyLoaderOptions = {
foo: string;
};
export default function myLoader(
this: LoaderContext<MyLoaderOptions>,
source: string,
) {
const options = this.getOptions();
console.log(options); // { foo: 'bar' }
return source;
}
Tip
参数 schema 是可选的,在 Rspack 中不会被使用。
为了提供最佳性能,Rspack 不会执行 schema 验证。如果你的 loader 需要 schema 验证,请自行调用 schema-utils 或其他 schema 验证库。
this.getResolve()
type ResolveFunction = {
(
context: string,
request: string,
callback: (
err: Error | null,
result?: string | false,
resolveRequest?: ResolveRequest,
) => void,
): void;
(context: string, request: string): Promise<string | false | undefined>;
};
function getResolve(options?: ResolveOptions): ResolveFunction;
创建一个类似于 this.resolve() 的解析函数。可以通过 options 自定义解析行为;省略时使用不带额外选项的普通解析器。
返回的解析函数同时支持回调和 Promise 两种调用方式。不传回调函数时,它会返回 Promise。
loader.mjs
export default async function loader(source) {
const resolve = this.getResolve({
extensions: ['.js', '.json'],
});
const result = await resolve(this.context, './dependency');
console.log(result);
return source;
}
this.hot
是否启用了 HMR。
loader.mjs
export default function (source) {
console.log(this.hot); // true if HMR is enabled
return source;
}
this.importModule()
interface ImportModuleOptions {
/**
* 指定模块的 layer
*/
layer?: string;
/**
* 构建模块时使用的 public path
*/
publicPath?: PublicPath;
/**
* 目标 base uri
*/
baseUri?: string;
}
// 传入回调函数
function importModule<T = any>(
request: string,
options: ImportModuleOptions | undefined,
callback: (err?: null | Error, exports?: T) => any,
): void;
// 不传入回调函数时,返回 Promise
function importModule<T = any>(
request: string,
options?: ImportModuleOptions,
): Promise<T>;
在构建过程中编译和执行一个模块。这是 child compiler 的轻量级替代方案。
在没有提供回调函数时,importModule 会返回一个 Promise。
loader.mjs
import path from 'node:path';
export default async function loader(source) {
const modulePath = path.resolve(this.rootContext, 'some-module.ts');
const moduleExports = await this.importModule(modulePath, {
// 可选参数
});
const result = someProcessing(source, moduleExports);
return result;
}
或者你可以传递一个回调函数给它。
loader.mjs
import path from 'node:path';
export default function loader(source) {
const callback = this.async();
const modulePath = path.resolve(this.rootContext, 'some-module.ts');
this.importModule(
modulePath,
// 可选参数
undefined,
(err, moduleExports) => {
if (err) {
return callback(err);
}
const result = someProcessing(source, moduleExports);
callback(null, result);
},
);
}
this.query
该值取决于 loader 的配置方式:
- 如果当前 loader 配置了一个选项对象,
this.query 将指向这个对象。
- 如果当前 loader 没有选项,但是通过查询字符串调用,这将是一个以
? 开头的字符串。
与 this.getOptions() 不同,查询字符串形式在这里不会被解析。例如,loader: './my-loader?s=foo+bar' 时,this.query === '?s=foo+bar'。
this.remainingRequest
this.remainingRequest 由 loader 链中位于当前 loader 之后的所有 loader 和当前资源组成,各部分使用 ! 连接。
例如,假设 loader 链为:
/path/to/loader1.mjs!/path/to/loader2.mjs!/path/to/resource.js
执行到 loader1.mjs 时,this.remainingRequest 为:
/path/to/loader2.mjs!/path/to/resource.js
它通常用于构造内联请求,避免再次执行当前 loader。可以参考 Inline matchResource 中的示例。
this.currentRequest
this.currentRequest 由当前 loader、loader 链中位于它之后的所有 loader 和当前资源组成,各部分使用 ! 连接。
例如,假设 loader 链为:
/path/to/loader1.mjs!/path/to/loader2.mjs!/path/to/resource.js
执行到 loader2.mjs 时,this.currentRequest 为:
/path/to/loader2.mjs!/path/to/resource.js
this.previousRequest
this.previousRequest 由 loader 链中位于当前 loader 之前的所有 loader 组成,各部分使用 ! 连接。它不包含当前资源。
例如,假设 loader 链为:
/path/to/loader1.mjs!/path/to/loader2.mjs!/path/to/resource.js
执行到 loader2.mjs 时,this.previousRequest 为:
this.request
完整的请求字符串,由所有 loader 和当前资源组成,各部分使用 ! 连接。
例如,如果 resource.js 被 loader1.mjs 和 loader2.mjs 处理,那么 this.request 的值为 /path/to/loader1.mjs!/path/to/loader2.mjs!/path/to/resource.js。
this.resolve()
function resolve(
context: string,
request: string,
callback: (
err: Error | null,
result?: string | false,
resolveRequest?: ResolveRequest,
) => void,
): void;
解析一个模块标识符。
context 必须是一个目录的绝对路径。此目录用作解析的起始位置。
request 是要被解析的模块标识符。
callback 会接收错误、解析后的路径(请求被忽略时为 false),以及可选的解析详情。
this.mode
mode 配置的值,可能为 'production'、'development'、'none' 或 undefined。如果没有配置 mode,this.mode 为 undefined,但 Rspack 仍会应用偏向生产环境的默认配置。
loader.mjs
export default function loader(source) {
console.log(this.mode); // 'production'、'development'、'none' 或 undefined
return source;
}
this.target
默认情况下,Rspack 会根据 target 所描述的运行环境能力,为 loader 生成一个更简单的目标值。能够判断目标环境时,该值会是 'web'、'node'、'nwjs' 或 'electron-main' 等。因此,this.target 不一定与原始的 target 配置完全相同。
loader.mjs
export default function loader(source) {
console.log(this.target); // 'web' or other values
return source;
}
this.environment
描述目标运行环境支持的能力。默认情况下,该值是最终生效的 output.environment:Rspack 会根据 target 判断目标环境支持的能力,再应用 output.environment 中的显式配置。
loader 可以根据这些信息选择输出环境支持的语法。
loader.mjs
export default function loader(source) {
if (this.environment.optionalChaining) {
console.log('Optional chaining is supported');
}
return source;
}
this.utils
type Utils = {
absolutify: (context: string, request: string) => string;
contextify: (context: string, request: string) => string;
createHash: (algorithm?: string) => Hash;
};
访问以下 utils:
absolutify: 返回一个新的 request 字符串,尽可能使用绝对路径。
contextify: 返回一个新的 request 字符串,尽可能避免使用绝对路径。
createHash: 基于提供的哈希函数返回一个新的 Hash 对象。
loader.mjs
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.resource
当前模块的路径字符串。比如 '/abc/resource.js?query#hash'。
loader.mjs
export default function loader(source) {
console.log(this.resource); // '/abc/resource.js?query#hash'
return source;
}
this.resourcePath
当前模块的路径字符串,不包括 query 和 fragment 参数。比如 '/abc/resource.js?query#hash' 中的 '/abc/resource.js'。
loader.mjs
export default function loader(source) {
console.log(this.resourcePath); // '/abc/resource.js'
return source;
}
this.resourceQuery
当前模块的路径字符串的 query 参数。比如 '/abc/resource.js?query#hash' 中的 '?query'。
loader.mjs
export default function loader(source) {
console.log(this.resourceQuery); // '?query'
return source;
}
this.resourceFragment
当前模块的路径字符串的 fragment 参数。比如 '/abc/resource.js?query#hash' 中的 '#hash'。
loader.mjs
export default function loader(source) {
console.log(this.resourceFragment); // '#hash'
return source;
}
this.rootContext
Rspack config 中通过 context 配置的基础路径。
loader.mjs
export default function loader(source) {
console.log(this.rootContext); // /path/to/project
return source;
}
this.sourceMap
是否应该生成 source map。
由于生成 source map 通常是一项耗费资源的任务,你应该检查是否需要 source map。
详见 处理 Source Map。
this.getLogger()
function getLogger(name?: string): Logger;
获取此次编译过程的 logger,可通过该 logger 记录消息。
this.version
loader API 的版本号。当前为 2。
这对于提供向后兼容性很有用。基于版本号,你可以为 breaking changes 指定自定义逻辑或降级方案。
内部属性
Warning
注意,使用 this._compiler 和 this._compilation 这些 Rspack 的内部属性会使你的 loader 失去独立性。
理想情况下,loader 应该只关注文件转换逻辑,对于给定的输入,输出是确定的,不依赖 Rspack 的内部状态。依赖这些内部对象会增加不可预测的行为,使测试和维护变得困难。
因此,建议仅在没有其他替代方案时才考虑使用这些属性。
this._compiler
访问当前的 Rspack Compiler 对象。
this._compilation
访问当前的 Rspack Compilation 对象。