摘要:在一個構建過程中,首先根據的依賴類型例如調用對應的構造函數來創建對應的模塊。
文章首發于個人github blog: Biu-blog,歡迎大家關注~
Webpack 系列文章:
Webpack Loader 高手進階(一)
Webpack Loader 高手進階(二)
Webpack Loader 高手進階(三)
Webpack 對于一個 module 所使用的 loader 對開發者提供了2種使用方式:
webpack config 配置形式,形如:
// webpack.config.js module.exports = { ... module: { rules: [{ test: /.vue$/, loader: "vue-loader" }, { test: /.scss$/, use: [ "vue-style-loader", "css-loader", { loader: "sass-loader", options: { data: "$color: red;" } } ] }] } ... }
inline 內聯形式
// module import a from "raw-loader!../../utils.js"
2種不同的配置形式,在 webpack 內部有著不同的解析方式。此外,不同的配置方式也決定了最終在實際加載 module 過程中不同 loader 之間相互的執行順序等。
loader 的匹配在講 loader 的匹配過程之前,首先從整體上了解下 loader 在整個 webpack 的 workflow 過程中出現的時機。
在一個 module 構建過程中,首先根據 module 的依賴類型(例如 NormalModuleFactory)調用對應的構造函數來創建對應的模塊。在創建模塊的過程中(new NormalModuleFactory()),會根據開發者的 webpack.config 當中的 rules 以及 webpack 內置的 rules 規則實例化 RuleSet 匹配實例,這個 RuleSet 實例在 loader 的匹配過濾過程中非常的關鍵,具體的源碼解析可參見Webpack Loader Ruleset 匹配規則解析。實例化 RuleSet 后,還會注冊2個鉤子函數:
class NormalModuleFactory { ... // 內部嵌套 resolver 的鉤子,完成相關的解析后,創建這個 normalModule this.hooks.factory.tap("NormalModuleFactory", () => (result, callback) => { ... }) // 在 hooks.factory 的鉤子內部進行調用,實際的作用為解析構建一共 module 所需要的 loaders 及這個 module 的相關構建信息(例如獲取 module 的 packge.json等) this.hooks.resolver.tap("NormalModuleFactory", () => (result, callback) => { ... }) ... }
當 NormalModuleFactory 實例化完成后,并在 compilation 內部調用這個實例的 create 方法開始真實開始創建這個 normalModule。首先調用hooks.factory獲取對應的鉤子函數,接下來就調用 resolver 鉤子(hooks.resolver)進入到了 resolve 的階段,在真正開始 resolve loader 之前,首先就是需要匹配過濾找到構建這個 module 所需要使用的所有的 loaders。首先進行的是對于 inline loaders 的處理:
// NormalModuleFactory.js // 是否忽略 preLoader 以及 normalLoader const noPreAutoLoaders = requestWithoutMatchResource.startsWith("-!"); // 是否忽略 normalLoader const noAutoLoaders = noPreAutoLoaders || requestWithoutMatchResource.startsWith("!"); // 忽略所有的 preLoader / normalLoader / postLoader const noPrePostAutoLoaders = requestWithoutMatchResource.startsWith("!!"); // 首先解析出所需要的 loader,這種 loader 為內聯的 loader let elements = requestWithoutMatchResource .replace(/^-?!+/, "") .replace(/!!+/g, "!") .split("!"); let resource = elements.pop(); // 獲取資源的路徑 elements = elements.map(identToLoaderRequest); // 獲取每個loader及對應的options配置(將inline loader的寫法變更為module.rule的寫法)
首先是根據模塊的路徑規則,例如模塊的路徑是以這些符號開頭的 ! / -! / !! 來判斷這個模塊是否只是使用 inline loader,或者剔除掉 preLoader, postLoader 等規則:
! 忽略 webpack.config 配置當中符合規則的 normalLoader
-! 忽略 webpack.config 配置當中符合規則的 preLoader/normalLoader
!! 忽略 webpack.config 配置當中符合規則的 postLoader/preLoader/normalLoader
這幾個匹配規則主要適用于在 webpack.config 已經配置了對應模塊使用的 loader,但是針對一些特殊的 module,你可能需要多帶帶的定制化的 loader 去處理,而不是走常規的配置,因此可以使用這些規則來進行處理。
接下來將所有的 inline loader 轉化為數組的形式,例如:
import "style-loader!css-loader!stylus-loader?a=b!../../common.styl"
最終 inline loader 統一格式輸出為:
[{ loader: "style-loader", options: undefined }, { loader: "css-lodaer", options: undefined }, { loader: "stylus-loader", options: "?a=b" }]
對于 inline loader 的處理便是直接對其進行 resolve,獲取對應 loader 的相關信息:
asyncLib.parallel([ callback => this.resolveRequestArray( contextInfo, context, elements, loaderResolver, callback ), callback => { // 對這個 module 進行 resolve ... callack(null, { resouceResolveData, // 模塊的基礎信息,包含 descriptionFilePath / descriptionFileData 等(即 package.json 等信息) resource // 模塊的絕對路徑 }) } ], (err, results) => { const loaders = results[0] // 所有內聯的 loaders const resourceResolveData = results[1].resourceResolveData; // 獲取模塊的基本信息 resource = results[1].resource; // 模塊的絕對路徑 ... // 接下來就要開始根據引入模塊的路徑開始匹配對應的 loaders let resourcePath = matchResource !== undefined ? matchResource : resource; let resourceQuery = ""; const queryIndex = resourcePath.indexOf("?"); if (queryIndex >= 0) { resourceQuery = resourcePath.substr(queryIndex); resourcePath = resourcePath.substr(0, queryIndex); } // 獲取符合條件配置的 loader,具體的 ruleset 是如何匹配的請參見 ruleset 解析(https://github.com/CommanderXL/Biu-blog/issues/30) const result = this.ruleSet.exec({ resource: resourcePath, // module 的絕對路徑 realResource: matchResource !== undefined ? resource.replace(/?.*/, "") : resourcePath, resourceQuery, // module 路徑上所帶的 query 參數 issuer: contextInfo.issuer, // 所解析的 module 的發布者 compiler: contextInfo.compiler }); // result 為最終根據 module 的路徑及相關匹配規則過濾后得到的 loaders,為 webpack.config 進行配置的 // 輸出的數據格式為: /* [{ type: "use", value: { loader: "vue-style-loader", options: {} }, enforce: undefined // 可選值還有 pre/post 分別為 pre-loader 和 post-loader }, { type: "use", value: { loader: "css-loader", options: {} }, enforce: undefined }, { type: "use", value: { loader: "stylus-loader", options: { data: "$color red" } }, enforce: undefined }] */ const settings = {}; const useLoadersPost = []; // post loader const useLoaders = []; // normal loader const useLoadersPre = []; // pre loader for (const r of result) { if (r.type === "use") { // postLoader if (r.enforce === "post" && !noPrePostAutoLoaders) { useLoadersPost.push(r.value); } else if ( r.enforce === "pre" && !noPreAutoLoaders && !noPrePostAutoLoaders ) { // preLoader useLoadersPre.push(r.value); } else if ( !r.enforce && !noAutoLoaders && !noPrePostAutoLoaders ) { // normal loader useLoaders.push(r.value); } } else if ( typeof r.value === "object" && r.value !== null && typeof settings[r.type] === "object" && settings[r.type] !== null ) { settings[r.type] = cachedMerge(settings[r.type], r.value); } else { settings[r.type] = r.value; } // 當獲取到 webpack.config 當中配置的 loader 后,再根據 loader 的類型進行分組(enforce 配置類型) // postLoader 存儲到 useLoaders 內部 // preLoader 存儲到 usePreLoaders 內部 // normalLoader 存儲到 useLoaders 內部 // 這些分組最終會決定加載一個 module 時不同 loader 之間的調用順序 // 當分組過程進行完之后,即開始 loader 模塊的 resolve 過程 asyncLib.parallel([ [ // resolve postLoader this.resolveRequestArray.bind( this, contextInfo, this.context, useLoadersPost, loaderResolver ), // resove normal loaders this.resolveRequestArray.bind( this, contextInfo, this.context, useLoaders, loaderResolver ), // resolve preLoader this.resolveRequestArray.bind( this, contextInfo, this.context, useLoadersPre, loaderResolver ) ], (err, results) => { ... // results[0] -> postLoader // results[1] -> normalLoader // results[2] -> preLoader // 這里將構建 module 需要的所有類型的 loaders 按照一定順序組合起來,對應于: // [postLoader, inlineLoader, normalLoader, preLoader] // 最終 loader 所執行的順序對應為: preLoader -> normalLoader -> inlineLoader -> postLoader // 不同類型 loader 上的 pitch 方法執行的順序為: postLoader.pitch -> inlineLoader.pitch -> normalLoader.pitch -> preLoader.pitch (具體loader內部執行的機制后文會多帶帶講解) loaders = results[0].concat(loaders, results[1], results[2]); process.nextTick(() => { ... // 執行回調,創建 module }) } ]) } })
簡單總結下匹配的流程就是:
首先處理 inlineLoaders,對其進行解析,獲取對應的 loader 模塊的信息,接下來利用 ruleset 實例上的匹配過濾方法對 webpack.config 中配置的相關 loaders 進行匹配過濾,獲取構建這個 module 所需要的配置的的 loaders,并進行解析,這個過程完成后,便進行所有 loaders 的拼裝工作,并傳入創建 module 的回調中。
文章首發于個人github blog: Biu-blog,歡迎大家關注~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/109088.html
摘要:相關的內容為這樣對于一個處理的第二階段也就結束了,通過去攔截不同類型的,并返回新的,跳過后面的的執行,同時在內部會剔除掉,這樣在進入到下一個處理階段的時候,不在使用的范圍之內,因此下一階段便不會經由來處理。 文章首發于個人github blog: Biu-blog,歡迎大家關注~ Webpack 系列文章: Webpack Loader 高手進階(一)Webpack Loader 高手...
摘要:如果函數沒有返回值的話,那么進入到下一個的函數的執行階段。這也是異步化的一種方式如果執行后有返回值,執行開始下一個執行以上就是對于在構建過程中執行流程的源碼分析。 文章首發于個人github blog: Biu-blog,歡迎大家關注~ Webpack 系列文章: Webpack Loader 高手進階(一)Webpack Loader 高手進階(二)Webpack Loader 高手...
webpack的loaders是一大特色,也是很重要的一部分。這遍博客我將分類講解一些常用的laodershowImg(https://segmentfault.com/img/remote/1460000005742040); 一、loaders之 預處理 css-loader 處理css中路徑引用等問題 style-loader 動態把樣式寫入css sass-loader scss編譯器 ...
摘要:,我想大家應該都知道或者聽過,是前端一個工具可以讓各個模塊進行加載預處理再進行打包。 webpack,我想大家應該都知道或者聽過,Webpack是前端一個工具,可以讓各個模塊進行加載,預處理,再進行打包。現代的前端開發很多環境都依賴webpack構建,比如vue官方就推薦使用webpack.廢話不多說,我們趕緊開始吧. 第一步、安裝webpack 新建文件夾webpack->再在web...
摘要:配置文件中設置引入文件之前講過在引用文件的時候可以引入和,諸如這樣現在提供一種更常用和更便捷的方法。同樣先在項目文件夾下安裝和模塊配置文件如下選項用來處理對應的模塊用正則匹配所有的文件使用模塊這樣就已經可以正常打包文件了。 配置文件中設置引入css文件 之前講過在引用css文件的時候可以引入style-loader和css=loader,諸如這樣: require(style-load...
閱讀 3544·2021-11-23 10:10
閱讀 3318·2019-08-30 14:03
閱讀 2072·2019-08-30 13:09
閱讀 3400·2019-08-29 15:29
閱讀 1547·2019-08-29 11:23
閱讀 2014·2019-08-28 18:28
閱讀 2849·2019-08-26 13:34
閱讀 2174·2019-08-26 11:32