成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

Webpack中怎么編寫loader

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ù)。

認(rèn)識(shí) Loader

  • 如果要做總結(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 基礎(chǔ)

代碼層面,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 也就能引用、使用它了。

返回多個(gè)結(jié)果

上例通過(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; };

上下文與 Side Effect

除了作為內(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ā)重新編譯。

Loader 鏈?zhǔn)秸{(diào)用

使用上,可以為某種資源文件配置多個(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)化后如:

Webpack 中怎么編寫loader

上述示例中,三個(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 的概念。

Loader 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 這一特性。

什么是 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 = {}

調(diào)度邏輯

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 階段:

Webpack 中怎么編寫loader

pitch 階段按配置順序從左到右逐個(gè)執(zhí)行 loader.pitch 函數(shù)(如果有的話),開發(fā)者可以在 pitch  返回任意值中斷后續(xù)的鏈路的執(zhí)行:

Webpack 中怎么編寫loader

那么為什么要設(shè)計(jì) pitch 這一特性呢?在分析了 style-loader、vue-loader、to-string-loader  等開源項(xiàng)目之后,我個(gè)人總結(jié)出兩個(gè)字:「阻斷」!

示例:style-loader

先回顧一下前面提到過(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 中怎么編寫loader

之后,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 原理分析》,這里就不展開講了。

進(jìn)階技巧

開發(fā)工具

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)用戶配置;如何拼接輸出文件名。

獲取并校驗(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;

代碼的核心邏輯:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 根據(jù) Loader 配置,調(diào)用 interpolateName 方法拼接目標(biāo)文件的完整路徑

  3. 調(diào)用上下文 this.emitFile 接口,寫出文件

  4. 返回 module.exports = ${publicPath} ,其它模塊可以引用到該文件路徑

除 file-loader 外,css-loader、eslint-loader 都有用到該接口,感興趣的同學(xué)請(qǐng)自行前往查閱源碼。

單元測(cè)試

在 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 為例:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 創(chuàng)建在 Webpack 實(shí)例,并運(yùn)行 Loader

  3. 獲取 Loader 執(zhí)行結(jié)果,比對(duì)、分析判斷是否符合預(yù)期

  4. 判斷執(zhí)行過(guò)程中是否出錯(cuò)

如何運(yùn)行 Loader

有兩種辦法,一是在 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ā)工作量大通用性低,了解了解即可。

比對(duì)結(jié)果

上例運(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é)果。

調(diào)試

開發(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)

成都seo排名網(wǎng)站優(yōu)化
沁阳市| 桐梓县| 丽江市| 文安县| 汤原县| 平遥县| 陵水| 澄江县| 文昌市| 剑河县| 洪江市| 遵化市| 田林县| 康平县| 永昌县| 宜州市| 斗六市| 武宁县| 江北区| 仁布县| 玉屏| 延寿县| 安岳县| 丹凤县| 航空| 二手房| 敖汉旗| 昌平区| 区。| 乐至县| 崇义县| 土默特左旗| 栖霞市| 永安市| 托克逊县| 富裕县| 长治市| 会理县| 衡东县| 张北县| 饶平县|