Webpack:Loader Interface

2023-05-10 09:56 更新

loader 本質上是導出為函數(shù)的 JavaScript 模塊。loader runner 會調(diào)用此函數(shù),然后將上一個 loader 產(chǎn)生的結果或者資源文件傳入進去。函數(shù)中的 this 作為上下文會被 webpack 填充,并且 loader runner 中包含一些實用的方法,比如可以使 loader 調(diào)用方式變?yōu)楫惒?,或者獲取 query 參數(shù)。

起始 loader 只有一個入?yún)ⅲ嘿Y源文件的內(nèi)容。compiler 預期得到最后一個 loader 產(chǎn)生的處理結果。這個處理結果應該為 String 或者 Buffer(能夠被轉換為 string)類型,代表了模塊的 JavaScript 源碼。另外,還可以傳遞一個可選的 SourceMap 結果(格式為 JSON 對象)。

如果是單個處理結果,可以在同步模式中直接返回。如果有多個處理結果,則必須調(diào)用 this.callback()。在異步模式中,必須調(diào)用 this.async() 來告知 loader runner 等待異步結果,它會返回 this.callback() 回調(diào)函數(shù)。隨后 loader 必須返回 undefined 并且調(diào)用該回調(diào)函數(shù)。

/**
 *
 * @param {string|Buffer} content 源文件的內(nèi)容
 * @param {object} [map] 可以被 https://github.com/mozilla/source-map 使用的 SourceMap 數(shù)據(jù)
 * @param {any} [meta] meta 數(shù)據(jù),可以是任何內(nèi)容
 */
function webpackLoader(content, map, meta) {
  // 你的 webpack loader 代碼
}

示例

以下部分提供了不同類型的 loader 的一些基本示例。注意,map 和 meta 參數(shù)是可選的,查看下面的this.callback

同步 Loaders

無論是 return 還是 this.callback 都可以同步地返回轉換后的 content 值:

sync-loader.js

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

this.callback 方法則更靈活,因為它允許傳遞多個參數(shù),而不僅僅是 content。

sync-loader-with-multiple-results.js

module.exports = function (content, map, meta) {
  this.callback(null, someSyncOperation(content), map, meta);
  return; // 當調(diào)用 callback() 函數(shù)時,總是返回 undefined
};

異步 Loaders

對于異步 loader,使用 this.async 來獲取 callback 函數(shù):

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

"Raw" Loader

默認情況下,資源文件會被轉化為 UTF-8 字符串,然后傳給 loader。通過設置 raw 為 true,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;

Pitching Loader

loader 總是 從右到左被調(diào)用。有些情況下,loader 只關心 request 后面的 元數(shù)據(jù)(metadata),并且忽略前一個 loader 的結果。在實際(從右到左)執(zhí)行 loader 之前,會先 從左到右 調(diào)用 loader 上的 pitch 方法。

對于以下 use 配置:

module.exports = {
  //...
  module: {
    rules: [
      {
        //...
        use: ['a-loader', 'b-loader', 'c-loader'],
      },
    ],
  },
};

將會發(fā)生這些步驟:

|- 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,在執(zhí)行階段也會暴露在 this.data 之下,并且可以用于在循環(huán)時,捕獲并共享前面的信息。

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

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

其次,如果某個 loader 在 pitch 方法中給出一個結果,那么這個過程會回過身來,并跳過剩下的 loader。在我們上面的例子中,如果 b-loader 的 pitch 方法返回了一些東西:

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

The Loader Context

loader context 表示在 loader 內(nèi)使用 this 可以訪問的一些方法或屬性。

loader 上下文示例

下面提供一個例子,將使用 require 進行調(diào)用:

在 /abc/file.js 中:

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

this.addContextDependency

addContextDependency(directory: string)

添加目錄作為 loader 結果的依賴。

this.addDependency

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

添加一個文件作為產(chǎn)生 loader 結果的依賴,使它們的任何變化可以被監(jiān)聽到。例如,sass-loaderless-loader 就使用了這個技巧,當它發(fā)現(xiàn)無論何時導入的 css 文件發(fā)生變化時就會重新編譯。

this.addMissingDependency

addMissingDependency(file: string)

添加一個不存在的文件作為 loader 結果的依賴項,以使它們可監(jiān)聽。類似于 addDependency,但是會在正確附加觀察者之前處理在編譯期間文件的創(chuàng)建。

this.async

告訴 loader-runner 這個 loader 將會異步地回調(diào)。返回 this.callback。

this.cacheable

設置是否可緩存標志的函數(shù):

cacheable(flag = true: boolean)

默認情況下,loader 的處理結果會被標記為可緩存。調(diào)用這個方法然后傳入 false,可以關閉 loader 處理結果的緩存能力。

一個可緩存的 loader 在輸入和相關依賴沒有變化時,必須返回相同的結果。這意味著 loader 除了 this.addDependency 里指定的以外,不應該有其它任何外部依賴。

this.callback

可以同步或者異步調(diào)用的并返回多個結果的函數(shù)。預期的參數(shù)是:

this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: SourceMap,
  meta?: any
);
  1. 第一個參數(shù)必須是 Error 或者 null
  2. 第二個參數(shù)是一個 string 或者 Buffer。
  3. 可選的:第三個參數(shù)必須是一個可以被 this module 解析的 source map。
  4. 可選的:第四個參數(shù),會被 webpack 忽略,可以是任何東西(例如一些元數(shù)據(jù))。

this.clearDependencies

clearDependencies();

移除 loader 結果的所有依賴,甚至自己和其它 loader 的初始依賴。考慮使用 pitch。

this.context

模塊所在的目錄 可以用作解析其他模塊成員的上下文。

在我們的 例子 中:因為 resource.js 在這個目錄中,這個屬性的值為 /abc

this.data

在 pitch 階段和 normal 階段之間共享的 data 對象。

this.emitError

emitError(error: Error)

emit 一個錯誤,也可以在輸出中顯示。

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: {...})

產(chǎn)生一個文件。這是 webpack 特有的。

this.emitWarning

emitWarning(warning: Error)

發(fā)出一個警告,在輸出中顯示如下:

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

用于訪問 compilation 的 inputFileSystem 屬性。

this.getOptions(schema)

提取給定的 loader 選項,接受一個可選的 JSON schema 作為參數(shù)

this.getResolve

getResolve(options: ResolveOptions): resolve

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

創(chuàng)建一個類似于 this.resolve 的解析函數(shù)。

webpack resolve 選項 下的任意配置項都是可能的。他們會被合并進 resolve 配置項中。請注意,"..." 可以在數(shù)組中使用,用于拓展 resolve 配置項的值。例如:{ extensions: [".sass", "..."] }。

options.dependencyType 是一個額外的配置。它允許我們指定依賴類型,用于從 resolve 配置項中解析 byDependency。

解析操作的所有依賴項都會自動作為依賴項添加到當前模塊中。

this.hot

loaders 的 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

一種可以選擇的輕量級解決方案,用于子編譯器在構建時編譯和執(zhí)行請求。

  • request: 加載模塊的請求字符串
  • options:layer:指定該模塊放置/編譯的層publicPath:用于構建模塊的公共路徑
  • callback:一個可選的 Node.js 風格的回調(diào),返回模塊的 exports 或 ESM 的命名空間對象。如果沒有提供回調(diào),importModule將返回一個 Promise。

webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /stylesheet\.js$/i,
        use: ['./a-pitching-loader.js'],
        type: 'asset/source', // 我們將 type 設置為 'asset/source',其會返回一個字符串。
      },
    ],
  },
};

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 在構建時會成為一個字符串:`body { background: #f00; color: #0f0; }`。

在上面的例子中你可能會注意到一些東西:

  1. 我們有一個 pitching loader,
  2. 我們在 pitching loader 中使用 !=! 語法來為請求設置 matchResource,例如,我們將使用 ?this.resourcePath? +? '.webpack[javascript/auto]' ?而不是原始資源匹配 module.rules,
  3. ?.webpack[javascript/auto] ?是? .webpack[type] ?模式的偽拓展,當沒有指定其他模塊類型時,我們使用它指定一個默認 模塊類型,它通常和 !=! 語法一起使用。

注意,上面的示例是一個簡化的示例,你可以查看 webpack 倉庫的完整示例。

this.loaderIndex

當前 loader 在 loader 數(shù)組中的索引。

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

this.loadModule

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

解析給定的 request 到模塊,應用所有配置的 loader,并且在回調(diào)函數(shù)中傳入生成的 source、sourceMap 和模塊實例(通常是 NormalModule 的一個實例)。如果你需要獲取其他模塊的源代碼來生成結果的話,你可以使用這個函數(shù)。

this.loadModule 在 loader 上下文中默認使用 CommonJS 來解析規(guī)則。用一個合適的 dependencyType 使用 this.getResolve。例如,在使用不同的語義之前使用 'esm'、'commonjs' 或者一個自定義的。

this.loaders

所有 loader 組成的數(shù)組。它在 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 對象的話,this 就指向這個對象。
  2. 如果 loader 中沒有 options,而是以 query 字符串作為參數(shù)調(diào)用時,this.query 就是一個以 ? 開頭的字符串。

this.request

被解析出來的 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 表達式一樣解析一個 request。

  • context 必須是一個目錄的絕對路徑。此目錄用作解析的起始位置。
  • request 是要被解析的 request。通常情況下,像 ./relative 的相對請求或者像 module/path 的模塊請求會被使用,但是像 /some/path 也有可能被當做 request。
  • callback 是一個給出解析路徑的 Node.js 風格的回調(diào)函數(shù)。

