Loader 接口

Loader 是一个导出函数的 JavaScript 模块。loader runner 调用此函数,并将前一个 loader 的结果或资源文件传递给它。函数的 this 上下文由 webpack 和 loader runner 填充了一些有用的方法,这些方法允许 loader(除其他外)将其调用方式更改为异步,或获取查询参数。

第一个 loader 会传入一个参数:资源文件的内容。编译器期望从最后一个 loader 获得一个结果。结果应该是一个 StringBuffer(它会被转换为字符串),表示模块的 JavaScript 源代码。还可以选择性地传递一个 SourceMap 结果(作为 JSON 对象)。

同步模式下可以返回单个结果。对于多个结果,必须调用 this.callback(),并且 loader 必须返回 undefined

异步模式下,你可以从 async function 返回单个结果。另外,你可以调用 this.async() 来指示 loader runner 应该等待异步结果。它返回 this.callback()。在这种情况下,loader 必须返回 undefined 并调用该回调。这是返回多个结果的唯一选项。

/**
 *
 * @param {string|Buffer} content Content of the resource file
 * @param {object} [map] SourceMap data consumable by https://github.com/mozilla/source-map
 * @param {any} [meta] Meta data, could be anything
 */
function webpackLoader(content, map, meta) {
  // code of your webpack loader
}

示例

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

同步加载器

可以使用 returnthis.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; // always return undefined when calling callback()
};

异步加载器

对于异步 loader,你可以从 async function 返回转换后的 content

async-loader.js

module.exports = async function (content, map, meta) {
  var result = await someAsyncOperation(content);
  return result;
};

或者你可以使用this.async 来获取 callback 函数

async-loader-with-callback.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);
  });
};

“Raw” 加载器

默认情况下,资源文件会转换为 UTF-8 字符串并传递给 loader。通过将 raw 标志设置为 true,loader 将接收原始 Buffer。每个 loader 都允许将其结果作为 StringBuffer 交付。编译器会在 loader 之间进行转换。

raw-loader.js

module.exports = function (content) {
  assert(content instanceof Buffer);
  return someSyncOperation(content);
  // return value can be a `Buffer` too
  // This is also allowed if loader is not "raw"
};
module.exports.raw = true;

Pitching 加载器

Loader 始终从右到左调用。在某些情况下,loader 只关心请求的**元数据**,可以忽略前一个 loader 的结果。loader 上的 pitch 方法在 loader 实际执行(从右到左)之前,会从**左到右**调用。

对于以下use 配置

