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
或 null
string
或 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-25
webpack 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
方法时提供回退。