0%

手写一个loader

loader是导出为一个函数的node模块,该函数在loader转换资源时调用,给定函数将调用loader API,并通过this上下文访问

最简单的loader源码:

1
2
3
4
5
module.exports = function(source){
//source为compiler传递给Loader的一个文件的原内容
//该函数需要返回处理后的内容
return source
}

获得Loader的options:

1
2
3
4
5
6
const loaderUtils = require("loader-utils")
module.exports = function(source) {
//获取到用户给当前loader传入的options
const options = loaderUtils.getOptions(this)
return source
}

返回其他结果

有些场景下还需要返回除了内容外的东西,比如source Map,以方便调试源码

1
2
3
4
5
6
7
8
module.exports = function(source){
//通过this.callback告诉webpack返回的结果
this.callback(null,source,sourceMaps)
//当使用this.callback返回内容时,该loader必须返回undefined
//以让webpack知道该loader返回的结果在thi.callback中,而不是在return中
return;

}

this.callback是webpack给loader注入的api,以方便loader和webpack之间的通信,this.callback详细用法:

1
2
3
4
5
6
7
8
9
10
11
this.callback(
// 当无法转换原内容时,给 Webpack 返回一个 Error
err: Error | null,
// 原内容转换后的内容
content: string | Buffer,
// 用于把转换后的内容得出原内容的 Source Map,方便调试
sourceMap?: SourceMap,
// 如果本次转换为原内容生成了 AST 语法树,可以把这个 AST 返回,
// 以方便之后需要 AST 的 Loader 复用该 AST,以避免重复生成 AST,提升性能
abstractSyntaxTree?: AST
);

同步和异步

loader有同步和异步,同步loader的转换流程都是同步的,转换完成后再返回结果,但在有些场景下转换是异步的,例如一些网络请求

1
2
3
4
5
6
7
8
module.exports = function(source) {
// 告诉 Webpack 本次转换是异步的,Loader 会在 callback 中回调结果
var callback = this.async();
someAsyncOperation(source, function(err, result, sourceMaps, ast) {
// 通过 callback 返回异步执行后的结果
callback(err, result, sourceMaps, ast);
});
};

本地测试自定义loader:

1.在rule对象使用path.resolve指定一个本地文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const path = require('path')
module.exports = {
module:{
rules:[
{
test:/\.js$/,
use:[
{
loader: path.resolve('path/to/loader.js'),
options:{

}
}
]
}
]
}
}

匹配多个loader可以使用resolveLoader.modules配置,webpack将会从这些目录中搜索这些loaders,例如你的项目中有一个/loaders本地目录:

webpack.config.js:

1
2
3
4
5
module.exports = {
resolveLoader:{
modules: ['node_modules',path.resolve(__dirname,'loaders')]
}
}