module.exports = {
  //...
  module: {
    rules: [
      {
        //...
        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

加载器上下文

loader 上下文表示在 loader 内部,赋值给 this 属性的可用属性。

loader 上下文示例

给定以下示例,使用了这个 require 调用

/abc/file.js

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

this.addContextDependency

addContextDependency(directory: string)

将一个目录作为 loader 结果的依赖项添加。

this.addDependency

addDependency(file: string)
dependency(file: string) // shortcut

将一个现有文件作为 loader 结果的依赖项添加,以便使其可被监听。例如,sass-loaderless-loader 使用此方法在任何导入的 css 文件更改时重新编译。

this.addMissingDependency

addMissingDependency(file: string)

将一个不存在的文件作为 loader 结果的依赖项添加,以便使其可被监听。与 addDependency 类似,但在正确附加观察者之前处理编译期间文件的创建。

this.async

告诉 loader-runner 该 loader 打算异步回调。返回 this.callback

this.cacheable

一个设置可缓存标志的函数

cacheable(flag = true: boolean)

默认情况下,loader 结果被标记为可缓存。调用此方法并传入 false 会使 loader 的结果不可缓存。

一个可缓存的 loader 在输入和依赖项未改变时必须具有确定的结果。这意味着 loader 除了使用 this.addDependency 指定的依赖项外,不应有其他依赖项。

this.callback

一个可以同步或异步调用的函数,用于返回多个结果。预期的参数是

this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: SourceMap,
  meta?: any
);
  1. 第一个参数必须是 Errornull
  2. 第二个参数是 stringBuffer
  3. 可选:第三个参数必须是可被此模块解析的 source map。
  4. 可选:第四个选项,被 webpack 忽略,可以是任何内容(例如一些元数据)。

如果调用此函数,你应该返回 undefined 以避免 loader 结果不明确。

this.clearDependencies

clearDependencies();

移除 loader 结果的所有依赖项,甚至是初始依赖项和其他 loader 的依赖项。考虑使用 pitch

this.context

模块所在的目录。 可用作解析其他内容的上下文。

示例中:/abc,因为 resource.js 位于此目录中

this.data

一个在 `pitch` 阶段和常规阶段之间共享的数据对象。

this.emitError

emitError(error: Error)

发出一个错误,该错误也可以显示在输出中。

ERROR in ./src/lib.js (./src/loader.js!./src/lib.js)
Module Error (from ./src/loader.js):
Here is an Error!
 @ ./src/index.js 1:0-25

this.emitFile

emitFile(name: string, content: Buffer|string, sourceMap: {...})

发出一个文件。这是 webpack 特有的。

this.emitWarning

emitWarning(warning: Error)

发出一个警告,该警告将显示在输出中,如下所示

WARNING in ./src/lib.js (./src/loader.js!./src/lib.js)
Module Warning (from ./src/loader.js):
Here is a Warning!
 @ ./src/index.js 1:0-25

this.environment

检查生成的运行时代码中可以使用哪种 ES 功能。

例如,

{
  // The environment supports arrow functions ('() => { ... }').
  "arrowFunction": true,
  // The environment supports BigInt as literal (123n).
  "bigIntLiteral": false,
  // The environment supports const and let for variable declarations.
  "const": true,
  // The environment supports destructuring ('{ a, b } = obj').
  "destructuring": true,
  // The environment supports an async import() function to import EcmaScript modules.
  "dynamicImport": false,
  // The environment supports an async import() when creating a worker, only for web targets at the moment.
  "dynamicImportInWorker": false,
  // The environment supports 'for of' iteration ('for (const x of array) { ... }').
  "forOf": true,
  // The environment supports 'globalThis'.
  "globalThis": true,
  // The environment supports ECMAScript Module syntax to import ECMAScript modules (import ... from '...').
  "module": false,
  // The environment supports optional chaining ('obj?.a' or 'obj?.()').
  "optionalChaining": true,
  // The environment supports template literals.
  "templateLiteral": true
}

this.fs

访问 compilationinputFileSystem 属性。

this.getOptions(schema)

提取给定的 loader 选项。可选地,接受 JSON schema 作为参数。

this.getResolve

getResolve(options: ResolveOptions): resolve

resolve(context: string, request: string, callback: function(err, result: string))
resolve(context: string, request: string): Promise<string>

创建一个类似于this.resolve 的解析函数。

可以使用 webpack resolve 选项下的任何选项。它们会与配置的 resolve 选项合并。请注意,"..." 可用于数组中以扩展 resolve 选项的值,例如 { extensions: [".sass", "..."] }

options.dependencyType 是一个附加选项。它允许我们指定依赖项的类型,该类型用于从 resolve 选项中解析 byDependency

解析操作的所有依赖项都会自动作为依赖项添加到当前模块中。

this.hot

关于 loader 的 HMR 信息。

module.exports = function (source) {
  console.log(this.hot); // true if HMR is enabled via --hot flag or webpack configuration
  return source;
};

this.hashDigest

string

5.95.0+

生成哈希时使用的编码。请参阅output.hashDigest

this.hashDigestLength

number

5.95.0+

要使用的哈希摘要的前缀长度。请参阅output.hashDigestLength

this.hashFunction

string function

5.95.0+

要使用的哈希算法。请参阅output.hashFunction

this.hashSalt

string

5.95.0+

一个可选的盐值,用于通过 Node.JS 的hash.update 更新哈希。请参阅output.hashSalt

this.importModule

5.32.0+

this.importModule(request, options, [callback]): Promise

一个轻量级的替代方案,用于子编译器在构建时编译和执行请求。

  • request:用于加载模块的请求字符串
  • 选项:
    • layer:指定此模块放置/编译所在的层
    • publicPath:用于构建模块的公共路径
  • callback:一个可选的 Node.js 风格回调函数,返回模块的导出或 ESM 的命名空间对象。如果未提供回调,importModule 将返回一个 Promise。

webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /stylesheet\.js$/i,
        use: ['./a-pitching-loader.js'],
        type: 'asset/source', // we set type to 'asset/source' as the loader will return a string
      },
    ],
  },
};

a-pitching-loader.js

exports.pitch = async function (remaining) {
  const result = await this.importModule(
    this.resourcePath + '.webpack[javascript/auto]' + '!=!' + remaining
  );
  return result.default || result;
};

src/stylesheet.js

import { green, red } from './colors.js';
export default `body { background: ${red}; color: ${green}; }`;

src/colors.js

export const red = '#f00';
export const green = '#0f0';

src/index.js

import stylesheet from './stylesheet.js';
// stylesheet will be a string `body { background: #f00; color: #0f0; }` at build time

