理解 loader 的工作流

分類
loader 分成哪幾類?如果指定類型?
loader 的疊加順序 = post(后置)+inline(內(nèi)聯(lián))+normal(正常)+pre(前置)。Rule.enforce 可能的值有:"pre" | "post",指定 loader 種類。沒有值表示是普通 loader。還有一個額外的種類"行內(nèi) loader",loader 被應(yīng)用在 import/require 行內(nèi)。
module: {rules: [{test: /\.css$/,use: [// 直接這樣配置就是normal'css-loader']},{test: /\.css$/,enforce: 'pre',use: [// 這樣是前置pre'style-loader']},{test: /\.less$/,enforce: 'post',use: [// 這樣是后置post'less-loader']}]}// 這樣是內(nèi)聯(lián)inlineimport Styles from 'style-loader!css-loader?modules!./styles.css';
特殊配置
加載模塊前的特殊符號是如何工作的?
| 符號 | 變量 | 含義 |
| -! | noPreAutoLoaders | 不要前置和普通 loader |
| ! | noAutoLoaders | 不要普通loader |
| !! | noPrePostAutoLoaders | 不要前后置和普通 loader,只要內(nèi)聯(lián) loader |
內(nèi)聯(lián)方式,可以在 import 語句或任何 與 "import" 方法同等的引用方式 中指定 loader。使用?!?將資源中的 loader 分開。每個部分都會相對于當(dāng)前目錄解析。
import Styles from 'style-loader!css-loader?modules!./styles.css';通過為內(nèi)聯(lián) import 語句添加前綴,可以覆蓋 配置 中的所有 loader, preLoader 和 postLoader:
使用?!?前綴,將禁用所有已配置的 normal loader(普通 loader)
import Styles from '!style-loader!css-loader?modules!./styles.css';使用?!!?前綴,將禁用所有已配置的 loader(preLoader, loader, postLoader)
import Styles from '!!style-loader!css-loader?modules!./styles.css';使用?-!?前綴,將禁用所有已配置的 preLoader 和 loader,但是不禁用 postLoaders
import Styles from '-!style-loader!css-loader?modules!./styles.css';選項可以傳遞查詢參數(shù),例如??key=value&foo=bar,或者一個 JSON 對象,例如??{"key":"value","foo":"bar"}。
組合
這些不同的loader是如何組合的?
還有一個額外的種類"行內(nèi) loader",loader 被應(yīng)用在 import/require 行內(nèi)。
所有一個接一個地進(jìn)入的 loader,都有兩個階段:
1、Pitching 階段: loader 上的 pitch 方法,按照 后置(post)、行內(nèi)(inline)、普通(normal)、前置(pre) 的順序調(diào)用。
2、Normal 階段: loader 上的 常規(guī)方法,按照 前置(pre)、普通(normal)、行內(nèi)(inline)、后置(post) 的順序調(diào)用。模塊源碼的轉(zhuǎn)換, 發(fā)生在這個階段。
loader 總是 從右到左被調(diào)用。有些情況下,loader 只關(guān)心 request 后面的 元數(shù)據(jù)(metadata),并且忽略前一個 loader 的結(jié)果。在實(shí)際(從右到左)執(zhí)行 loader 之前,會先 從左到右 調(diào)用 loader 上的 pitch 方法。
loader 可以通過 request 添加或者禁用內(nèi)聯(lián)前綴,這將影響到 pitch 和執(zhí)行的順序。
對于以下 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 方法中給出一個結(jié)果,那么這個過程會回過身來,并跳過剩下的 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-runner
所謂 loader 只是一個導(dǎo)出為函數(shù)的 JavaScript 模塊。它接收上一個 loader 產(chǎn)生的結(jié)果或者資源文件(resource file)作為入?yún)?。也可以用多個 loader 函數(shù)組成 loader chain。compiler 需要得到最后一個 loader 產(chǎn)生的處理結(jié)果。這個處理結(jié)果應(yīng)該是 String 或者 Buffer(被轉(zhuǎn)換為一個 string)。loader-runner 是一個執(zhí)行 loader chain 的模塊。
const { runLoaders } = require("loader-runner")const fs = require("fs")runLoaders({resource: "/Users/didi/workspace/2021/webpack5/source.js?query", // 你要加載的資源loaders: ["/Users/didi/workspace/2021/webpack5/loader.js?query"], // 需要處理的loadercontext: { minimize: true }, // 保存一些狀態(tài)和值readResource: fs.readFile.bind(fs)}, function(err, result) {console.log(err, '運(yùn)行錯誤')console.log(result, '運(yùn)行結(jié)果')})
經(jīng)過 loader-runner 處理的結(jié)果為:
{result: [ "console.log('hello world')// hello world" ],resourceBuffer:63 6f 6e 73 6f 6c 65 2e 6c 6f 67 28 27 68 65 6c 6c 6f 20 77 6f 72 6c 64 27 29>,cacheable: true,fileDependencies: [ '/Users/didi/workspace/2021/webpack5/source.js' ],contextDependencies: [],missingDependencies: []}
