如何写加载器

加载器是一个返回函数的node模块。
当加载器转换资源时,这个函数将被调用。
一个很简单的实例是,当只有一个加载器应用于原资源文件时,传递一个参数调用加载器:资源文件
内容是字符串.
加载器通过这个函数的this上下文访问loader API
同步加载器可以通过return仅返回一个值,在其他情况下,可以通过this.callback(err, values...)
返回任意数量的值,错误将传给回调函数,或者抛给一个同步加载器。
加载器被期望返回两个值,第一个值是返回JavaScript的字符串代码,第二个值是返回一个资源图的
JavaScript对象。
在复杂的情况下,当有多个加载器链式调用,只有最后一个加载器获得资源文件,仅第一个加载器会
输出两个值(JavaScript和SourceMap),其余的加载器传递值给前一个加载器。

Examples

1
2
3
4
// Identity loader
module.exports = function(source) {
return source;
};

1
2
3
4
// Identity loader with SourceMap support
module.exports = function(source, map) {
this.callback(null, source, map);
};

指导方针(参考)

优先级顺序,第一个应该给最高的优先级,加载器应该:
只做一个简单的任务
加载器可以链式化,为每一步创建加载器,替代一个加载器立刻做任何事。同样的意思就是,如果
没有必要的话不必转换为JavaScript.
例如:通过应用查询参数从模板文件渲染HTML。
写一个加载器从源文件里编译模板,执行然后返回一个组件,它是一个返回了包含HTML代码的字符串
这太糟糕了。
替代方案是,为每一个任务写一个加载器然后全部调用(管道的形式)。

  • jade-loader: 转换模板为一个模块输出一个函数
  • apply-loader: 拿到输出函数并且返回通过应用查询参数返回行结果
  • html-loader: 拿到HTML并输出一个字符串形式的模块
    生产标准的模块
    加载器生成的模块需要像标准的模块遵守同样的管道设计方式。
    例如:这是一个糟糕的设计方式(非标准,通用场景)
    1
    2
    3
    require("any-template-language-loader!./xyz.atl");

    var html = anyTemplateLanguage.render("xyz");

尽可能的标记自己为可缓存

大多数加载器可缓存,所以他们得标记自己为可缓存。
只需在加载器中调用cacheable

1
2
3
4
5
// Cacheable identity loader
module.exports = function(source) {
this.cacheable();
return source;
};

不要在模块和流之间保持状态
一个加载器需要在其它加载器编译时保持独立。
一个模块先前的编译结果,和当前加载器应该保持独立.
标记依赖
如果一个加载器使用了外部资源(从文件系统读),他们必须被告知,被用来使缓存加载器无效
并且在监听方法中重新编译

1
2
3
4
5
6
7
8
9
10
11
12
// Loader adding a header
var path = require("path");
module.exports = function(source) {
this.cacheable();
var callback = this.async();
var headerPath = path.resolve("header.js");
this.addDependency(headerPath);
fs.readFile(headerPath, "utf-8", function(err, header) {
if(err) return callback(err);
callback(null, header + "\n" + source);
});
};

解析依赖
在很多种语言中,有多重模式去指定依赖,比如在css中的@import和url(…),这些依赖关系需
要模块系统去解析。有两种方式去处理:

  • 转换他们为requires
  • 使用this.resolve函数去解析路径
    实例一 css-loader: css-loader在样式表中替换@importurl(...)为require的方式去编译
    依赖关系。
    实例二 less-loader: less-loader不能将@import转换为require因为所有的less文件都会
    被编译到一个临时变量中,因此,less-loader继承了less编译器的通过定义路径解析的逻辑,这个
    定义逻辑使用this.resolve通过模块系统的配置去解析文件(或者定义的模块目录等等)
    如果语言之允许相对路径(比如css中:url(file)意思就是./file),这儿用~的惯例去引用模块
    1
    2
    url(file) -> require("./file")
    url(~module) -> require("module")

提取公共代码
不要在加载器中去处理每个模块中相同的代码,不要生成这些公共代码,在加载器中创建一个运行时
文件,通过require来使用这些公共代码
不要嵌入绝对路劲
不要在模块代码中使用绝对路径,当项目被移动时会破坏hash,在loader-utils中有一个
stringifyRequest的方法将绝对路径转换为相对路径。

1
2
3
4
var loaderUtils = require("loader-utils");
return "var runtime = require(" +
loaderUtils.stringifyRequest(this, "!" + require.resolve("module/runtime")) +
");";