加载器接口

加载器是一个导出函数的 JavaScript 模块。 加载器运行器 调用此函数并将前一个加载器或资源文件的执行结果传递给它。 函数的 this 上下文由 webpack 和 加载器运行器 填充,其中包含一些有用的方法,允许加载器(除其他事项外)更改其调用方式为异步,或获取查询参数。

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

可以在 同步模式 中返回单个结果。 对于多个结果,必须调用 this.callback()。 在 异步模式 中,必须调用 this.async() 来指示 加载器运行器 应该等待异步结果。 它返回 this.callback()。 然后加载器必须返回 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
}

示例

以下部分提供了一些不同类型加载器的基本示例。 请注意,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()
};

异步加载器

对于异步加载器,使用 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);
  });
};

"原始" 加载器

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

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;

投掷加载器

加载器始终从右到左调用。在某些情况下,加载器只关心请求背后的元数据,并且可以忽略先前加载器的结果。加载器上的 pitch 方法从左到右调用,在实际执行加载器(从右到左)之前调用。

对于以下 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

那么为什么加载器可能利用“投掷”阶段呢?

首先,传递给 pitch 方法的 data 在执行阶段也作为 this.data 公开,这对于捕获和共享循环早期信息可能很有用。

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

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

其次,如果加载器在 pitch 方法中传递结果,则进程将转向并跳过剩余的加载器。在我们上面的示例中,如果 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

加载器上下文

加载器上下文表示分配给 this 属性的加载器内部可用的属性。

加载器上下文的示例

鉴于以下示例,此 require 调用被使用

/abc/file.js

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

this.addContextDependency

addContextDependency(directory: string)

将目录添加为加载器结果的依赖项。

this.addDependency

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

将现有文件作为加载器结果的依赖项添加,以便使它们可观察。例如,sass-loaderless-loader 使用此方法在任何导入的 css 文件更改时重新编译。

this.addMissingDependency

addMissingDependency(file: string)

将一个不存在的文件作为加载器结果的依赖项添加,以便使它们可观察。类似于 addDependency,但在编译过程中处理文件的创建,并在观察者正确附加之前。

this.async

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

this.cacheable

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

cacheable(flag = true: boolean)

默认情况下,加载器结果被标记为可缓存的。调用此方法并传递 false 以使加载器的结果不可缓存。

可缓存的加载器在输入和依赖项没有改变时必须具有确定性的结果。这意味着加载器不应该具有除使用 this.addDependency 指定的依赖项之外的任何其他依赖项。

this.callback

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

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

如果调用此函数,则应返回 undefined 以避免加载器结果不明确。

this.clearDependencies

clearDependencies();

移除加载器结果的所有依赖项,包括初始依赖项和其他加载器的依赖项。考虑使用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)

提取给定的加载器选项。可选地,接受 JSON 模式作为参数。

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

有关 HMR 加载器的信息。

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

this.importModule

5.32.0+

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

子编译器在构建时编译和执行请求的另一种轻量级解决方案。

  • request: 从中加载模块的请求字符串
  • options:
    • 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 加载器,
  2. 我们在该 pitching 加载器中使用 !=! 语法来设置 matchResource 用于请求,即,我们将使用 this.resourcePath + '.webpack[javascript/auto]'module.rules 匹配,而不是原始资源,
  3. .webpack[javascript/auto].webpack[type] 模式的伪扩展,我们使用它来指定默认的 模块类型,当没有其他模块类型指定时。它通常与 !=! 语法结合使用。

请注意,上面的示例是一个简化的示例,您可以查看 webpack 存储库中的完整示例

this.loaderIndex

当前加载器在加载器数组中的索引。

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

this.loadModule

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

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

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

this.loaders

所有加载器的数组。它在 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. 如果加载器配置了 options 对象,则它将指向该对象。
  2. 如果加载器没有 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

指示是否应该生成源映射。由于生成源映射可能是一项昂贵的任务,因此您应该检查是否实际需要源映射。

this.target

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

示例值:'web''node'

this.utils

5.27.0+

访问 contextifyabsolutify 工具。

  • contextify:返回一个新的请求字符串,尽可能避免使用绝对路径。
  • absolutify:返回一个新的请求字符串,尽可能使用绝对路径。

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);
  // …
  return content;
};

this.version

加载器 API 版本。当前为 2。这对于提供向后兼容性很有用。使用版本,您可以为重大更改指定自定义逻辑或回退。

this.webpack

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

Webpack 特定属性

加载器接口提供所有与模块相关的信息。但是,在极少数情况下,您可能需要访问编译器 API 本身。

因此,您应该只在万不得已的情况下使用它们。使用它们会降低加载器的可移植性。

this._compilation

访问 webpack 的当前 Compilation 对象。

this._compiler

访问 webpack 的当前 Compiler 对象。

已弃用的上下文属性

this.debug

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

this.inputValue

从最后一个加载器传递。如果您要将输入参数作为模块执行,请考虑读取此变量以获得快捷方式(为了性能)。

this.minimize

指示结果是否应最小化。

this.value

将值传递给下一个加载器。如果您知道结果作为模块执行时导出的内容,请在此处设置此值(作为仅包含一个元素的数组)。

this._module

对正在加载的 Module 对象进行黑客访问。

错误报告

您可以通过以下方式从加载器内部报告错误

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

例如

./src/index.js

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

从加载器抛出错误

./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

如下所示,不仅显示错误消息,还显示有关哪些加载器和模块参与的信息

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

内联 matchResource

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

设置matchResource后,它将用于与module.rules匹配,而不是原始资源。如果需要对资源应用更多加载器,或者需要更改模块类型,这将非常有用。它也会显示在统计信息中,并用于匹配Rule.issuersplitChunks中的test

示例

file.js

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

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

file.js(由加载器转换)

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$/的规则,它将应用于此依赖关系。

加载器可能如下所示

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];
};

日志记录

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

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

12 位贡献者

TheLarkInnjhnnstbroadleybyzyksokraEugeneHlushkojantimonsuperburritowizardofhogwartssnitin315chenxsanjamesgeorge007