Loader 接口
Loader 接口
Loader 是一个导出函数的 JavaScript 模块。loader runner 会调用该函数,并将上一个 loader 的结果或资源文件传入其中。函数的 this 上下文由 webpack 和 loader runner 填充,包含一些有用的方法,允许 loader(除其他功能外)将其调用方式改为异步,或获取查询参数。
第一个 loader 会接收到一个参数:资源文件的内容。编译器期望最后一个 loader 返回结果。结果应为 String 或 Buffer(会被转换为字符串),表示模块的 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 的基本示例。请注意,map 和 meta 参数是可选的,参见下文 this.callback。
同步 Loaders
可以使用 return 或 this.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 都可以将结果作为 String 或 Buffer 传递。编译器会在 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-loader 的 pitch 方法返回了一些内容:
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-loader、less-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
);
- 第一个参数必须是
Error或null - 第二个参数是
string或Buffer。 - 可选:第三个参数必须是可由此模块解析的 source map。
- 可选:第四个参数会被 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 使用了其他省略设置,如 none 或 errors-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
访问 compilation 的 inputFileSystem 属性。
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; }`
你可能注意到上面示例中的几点:
- 我们有一个 pitching loader。
- 我们在该 pitching loader 中使用了
!=!语法来为请求设置 matchResource,即,我们将使用this.resourcePath + '.webpack[javascript/auto]'来匹配module.rules而不是原始资源。 .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
- 如果 loader 是通过
options对象配置的,这将指向该对象。 - 如果 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.issuer 和 splitChunks 中的 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 时提供回退。
帮助我们改进文档
发现翻译问题或内容错误?请告诉我们。
