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

关于 loader

其实就可以将 loader 看成是一个 node 模块,导出一个函数,该函数只能接受一个参数,那就是资源文件的内容,类型为字符串。另外,该函数的返回也有讲究,根据同步或异步 loader 进行区分。

以下代码,除了 content 之外,mapmeta 都是可选的。

同步 loader 格式:

1
2
3
4
5
6
7
8
9
10
11
// 仅有单个处理结果
module.exports = function(content, map, meta){
// ...
return someSyncOperation(content);
}

// 有多个处理结果
module.exports = function(content, map, meta){
this.callback(null, someSyncOperation(content), map, meta);
return ; // 当调用 this.callback 时,需要总是返回 undefined
};

异步 loader 格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 仅有单个处理结果
module.exports = function(content, map, meta){
var cb = this.async();
someAsyncOperation(content, function(err, result){
if (err) return cb(err);
cb(null, result, map, meta);
});
};

// 有多个处理结果
module.exports = function(content, map, meta){
var cb = this.async();
someAsyncOperation(content, function(err, result, sourceMaps, meta){
if (err) return cb(err);
cb(null, result, sourceMaps, meta);
});
};

使用本地 loader

平时我们都是先用 npm install 一个 loader,然后在 webpack 配置文件进行设置。那我们写一个自己的 loader 时,要怎么办呢?很简单,在 rule 中提供本地 loader 的绝对路径即可。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// webpack.config.js
module.exports = {
// ...
module: {
rules: [
test: /\.swig$/,
use: [
{
loader: path.resolve('path/to/loader.js');
options: { /* ... */ },
}
]
]
}
};

loader 类型

loader 可以是如下类型:

  • 同步类型
  • 异步类型

编写一个自己的 loader

背景:

  • 由于 swig-loader 不支持动态传入变量数据,也不支持对象的传入形式

需求:

  • 可以动态地传入变量数据,支持对象传入形式

准备:

  • swig-loader 提供了几个钩子函数,允许中途自定义修改相关数据,其中一个重要的就是 module.exports.resourceQueryCustomizer 函数

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
var swigLoader = require('swig-loader');
// var qs = require('query-string');
var loaderUtils = require('loader-utils');
var path = require('path');
var fs = require('fs');

var LocalsFilename = 'locals.js';

var dependencies = [];

if (!Object.keys) {
Object.keys = function(obj){
var keys = [];
if (typeof obj === 'object' && Object.getPrototypeOf(obj) !== Array.prototype) {
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
keys.push(k);
}
}
}
return keys;
}
}

// 扩展对象函数
function assign(result) {
var args = Array.prototype.slice.call(arguments, 1);
args.forEach(function(a) {
Object.keys(a || {}).forEach(function(key) {
result[key] = a[key];
});
});
return result;
}


// 导出模块
module.exports = function(content){
var _this = this;
var options = loaderUtils.getOptions(this) || {};
var addDependency = function(file){
if (dependencies.indexOf(file) === -1) {
// global.console.log('HHHHHHHHHHHHHHHHHHHHH');
// global.console.log(dependencies);
// 添加依赖
_this.addDependency(file);
// 添加到缓存数组
dependencies.push(file);
}
};
// 使用 swig-loader 的钩子函数
swigLoader.resourceQueryCustomizer(function(resQuery, resPath){
// 每个页面模块下的变量文件
var localsFilename = options.localsFilename || LocalsFilename;
var localsPath = path.resolve(resPath, path.join('../', localsFilename));
var locals = {};

// 如果存在则 require,且 assign
if (fs.existsSync(localsPath)) {
// 添加文件依赖
addDependency(localsPath);
// 删除缓存
delete require.cache[require.resolve(localsPath)];
// 重新 require
locals = require(localsPath);
assign(resQuery, locals);
}
});

// 因为我们这个 loader 是 swig-loader 的包装体
// 实际上还是调用了 swig-loader
// 所以还要将 loader 内部的上下文 this,绑定到 swig-loader 的上下文中
return swigLoader.call(this, content);
};

关于同等依赖

由于我们的上面这个本地 loader 只是对 swig-loader 的简单包裹,所以我们在将本地 loader 制作成 npm 包时,需在 package.json 中的 peerDependency 引入 swig-loader,如下:

1
2
3
4
5
6
7
{
// ...
"peerDependency": {
// ...
"swig-loader": "^2.1.0"
}
}

这样一来,我们需要在项目目录中,手动地自行安装 npm install -D swig-loader

参考链接