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ì)把它們?cè)?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)中。請(qǐ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í)行請(qǐng)求。
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è)簡(jiǎn)化的示例,你可以查看 webpack 倉(cāng)庫(kù)的完整示例。
當(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é)果,請(qǐng)?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)請(qǐng)求語法。前綴為 <match-resource>!=! 將為此請(qǐng)求設(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)打印出來。
更多建議: