loader 本質(zhì)上是導(dǎo)出為函數(shù)的 JavaScript 模塊。loader runner 會(huì)調(diào)用此函數(shù),然后將上一個(gè) loader 產(chǎn)生的結(jié)果或者資源文件傳入進(jìn)去。函數(shù)中的 this 作為上下文會(huì)被 webpack 填充,并且 loader runner 中包含一些實(shí)用的方法,比如可以使 loader 調(diào)用方式變?yōu)楫惒?,或者獲取 query 參數(shù)。
起始 loader 只有一個(gè)入?yún)ⅲ嘿Y源文件的內(nèi)容。compiler 預(yù)期得到最后一個(gè) loader 產(chǎn)生的處理結(jié)果。這個(gè)處理結(jié)果應(yīng)該為 String 或者 Buffer(能夠被轉(zhuǎn)換為 string)類型,代表了模塊的 JavaScript 源碼。另外,還可以傳遞一個(gè)可選的 SourceMap 結(jié)果(格式為 JSON 對(duì)象)。
如果是單個(gè)處理結(jié)果,可以在同步模式中直接返回。如果有多個(gè)處理結(jié)果,則必須調(diào)用 this.callback()。在異步模式中,必須調(diào)用 this.async() 來告知 loader runner 等待異步結(jié)果,它會(huì)返回 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。
無論是 return 還是 this.callback 都可以同步地返回轉(zhuǎn)換后的 content 值:
sync-loader.js
module.exports = function (content, map, meta) {
return someSyncOperation(content);
};
this.callback 方法則更靈活,因?yàn)樗试S傳遞多個(gè)參數(shù),而不僅僅是 content。
sync-loader-with-multiple-results.js
module.exports = function (content, map, meta) {
this.callback(null, someSyncOperation(content), map, meta);
return; // 當(dāng)調(diào)用 callback() 函數(shù)時(shí),總是返回 undefined
};
對(duì)于異步 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);
});
};
默認(rèn)情況下,資源文件會(huì)被轉(zhuǎn)化為 UTF-8 字符串,然后傳給 loader。通過設(shè)置 raw 為 true,loader 可以接收原始的 Buffer。每一個(gè) loader 都可以用 String 或者 Buffer 的形式傳遞它的處理結(jié)果。complier 將會(huì)把它們在 loader 之間相互轉(zhuǎn)換。
raw-loader.js
module.exports = function (content) {
assert(content instanceof Buffer);
return someSyncOperation(content);
// 返回值也可以是一個(gè) `Buffer`
// 即使不是 "raw",loader 也沒問題
};
module.exports.raw = true;
loader 總是 從右到左被調(diào)用。有些情況下,loader 只關(guān)心 request 后面的 元數(shù)據(jù)(metadata),并且忽略前一個(gè) loader 的結(jié)果。在實(shí)際(從右到左)執(zhí)行 loader 之前,會(huì)先 從左到右 調(diào)用 loader 上的 pitch 方法。
對(duì)于以下 use 配置:
module.exports = {
//...
module: {
rules: [
{
//...
use: ['a-loader', 'b-loader', 'c-loader'],
},
],
},
};
將會(huì)發(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í)行階段也會(huì)暴露在 this.data 之下,并且可以用于在循環(huán)時(shí),捕獲并共享前面的信息。
module.exports = function (content) {
return someSyncOperation(content, this.data.value);
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
data.value = 42;
};
其次,如果某個(gè) loader 在 pitch 方法中給出一個(gè)結(jié)果,那么這個(gè)過程會(huì)回過身來,并跳過剩下的 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
loader context 表示在 loader 內(nèi)使用 this 可以訪問的一些方法或?qū)傩浴?/p>
下面提供一個(gè)例子,將使用 require 進(jìn)行調(diào)用:
在 /abc/file.js 中:
require('./loader1?xyz!loader2!./resource?rrr');
addContextDependency(directory: string)
添加目錄作為 loader 結(jié)果的依賴。
addDependency(file: string)
dependency(file: string) // shortcut
添加一個(gè)文件作為產(chǎn)生 loader 結(jié)果的依賴,使它們的任何變化可以被監(jiān)聽到。例如,sass-loader, less-loader 就使用了這個(gè)技巧,當(dāng)它發(fā)現(xiàn)無論何時(shí)導(dǎo)入的 css 文件發(fā)生變化時(shí)就會(huì)重新編譯。
addMissingDependency(file: string)
添加一個(gè)不存在的文件作為 loader 結(jié)果的依賴項(xiàng),以使它們可監(jiān)聽。類似于 addDependency,但是會(huì)在正確附加觀察者之前處理在編譯期間文件的創(chuàng)建。
告訴 loader-runner 這個(gè) loader 將會(huì)異步地回調(diào)。返回 this.callback。
設(shè)置是否可緩存標(biāo)志的函數(shù):
cacheable(flag = true: boolean)
默認(rèn)情況下,loader 的處理結(jié)果會(huì)被標(biāo)記為可緩存。調(diào)用這個(gè)方法然后傳入 false,可以關(guān)閉 loader 處理結(jié)果的緩存能力。
一個(gè)可緩存的 loader 在輸入和相關(guān)依賴沒有變化時(shí),必須返回相同的結(jié)果。這意味著 loader 除了 this.addDependency 里指定的以外,不應(yīng)該有其它任何外部依賴。
可以同步或者異步調(diào)用的并返回多個(gè)結(jié)果的函數(shù)。預(yù)期的參數(shù)是:
this.callback(
err: Error | null,
content: string | Buffer,
sourceMap?: SourceMap,
meta?: any
);
clearDependencies();
移除 loader 結(jié)果的所有依賴,甚至自己和其它 loader 的初始依賴??紤]使用 pitch。
模塊所在的目錄 可以用作解析其他模塊成員的上下文。
在我們的 例子 中:因?yàn)?nbsp;resource.js 在這個(gè)目錄中,這個(gè)屬性的值為 /abc
在 pitch 階段和 normal 階段之間共享的 data 對(duì)象。
emitError(error: Error)
emit 一個(gè)錯(cuò)誤,也可以在輸出中顯示。
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
emitFile(name: string, content: Buffer|string, sourceMap: {...})
產(chǎn)生一個(gè)文件。這是 webpack 特有的。
emitWarning(warning: Error)
發(fā)出一個(gè)警告,在輸出中顯示如下:
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
用于訪問 compilation 的 inputFileSystem 屬性。
提取給定的 loader 選項(xiàng),接受一個(gè)可選的 JSON schema 作為參數(shù)
getResolve(options: ResolveOptions): resolve
resolve(context: string, request: string, callback: function(err, result: string))
resolve(context: string, request: string): Promise<string>
創(chuàng)建一個(gè)類似于 this.resolve 的解析函數(shù)。
在 webpack resolve 選項(xiàng) 下的任意配置項(xiàng)都是可能的。他們會(huì)被合并進(jìn) resolve 配置項(xiàng)中。請注意,"..." 可以在數(shù)組中使用,用于拓展 resolve 配置項(xiàng)的值。例如:{ extensions: [".sass", "..."] }。
options.dependencyType 是一個(gè)額外的配置。它允許我們指定依賴類型,用于從 resolve 配置項(xiàng)中解析 byDependency。
解析操作的所有依賴項(xiàng)都會(huì)自動(dòng)作為依賴項(xiàng)添加到當(dāng)前模塊中。
loaders 的 HMR(熱模塊替換)相關(guān)信息。
module.exports = function (source) {
console.log(this.hot); // true if HMR is enabled via --hot flag or webpack configuration
return source;
};
5.32.0+
this.importModule(request, options, [callback]): Promise
一種可以選擇的輕量級(jí)解決方案,用于子編譯器在構(gòu)建時(shí)編譯和執(zhí)行請求。
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /stylesheet\.js$/i,
use: ['./a-pitching-loader.js'],
type: 'asset/source', // 我們將 type 設(shè)置為 'asset/source',其會(huì)返回一個(gè)字符串。
},
],
},
};
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 在構(gòu)建時(shí)會(huì)成為一個(gè)字符串:`body { background: #f00; color: #0f0; }`。
在上面的例子中你可能會(huì)注意到一些東西:
this.resourcePath
? +? '.webpack[javascript/auto]'
?而不是原始資源匹配 module.rules,.webpack[javascript/auto]
?是? .webpack[type]
?模式的偽拓展,當(dāng)沒有指定其他模塊類型時(shí),我們使用它指定一個(gè)默認(rèn) 模塊類型,它通常和 !=! 語法一起使用。注意,上面的示例是一個(gè)簡化的示例,你可以查看 webpack 倉庫的完整示例。
當(dāng)前 loader 在 loader 數(shù)組中的索引。
在示例中:loader1 中得到:0,loader2 中得到:1
loadModule(request: string, callback: function(err, source, sourceMap, module))
解析給定的 request 到模塊,應(yīng)用所有配置的 loader,并且在回調(diào)函數(shù)中傳入生成的 source、sourceMap 和模塊實(shí)例(通常是 NormalModule 的一個(gè)實(shí)例)。如果你需要獲取其他模塊的源代碼來生成結(jié)果的話,你可以使用這個(gè)函數(shù)。
this.loadModule 在 loader 上下文中默認(rèn)使用 CommonJS 來解析規(guī)則。用一個(gè)合適的 dependencyType 使用 this.getResolve。例如,在使用不同的語義之前使用 'esm'、'commonjs' 或者一個(gè)自定義的。
所有 loader 組成的數(shù)組。它在 pitch 階段的時(shí)候是可以寫入的。
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],
},
];
當(dāng) webpack 運(yùn)行時(shí)讀取 mode 的值
可能的值為:'production', 'development', 'none'
被解析出來的 request 字符串。
在我們的示例中:'/abc/loader1.js?xyz!/abc/node_modules/loader2/index.js!/abc/resource.js?rrr'
resolve(context: string, request: string, callback: function(err, result: string))
像 require 表達(dá)式一樣解析一個(gè) request。
解析操作的所有依賴項(xiàng)都會(huì)自動(dòng)作為依賴項(xiàng)添加到當(dāng)前模塊中。
request 中的資源部分,包括 query 參數(shù)。
在示例中:'/abc/resource.js?rrr'
資源文件的路徑。
在【示例](#example-for-the-loader-context)中:'/abc/resource.js'
資源的 query 參數(shù)。
在示例中:'?rrr'
從 webpack 4 開始,原先的 this.options.context 被改為 this.rootContext。
是否應(yīng)該生成一個(gè) source map。因?yàn)樯?source map 可能會(huì)非常耗時(shí),你應(yīng)該確認(rèn) source map 確實(shí)需要。
compilation 的目標(biāo)。從配置選項(xiàng)中傳遞。
示例:'web', 'node'
5.27.0+
可以訪問 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;
};
loader API 的版本號(hào) 目前是 2。這對(duì)于向后兼容性有一些用處。通過這個(gè)版本號(hào),你可以自定義邏輯或者降級(jí)處理。
如果是由 webpack 編譯的,這個(gè)布爾值會(huì)被設(shè)置為 true。
loader 接口提供所有模塊的相關(guān)信息。然而,在極少數(shù)情況下,你可能需要訪問 compiler api 本身。
因此,你應(yīng)該把它們作為最后的手段。使用它們將降低 loader 的可移植性。
用于訪問 webpack 的當(dāng)前 Compilation 對(duì)象。
用于訪問 webpack 的當(dāng)前 Compiler 對(duì)象。
由于我們計(jì)劃將這些屬性從上下文中移除,因此不鼓勵(lì)使用這些屬性。它們?nèi)匀涣性谶@里,以備參考。
一個(gè)布爾值,當(dāng)處于 debug 模式時(shí)為 true。
從上一個(gè) loader 那里傳遞過來的值。如果你會(huì)以模塊的方式處理輸入?yún)?shù),建議預(yù)先讀入這個(gè)變量(為了性能因素)。
決定處理結(jié)果是否應(yīng)該被壓縮。
向下一個(gè) loader 傳值。如果你知道了作為模塊執(zhí)行后的結(jié)果,請?jiān)谶@里賦值(以元素?cái)?shù)組的形式)。
一種 hack 寫法。用于訪問當(dāng)前加載的 Module 對(duì)象。
您可以通過以下方式從 loader 內(nèi)部報(bào)告錯(cuò)誤:
示例:
./src/index.js
require('./loader!./lib');
從 loader 當(dāng)中拋出錯(cuò)誤:
./src/loader.js
module.exports = function (source) {
throw new Error('This is a Fatal Error!');
};
或者在異步模式下,傳入一個(gè)錯(cuò)誤給 callback:
./src/loader.js
module.exports = function (source) {
const callback = this.async();
//...
callback(new Error('This is a Fatal Error!'), source);
};
這個(gè)模塊將獲取像下面的 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)");
/***/ })
然后構(gòu)建輸出結(jié)果將顯示錯(cuò)誤,與 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
如下所示,不僅有錯(cuò)誤消息,還提供了有關(guān)所涉及的 loader 和模塊的詳細(xì)信息:
在 webpack v4 中引入了一種新的內(nèi)聯(lián)請求語法。前綴為 <match-resource>!=! 將為此請求設(shè)置 matchResource。
當(dāng) matchResource 被設(shè)置時(shí),它將會(huì)被用作匹配 module.rules 而不是源文件。如果需要對(duì)資源應(yīng)用進(jìn)一步的 loader,或者需要更改模塊類型,這可能會(huì)很有用。 它也顯示在統(tǒng)計(jì)數(shù)據(jù)中,用于匹配 Rule.issuer 和 test in splitChunks。
示例:
file.js
/* STYLE: body { background: red; } */
console.log('yep');
loader 可以將文件轉(zhuǎn)換為以下文件,并使用 matchResource 應(yīng)用用戶指定的 CSS 處理規(guī)則:
file.js (transformed by loader)
import './file.js.css!=!extract-style-loader/getStyles!./file.js';
console.log('yep');
這將會(huì)向 extract-style-loader/getStyles!./file.js 中添加一個(gè)依賴,并將結(jié)果視為 file.js.css。因?yàn)?nbsp;module.rules 有一條匹配 /\.css$/ 的規(guī)則,并且將會(huì)應(yīng)用到依賴中。
這個(gè) 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 發(fā)布以來,Logging API 就可用了。當(dāng) stats configuration 或者 infrastructure logging 中啟用 logging 時(shí),loader 可以記錄消息,這些消息將以相應(yīng)的日志格式(stats,infrastructure)打印出來。
更多建議: