如何编写一个自定义的 Loader?处理同步/异步 Loader 的 API 是什么?
在 Webpack 中,Loader 本质上是一个导出的 JavaScript 函数。它接收输入的源文件内容,对其进行转换,然后返回转换后的结果。
下面将详细介绍如何编写自定义 Loader,以及处理同步和异步 Loader 的核心 API。
一、 编写 Loader 的基本结构
一个最简单的 Loader 结构如下:
javascript
// my-loader.js
module.exports = function (content, map, meta) {
// content: 上一个 loader 产生的内容(字符串或 Buffer)
// map: 可选,上一个 loader 产生的 SourceMap
// meta: 可选,上一个 loader 产生的自定义元数据
// 对 content 进行处理...
const result = content.replace(/console\.log\(.*?\);?/g, '');
// 返回处理后的内容
return result;
};
二、 同步 Loader(Synchronous Loaders)
如果你的 Loader 转换过程是同步的,有两种方式返回结果:
1. 直接 return
适合只需要返回转换后的字符串/Buffer,不需要返回 SourceMap 的简单场景。
javascript
module.exports = function (content) {
const transformed = content + '\n/* append by my-loader */';
return transformed; // 直接 return
};
2. 使用 this.callback() API
当你需要返回多个结果(例如同时返回转换后的代码、SourceMap、AST 或 Meta 信息)时,必须使用 this.callback。
- API 签名:
this.callback(err: Error | null, content: string | Buffer, sourceMap?: SourceMap, meta?: any)
javascript
module.exports = function (content, map, meta) {
const transformedContent = content.toUpperCase();
// 使用 this.callback 返回多个值
this.callback(
null, // error: 如果有错误则传入 Error 对象,没有传 null
transformedContent, // content: 处理后的代码
map, // sourceMap: 继续传递 SourceMap
meta // meta: 继续传递元数据
);
// 注意:使用 this.callback 时,函数内部不要再 return 内容,直接 return 即可(或者不写 return)
return;
};
三、 异步 Loader(Asynchronous Loaders)
如果 Loader 内部包含异步操作(例如文件读取、网络请求、定时器、复杂计算等),绝对不能直接 return 或直接调用 this.callback,否则 Webpack 会在异步操作完成前就认为 Loader 执行结束了。
核心 API:this.async()
- 调用
this.async()告诉 Webpack“这是一个异步 Loader”。 this.async()会返回一个与this.callback参数相同的回调函数。- 在异步操作完成后,调用该回调函数。
javascript
module.exports = function (content, map, meta) {
// 1. 告诉 Webpack 这是异步 Loader,并获取回调函数
const callback = this.async();
// 2. 执行异步操作
someAsyncOperation(content)
? .then(result => {
// 3. 异步成功,将结果传给 callback
// 参数:callback(err, content, map, meta)
callback(null, result.content, result.map);
})
: .catch(err => {
// 4. 异步失败,将错误传给 callback
callback(err);
});
};
四、 常用 Loader 内置 API (this 上下文)
Webpack 为 Loader 函数的 this 上下文绑定了许多实用的 API(注意:Loader 不能写成箭头函数,否则会丢失 this):
| API | 说明 |
|---|---|
this.getOptions() |
(Webpack 5 推荐) 获取在 webpack.config.js 中给该 Loader 配置的 options 参数。 |
this.async() |
标记 Loader 为异步,并返回 callback 函数。 |
this.callback() |
同步 Loader 中用于返回多个结果。 |
this.addDependency(filepath) |
添加文件依赖。当该文件发生变化时,Webpack 会重新触发重新编译(用于开发模式)。 |
this.emitFile(name, content) |
直接输出一个新文件到构建目录(常用于处理图片、字体等静态资源)。 |
this.cacheable(boolean) |
设置是否缓存 Loader 的处理结果(Webpack 默认开启)。 |
this.resourcePath |
当前正在处理的文件的绝对路径。 |
五、 实战演练:编写一个带 Options 的异步 Banner Loader
这个 Loader 会在代码顶部异步注入一段自定义的注释文本(Banner)。
1. 编写 Loader (src/loaders/async-banner-loader.js)
javascript
module.exports = function (content, map, meta) {
// 1. 获取 webpack.config.js 中配置的 options
const options = this.getOptions() || {};
const author = options.author || 'Anonymous';
// 2. 声明异步
const callback = this.async();
// 3. 模拟异步操作(如:向服务器发送请求或进行耗时计算)
setTimeout(() => {
const prefix = `/**\n * Author: ${author}\n * Date: ${new Date().toISOString()}\n */\n\n`;
const result = prefix + content;
// 4. 完成异步处理并返回结果
callback(null, result, map, meta);
}, 1000);
};
2. 在 Webpack 中配置并使用它 (webpack.config.js)
javascript
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
use: [
{
// 使用自定义 Loader
loader: path.resolve(__dirname, 'src/loaders/async-banner-loader.js'),
options: {
author: 'Developer High',
},
},
],
},
],
},
// 优化:如果你不想写冗长的 path.resolve,可以配置 resolveLoader
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, 'src/loaders')],
},
};
总结 Cheat Sheet
- 同步简洁版:
return newContent; - 同步全能版:
this.callback(null, newContent, map, meta); - 异步标准版:javascript
const callback = this.async(); asyncTask().then(res => callback(null, res));