自定义loader
自定义 Loader 是扩展 Webpack 功能的重要方式,它允许你自定义处理各种类型的文件。以下从实现方法、核心原理到注意事项进行详细解析:
1. 自定义 Loader 基础结构
Loader 本质上是一个 函数,接收源文件内容作为参数,返回转换后的内容。
最简 Loader 示例(将文本转为大写):
// upper-loader.js
module.exports = function(source) {
return source.toUpperCase();
};
配置使用:
// webpack.config.js
{
test: /\.txt$/,
use: 'upper-loader'
}
2. 核心实现模式
同步 Loader
适用于简单转换,直接返回处理结果:
module.exports = function(source) {
// 同步处理逻辑
return source.replace(/hello/g, 'hi');
};
异步 Loader
适用于异步操作(如 API 请求、文件读取):
module.exports = function(source) {
const callback = this.async(); // 获取异步回调
setTimeout(() => {
const result = source.replace(/hello/g, 'hi');
callback(null, result); // 第一个参数为错误信息
}, 1000);
};
链式 Loader
多个 Loader 按顺序处理,执行顺序从后往前:
// webpack.config.js
{
test: /\.js$/,
use: ['loader3', 'loader2', 'loader1'] // 执行顺序:1 → 2 → 3
}
3. Loader API 与工具
常用 Loader 上下文属性
this.callback
:返回多个结果(内容、source map 等)this.async
:异步处理this.resourcePath
:当前文件路径this.addDependency
:添加额外依赖(如配置文件)
常用工具库
-
loader-utils:获取 Loader 选项、解析资源路径
const { getOptions } = require('loader-utils');
module.exports = function(source) {
const options = getOptions(this) || {};
return source.replace(new RegExp(options.search, 'g'), options.replace);
}; -
schema-utils:校验 Loader 选项
const { validate } = require('schema-utils');
const schema = {
type: 'object',
properties: {
search: { type: 'string' },
replace: { type: 'string' }
}
};
module.exports = function(source) {
const options = getOptions(this) || {};
validate(schema, options, { name: 'Replace Loader' });
// ...
};
4. 高级 Loader 实现
处理资源路径(如 url())
使用 this.resolve
解析相对路径:
const { getOptions } = require('loader-utils');
const { parse } = require('path');
module.exports = function(source) {
const callback = this.async();
const options = getOptions(this) || {};
// 替换所有 url(...)
const result = source.replace(/url\(([^)]+)\)/g, (match, url) => {
const filePath = url.replace(/['"]/g, '');
this.resolve(this.context, filePath, (err, resolvedPath) => {
if (err) return callback(err);
// 处理 resolvedPath...
});
return `url(${newUrl})`;
});
callback(null, result);
};
生成 Source Map
使用 this.callback
返回 source map:
const { SourceMapConsumer, SourceMapGenerator } = require('source-map');
module.exports = function(source, inputSourceMap) {
const callback = this.async();
// 处理 source...
// 如果有输入的 source map,则合并
if (inputSourceMap) {
const generator = SourceMapGenerator.fromSourceMap(
new SourceMapConsumer(inputSourceMap)
);
// 更新 generator...
const outputSourceMap = generator.toJSON();
callback(null, source, outputSourceMap);
} else {
callback(null, source);
}
};
5. 开发注意事项
核心原则
- 单一职责:每个 Loader 只做一件事,通过链式组合实现复杂功能
- 无状态:相同输入必须产生相同输出,不依赖外部状态
- 异步优先:涉及 I/O 或耗时操作时使用异步模式
性能优化
- 缓存结果:使用
this.cacheable(false)
禁用缓存(默认启用) - 处理大文件:使用流式处理(stream)避免内存溢出
错误处理
- 同步模式:直接抛出错误
throw new Error('Invalid format')
- 异步模式:通过回调传递错误
callback(new Error('...'))
调试技巧
- 使用
console.log
输出中间结果 - 使用
loader-runner
单独测试 Loader:const { runLoaders } = require('loader-runner');
runLoaders({
resource: '/path/to/file.js',
loaders: ['upper-loader'],
context: { minimize: true },
readResource: fs.readFile.bind(fs)
}, (err, result) => {
// 处理结果
});
6. 常见 Loader 场景
1. 模板编译(如 Pug → HTML)
const pug = require('pug');
module.exports = function(source) {
const callback = this.async();
try {
const html = pug.compile(source)({});
callback(null, `module.exports = ${JSON.stringify(html)}`);
} catch (err) {
callback(err);
}
};
2. 注入全局变量
module.exports = function(source) {
return `const ENV = '${process.env.NODE_ENV}';\n${source}`;
};
3. 图片压缩
const imagemin = require('imagemin');
const imageminPngquant = require('imagemin-pngquant');
module.exports = function(source) {
const callback = this.async();
imagemin.buffer(source, {
plugins: [imageminPngquant()]
}).then(buffer => {
callback(null, buffer);
}).catch(err => {
callback(err);
});
};
7. 发布与维护
- 发布到 npm:编写
package.json
,添加必要字段 - 版本控制:遵循语义化版本(SemVer)
- 文档完善:说明功能、配置选项和使用示例
8. 面试高频问题
-
Loader 的执行顺序是怎样的?
从后往前执行(如use: ['loader3', 'loader2', 'loader1']
→ 1 → 2 → 3)。 -
如何在 Loader 中处理异步操作?
使用this.async()
获取异步回调,处理完成后调用callback(err, result)
。 -
Loader 和 Plugin 的区别是什么?
- Loader:处理特定类型的文件(如
.css
、.js
)。 - Plugin:基于事件钩子,在构建流程的特定阶段执行自定义逻辑。
- Loader:处理特定类型的文件(如