webpack loader配置全流程詳解
前言
1.主要目的為稍微梳理從配置到裝載的流程。另外詳解當然要加點原始碼提升格調(本人菜鳥,有錯還請友善指正)
2.被的WebPack打包的檔案,都被轉化為一個模組,比如import './xxx/x.jpg'或require('./xxx/x.js')。至於具體實際怎麼轉化,交由裝載機處理
3.下文會使用打字稿(勸退警告?)以方便說明有哪些選項和各個選項的值型別
配置語法解析
模組屬性
module.exports = { ... module: { noParse: /jquery/, rules: [ { test: /\.js/, exclude: /node_modules/, use:[ { loader: './loader1.js?num=1', options: {myoptions:false}, }, "./loader2.js?num=2", ] }, { test: /\.js/, include: /src/, loader: './loader1.js!./loader2.js', }, ] } }
上述是展示常見的配置寫法.webpack為其選項都編寫了打字稿宣告,這個模組屬性的宣告在的WebPack /宣告中可見:
export interface ModuleOptions { // 一般下面這兩個 noParse?: RegExp[] | RegExp | Function | string[] | string; rules?: RuleSetRules; // 這些...已被廢棄,即將被刪除,不用看 defaultRules?: RuleSetRules; exprContextCritical?: boolean; exprContextRecursive?: boolean; exprContextRegExp?: boolean | RegExp; exprContextRequest?: string; strictExportPresence?: boolean; strictThisContextOnImports?: boolean; unknownContextCritical?: boolean; unknownContextRecursive?: boolean; unknownContextRegExp?: boolean | RegExp; unknownContextRequest?: string; unsafeCache?: boolean | Function; wrappedContextCritical?: boolean; wrappedContextRecursive?: boolean; wrappedContextRegExp?: RegExp; }
noParse 用於讓的WebPack跳過對這些檔案的轉化,也就是他們不會被載入程式所處理(但還是會被打包並輸出到DIST目錄)
rules 核心配置,見下文
module.rules屬性
module.rules型別是RuleSetRule[],請繼續的WebPack /宣告檢視其打字稿,有哪些屬性,屬性型別一目瞭然。
注意RuleSetConditionsRecursive這個東西在另外一個檔案宣告,是interface RuleSetConditionsRecursive extends Array<import("./declarations/WebpackOptions").RuleSetCondition> {},其實就是export type RuleSetConditionsRecursive = RuleSetCondition[];,代表一個RuleSetCondition陣列
意義直接貼中文文件:模組。
好了,上面基本是搬運打字稿宣告,結合文件基本能知道有哪些屬性,屬性的型別和含義。下面結合原始碼對文件一些難以理解的地方補充說明。
正文
規則集
規則的規範化(型別收斂)
由上可知一個規則物件,其屬性型別有多種可能,所以應該對其規範化,底層減少程式碼的大量typeof等判斷。這是由RuleSet.js進行規範化的。下面是經過規則集處理後的一個規則物件大致形式:
// rule 物件規範化後的形狀應該是: { resource: function(), resourceQuery: function(), compiler: function(), issuer: function(), use: [ { loader: string, options: string | object, // 原始碼的註釋可能是歷史遺留原因,options也可為object型別 <any>: <any> } // 下文稱呼這個為use陣列的單個元素為 loader物件,規範化後它一般只有loader和options屬性 ], rules: [<rule>], oneOf: [<rule>], <any>: <any>, }
rules狀語從句:oneOf的英文用來巢狀的,裡面的也是規範過的規則物件。
它這裡的四個函式是的WebPack用來判斷是否需要把檔案內容交給裝載器處理的。如的WebPack遇到了import './a.js',那麼rule.resource('f:/a.js')===true時會才把檔案交由規則中指定的裝載機去處理,resourceQuery等同理。
的這裡的傳入引數'f:/a.js'就是官網所說的
條件已經兩個輸入值:
資源:請求檔案的絕對路徑。它已經根據resolve規則解析。issuer :被請求資源(請求的資源)的模組檔案的絕對路徑。是匯入時的位置。
首先要做的是把Rule.loader, ,Rule.options(Rule.query已廢棄,但尚未刪除),移動全部到Rule.use陣列元素的物件裡。主要這由static normalizeRule(rule, refs, ident)函式處理,程式碼主要是處理各種“簡寫”,把值搬運到裝載器物件,做一些報錯處理,難度不大看一下即可,下面挑它裡面的“條件函式”規範化來說一說。
Rule.resource規範化
由上可知這是一個“條件函式”,它是根據我們的配置中的test,include,exclude,resource規範化而生成的原始碼180多行中:
if (rule.test || rule.include || rule.exclude) { checkResourceSource("test + include + exclude"); condition = { test: rule.test, include: rule.include, exclude: rule.exclude }; try { newRule.resource = RuleSet.normalizeCondition(condition); } catch (error) { throw new Error(RuleSet.buildErrorMessage(condition, error)); } } if (rule.resource) { checkResourceSource("resource"); try { newRule.resource = RuleSet.normalizeCondition(rule.resource); } catch (error) { throw new Error(RuleSet.buildErrorMessage(rule.resource, error)); } }
中文件說Rule.test的英文Rule.resource.test的簡寫,實際就是這串程式碼。
checkResourceSource用來檢查是否重複配置,即文件中提到的:你如果提供了一個Rule.test選項對話,就不能再提供Rule.resource
求最後RuleSet.normalizeCondition生成一個“條件函式”,如下:
static normalizeCondition(condition) { if (!condition) throw new Error("Expected condition but got falsy value"); if (typeof condition === "string") { return str => str.indexOf(condition) === 0; } if (typeof condition === "function") { return condition; } if (condition instanceof RegExp) { return condition.test.bind(condition); } if (Array.isArray(condition)) { const items = condition.map(c => RuleSet.normalizeCondition(c)); return orMatcher(items); } if (typeof condition !== "object") { throw Error( "Unexcepted " + typeof condition + " when condition was expected (" + condition + ")" ); } const matchers = []; Object.keys(condition).forEach(key => { const value = condition[key]; switch (key) { case "or": case "include": case "test": if (value) matchers.push(RuleSet.normalizeCondition(value)); break; case "and": if (value) { const items = value.map(c => RuleSet.normalizeCondition(c)); matchers.push(andMatcher(items)); } break; case "not": case "exclude": if (value) { const matcher = RuleSet.normalizeCondition(value); matchers.push(notMatcher(matcher)); } break; default: throw new Error("Unexcepted property " + key + " in condition"); } }); if (matchers.length === 0) { throw new Error("Excepted condition but got " + condition); } if (matchers.length === 1) { return matchers[0]; } return andMatcher(matchers); }
這串程式碼主要就是根據字串,正規表示式,物件,功能型別來生成不同的“條件函式”,難度不大。
notMatcher,orMatcher,andMatcher這三個是輔助函式,看名字就知道了,實現上非常簡單,不貼原始碼了。有什麼不明白的邏輯,代入進去跑一跑就知道了
規則使用規範化
我們接下來要把Rule.use給規範分類中翻譯上面提到的那種形式,即讓裝載機只物件保留loader狀語從句:options這兩個屬性(當然,並不是它一定只有這兩個屬性)原始碼如下:
static normalizeUse(use, ident) { if (typeof use === "function") { return data => RuleSet.normalizeUse(use(data), ident); } if (Array.isArray(use)) { return use .map((item, idx) => RuleSet.normalizeUse(item, `${ident}-${idx}`)) .reduce((arr, items) => arr.concat(items), []); } return [RuleSet.normalizeUseItem(use, ident)]; } static normalizeUseItemString(useItemString) { const idx = useItemString.indexOf("?"); if (idx >= 0) { return { loader: useItemString.substr(0, idx), options: useItemString.substr(idx + 1) }; } return { loader: useItemString, options: undefined }; } static normalizeUseItem(item, ident) { if (typeof item === "string") { return RuleSet.normalizeUseItemString(item); } const newItem = {}; if (item.options && item.query) { throw new Error("Provided options and query in use"); } if (!item.loader) { throw new Error("No loader specified"); } newItem.options = item.options || item.query; if (typeof newItem.options === "object" && newItem.options) { if (newItem.options.ident) { newItem.ident = newItem.options.ident; } else { newItem.ident = ident; } } const keys = Object.keys(item).filter(function(key) { return !["options", "query"].includes(key); }); for (const key of keys) { newItem[key] = item[key]; } return newItem; }
這幾個函式比較繞,但總體來說難度不大。
這裡再稍微總結幾點現象:
1.loader: './loader1!./loader2',如果在Rule.loader指明瞭兩個以以上裝載機,那麼不可設定Rule.options,因為不知道該把這個選項傳給哪個裝載機,直接報錯
2.-loader不可省略,如babel!./loader的英文非法的,因為在webpack/lib/NormalModuleFactory.js440行左右,已經不再支援這種寫法,直接報錯叫你寫成babel-loader
3.loader: './loader1?num1=1&num2=2'將被處理成{loader: './loader', options: 'num=1&num=2'},以?進行了字串分割,最終處理成規範化裝載機物件
規則集規範化到此結束,有興趣的可以繼續圍觀原始碼的高管方法和建構函式
裝載機
接下來算是番外,討論各種裝載機如何讀取我們配置的物件。
**屬性在的WebPack的傳遞與處理選項**
首先一個裝載機就是簡單的匯出一個函式即可,比如上面舉例用到的
loader1.js: module.exports = function (content){ console.log(this) console.log(content) return content }
這個函式里面的這個被繫結到一個loaderContext(loader上下文)中,官方api:loader API。
直接把這個loader1.js加入到配置檔案webpack.config.js裡面即可,在編譯時他就會列印出一些東西。
簡單而言,就是在裝載機中,可以我們透過this.query來訪問到規範化裝載機物件options屬性。比如{loader: './loader1.js', options: 'num1=1&num=2'},那麼this.query === '?num1=1&num=2'。
問題來了,這個問號哪裡來的如果它是一個物件?
的WebPack透過裝載機的領先者來執行裝載機,這個問題可以去loader-runner/lib/LoaderRunner.js,在createLoaderObject函式中有這麼一段:
if (obj.options === null) obj.query = ""; else if (obj.options === undefined) obj.query = ""; else if (typeof obj.options === "string") obj.query = "?" + obj.options; else if (obj.ident) { obj.query = "??" + obj.ident; } else if (typeof obj.options === "object" && obj.options.ident) obj.query = "??" + obj.options.ident; else obj.query = "?" + JSON.stringify(obj.options);
在以及runLoaders函式里面的這段:
Object.defineProperty(loaderContext, "query", { enumerable: true, get: function() { var entry = loaderContext.loaders[loaderContext.loaderIndex]; return entry.options && typeof entry.options === "object" ? entry.options : entry.query; } });
總結來說,當選項存在且是一個物件時,那麼this.query就是這個物件;如果選項是一個字串,那麼this.query等於一個問號+這個字串
多數裝載機讀取選項的方法
const loaderUtils=require('loader-utils') module.exports = function (content){ console.log(loaderUtils.getOptions(this)) return content }
藉助架utils的讀取那麼接下來走進loaderUtils.getOptions看看:
const query = loaderContext.query; if (typeof query === 'string' && query !== '') { return parseQuery(loaderContext.query); } if (!query || typeof query !== 'object') { return null; } return query;
這裡只複製了關鍵程式碼,它主要是做一些簡單判斷,對字串的核心轉換在parseQuery上,接著看:
const JSON5 = require('json5'); function parseQuery(query) { if (query.substr(0, 1) !== '?') { throw new Error( "A valid query string passed to parseQuery should begin with '?'" ); } query = query.substr(1); if (!query) { return {}; } if (query.substr(0, 1) === '{' && query.substr(-1) === '}') { return JSON5.parse(query); } const queryArgs = query.split(/[,&]/g); const result = {}; queryArgs.forEach((arg) => { const idx = arg.indexOf('='); if (idx >= 0) { let name = arg.substr(0, idx); let value = decodeURIComponent(arg.substr(idx + 1)); if (specialValues.hasOwnProperty(value)) { value = specialValues[value]; } if (name.substr(-2) === '[]') { name = decodeURIComponent(name.substr(0, name.length - 2)); if (!Array.isArray(result[name])) { result[name] = []; } result[name].push(value); } else { name = decodeURIComponent(name); result[name] = value; } } else { if (arg.substr(0, 1) === '-') { result[decodeURIComponent(arg.substr(1))] = false; } else if (arg.substr(0, 1) === '+') { result[decodeURIComponent(arg.substr(1))] = true; } else { result[decodeURIComponent(arg)] = true; } } }); return result; }
使用了json5庫,以及自己的一套引數的轉換。
總結來說,只要你能確保自己使用的裝載器是透過loader-utils來獲取選項物件的,那麼你可以直接給選項寫成如下字串(inline loader中常用,如import 'loader1?a=1&b=2!./a.js'):
options: "{a: '1', b: '2'}" // 非json,是json5格式字串,略有出入,請右轉百度 options: "list[]=1&list=2[]&a=1&b=2" // http請求中常見的url引數部分
更多示例可在的WebPack /架utils的中檢視
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69946034/viewspace-2658246/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- webpack-loader詳解Web
- webpack系列之四loader詳解1Web
- webpack系列之四loader詳解3Web
- webpack系列之四loader詳解2Web
- webpack3–loader全解析Web
- Webpack解讀之loaderWeb
- Webpack + gulp + babel-loader 配置踩坑WebBabel
- P11.6-webpack配置less-loaderWeb
- webpack(2)——配置項詳解Web
- webpack基礎配置與css相關loaderWebCSS
- webpack-loaderWeb
- webpack系列-loaderWeb
- webpack loader的"套路"Web
- webpack loader的”套路”Web
- react中webpack.config.js配置lessless-loader lessReactWebJS
- 乾貨!擼一個webpack外掛(內含tapable詳解+webpack流程)Web
- [Webpack] 核心概念、基礎配置、常用loader和常用外掛Web
- 如何開發webpack loaderWeb
- webpack4配置詳解之慢嚼細嚥Web
- Webpack(含 4)配置詳解——關注細節Web
- webpack詳解Web
- 18張圖,詳解SpringBoot解析yml全流程Spring Boot
- webpack loader和plugin編寫WebPlugin
- webpack 入門之 loader 案例Web
- webpack 圖片處理 loaderWeb
- 初探webpack之編寫loaderWeb
- webpack4配置詳解之常用外掛分享Web
- 【webpack進階】你真的掌握了loader麼?- loader十問Web
- 手寫webpack系列一:瞭解認識loader-utilsWeb
- webpack loader 的執行過程Web
- webpack入口、出口、模式、loader、外掛Web模式
- Vue-cli升級webpack4以及各種loader升級配置VueWeb
- apache伺服器全域性配置詳解(全)Apache伺服器
- Webpack Tapable原理詳解Web
- Webpack中publicPath詳解Web
- webpack4入門筆記——loaderWeb筆記
- 從0實現一個webpack loaderWeb
- RPA機器人資料抓取典型案例全流程詳解機器人