loader 本质上是 JS 函数。loader runner 会调用此函数。
js
function webpackLoader(content, map, meta) {
// ...
}
参数说明:
-
content: string | Buffer
- 资源源文件内容 或者 是上一个 loader 处理的结果;
-
map: object
- SourceMap 数据
-
meta: any
- meta 数据,可以是任何内容(可以是其他 loader 传递过来的参数)
同步 Loader
通过 return
或 this.callback
都可以同步地返回转换后的 content。
函数中的 this
作为上下文会被 webpack 填充。
js
// 通过 return 同步返回转换后的 content
module.exports = function (content, map, meta) {
return someSyncOperation(content);
};
// this.callback 可以传递更多参数
module.exports = function (content, map, meta) {
this.callback(null, someSyncOperation(content), map, meta);
return; // 当调用 callback() 函数时,总是返回 undefined
};
异步 Loader
通过 this.async 获取 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);
});
};
由于同步计算过于耗时,建议尽可能地使你的 loader 异步化。但如果计算量很小,同步 loader 也是可以的。
"Raw" Loader
默认情况下,资源文件会被转化为 UTF-8 字符串,然后传给 loader。通过设置 raw 为 true,loader 可以接收原始的 Buffer。每一个 loader 都可以用 String 或者 Buffer 的形式传递它的处理结果。complier 将会把它们在 loader 之间相互转换。
js
// "Raw" Loader 常用来处理 图片,字体图标等媒体资源
module.exports = function (content) {
return someSyncOperation(content);
};
module.exports.raw = true;
Pitching Loader
loader 总是 从右到左被调用。在实际(从右到左)执行 loader 之前,会先 从左到右 调用 loader 上的 pitch 方法。
js
// 传递给 pitch 方法的 data,在执行阶段也会暴露在 this.data 之下
module.exports = function (content) {
return someSyncOperation(content, this.data.value);
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
data.value = 42;
};
rust
比如处理 scss 样式文件时,webpack 有以下配置:
use: ['style-loader', 'css-loader', 'sass-loader']
执行顺序:
-> style-loader pitch
-> css-loader pitch
-> sass-loader pitch
-> sass-loader
-> css-loader
-> style-loader
如果 css-loader 的 pitch 方法 return 了一个结果,将跳过剩余 loader
顺序如下:
-> style-loader pitch
-> css-loader pitch
-> style-loader
The Loader Context
webpack.docschina.org/api/loaders...
loader context 表示在 loader 内使用 this 可以访问的一些方法或属性。
示例
js
// 清除 console.log 的 loader
module.exports = function (source, map, meta) {
const newSource = source.replace(/console.log(.+?)(;\n)?/g, "");
this.callback(null, newSource, map, meta);
};
// 简易 style-loader
module.exports.pitch = function (remainingRequest) {
// 将绝对路径转换为相对路径后,直接引用
const relativePath = remainingRequest
.split("!")
.map((absolutePath) => {
// 将每个绝对路径转换为相对路径
return this.utils.contextify(this.context, absolutePath);
})
.join("!");
const newSource = `
import style from "!!${relativePath}";
const _style = document.createElement("style");
_style.innerHTML = style;
document.head.appendChild(_style);
`;
return newSource;
};
// 简易 file-loader
const loaderUtils = require("loader-utils");
module.exports = function (source) {
// 根据文件内容生成 hash
const interpolatedName = loaderUtils.interpolateName(
this,
"[hash].[ext][query]",
{
content: source,
}
);
// 将文件输出
this.emitFile(interpolatedName, source);
return `module.exports = "${interpolatedName}"`;
};
module.exports.raw = true;
// 简易 babel-loader
const babel = require("@babel/core");
const schema = require("./schema.json");
module.exports = function (source) {
// 异步loader
const callback = this.async();
// schema 对参数进行校验
const options = this.getOptions(schema);
babel.transform(source, options, function (err, result) {
if (err) callback(err);
callback(null, result.code);
});
};
// schema.json
{
"type": "object",
"properties": {
"presets": {
"type": "array"
}
},
"additionalProperties": true
}