加载器 API

所谓 loader 只是一个导出为函数的 JavaScript 模块。loader runner 会调用这个函数,然后把上一个 loader 产生的结果或者资源文件(resource file)传入进去。函数的 this 上下文将由 webpack 填充,并且 loader runner 具有一些有用方法,可以使 loader 改变为异步调用方式,或者获取 query 参数。

第一个 loader 的传入参数只有一个:资源文件(resource file)的内容。compiler 需要得到最后一个 loader 产生的处理结果。这个处理结果应该是 String 或者 Buffer(被转换为一个 string),代表了模块的 JavaScript 源码。另外还可以传递一个可选的 SourceMap 结果(格式为 JSON 对象)。

如果是单个处理结果,可以在同步模式中直接返回。如果有多个处理结果,则必须调用 this.callback()。在异步模式中,必须调用 this.async(),来指示 loader runner 等待异步结果,它会返回 this.callback() 回调函数,随后 loader 必须返回 undefined 并且调用该回调函数。

示例

以下部分提供了不同类型的 loader 的一些基本示例。注意,mapmeta 参数是可选的,查看下面的 this.callback

同步 loader

无论是 return 还是 this.callback 都可以同步地返回转换后的 content 内容:

sync-loader.js

module.exports = function(content, map, meta) {
  return someSyncOperation(content);
};

this.callback 方法则更灵活,因为它允许传递多个参数,而不仅仅是content

sync-loader-with-multiple-results.js

module.exports = function(content, map, meta) {
  this.callback(null, someSyncOperation(content), map, meta);
  return; // 当调用 callback() 时总是返回 undefined
};

异步 loader

对于异步 loader,使用 this.async 来获取 callback 函数:

async-loader.js

module.exports = function(content, map, meta) {
  var callback = this.async();
  someAsyncOperation(content, function(err, result) {
    if (err) return callback(err);
    callback(null, result, map, meta);
  });
};

async-loader-with-multiple-results.js

module.exports = function(content, map, meta) {
  var callback = this.async();
  someAsyncOperation(content, function(err, result, sourceMaps, meta) {
    if (err) return callback(err);
    callback(null, result, sourceMaps, meta);
  });
};

T> loader 最初被设计为可以在同步 loader pipeline(如 Node.js ,使用 enhanced-require),与异步 pipeline(如 webpack )中运行。然而在 Node.js 这样的单线程环境下进行耗时长的同步计算不是个好主意,我们建议尽可能地使你的 loader 异步化。但如果计算量很小,同步 loader 也是可以的。

"Raw" loader

默认情况下,资源文件会被转化为 UTF-8 字符串,然后传给 loader。通过设置 raw,loader 可以接收原始的 Buffer。每一个 loader 都可以用 String 或者 Buffer 的形式传递它的处理结果。Complier 将会把它们在 loader 之间相互转换。

raw-loader.js

module.exports = function(content) {
    assert(content instanceof Buffer);
    return someSyncOperation(content);
    // 返回值也可以是一个 `Buffer`
    // 即使不是 raw loader 也没问题
};
module.exports.raw = true;

越过 loader(Pitching loader)

loader 总是从右到左地被调用。有些情况下,loader 只关心 request 后面的元数据(metadata),并且忽略前一个 loader 的结果。在实际(从右到左)执行 loader 之前,会先从左到右调用 loader 上的 pitch 方法。对于以下 use 配置:

use: [
  'a-loader',
  'b-loader',
  'c-loader'
]

将会发生这些步骤:

|- a-loader `pitch`
  |- b-loader `pitch`
    |- c-loader `pitch`
      |- requested module is picked up as a dependency
    |- c-loader normal execution
  |- b-loader normal execution
|- a-loader normal execution

那么,为什么 loader 可以利用 "跳跃(pitching)" 阶段呢?

首先,传递给 pitch 方法的 data,在执行阶段也会暴露在 this.data 之下,并且可以用于在循环时,捕获和共享前面的信息。

module.exports = function(content) {
    return someSyncOperation(content, this.data.value);
};

module.exports.pitch = function(remainingRequest, precedingRequest, data) {
    data.value = 42;
};

其次,如果某个 loader 在 pitch 方法中给出一个结果,那么这个过程会回过身来,并跳过剩下的 loader。在我们上面的例子中,如果 b-loaderpitch 方法返回了一些东西:

module.exports = function(content) {
  return someSyncOperation(content);
};

module.exports.pitch = function(remainingRequest, precedingRequest, data) {
  if (someCondition()) {
    return "module.exports = require(" + JSON.stringify("-!" + remainingRequest) + ");";
  }
};

上面的步骤将被缩短为:

|- a-loader `pitch`
  |- b-loader `pitch` returns a module
|- a-loader normal execution

查看 bundle-loader,了解如何以更有意义的方式使用此过程。

loader 上下文

loader context 表示在 loader 内使用 this 可以访问的一些方法或属性。

假设我们这样请求加载别的模块: 在 /abc/file.js 中:

require("./loader1?xyz!loader2!./resource?rrr");

this.version

loader API 的版本号。目前是 2。这对于向后兼容性有一些用处。通过这个版本号,你可以为不同版本间的破坏性变更编写不同的逻辑,或做降级处理。

this.context

模块所在的目录。可以用作解析其他模块路径的上下文。

在我们的例子中:这个属性为 /abc,因为 resource.js 在这个目录中

this.request

被解析出来的 request 字符串。

在我们的例子中:"/abc/loader1.js?xyz!/abc/node_modules/loader2/index.js!/abc/resource.js?rrr"

this.query