Webpack4.0各個擊破(5)module篇
一. 模組化亂燉
指令碼合併是基於模組化規範的,javascript
模組化是一個非常混亂的話題,各種【*MD】規範亂飛還要外加一堆【*.js】的規範實現。現代化前端專案多基於框架進行開發,較為流行的框架內部基本已經統一遵循ES6
的模組化標準,儘管支援度不一,但通過構建工具可以解決瀏覽器支援滯後的問題;基於nodejs
的服務端專案原生支援CommonJs
標準;而開發中引入的一些工具類的庫,熱門的工具類庫為了能同時相容瀏覽器和node環境,通常會使用UMD
標準(Universal Module Definition) 來實現模組化,對UMD
正規化不瞭解的讀者可以先閱讀《javascript基礎修煉(4)——UMD規範的程式碼推演》
二. webpack與模組化
webpack
預設支援的是CommonJs
規範,畢竟它是nodejs
支援的模組管理方式,而沒有node
哪來的webpack
。但同時為了擴充套件其使用場景,webpack
在版本迭代中也加入了對ES harmony
規範和AMD
規範的相容。
webpack如何識別CommonJs模組
webpack
打包後輸出檔案的基本結構是下面這個樣子的:
(function(modules) { // webpackBootstrap // 模組快取物件 var installedModules = {}; // webpack內部的模組引用函式 function __webpack_require__(moduleId) { // 載入入口JS // 輸出 return module.exports; } // 掛載模組陣列 __webpack_require__.m = modules; // ... // 在__webpack_require__掛載多個屬性 // 傳入入口JS模組ID執行函式並輸出模組 return __webpack_require__(__webpack_require__.s = 0); }); // 包含所有模組的陣列 ([ /* id為0 */ (function(module, exports) { console.log('1') }) ]);
簡化以後實際上就是一個自執行函式:
(function(modules){
return __webpack_require__(0);
}([Module0,Module1...]))
可以看到__webpack_reqruie__( )
這個方法的引數就是模組的唯一ID標識,返回值就是module.exports
,所以webpack
對於CommonJs
規範是原生支援的。
webpack如何識別ES Harmony模組
對於ES Harmony
規範不熟悉的可以檢視《ES6 Module語法》一文。
先使用import
命令載入一個CommonJs
規範匯出的模組,檢視打包後的程式碼可以看到模組引用的部分被轉換成了下面這樣:
__webpack_require__.r(__webpack_exports__);
/* harmony import */
var _components_component10k_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./components/component10k.js");
/* harmony import */
var _components_component10k_js__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(_components_component10k_js__WEBPACK_IMPORTED_MODULE_0__);
簡化一下再來看:
__webpack_require__.r(__webpack_exports__);
var a = __webpack_require__("./components/component10k.js");
var b = __webpack_require__.n(a);
這裡涉及到兩個工具函式:
這個方法是給模組的exports
物件加上ES Harmony
規範的標記,如果支援Symbol
物件,則為exports
物件的Symbol.toStringTag屬性賦值Module,這樣做的結果是exports
物件在呼叫toString方法時會返回'Module'(筆者並沒有查到這種寫法的緣由);如果不支援Symbol
物件,則將exports.__esModule
賦值為true。
另一個工具函式是:
傳入了一個模組,返回一個getter方法,此處是一個高階函式的應用,實現的功能是當模組的__esModule
屬性為真時,返回一個getDefault( )
方法,否則返回getModuleExports( )
方法.
回過頭再來看上面的簡化程式碼:
// 新增ES Harmony規範模組標記
__webpack_require__.r(__webpack_exports__);
// a實際上得到了模組通過module.exports輸出的物件
var a = __webpack_require__("./components/component10k.js");
// 根據a的模組化規範型別返回不同的getter函式,當getter函式執行時才會真正得到模組物件
var b = __webpack_require__.n(a);
總結一下,
webpack
所做的處理相當於對模組增加了代理,如果被載入模組符合ES Harmony
規範,則返回module['default']
,否則返回module
。這裡的module泛指模組輸出的物件。
再使用import
載入一個使用export
語法輸出的ES Harmony
模組,檢視打包結果中的模組檔案可以看到:
//component10k.js模組檔案在main.bundle.js中的內容
__webpack_require__.r(__webpack_exports__);
__webpack_exports__["default"] = (function(){
Array.from('component10k');
})
可以看到輸出的內容直接繫結到了輸出模組的default屬性上,由於這個模組被打上了__esModule
的標記,所以引用它的模組會通過module['default']來取用其內容,也就正好命中了模組的輸出內容。
webpack如何識別AMD模組
我們將component10k.js
模組改為用AMD
規範定義:
define(function(){
console.log('test');
})
檢視經過webpack
打包後,這個模組變成了如下的樣子:
var __WEBPACK_AMD_DEFINE_RESULT__;
!(__WEBPACK_AMD_DEFINE_RESULT__ = (function(){
console.log('test');
}).call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
簡化一下:
var result;
!(result=(function(){}).call(...),result!==undefined && module.exports = result);
抽象一下:
var result;
!(expression1,expression2 && expression3)
這裡涉及的javascript的基本知識較多,逗號表示式的優先順序最低,所以最後參與運算,逗號表示式會從左到右依次執行語句,並返回最後一個表示式的結果,&&為短路運算語法,即前一個條件成立時才計算後面的表示式,賦值語句執行完後會將所賦的值返回。此處外層的!(expression )
語法起了什麼作用,筆者也沒看懂,希望瞭解的讀者多多指教。
所以,
webpack
對於AMD
模組的處理,實際上是加了一層封裝,將模組執行的結果掛載到了webpack
模組的module.exports物件上。
作者:大史不說話
連結:Webpack4.0各個擊破(5)module篇
來源:部落格園
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。