loader 本质上是导出为函数的 JavaScript 模块。loader runner 会调用此函数,然后将上一个 loader 产生的结果或者资源文件传入进去。函数中的 this
作为上下文会被 webpack 填充,并且 loader runner 中包含一些实用的方法,比如可以使 loader 调用方式变为异步,或者获取 query 参数。
起始 loader 只有一个入参:资源文件的内容。compiler 预期得到最后一个 loader 产生的处理结果。这个处理结果应该为 String
或者 Buffer
(能够被转换为 string)类型,代表了模块的 JavaScript 源码。另外,还可以传递一个可选的 SourceMap 结果(格式为 JSON 对象)。
如果是单个处理结果,可以在 同步模式 中直接返回。如果有多个处理结果,则必须调用 this.callback()
。在 异步模式 中,必须调用 this.async()
来告知 loader runner 等待异步结果,它会返回 this.callback()
回调函数。随后 loader 必须返回 undefined
并且调用该回调函数。
/**
*
* @param {string|Buffer} content 源文件的内容
* @param {object} [map] 可以被 https://github.com/mozilla/source-map 使用的 SourceMap 数据
* @param {any} [meta] meta 数据,可以是任何内容
*/
function webpackLoader(content, map, meta) {
// 你的 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; // 当调用 callback() 函数时,总是返回 undefined
};
对于异步 loader,使用 this.async
来获取 callback
函数:
async-loader.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
的形式传递它的处理结果。complier 将会把它们在 loader 之间相互转换。
raw-loader.js
module.exports = function (content) {
assert(content instanceof Buffer);
return someSyncOperation(content);
// 返回值也可以是一个 `Buffer`
// 即使不是 "raw",loader 也没问题
};
module.exports.raw = true;
loader 总是 从右到左被调用。有些情况下,loader 只关心 request 后面的 元数据(metadata),并且忽略前一个 loader 的结果。在实际(从右到左)执行 loader 之前,会先 从左到右 调用 loader 上的 pitch
方法。
对于以下 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 context 表示在 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
。
模块所在的目录 可以用作解析其他模块成员的上下文。
在我们的 例子 中:因为 resource.js
在这个目录中,这个属性的值为 /abc
在 pitch 阶段和 normal 阶段之间共享的 data 对象。
emitError(error: Error)
emit 一个错误,也可以在输出中显示。
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
用于访问 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
。
解析操作的所有依赖项都会自动作为依赖项添加到当前模块中。
loaders 的 HMR(热模块替换)相关信息。
module.exports = function (source) {
console.log(this.hot); // true if HMR is enabled via --hot flag or webpack configuration
return source;
};
this.importModule(request, options, [callback]): Promise
一种可以选择的轻量级解决方案,用于子编译器在构建时编译和执行请求。
request
: 加载模块的请求字符串options
:layer
:指定该模块放置/编译的层publicPath
:用于构建模块的公共路径callback
:一个可选的 Node.js 风格的回调,返回模块的 exports 或 ESM 的命名空间对象。如果没有提供回调,importModule
将返回一个 Promise。webpack.config.js
module.exports = {
module: {
rules: [
{
test: /stylesheet\.js$/i,
use: ['./a-pitching-loader.js'],
type: 'asset/source', // 我们将 type 设置为 'asset/source',其会返回一个字符串。
},
],
},
};
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 在构建时会成为一个字符串:`body { background: #f00; color: #0f0; }`。
在上面的例子中你可能会注意到一些东西:
!=!
语法来为请求设置 matchResource,例如,我们将使用 this.resourcePath + '.webpack[javascript/auto]'
而不是原始资源匹配 module.rules
,.webpack[javascript/auto]
是 .webpack[type]
模式的伪拓展,当没有指定其他模块类型时,我们使用它指定一个默认 模块类型,它通常和 !=!
语法一起使用。注意,上面的示例是一个简化的示例,你可以查看 webpack 仓库的完整示例。
当前 loader 在 loader 数组中的索引。
在示例中:loader1 中得到:0
,loader2 中得到:1
loadModule(request: string, callback: function(err, source, sourceMap, module))
解析给定的 request 到模块,应用所有配置的 loader,并且在回调函数中传入生成的 source、sourceMap 和模块实例(通常是 NormalModule
的一个实例)。如果你需要获取其他模块的源代码来生成结果的话,你可以使用这个函数。
this.loadModule
在 loader 上下文中默认使用 CommonJS 来解析规则。用一个合适的 dependencyType
使用 this.getResolve
。例如,在使用不同的语义之前使用 '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
对象的话,this 就指向这个对象。options
,而是以 query 字符串作为参数调用时,this.query 就是一个以 ?
开头的字符串。被解析出来的 request 字符串。
在我们的示例中:'/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 表达式一样解析一个 request。
context
必须是一个目录的绝对路径。此目录用作解析的起始位置。request
是要被解析的 request。通常情况下,像 ./relative
的相对请求或者像 module/path
的模块请求会被使用,但是像 /some/path
也有可能被当做 request。callback
是一个给出解析路径的 Node.js 风格的回调函数。解析操作的所有依赖项都会自动作为依赖项添加到当前模块中。
request 中的资源部分,包括 query 参数。
在示例中:'/abc/resource.js?rrr'
资源文件的路径。
在【示例](#example-for-the-loader-context)中:'/abc/resource.js'
资源的 query 参数。
在示例中:'?rrr'
从 webpack 4 开始,原先的 this.options.context
被改为 this.rootContext
。
是否应该生成一个 source map。因为生成 source map 可能会非常耗时,你应该确认 source map 确实需要。
compilation 的目标。从配置选项中传递。
示例:'web'
, 'node'
可以访问 contextify
与 absolutify
功能。
contextify
: 返回一个新的请求字符串,尽可能避免使用绝对路径。absolutify
: 尽可能使用相对路径返回一个新的请求字符串。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);
// …
return content;
};
loader API 的版本号 目前是 2
。这对于向后兼容性有一些用处。通过这个版本号,你可以自定义逻辑或者降级处理。
如果是由 webpack 编译的,这个布尔值会被设置为 true。
loader 接口提供所有模块的相关信息。然而,在极少数情况下,你可能需要访问 compiler api 本身。
因此,你应该把它们作为最后的手段。使用它们将降低 loader 的可移植性。
用于访问 webpack 的当前 Compilation 对象。
用于访问 webpack 的当前 Compiler 对象。
由于我们计划将这些属性从上下文中移除,因此不鼓励使用这些属性。它们仍然列在这里,以备参考。
一个布尔值,当处于 debug 模式时为 true。
从上一个 loader 那里传递过来的值。如果你会以模块的方式处理输入参数,建议预先读入这个变量(为了性能因素)。
决定处理结果是否应该被压缩。
向下一个 loader 传值。如果你知道了作为模块执行后的结果,请在这里赋值(以元素数组的形式)。
一种 hack 写法。用于访问当前加载的 Module 对象。
您可以通过以下方式从 loader 内部报告错误:
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!');
};
或者在异步模式下,传入一个错误给 callback:
./src/loader.js
module.exports = function (source) {
const callback = this.async();
//...
callback(new Error('This is a Fatal Error!'), source);
};
这个模块将获取像下面的 bundle:
/***/ "./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
和 test
in splitChunks
。
示例:
file.js
/* STYLE: body { background: red; } */
console.log('yep');
loader 可以将文件转换为以下文件,并使用 matchResource
应用用户指定的 CSS 处理规则:
file.js (transformed by 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 发布以来,Logging API 就可用了。当 stats configuration
或者 infrastructure logging
中启用 logging
时,loader 可以记录消息,这些消息将以相应的日志格式(stats,infrastructure)打印出来。
this.getLogger()
进行日志记录,这是指向 compilation.getLogger()
具有 loader 路径和已处理的文件。这种日志记录被存储到 Stats 中并相应地格式化。它可以被 webpack 用户过滤和导出。this.getLogger('name')
获取具有子名称的独立记录器。仍会添加 loader 路径和已处理的文件。this.getLogger() ? this.getLogger() : console
。在使用不支持 getLogger
方法的旧 webpack 版本时提供回退方法。