如何编写自定义webpack loader?
2021-06-22 本文已影响0人
努力学习的小丸子
https://webpack.docschina.org/contribute/writing-a-loader/
书写规范
loader 需导出一个函数
监听文件实时编译
const path = require("path");
const fs = require("fs");
export default function (source) {
var callback = this.async();
var headerPath = path.resolve("header.js");
// 当header.js文件发生改变时会触发编译
this.addDependency(headerPath);
fs.readFile(headerPath, "utf-8", function (err, header) {
if (err) return callback(err);
callback(null, header + "\n" + source);
});
}
webpack 5 新特性
webpack 5 发布后,在 loader 的上下文中,会带有内置的 this.getOptions 方法。这对于那些使用之前推荐 schema-utils 中的 getOptions 方法的 loader 而言,这是一个重大更新。
模块依赖
根据模块的类型,可以使用不同的模式来指定依赖项。例如在CSS中,使用@import和url(…)语句。这些依赖关系应该由模块系统来解决。
这可以通过两种方式之一来实现:
- 通过将它们转换为require语句。
- 使用
this.resolve
解析路径函数。
对于 less-loader
,它不能将每个@import转换为require,因为所有的.less文件必须一次编译一次,用于变量和mixin跟踪。因此,less loader使用自定义路径解析逻辑扩展了less compiler。然后它利用了第二种方法this.resolve
来解析依赖。
绝对路径
不要在模块代码中插入绝对路径,因为当项目的根被移动时,它们会破坏散列。loader-utils中有一个stringifyRequest方法,可以用来将绝对路径转换为相对路径。
jtest 测试
使用webpack和memfs来避免输出文件到磁盘
// compiler.js
import path from 'path';
import webpack from 'webpack';
import { createFsFromVolume, Volume } from 'memfs';
export default (fixture, options = {}) => {
const compiler = webpack({
context: __dirname,
entry: `./${fixture}`,
output: {
path: path.resolve(__dirname),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'replace-loader',
options,
},
},
],
},
resolveLoader: {
modules: ["../loader"], // 配置loader的查找目录
},
});
compiler.outputFileSystem = createFsFromVolume(new Volume());
compiler.outputFileSystem.join = path.join.bind(path);
return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
if (err) reject(err);
if (stats.hasErrors()) reject(stats.toJson().errors);
resolve(stats);
});
});
};
// loader.test.js
import compiler from './compiler.js';
test('Inserts name and outputs JavaScript', async () => {
const stats = await compiler('example.js', { name: 'Alice' });
const output = stats.toJson({ source: true }).modules[0].source;
console.log(output);
expect(output).toBe("console.log('hello Alice');");
});
// example.js
console.log('hello world');
// package.json
"test": "jest"
其他代码:
// loader/replace-loader.js
// 注意导出的函数不能是箭头函数
// webpack 5 发布后,在 loader 的上下文中,会带有内置的 this.getOptions 方法。
// 这对于那些使用之前推荐 schema-utils 中的 getOptions 方法的 loader 而言,这是一个重大更新:
import { getOptions } from 'loader-utils';
import { validate } from 'schema-utils';
const schema = {
type: 'object',
properties: {
test: {
type: 'string'
}
}
};
export default function(source) {
const options = getOptions(this);
validate(schema, options, {
name: 'Example Loader',
baseDataPath: 'options'
});
return source.replace(/world/g, options.text);
}
// vue.config.js
configureWebpack: {
module: {
rules: [
{
test: /\.js$/i,
use: ["replace-loader"],
},
],
},
resolveLoader: {
modules: ["./node_modules", "./loader"], // 配置loader的查找目录
},
}