webpack
做爲前端最火的構建工具,是前端自動化工具鏈最重要的部分,使用門檻較高。本系列是筆者本身的學習記錄,比較基礎,但願經過問題 + 解決方式的模式,之前端構建中遇到的具體需求爲出發點,學習webpack
工具中相應的處理辦法。(本篇中的參數配置及使用方式均基於webpack4.0版本
)javascript
使用webpack
對腳本進行合併是很是方便的,由於webpack
實現了對各類不一樣模塊規範的兼容處理,對前端開發者來講,理解這種實現方式比學習如何配置webpack
更爲重要,本節的內容實用性較低。
html
腳本合併是基於模塊化規範的,javascript
模塊化是一個很是混亂的話題,各類**【*MD】**規範亂飛還要外加一堆【*.js】的規範實現。現代化前端項目多基於框架進行開發,較爲流行的框架內部基本已經統一遵循ES6
的模塊化標準,儘管支持度不一,但經過構建工具能夠解決瀏覽器支持滯後的問題;基於nodejs
的服務端項目原生支持CommonJs
標準;而開發中引入的一些工具類的庫,熱門的工具類庫爲了能同時兼容瀏覽器和node環境,一般會使用UMD
標準(Universal Module Definition) 來實現模塊化,對UMD
範式不瞭解的讀者能夠先閱讀《javascript基礎修煉(4)——UMD規範的代碼推演》一文,甚至有些第三方庫並無遵循任何模塊化方案。若是不借助構建工具,想要對各種方案實現兼容是很是複雜的。前端
webpack
默認支持的是CommonJs
規範,畢竟它是nodejs
支持的模塊管理方式,而沒有node
哪來的webpack
。但同時爲了擴展其使用場景,webpack
在版本迭代中也加入了對ES harmony
規範和AMD
規範的兼容。java
webpack
打包後輸出文件的基本結構是下面這個樣子的:node
(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') }) ]);
簡化之後實際上就是一個自執行函數:webpack
(function(modules){ return __webpack_require__(0); }([Module0,Module1...]))
能夠看到__webpack_reqruie__( )
這個方法的參數就是模塊的惟一ID標識,返回值就是module.exports
,因此webpack
對於CommonJs
規範是原生支持的。web
對於ES Harmony
規範不熟悉的能夠查看《ES6 Module語法》一文。express
先使用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']來取用其內容,也就正好命中了模塊的輸出內容。
咱們將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對象上。