一. 模塊化亂燉

腳本合并是基于模塊化規范的,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篇
來源:博客園
著作權歸作者所有,商業轉載請聯系作者獲得授權,非商業轉載請注明出處,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/242280.html
標籤:JavaScript
下一篇:每天一道面試題