解析操作的所有依賴項都會自動作為依賴項添加到當前模塊中。

this.resource

request 中的資源部分,包括 query 參數(shù)。

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

this.resourcePath

資源文件的路徑。

在【示例](#example-for-the-loader-context)中:'/abc/resource.js'

this.resourceQuery

資源的 query 參數(shù)。

示例中:'?rrr'

this.rootContext

從 webpack 4 開始,原先的 this.options.context 被改為 this.rootContext。

this.sourceMap

是否應該生成一個 source map。因為生成 source map 可能會非常耗時,你應該確認 source map 確實需要。

this.target

compilation 的目標。從配置選項中傳遞。

示例:'web', 'node'

this.utils

5.27.0+

可以訪問 contextify 與 absolutify 功能。

  • 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

loader API 的版本號 目前是 2。這對于向后兼容性有一些用處。通過這個版本號,你可以自定義邏輯或者降級處理。

this.webpack

如果是由 webpack 編譯的,這個布爾值會被設置為 true。

Webpack 特有屬性

loader 接口提供所有模塊的相關信息。然而,在極少數(shù)情況下,你可能需要訪問 compiler api 本身。

因此,你應該把它們作為最后的手段。使用它們將降低 loader 的可移植性。

this._compilation

用于訪問 webpack 的當前 Compilation 對象。

this._compiler

用于訪問 webpack 的當前 Compiler 對象。

過時的上下文屬性

由于我們計劃將這些屬性從上下文中移除,因此不鼓勵使用這些屬性。它們?nèi)匀涣性谶@里,以備參考。

this.debug

一個布爾值,當處于 debug 模式時為 true。

this.inputValue

從上一個 loader 那里傳遞過來的值。如果你會以模塊的方式處理輸入?yún)?shù),建議預先讀入這個變量(為了性能因素)。

this.minimize

決定處理結果是否應該被壓縮。

this.value

向下一個 loader 傳值。如果你知道了作為模塊執(zhí)行后的結果,請在這里賦值(以元素數(shù)組的形式)。

this._module

一種 hack 寫法。用于訪問當前加載的 Module 對象。

錯誤報告

您可以通過以下方式從 loader 內(nèi)部報告錯誤:

  • 使用 this.emitError. 將在不中斷模塊編譯的情況下報告錯誤。
  • 使用 throw(或其他未捕獲的意外異常)。loader 運行時引發(fā)錯誤將導致當前模塊編譯失敗。
  • 使用 callback(異步模式)。向回調(diào)傳遞錯誤也會導致模塊編譯失敗。

示例:

./src/index.js

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

從 loader 當中拋出錯誤:

./src/loader.js

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

或者在異步模式下,傳入一個錯誤給 callback:

./src/loader.js

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

這個模塊將獲取像下面的 bundle:

/***/ "./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
  • request 字符串:(./src/loader.js!./src/lib.js)
  • loader 路徑:(from ./src/loader.js)
  • 調(diào)用路徑:@ ./src/index.js 1:0-25

Inline matchResource

在 webpack v4 中引入了一種新的內(nèi)聯(lián)請求語法。前綴為 <match-resource>!=! 將為此請求設置 matchResource。

當 matchResource 被設置時,它將會被用作匹配 module.rules 而不是源文件。如果需要對資源應用進一步的 loader,或者需要更改模塊類型,這可能會很有用。 它也顯示在統(tǒng)計數(shù)據(jù)中,用于匹配 Rule.issuer 和 test in splitChunks。

示例:

file.js

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

loader 可以將文件轉換為以下文件,并使用 matchResource 應用用戶指定的 CSS 處理規(guī)則:

file.js (transformed by 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$/ 的規(guī)則,并且將會應用到依賴中。

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

Logging

自 webpack 4.37 發(fā)布以來,Logging API 就可用了。當 stats configuration 或者 infrastructure logging 中啟用 logging 時,loader 可以記錄消息,這些消息將以相應的日志格式(stats,infrastructure)打印出來。

  • Loaders 最好使用 this.getLogger() 進行日志記錄,這是指向 compilation.getLogger() 具有 loader 路徑和已處理的文件。這種日志記錄被存儲到 Stats 中并相應地格式化。它可以被 webpack 用戶過濾和導出。
  • Loaders 可以使用 this.getLogger('name') 獲取具有子名稱的獨立記錄器。仍會添加 loader 路徑和已處理的文件。
  • Loaders 可以使用特殊的回退邏輯來檢測日志支持 this.getLogger() ? this.getLogger() : console。在使用不支持 getLogger 方法的舊 webpack 版本時提供回退方法。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號