你可能注意到了上面的例子中的一些东西

  1. 我们有一个pitching loader
  2. 我们在该 pitching loader 中使用 !=! 语法来为请求设置matchResource,即,我们将使用 this.resourcePath + '.webpack[javascript/auto]' 来匹配module.rules,而不是原始资源,
  3. .webpack[javascript/auto].webpack[type] 模式的伪扩展,我们用它来在未指定其他模块类型时指定默认的模块类型。它通常与 !=! 语法结合使用。

请注意,上述示例是简化版,你可以在 webpack 仓库中查看完整示例。

this.loaderIndex

当前 loader 在 loaders 数组中的索引。

示例中:在 loader1 中为 0,在 loader2 中为 1

this.loadModule

loadModule(request: string, callback: function(err, source, sourceMap, module))

将给定请求解析为一个模块,应用所有配置的 loader,并通过生成的源代码、sourceMap 和模块实例(通常是 NormalModule 的一个实例)进行回调。如果你需要知道另一个模块的源代码来生成结果,请使用此函数。

在 loader 上下文中,this.loadModule 默认使用 CommonJS 解析规则。在使用不同的语义之前,请使用 this.getResolve 和适当的 dependencyType,例如 'esm''commonjs' 或自定义类型。

this.loaders

所有 loader 的数组。在 `pitch` 阶段可写入。

loaders = [{request: string, path: string, query: string, module: function}]

示例

[
  {
    request: '/abc/loader1.js?xyz',
    path: '/abc/loader1.js',
    query: '?xyz',
    module: [Function],
  },
  {
    request: '/abc/node_modules/loader2/index.js',
    path: '/abc/node_modules/loader2/index.js',
    query: '',
    module: [Function],
  },
];

this.mode

读取 webpack 正在运行的mode

可能的值:'production''development''none'

this.query

  1. 如果 loader 是通过options 对象配置的,这将指向该对象。
  2. 如果 loader 没有 options,但通过查询字符串调用,这将是一个以 ? 开头的字符串。

this.request

已解析的请求字符串。

示例中:'/abc/loader1.js?xyz!/abc/node_modules/loader2/index.js!/abc/resource.js?rrr'

this.resolve

resolve(context: string, request: string, callback: function(err, result: string))

解析一个像 `require` 表达式一样的请求。

  • context 必须是目录的绝对路径。此目录用作解析的起始位置。
  • request 是要解析的请求。通常使用相对请求(如 ./relative)或模块请求(如 module/path),但绝对路径(如 /some/path)也可以作为请求。
  • callback 是一个正常的 Node.js 风格回调函数,返回解析后的路径。

解析操作的所有依赖项都会自动作为依赖项添加到当前模块中。

this.resource

请求的资源部分,包括查询字符串。

示例中:'/abc/resource.js?rrr'

this.resourcePath

资源文件。

示例中:'/abc/resource.js'

this.resourceQuery

资源的查询字符串。

示例中:'?rrr'

this.rootContext

从 webpack 4 开始,之前的 this.options.context 作为 this.rootContext 提供。

this.sourceMap

指示是否应该生成 source map。由于生成 source map 可能是一项昂贵的任务,因此你应该检查是否确实请求了 source map。

this.target

编译目标。从配置选项中传递。

示例值:'web''node'

this.utils

5.27.0+

访问以下实用工具。

  • absolutify:如果可能,返回一个使用绝对路径的新请求字符串。
  • contextify:如果可能,返回一个避免使用绝对路径的新请求字符串。
  • createHash:从提供的哈希函数返回一个新的 Hash 对象。

my-sync-loader.js

module.exports = function (content) {
  this.utils.contextify(
    this.context,
    this.utils.absolutify(this.context, './index.js')
  );
  this.utils.absolutify(this.context, this.resourcePath);
  const mainHash = this.utils.createHash(
    this._compilation.outputOptions.hashFunction
  );
  mainHash.update(content);
  mainHash.digest('hex');
  // …
  return content;
};

this.version

Loader API 版本。 当前为 2。这对于提供向后兼容性很有用。通过版本,你可以为破坏性变更指定自定义逻辑或回退。

this.webpack

当由 webpack 编译时,此布尔值设置为 true。

webpack 特有属性

loader 接口提供了所有模块相关信息。但在极少数情况下,你可能需要访问编译器 API 本身。

因此,你只应将它们作为最后的手段。使用它们会降低你的 loader 的可移植性。

this._compilation

访问 webpack 当前的 Compilation 对象。

this._compiler

访问 webpack 当前的 Compiler 对象。

已弃用的上下文属性

this.debug

一个布尔标志。在调试模式下会设置为 true

this.inputValue

从上一个 loader 传递。如果你要将输入参数作为模块执行,可以考虑读取此变量以获取快捷方式(为了性能)。

this.minimize

