Webpack 中怎么編寫loader,針對(duì)這個(gè)問(wèn)題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。
創(chuàng)新互聯(lián)建站專注于宿松網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供宿松營(yíng)銷型網(wǎng)站建設(shè),宿松網(wǎng)站制作、宿松網(wǎng)頁(yè)設(shè)計(jì)、宿松網(wǎng)站官網(wǎng)定制、小程序設(shè)計(jì)服務(wù),打造宿松網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供宿松網(wǎng)站排名全網(wǎng)營(yíng)銷落地服務(wù)。
如果要做總結(jié)的話,我認(rèn)為 Loader 是一個(gè)帶有副作用的內(nèi)容轉(zhuǎn)譯器!
Webpack Loader 最核心的只能是實(shí)現(xiàn)內(nèi)容轉(zhuǎn)換器 —— 將各式各樣的資源轉(zhuǎn)化為標(biāo)準(zhǔn) JavaScript 內(nèi)容格式,例如:
css-loader 將 css 轉(zhuǎn)換為 __WEBPACK_DEFAULT_EXPORT__ = ".a{ xxx }"格式
html-loader 將 html 轉(zhuǎn)換為 __WEBPACK_DEFAULT_EXPORT__ = "
vue-loader 更復(fù)雜一些,會(huì)將 .vue 文件轉(zhuǎn)化為多個(gè) JavaScript 函數(shù),分別對(duì)應(yīng) template、js、css、custom block
那么為什么需要做這種轉(zhuǎn)換呢?本質(zhì)上是因?yàn)?Webpack 只認(rèn)識(shí)符合 JavaScript 規(guī)范的文本(Webpack 5之后增加了其它 parser):在構(gòu)建(make)階段,解析模塊內(nèi)容時(shí)會(huì)調(diào)用 acorn 將文本轉(zhuǎn)換為 AST 對(duì)象,進(jìn)而分析代碼結(jié)構(gòu),分析模塊依賴;這一套邏輯對(duì)圖片、json、Vue SFC等場(chǎng)景就不 work 了,就需要 Loader 介入將資源轉(zhuǎn)化成 Webpack 可以理解的內(nèi)容形態(tài)。
Plugin 是 Webpack 另一套擴(kuò)展機(jī)制,功能更強(qiáng),能夠在各個(gè)對(duì)象的鉤子中插入特化處理邏輯,它可以覆蓋 Webpack 全生命流程,能力、靈活性、復(fù)雜度都會(huì)比 Loader 強(qiáng)很多,我們下次再講。
代碼層面,Loader 通常是一個(gè)函數(shù),結(jié)構(gòu)如下:
module.exports = function(source, sourceMap?, data?) { // source 為 loader 的輸入,可能是文件內(nèi)容,也可能是上一個(gè) loader 處理結(jié)果 return source; };
Loader 函數(shù)接收三個(gè)參數(shù),分別為:
source:資源輸入,對(duì)于第一個(gè)執(zhí)行的 loader 為資源文件的內(nèi)容;后續(xù)執(zhí)行的 loader 則為前一個(gè) loader 的執(zhí)行結(jié)果
sourceMap: 可選參數(shù),代碼的 sourcemap 結(jié)構(gòu)
data: 可選參數(shù),其它需要在 Loader 鏈中傳遞的信息,比如 posthtml/posthtml-loader 就會(huì)通過(guò)這個(gè)參數(shù)傳遞參數(shù)的 AST 對(duì)象
其中 source 是最重要的參數(shù),大多數(shù) Loader 要做的事情就是將 source 轉(zhuǎn)譯為另一種形式的 output ,比如 webpack-contrib/raw-loader 的核心源碼:
//... export default function rawLoader(source) { // ... const json = JSON.stringify(source) .replace(/\u2028/g, '\\u2028') .replace(/\u2029/g, '\\u2029'); const esModule = typeof options.esModule !== 'undefined' ? options.esModule : true; return `${esModule ? 'export default' : 'module.exports ='} ${json};`; }
這段代碼的作用是將文本內(nèi)容包裹成 JavaScript 模塊,例如:
// source I am Tecvan // output module.exports = "I am Tecvan"
經(jīng)過(guò)模塊化包裝之后,這段文本內(nèi)容轉(zhuǎn)身變成 Webpack 可以處理的資源模塊,其它 module 也就能引用、使用它了。
上例通過(guò) return 語(yǔ)句返回處理結(jié)果,除此之外 Loader 還可以以 callback 方式返回更多信息,供下游 Loader 或者 Webpack 本身使用,例如在 webpack-contrib/eslint-loader 中:
export default function loader(content, map) { // ... linter.printOutput(linter.lint(content)); this.callback(null, content, map); }
通過(guò) this.callback(null, content, map) 語(yǔ)句同時(shí)返回轉(zhuǎn)譯后的內(nèi)容與 sourcemap 內(nèi)容。callback 的完整簽名如下:
this.callback( // 異常信息,Loader 正常運(yùn)行時(shí)傳遞 null 值即可 err: Error | null, // 轉(zhuǎn)譯結(jié)果 content: string | Buffer, // 源碼的 sourcemap 信息 sourceMap?: SourceMap, // 任意需要在 Loader 間傳遞的值 // 經(jīng)常用來(lái)傳遞 ast 對(duì)象,避免重復(fù)解析 data?: any );
及到異步或 CPU 密集操作時(shí),Loader 中還可以以異步形式返回處理結(jié)果,例如 webpack-contrib/less-loader 的核心邏輯:
import less from "less"; async function lessLoader(source) { // 1. 獲取異步回調(diào)函數(shù) const callback = this.async(); // ... let result; try { // 2. 調(diào)用less 將模塊內(nèi)容轉(zhuǎn)譯為 css result = await (options.implementation || less).render(data, lessOptions); } catch (error) { // ... } const { css, imports } = result; // ... // 3. 轉(zhuǎn)譯結(jié)束,返回結(jié)果 callback(null, css, map); } export default lessLoader;
在 less-loader 中,邏輯分三步:
調(diào)用 this.async 獲取異步回調(diào)函數(shù),此時(shí) Webpack 會(huì)將該 Loader 標(biāo)記為異步加載器,會(huì)掛起當(dāng)前執(zhí)行隊(duì)列直到 callback 被觸發(fā)
調(diào)用 less 庫(kù)將 less 資源轉(zhuǎn)譯為標(biāo)準(zhǔn) css
調(diào)用異步回調(diào) callback 返回處理結(jié)果
this.async 返回的異步回調(diào)函數(shù)簽名與上一節(jié)介紹的 this.callback 相同,此處不再贅述。
Loader 為開發(fā)者提供了一種便捷的擴(kuò)展方法,但在 Loader 中執(zhí)行的各種資源內(nèi)容轉(zhuǎn)譯操作通常都是 CPU 密集型 —— 這放在單線程的 Node 場(chǎng)景下可能導(dǎo)致性能問(wèn)題;又或者異步 Loader 會(huì)掛起后續(xù)的加載器隊(duì)列直到異步 Loader 觸發(fā)回調(diào),稍微不注意就可能導(dǎo)致整個(gè)加載器鏈條的執(zhí)行時(shí)間過(guò)長(zhǎng)。
為此,默認(rèn)情況下 Webpack 會(huì)緩存 Loader 的執(zhí)行結(jié)果直到資源或資源依賴發(fā)生變化,開發(fā)者需要對(duì)此有個(gè)基本的理解,必要時(shí)可以通過(guò) this.cachable 顯式聲明不作緩存,例如:
module.exports = function(source) { this.cacheable(false); // ... return output; };
除了作為內(nèi)容轉(zhuǎn)換器外,Loader 運(yùn)行過(guò)程還可以通過(guò)一些上下文接口,有限制地影響 Webpack 編譯過(guò)程,從而產(chǎn)生內(nèi)容轉(zhuǎn)換之外的副作用。
上下文信息可通過(guò) this 獲取,this 對(duì)象由 NormolModule.createLoaderContext 函數(shù)在調(diào)用 Loader 前創(chuàng)建,常用的接口包括:
const loaderContext = { // 獲取當(dāng)前 Loader 的配置信息 getOptions: schema => {}, // 添加警告 emitWarning: warning => {}, // 添加錯(cuò)誤信息,注意這不會(huì)中斷 Webpack 運(yùn)行 emitError: error => {}, // 解析資源文件的具體路徑 resolve(context, request, callback) {}, // 直接提交文件,提交的文件不會(huì)經(jīng)過(guò)后續(xù)的chunk、module處理,直接輸出到 fs emitFile: (name, content, sourceMap, assetInfo) => {}, // 添加額外的依賴文件 // watch 模式下,依賴文件發(fā)生變化時(shí)會(huì)觸發(fā)資源重新編譯 addDependency(dep) {}, };
其中,addDependency、emitFile 、emitError、emitWarning 都會(huì)對(duì)后續(xù)編譯流程產(chǎn)生副作用,例如 less-loader 中包含這樣一段代碼:
try { result = await (options.implementation || less).render(data, lessOptions); } catch (error) { // ... } const { css, imports } = result; imports.forEach((item) => { // ... this.addDependency(path.normalize(item)); });
解釋一下,代碼中首先調(diào)用 less 編譯文件內(nèi)容,之后遍歷所有 import 語(yǔ)句,也就是上例 result.imports 數(shù)組,一一調(diào)用 this.addDependency 函數(shù)將 import 到的其它資源都注冊(cè)為依賴,之后這些其它資源文件發(fā)生變化時(shí)都會(huì)觸發(fā)重新編譯。
使用上,可以為某種資源文件配置多個(gè) Loader,Loader 之間按照配置的順序從前到后(pitch),再?gòu)暮蟮角耙来螆?zhí)行,從而形成一套內(nèi)容轉(zhuǎn)譯工作流,例如對(duì)于下面的配置:
module.exports = { module: { rules: [ { test: /\.less$/i, use: [ "style-loader", "css-loader", "less-loader", ], }, ], }, };
這是一個(gè)典型的 less 處理場(chǎng)景,針對(duì) .less 后綴的文件設(shè)定了:less、css、style 三個(gè) loader 協(xié)作處理資源文件,按照定義的順序,Webpack 解析 less 文件內(nèi)容后先傳入 less-loader;less-loader 返回的結(jié)果再傳入 css-loader 處理;css-loader 的結(jié)果再傳入 style-loader;最終以 style-loader 的處理結(jié)果為準(zhǔn),流程簡(jiǎn)化后如:
上述示例中,三個(gè) Loader 分別起如下作用:
less-loader:實(shí)現(xiàn) less => css 的轉(zhuǎn)換,輸出 css 內(nèi)容,無(wú)法被直接應(yīng)用在 Webpack 體系下
css-loader:將 css 內(nèi)容包裝成類似 module.exports = "${css}" 的內(nèi)容,包裝后的內(nèi)容符合 JavaScript 語(yǔ)法
style-loader:做的事情非常簡(jiǎn)單,就是將 css 模塊包進(jìn) require 語(yǔ)句,并在運(yùn)行時(shí)調(diào)用 injectStyle 等函數(shù)將內(nèi)容注入到頁(yè)面的 style 標(biāo)簽
三個(gè) Loader 分別完成內(nèi)容轉(zhuǎn)化工作的一部分,形成從右到左的調(diào)用鏈條。鏈?zhǔn)秸{(diào)用這種設(shè)計(jì)有兩個(gè)好處,一是保持單個(gè) Loader 的單一職責(zé),一定程度上降低代碼的復(fù)雜度;二是細(xì)粒度的功能能夠被組裝成復(fù)雜而靈活的處理鏈條,提升單個(gè) Loader 的可復(fù)用性。
不過(guò),這只是鏈?zhǔn)秸{(diào)用的一部分,這里面有兩個(gè)問(wèn)題:
Loader 鏈條一旦啟動(dòng)之后,需要所有 Loader 都執(zhí)行完畢才會(huì)結(jié)束,沒(méi)有中斷的機(jī)會(huì) —— 除非顯式拋出異常
某些場(chǎng)景下并不需要關(guān)心資源的具體內(nèi)容,但 Loader 需要在 source 內(nèi)容被讀取出來(lái)之后才會(huì)執(zhí)行
為了解決這兩個(gè)問(wèn)題,Webpack 在 loader 基礎(chǔ)上疊加了 pitch 的概念。
網(wǎng)絡(luò)上關(guān)于 Loader 的文章已經(jīng)有非常非常多,但多數(shù)并沒(méi)有對(duì) pitch 這一重要特性做足夠深入的介紹,沒(méi)有講清楚為什么要設(shè)計(jì) pitch 這個(gè)功能,pitch 有哪些常見(jiàn)用例等。
在這一節(jié),我會(huì)從 what、how、why 三個(gè)維度展開聊聊 loader pitch 這一特性。
Webpack 允許在這個(gè)函數(shù)上掛載名為 pitch 的函數(shù),運(yùn)行時(shí) pitch 會(huì)比 Loader 本身更早執(zhí)行,例如:
const loader = function (source){ console.log('后執(zhí)行') return source; } loader.pitch = function(requestString) { console.log('先執(zhí)行') } module.exports = loader
Pitch 函數(shù)的完整簽名:
function pitch( remainingRequest: string, previousRequest: string, data = {} ): void { }
包含三個(gè)參數(shù):
remainingRequest : 當(dāng)前 loader 之后的資源請(qǐng)求字符串
previousRequest : 在執(zhí)行當(dāng)前 loader 之前經(jīng)歷過(guò)的 loader 列表
data : 與 Loader 函數(shù)的 data 相同,用于傳遞需要在 Loader 傳播的信息
這些參數(shù)不復(fù)雜,但與 requestString 緊密相關(guān),我們看個(gè)例子加深了解:
module.exports = { module: { rules: [ { test: /\.less$/i, use: [ "style-loader", "css-loader", "less-loader" ], }, ], }, };
css-loader.pitch 中拿到的參數(shù)依次為:
// css-loader 之后的 loader 列表及資源路徑 remainingRequest = less-loader!./xxx.less // css-loader 之前的 loader 列表 previousRequest = style-loader // 默認(rèn)值 data = {}
Pitch 翻譯成中文是拋、球場(chǎng)、力度、事物最高點(diǎn)等,我覺(jué)得 pitch 特性之所以被忽略完全是這個(gè)名字的鍋,它背后折射的是一整套 Loader 被執(zhí)行的生命周期概念。
實(shí)現(xiàn)上,Loader 鏈條執(zhí)行過(guò)程分三個(gè)階段:pitch、解析資源、執(zhí)行,設(shè)計(jì)上與 DOM 的事件模型非常相似,pitch 對(duì)應(yīng)到捕獲階段;執(zhí)行對(duì)應(yīng)到冒泡階段;而兩個(gè)階段之間 Webpack 會(huì)執(zhí)行資源內(nèi)容的讀取、解析操作,對(duì)應(yīng) DOM 事件模型的 AT_TARGET 階段:
pitch 階段按配置順序從左到右逐個(gè)執(zhí)行 loader.pitch 函數(shù)(如果有的話),開發(fā)者可以在 pitch 返回任意值中斷后續(xù)的鏈路的執(zhí)行:
那么為什么要設(shè)計(jì) pitch 這一特性呢?在分析了 style-loader、vue-loader、to-string-loader 等開源項(xiàng)目之后,我個(gè)人總結(jié)出兩個(gè)字:「阻斷」!
先回顧一下前面提到過(guò)的 less 加載鏈條:
less-loader :將 less 規(guī)格的內(nèi)容轉(zhuǎn)換為標(biāo)準(zhǔn) css
css-loader :將 css 內(nèi)容包裹為 JavaScript 模塊
style-loader :將 JavaScript 模塊的導(dǎo)出結(jié)果以 link 、style 標(biāo)簽等方式掛載到 html 中,讓 css 代碼能夠正確運(yùn)行在瀏覽器上
實(shí)際上, style-loader 只是負(fù)責(zé)讓 css 能夠在瀏覽器環(huán)境下跑起來(lái),本質(zhì)上并不需要關(guān)心具體內(nèi)容,很適合用 pitch 來(lái)處理,核心代碼:
// ... // Loader 本身不作任何處理 const loaderApi = () => {}; // pitch 中根據(jù)參數(shù)拼接模塊代碼 loaderApi.pitch = function loader(remainingRequest) { //... switch (injectType) { case 'linkTag': { return `${ esModule ? `...` // 引入 runtime 模塊 : `var api = require(${loaderUtils.stringifyRequest( this, `!${path.join(__dirname, 'runtime/injectStylesIntoLinkTag.js')}` )}); // 引入 css 模塊 var content = require(${loaderUtils.stringifyRequest( this, `!!${remainingRequest}` )}); content = content.__esModule ? content.default : content;` } // ...`; } case 'lazyStyleTag': case 'lazySingletonStyleTag': { //... } case 'styleTag': case 'singletonStyleTag': default: { // ... } } }; export default loaderApi;
關(guān)鍵點(diǎn):
loaderApi 為空函數(shù),不做任何處理
loaderApi.pitch 中拼接結(jié)果,導(dǎo)出的代碼包含:
引入運(yùn)行時(shí)模塊 runtime/injectStylesIntoLinkTag.js復(fù)用 remainingRequest 參數(shù),重新引入 css 文件
運(yùn)行結(jié)果大致如:
var api = require('xxx/style-loader/lib/runtime/injectStylesIntoLinkTag.js') var content = require('!!css-loader!less-loader!./xxx.less');
注意了,到這里 style-loader 的 pitch 函數(shù)返回這一段內(nèi)容,后續(xù)的 Loader 就不會(huì)繼續(xù)執(zhí)行,當(dāng)前調(diào)用鏈條中斷了:
之后,Webpack 繼續(xù)解析、構(gòu)建 style-loader 返回的結(jié)果,遇到 inline loader 語(yǔ)句:
var content = require('!!css-loader!less-loader!./xxx.less');
所以從 Webpack 的角度看,實(shí)際上對(duì)同一個(gè)文件調(diào)用了兩次 loader 鏈,第一次在 style-loader 的 pitch 中斷,第二次根據(jù) inline loader 的內(nèi)容跳過(guò)了 style-loader。
相似的技巧在其它倉(cāng)庫(kù)也有出現(xiàn),比如 vue-loader,感興趣的同學(xué)可以查看我之前發(fā)在 ByteFE 公眾號(hào)上的文章《Webpack 案例 ——vue-loader 原理分析》,這里就不展開講了。
Webpack 為 Loader 開發(fā)者提供了兩個(gè)實(shí)用工具,在諸多開源 Loader 中出現(xiàn)頻率極高:
webpack/loader-utils:提供了一系列諸如讀取配置、requestString 序列化與反序列化、計(jì)算 hash 值之類的工具函數(shù)
webpack/schema-utils:參數(shù)校驗(yàn)工具
這些工具的具體接口在相應(yīng)的 readme 上已經(jīng)有明確的說(shuō)明,不贅述,這里總結(jié)一些編寫 Loader 時(shí)經(jīng)常用到的樣例:如何獲取并校驗(yàn)用戶配置;如何拼接輸出文件名。
Loader 通常都提供了一些配置項(xiàng),供開發(fā)者定制運(yùn)行行為,用戶可以通過(guò) Webpack 配置文件的 use.options 屬性設(shè)定配置,例如:
module.exports = { module: { rules: [{ test: /\.less$/i, use: [ { loader: "less-loader", options: { cacheDirectory: false } }, ], }], }, };
在 Loader 內(nèi)部,需要使用 loader-utils 庫(kù)的 getOptions 函數(shù)獲取用戶配置,用 schema-utils 庫(kù)的 validate 函數(shù)校驗(yàn)參數(shù)合法性,例如 css-loader:
// css-loader/src/index.js import { getOptions } from "loader-utils"; import { validate } from "schema-utils"; import schema from "./options.json"; export default async function loader(content, map, meta) { const rawOptions = getOptions(this); validate(schema, rawOptions, { name: "CSS Loader", baseDataPath: "options", }); // ... }
使用 schema-utils 做校驗(yàn)時(shí)需要提前聲明配置模板,通常會(huì)處理成一個(gè)額外的 json 文件,例如上例中的 "./options.json"。
Webpack 支持以類似 [path]/[name]-[hash].js 方式設(shè)定 output.filename即輸出文件的命名,這一層規(guī)則通常不需要關(guān)注,但某些場(chǎng)景例如 webpack-contrib/file-loader 需要根據(jù) asset 的文件名拼接結(jié)果。
file-loader 支持在 JS 模塊中引入諸如 png、jpg、svg 等文本或二進(jìn)制文件,并將文件寫出到輸出目錄,這里面有一個(gè)問(wèn)題:假如文件叫 a.jpg ,經(jīng)過(guò) Webpack 處理后輸出為 [hash].jpg ,怎么對(duì)應(yīng)上呢?此時(shí)就可以使用 loader-utils 提供的 interpolateName 在 file-loader 中獲取資源寫出的路徑及名稱,源碼:
import { getOptions, interpolateName } from 'loader-utils'; export default function loader(content) { const context = options.context || this.rootContext; const name = options.name || '[contenthash].[ext]'; // 拼接最終輸出的名稱 const url = interpolateName(this, name, { context, content, regExp: options.regExp, }); let outputPath = url; // ... let publicPath = `__webpack_public_path__ + ${JSON.stringify(outputPath)}`; // ... if (typeof options.emitFile === 'undefined' || options.emitFile) { // ... // 提交、寫出文件 this.emitFile(outputPath, content, null, assetInfo); } // ... const esModule = typeof options.esModule !== 'undefined' ? options.esModule : true; // 返回模塊化內(nèi)容 return `${esModule ? 'export default' : 'module.exports ='} ${publicPath};`; } export const raw = true;
代碼的核心邏輯:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
根據(jù) Loader 配置,調(diào)用 interpolateName 方法拼接目標(biāo)文件的完整路徑
調(diào)用上下文 this.emitFile 接口,寫出文件
返回 module.exports = ${publicPath} ,其它模塊可以引用到該文件路徑
除 file-loader 外,css-loader、eslint-loader 都有用到該接口,感興趣的同學(xué)請(qǐng)自行前往查閱源碼。
在 Loader 中編寫單元測(cè)試收益非常高,一方面對(duì)開發(fā)者來(lái)說(shuō)不用去怎么寫 demo,怎么搭建測(cè)試環(huán)境;一方面對(duì)于最終用戶來(lái)說(shuō),帶有一定測(cè)試覆蓋率的項(xiàng)目通常意味著更高、更穩(wěn)定的質(zhì)量。
閱讀了超過(guò) 20 個(gè)開源項(xiàng)目后,我總結(jié)了一套 Webpack Loader 場(chǎng)景下常用的單元測(cè)試流程,以 Jest · ? Delightful JavaScript Testing 為例:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
創(chuàng)建在 Webpack 實(shí)例,并運(yùn)行 Loader
獲取 Loader 執(zhí)行結(jié)果,比對(duì)、分析判斷是否符合預(yù)期
判斷執(zhí)行過(guò)程中是否出錯(cuò)
有兩種辦法,一是在 node 環(huán)境下運(yùn)行調(diào)用 Webpack 接口,用代碼而非命令行執(zhí)行編譯,很多框架都會(huì)采用這種方式,例如 vue-loader、stylus-loader、babel-loader 等,優(yōu)點(diǎn)的運(yùn)行效果最接近最終用戶,缺點(diǎn)是運(yùn)行效率相對(duì)較低(可以忽略)。
以 posthtml/posthtml-loader 為例,它會(huì)在啟動(dòng)測(cè)試之前創(chuàng)建并運(yùn)行 Webpack 實(shí)例:
// posthtml-loader/test/helpers/compiler.js 文件 module.exports = function (fixture, config, options) { config = { /*...*/ } options = Object.assign({ output: false }, options) // 創(chuàng)建 Webpack 實(shí)例 const compiler = webpack(config) // 以 MemoryFS 方式輸出構(gòu)建結(jié)果,避免寫磁盤 if (!options.output) compiler.outputFileSystem = new MemoryFS() // 執(zhí)行,并以 promise 方式返回結(jié)果 return new Promise((resolve, reject) => compiler.run((err, stats) => { if (err) reject(err) // 異步返回執(zhí)行結(jié)果 resolve(stats) })) }
小技巧:如上例所示,用 compiler.outputFileSystem = new MemoryFS()語(yǔ)句將 Webpack 設(shè)定成輸出到內(nèi)存,能避免寫盤操作,提升編譯速度。
另外一種方法是編寫一系列 mock 方法,搭建起一個(gè)模擬的 Webpack 運(yùn)行環(huán)境,例如 emaphp/underscore-template-loader ,優(yōu)點(diǎn)的運(yùn)行速度更快,缺點(diǎn)是開發(fā)工作量大通用性低,了解了解即可。
上例運(yùn)行結(jié)束之后會(huì)以 resolve(stats) 方式返回執(zhí)行結(jié)果,stats 對(duì)象中幾乎包含了編譯過(guò)程所有信息,包括耗時(shí)、產(chǎn)物、模塊、chunks、errors、warnings 等等,我在之前的文章 分享幾個(gè) Webpack 實(shí)用分析工具 對(duì)此已經(jīng)做了較深入的介紹,感興趣的同學(xué)可以前往閱讀。
在測(cè)試場(chǎng)景下,可以從 stats 對(duì)象中讀取編譯最終輸出的產(chǎn)物,例如 style-loader 的實(shí)現(xiàn):
// style-loader/src/test/helpers/readAsset.js 文件 function readAsset(compiler, stats, assets) => { const usedFs = compiler.outputFileSystem const outputPath = stats.compilation.outputOptions.path const queryStringIdx = targetFile.indexOf('?') if (queryStringIdx >= 0) { // 解析出輸出文件路徑 asset = asset.substr(0, queryStringIdx) } // 讀文件內(nèi)容 return usedFs.readFileSync(path.join(outputPath, targetFile)).toString() }
解釋一下,這段代碼首先計(jì)算 asset 輸出的文件路徑,之后調(diào)用 outputFileSystem 的 readFile 方法讀取文件內(nèi)容。
接下來(lái),有兩種分析內(nèi)容的方法:
調(diào)用 Jest 的 expect(xxx).toMatchSnapshot() 斷言判斷當(dāng)前運(yùn)行結(jié)果是否與之前的運(yùn)行結(jié)果一致,從而確保多次修改的結(jié)果一致性,很多框架都大量用了這種方法
解讀資源內(nèi)容,判斷是否符合預(yù)期,例如 less-loader 的單元測(cè)試中會(huì)對(duì)同一份代碼跑兩次 less 編譯,一次由 Webpack 執(zhí)行,一次直接調(diào)用 less 庫(kù),之后分析兩次運(yùn)行結(jié)果是否相同
對(duì)此有興趣的同學(xué),強(qiáng)烈建議看看 less-loader 的 test 目錄。
最后,還需要判斷編譯過(guò)程是否出現(xiàn)異常,同樣可以從 stats 對(duì)象解析:
export default getErrors = (stats) => { const errors = stats.compilation.errors.sort() return errors.map( e => e.toString() ) }
大多數(shù)情況下都希望編譯沒(méi)有錯(cuò)誤,此時(shí)只要判斷結(jié)果數(shù)組是否為空即可。某些情況下可能需要判斷是否拋出特定異常,此時(shí)可以 expect(xxx).toMatchSnapshot() 斷言,用快照對(duì)比更新前后的結(jié)果。
開發(fā) Loader 的過(guò)程中,有一些小技巧能夠提升調(diào)試效率,包括:
使用 ndb 工具實(shí)現(xiàn)斷點(diǎn)調(diào)試
使用 npm link 將 Loader 模塊鏈接到測(cè)試項(xiàng)目
使用 resolveLoader 配置項(xiàng)將 Loader 所在的目錄加入到測(cè)試項(xiàng)目中,如:
// webpack.config.js module.exports = { resolveLoader:{ modules: ['node_modules','./loaders/'], } }
關(guān)于Webpack 中怎么編寫loader問(wèn)題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒(méi)有解開,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識(shí)。
新聞標(biāo)題:Webpack中怎么編寫loader
本文URL:http://jinyejixie.com/article2/pocjoc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站營(yíng)銷、虛擬主機(jī)、網(wǎng)站維護(hù)、外貿(mào)建站、面包屑導(dǎo)航、做網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)