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 并调用该回调。这是返回多个结果的唯一选项。
/**
*
* @param {string|Buffer} content Content of the resource file
* @param {object} [map] SourceMap data consumable by https://github.com/mozilla/source-map
* @param {any} [meta] Meta data, could be anything
*/
function webpackLoader(content, map, meta) {
// code of your webpack loader
}
以下部分提供了一些不同类型 loader 的基本示例。请注意,map 和 meta 参数是可选的,请参见下面的this.callback。
可以使用 return 或 this.callback 同步返回转换后的 content
sync-loader.js
module.exports = function (content, map, meta) {
return someSyncOperation(content);
};
this.callback 方法更灵活,因为它允许你传递多个参数,而不仅仅是 content。
sync-loader-with-multiple-results.js
module.exports = function (content, map, meta) {
this.callback(null, someSyncOperation(content), map, meta);
return; // always return undefined when calling callback()
};
对于异步 loader,你可以从 async function 返回转换后的 content
async-loader.js
module.exports = async function (content, map, meta) {
var result = await someAsyncOperation(content);
return result;
};
或者你可以使用this.async 来获取 callback 函数
async-loader-with-callback.js
module.exports = function (content, map, meta) {
var callback = this.async();
someAsyncOperation(content, function (err, result) {
if (err) return callback(err);
callback(null, result, map, meta);
});
};
async-loader-with-multiple-results.js
module.exports = function (content, map, meta) {
var callback = this.async();
someAsyncOperation(content, function (err, result, sourceMaps, meta) {
if (err) return callback(err);
callback(null, result, sourceMaps, meta);
});
};
默认情况下,资源文件会转换为 UTF-8 字符串并传递给 loader。通过将 raw 标志设置为 true,loader 将接收原始 Buffer。每个 loader 都允许将其结果作为 String 或 Buffer 交付。编译器会在 loader 之间进行转换。
raw-loader.js
module.exports = function (content) {
assert(content instanceof Buffer);
return someSyncOperation(content);
// return value can be a `Buffer` too
// This is also allowed if loader is not "raw"
};
module.exports.raw = true;
Loader 始终从右到左调用。在某些情况下,loader 只关心请求的**元数据**,可以忽略前一个 loader 的结果。loader 上的 pitch 方法在 loader 实际执行(从右到左)之前,会从**左到右**调用。
对于以下use 配置
module.exports = {
//...
module: {
rules: [
{
//...
use: ['a-loader', 'b-loader', 'c-loader'],
},
],
},
};
会发生以下步骤
|- a-loader `pitch`
|- b-loader `pitch`
|- c-loader `pitch`
|- requested module is picked up as a dependency
|- c-loader normal execution
|- b-loader normal execution
|- a-loader normal execution
那么,为什么 loader 会利用“pitching”阶段呢?
首先,传递给 pitch 方法的 data 也会在执行阶段通过 this.data 暴露出来,这对于捕获和共享生命周期早期的数据很有用。
module.exports = function (content) {
return someSyncOperation(content, this.data.value);
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
data.value = 42;
};
其次,如果 loader 在 pitch 方法中返回一个结果,则进程会立即返回,并跳过剩余的 loader。在上面的示例中,如果 b-loader 的 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` returns a module
|- a-loader normal execution
loader 上下文表示在 loader 内部,赋值给 this 属性的可用属性。
给定以下示例,使用了这个 require 调用
在 /abc/file.js 中
require('./loader1?xyz!loader2!./resource?rrr');
addContextDependency(directory: string)
将一个目录作为 loader 结果的依赖项添加。
addDependency(file: string)
dependency(file: string) // shortcut
将一个现有文件作为 loader 结果的依赖项添加,以便使其可被监听。例如,sass-loader、less-loader 使用此方法在任何导入的 css 文件更改时重新编译。
addMissingDependency(file: string)
将一个不存在的文件作为 loader 结果的依赖项添加,以便使其可被监听。与 addDependency 类似,但在正确附加观察者之前处理编译期间文件的创建。
告诉 loader-runner 该 loader 打算异步回调。返回 this.callback。
一个设置可缓存标志的函数
cacheable(flag = true: boolean)
默认情况下,loader 结果被标记为可缓存。调用此方法并传入 false 会使 loader 的结果不可缓存。
一个可缓存的 loader 在输入和依赖项未改变时必须具有确定的结果。这意味着 loader 除了使用 this.addDependency 指定的依赖项外,不应有其他依赖项。
一个可以同步或异步调用的函数,用于返回多个结果。预期的参数是
this.callback(
err: Error | null,
content: string | Buffer,
sourceMap?: SourceMap,
meta?: any
);
Error 或 nullstring 或 Buffer。如果调用此函数,你应该返回 undefined 以避免 loader 结果不明确。
clearDependencies();
移除 loader 结果的所有依赖项,甚至是初始依赖项和其他 loader 的依赖项。考虑使用 pitch。
模块所在的目录。 可用作解析其他内容的上下文。
在示例中:/abc,因为 resource.js 位于此目录中
一个在 `pitch` 阶段和常规阶段之间共享的数据对象。
emitError(error: Error)
发出一个错误,该错误也可以显示在输出中。
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
emitFile(name: string, content: Buffer|string, sourceMap: {...})
发出一个文件。这是 webpack 特有的。
emitWarning(warning: Error)
发出一个警告,该警告将显示在输出中,如下所示
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
检查生成的运行时代码中可以使用哪种 ES 功能。
例如,
{
// The environment supports arrow functions ('() => { ... }').
"arrowFunction": true,
// The environment supports BigInt as literal (123n).
"bigIntLiteral": false,
// The environment supports const and let for variable declarations.
"const": true,
// The environment supports destructuring ('{ a, b } = obj').
"destructuring": true,
// The environment supports an async import() function to import EcmaScript modules.
"dynamicImport": false,
// The environment supports an async import() when creating a worker, only for web targets at the moment.
"dynamicImportInWorker": false,
// The environment supports 'for of' iteration ('for (const x of array) { ... }').
"forOf": true,
// The environment supports 'globalThis'.
"globalThis": true,
// The environment supports ECMAScript Module syntax to import ECMAScript modules (import ... from '...').
"module": false,
// The environment supports optional chaining ('obj?.a' or 'obj?.()').
"optionalChaining": true,
// The environment supports template literals.
"templateLiteral": true
}
访问 compilation 的 inputFileSystem 属性。
提取给定的 loader 选项。可选地,接受 JSON schema 作为参数。
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 选项下的任何选项。它们会与配置的 resolve 选项合并。请注意,"..." 可用于数组中以扩展 resolve 选项的值,例如 { extensions: [".sass", "..."] }。
options.dependencyType 是一个附加选项。它允许我们指定依赖项的类型,该类型用于从 resolve 选项中解析 byDependency。
解析操作的所有依赖项都会自动作为依赖项添加到当前模块中。
关于 loader 的 HMR 信息。
module.exports = function (source) {
console.log(this.hot); // true if HMR is enabled via --hot flag or webpack configuration
return source;
};
string
生成哈希时使用的编码。请参阅output.hashDigest。
number
要使用的哈希摘要的前缀长度。请参阅output.hashDigestLength。
string function
要使用的哈希算法。请参阅output.hashFunction。
string
一个可选的盐值,用于通过 Node.JS 的hash.update 更新哈希。请参阅output.hashSalt。
this.importModule(request, options, [callback]): Promise
一个轻量级的替代方案,用于子编译器在构建时编译和执行请求。
request:用于加载模块的请求字符串选项:
layer:指定此模块放置/编译所在的层publicPath:用于构建模块的公共路径callback:一个可选的 Node.js 风格回调函数,返回模块的导出或 ESM 的命名空间对象。如果未提供回调,importModule 将返回一个 Promise。webpack.config.js
module.exports = {
module: {
rules: [
{
test: /stylesheet\.js$/i,
use: ['./a-pitching-loader.js'],
type: 'asset/source', // we set type to 'asset/source' as the loader will return a string
},
],
},
};
a-pitching-loader.js
exports.pitch = async function (remaining) {
const result = await this.importModule(
this.resourcePath + '.webpack[javascript/auto]' + '!=!' + remaining
);
return result.default || result;
};
src/stylesheet.js
import { green, red } from './colors.js';
export default `body { background: ${red}; color: ${green}; }`;
src/colors.js
export const red = '#f00';
export const green = '#0f0';
src/index.js
import stylesheet from './stylesheet.js';
// stylesheet will be a string `body { background: #f00; color: #0f0; }` at build time
你可能注意到了上面的例子中的一些东西
!=! 语法来为请求设置matchResource,即,我们将使用 this.resourcePath + '.webpack[javascript/auto]' 来匹配module.rules,而不是原始资源,.webpack[javascript/auto] 是 .webpack[type] 模式的伪扩展,我们用它来在未指定其他模块类型时指定默认的模块类型。它通常与 !=! 语法结合使用。请注意,上述示例是简化版,你可以在 webpack 仓库中查看完整示例。
当前 loader 在 loaders 数组中的索引。
在示例中:在 loader1 中为 0,在 loader2 中为 1
loadModule(request: string, callback: function(err, source, sourceMap, module))
将给定请求解析为一个模块,应用所有配置的 loader,并通过生成的源代码、sourceMap 和模块实例(通常是 NormalModule 的一个实例)进行回调。如果你需要知道另一个模块的源代码来生成结果,请使用此函数。
在 loader 上下文中,this.loadModule 默认使用 CommonJS 解析规则。在使用不同的语义之前,请使用 this.getResolve 和适当的 dependencyType,例如 'esm'、'commonjs' 或自定义类型。
所有 loader 的数组。在 `pitch` 阶段可写入。
loaders = [{request: string, path: string, query: string, module: function}]
在示例中
[
{
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],
},
];
读取 webpack 正在运行的mode。
可能的值:'production'、'development'、'none'
options 对象配置的,这将指向该对象。options,但通过查询字符串调用,这将是一个以 ? 开头的字符串。已解析的请求字符串。
在示例中:'/abc/loader1.js?xyz!/abc/node_modules/loader2/index.js!/abc/resource.js?rrr'
resolve(context: string, request: string, callback: function(err, result: string))
解析一个像 `require` 表达式一样的请求。
context 必须是目录的绝对路径。此目录用作解析的起始位置。request 是要解析的请求。通常使用相对请求(如 ./relative)或模块请求(如 module/path),但绝对路径(如 /some/path)也可以作为请求。callback 是一个正常的 Node.js 风格回调函数,返回解析后的路径。解析操作的所有依赖项都会自动作为依赖项添加到当前模块中。
请求的资源部分,包括查询字符串。
在示例中:'/abc/resource.js?rrr'
资源文件。
在示例中:'/abc/resource.js'
资源的查询字符串。
在示例中:'?rrr'
从 webpack 4 开始,之前的 this.options.context 作为 this.rootContext 提供。
指示是否应该生成 source map。由于生成 source map 可能是一项昂贵的任务,因此你应该检查是否确实请求了 source map。
编译目标。从配置选项中传递。
示例值:'web'、'node'
访问以下实用工具。
absolutify:如果可能,返回一个使用绝对路径的新请求字符串。contextify:如果可能,返回一个避免使用绝对路径的新请求字符串。createHash:从提供的哈希函数返回一个新的 Hash 对象。my-sync-loader.js
module.exports = 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;
};
Loader API 版本。 当前为 2。这对于提供向后兼容性很有用。通过版本,你可以为破坏性变更指定自定义逻辑或回退。
当由 webpack 编译时,此布尔值设置为 true。
loader 接口提供了所有模块相关信息。但在极少数情况下,你可能需要访问编译器 API 本身。
因此,你只应将它们作为最后的手段。使用它们会降低你的 loader 的可移植性。
访问 webpack 当前的 Compilation 对象。
访问 webpack 当前的 Compiler 对象。
一个布尔标志。在调试模式下会设置为 true。
从上一个 loader 传递。如果你要将输入参数作为模块执行,可以考虑读取此变量以获取快捷方式(为了性能)。
指示结果是否应该被最小化。
将值传递给下一个 loader。如果你知道结果作为模块执行时会导出什么,在此处设置此值(作为仅包含一个元素的数组)。
对正在加载的 `Module` 对象的“hacky”访问。
你可以通过以下方式从 loader 内部报告错误:
this.emitError。这会报告错误,但不会中断模块的编译。throw(或其他未捕获的异常)。在 loader 运行时抛出错误将导致当前模块编译失败。callback(在异步模式下)。将错误传递给回调也会导致模块编译失败。例如
./src/index.js
require('./loader!./lib');
从 loader 抛出错误
./src/loader.js
module.exports = function (source) {
throw new Error('This is a Fatal Error!');
};
或者在异步模式下将错误传递给回调
./src/loader.js
module.exports = function (source) {
const callback = this.async();
//...
callback(new Error('This is a Fatal Error!'), source);
};
模块将像这样打包
/***/ "./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)
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)(from ./src/loader.js)@ ./src/index.js 1:0-25webpack v4 引入了一种新的内联请求语法。在一个请求前加上 <match-resource>!=! 将为该请求设置 matchResource。
当设置 matchResource 时,它将用于匹配module.rules 而不是原始资源。这在需要对资源应用更多 loader,或者需要更改模块类型时会很有用。它也显示在统计信息中,并用于匹配Rule.issuer 和splitChunks 中的 test。
示例
file.js
/* STYLE: body { background: red; } */
console.log('yep');
loader 可以将文件转换为以下文件,并使用 matchResource 应用用户指定的 CSS 处理规则
file.js (由 loader 转换)
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
const getStylesLoader = require.resolve('./getStyles');
module.exports = 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
module.exports = function (source) {
const match = source.match(STYLES_REGEXP);
return match[0];
};
自 webpack 4.37 发布以来,日志 API 已可用。当在stats 配置中启用 logging 和/或启用infrastructure logging 时,loader 可能会记录消息,这些消息将以相应的日志格式(统计信息、基础设施)打印出来。
this.getLogger() 进行日志记录,这是 compilation.getLogger() 的一个快捷方式,包含 loader 路径和已处理文件。此类日志记录存储在统计信息中,并相应地进行格式化。它可以由 webpack 用户过滤和导出。this.getLogger('name') 获取一个带有子名称的独立 logger。Loader 路径和已处理文件仍然会添加。this.getLogger ? this.getLogger() : console,以便在使用的 webpack 版本较旧且不支持 getLogger 方法时提供回退。