指示结果是否应该被最小化。

this.value

将值传递给下一个 loader。如果你知道结果作为模块执行时会导出什么,在此处设置此值(作为仅包含一个元素的数组)。

this._module

对正在加载的 `Module` 对象的“hacky”访问。

错误报告

你可以通过以下方式从 loader 内部报告错误:

  • 使用this.emitError。这会报告错误,但不会中断模块的编译。
  • 使用 throw(或其他未捕获的异常)。在 loader 运行时抛出错误将导致当前模块编译失败。
  • 使用 callback(在异步模式下)。将错误传递给回调也会导致模块编译失败。

例如

./src/index.js

require('./loader!./lib');

从 loader 抛出错误

./src/loader.js

module.exports = function (source) {
  throw new Error('This is a Fatal Error!');
};

或者在异步模式下将错误传递给回调

./src/loader.js

module.exports = function (source) {
  const callback = this.async();
  //...
  callback(new Error('This is a Fatal Error!'), source);
};

模块将像这样打包

/***/ "./src/loader.js!./src/lib.js":
/*!************************************!*\
  !*** ./src/loader.js!./src/lib.js ***!
  \************************************/
/*! no static exports found */
/***/ (function(module, exports) {

throw new Error("Module build failed (from ./src/loader.js):\nError: This is a Fatal Error!\n    at Object.module.exports (/workspace/src/loader.js:3:9)");

/***/ })

然后构建输出也会显示错误(类似于 this.emitError

ERROR in ./src/lib.js (./src/loader.js!./src/lib.js)
Module build failed (from ./src/loader.js):
Error: This is a Fatal Error!
    at Object.module.exports (/workspace/src/loader.js:2:9)
 @ ./src/index.js 1:0-25

如下所示,不仅有错误信息,还有涉及的 loader 和模块的详细信息

  • 模块路径:ERROR in ./src/lib.js
  • 请求字符串:(./src/loader.js!./src/lib.js)
  • loader 路径:(from ./src/loader.js)
  • 调用者路径:@ ./src/index.js 1:0-25

内联 matchResource

webpack v4 引入了一种新的内联请求语法。在一个请求前加上 <match-resource>!=! 将为该请求设置 matchResource

当设置 matchResource 时,它将用于匹配module.rules 而不是原始资源。这在需要对资源应用更多 loader,或者需要更改模块类型时会很有用。它也显示在统计信息中,并用于匹配Rule.issuersplitChunks 中的 test

示例

file.js

/* STYLE: body { background: red; } */
console.log('yep');

loader 可以将文件转换为以下文件,并使用 matchResource 应用用户指定的 CSS 处理规则

file.js (由 loader 转换)

import './file.js.css!=!extract-style-loader/getStyles!./file.js';
console.log('yep');

这将为 extract-style-loader/getStyles!./file.js 添加一个依赖项,并将结果视为 file.js.css。因为module.rules 有一个匹配 /\.css$/ 的规则,并且它将应用于此依赖项。

loader 可能如下所示

extract-style-loader/index.js

const getStylesLoader = require.resolve('./getStyles');

module.exports = function (source) {
  if (STYLES_REGEXP.test(source)) {
    source = source.replace(STYLES_REGEXP, '');
    return `import ${JSON.stringify(
      this.utils.contextify(
        this.context || this.rootContext,
        `${this.resource}.css!=!${getStylesLoader}!${this.remainingRequest}`
      )
    )};${source}`;
  }
  return source;
};

extract-style-loader/getStyles.js

module.exports = function (source) {
  const match = source.match(STYLES_REGEXP);
  return match[0];
};

日志记录

自 webpack 4.37 发布以来,日志 API 已可用。当在stats 配置中启用 logging 和/或启用infrastructure logging 时,loader 可能会记录消息,这些消息将以相应的日志格式(统计信息、基础设施)打印出来。

  • Loader 应该优先使用 this.getLogger() 进行日志记录,这是 compilation.getLogger() 的一个快捷方式,包含 loader 路径和已处理文件。此类日志记录存储在统计信息中,并相应地进行格式化。它可以由 webpack 用户过滤和导出。
  • Loader 可以使用 this.getLogger('name') 获取一个带有子名称的独立 logger。Loader 路径和已处理文件仍然会添加。
  • Loader 可以使用特定的回退逻辑来检测日志支持:this.getLogger ? this.getLogger() : console,以便在使用的 webpack 版本较旧且不支持 getLogger 方法时提供回退。

13 贡献者

TheLarkInnjhnnstbroadleybyzyksokraEugeneHlushkojantimonsuperburritowizardofhogwartssnitin315chenxsanjamesgeorge007alexeyr