webpack打包bundle文件解析

### 一、一個入口,一個文件
webpack.config.js

```
module.exports = {
  entry: './main.js',  // 一個入口
  output: {
    filename: 'bundle.js'
  }
};
```

main.js
```
document.write('<h1>Hello World</h1>');  // 一個文件
```

bundle.js
```
/******/ (function(modules) { // webpackBootstrap
/******/    // module緩存對象
/******/    var installedModules = {};
/******/
/******/    // require函數
/******/    function __webpack_require__(moduleId) {
/******/
/******/        // 檢查module是否在緩存當中,若在,則返回exports對象
/******/        if(installedModules[moduleId]) {
/******/            return installedModules[moduleId].exports;
/******/        }
/******/        // 若不在,則以moduleId爲key創建一個module,並放入緩存當中
/******/        var module = installedModules[moduleId] = {
/******/            i: moduleId,
/******/            l: false,
/******/            exports: {}
/******/        };
/******/
/******/        // 執行module函數
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/        // 標誌module已經加載
/******/        module.l = true;
/******/
/******/        // 返回module的導出模塊
/******/        return module.exports;
/******/    }
/******/
/******/
/******/    // 暴露modules對象(__webpack_modules__)
/******/    __webpack_require__.m = modules;
/******/
/******/    // 暴露module緩存
/******/    __webpack_require__.c = installedModules;
/******/
/******/    // 認證和諧導入模塊具有正確的上下文的函數
/******/    __webpack_require__.i = function(value) { return value; };
/******/
/******/    // 爲和諧導入模塊定義getter函數
/******/    __webpack_require__.d = function(exports, name, getter) {
/******/        if(!__webpack_require__.o(exports, name)) {
/******/            Object.defineProperty(exports, name, {
/******/                configurable: false,
/******/                enumerable: true,
/******/                get: getter
/******/            });
/******/        }
/******/    };
/******/
/******/    // 兼容非和諧模塊的getDefaultExport函數
/******/    __webpack_require__.n = function(module) {
/******/        var getter = module && module.__esModule ?
/******/            function getDefault() { return module['default']; } :
/******/            function getModuleExports() { return module; };
/******/        __webpack_require__.d(getter, 'a', getter);
/******/        return getter;
/******/    };
/******/
/******/    // Object.prototype.hasOwnProperty.call
/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/    // 設置webpack公共路徑__webpack_public_path__
/******/    __webpack_require__.p = "";
/******/
/******/    // 讀取入口模塊,返回exports導出
/******/    return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {  // 模塊ID爲0
document.write('<h1>Hello World</h1>');
/***/ })
/******/ ]);
```

整體分析
整個的bundle.js是一個立即執行函數表達式(IIFE),傳入的參數modules是一個數組,數組的每一項都是一個匿名函數,代表一個模塊。在這裏,數組的第一個參數是一個function,裏面的內容就是原先main.js裏面的內容。

IIFE裏面存在一個閉包,_webpack_require__是模塊加載函數,作用是聲明對其他模塊的依賴,並返回exports。參數爲模塊的Id(每一個模塊都有一個唯一的id),不需要提供模塊的相對路徑,可以省掉模塊標識符的解析過程(準確說,webpack是把require模塊的解析過程提前到了構建期),從而可以獲得更好的運行性能。
執行modules函數的是:

```
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
```

通過借用call來使函數的this始終爲module本身,參數__webpack_require__是爲了讓modules有加載其他模塊的能力。

程序流程

bundle通過__webpack_require__(__webpack_require__.s = 0)來啓動整個程序。首先看看緩存中有沒有ID爲0的模塊,若存在則返回緩存中的exports暴露出來的對象;若不存在,則新建module對象,並放入緩存當中。此時,module對象和該模塊的緩存對象installedModules[moduleId]還沒有數據,所以要執行該模塊來返回具體require其他模塊的數據。傳入的context是module.exports(等於installedModules[moduleId].exports)。

### 二、一個入口,多個文件

webpack.config.js

```
module.exports = {
    entry: './main1.js',
    output: {
        filename: 'bundle.js'
    }
}
```

main1.js

```
var main = require('./main2.js')
document.write('<h1>Hello World</h1>');
main.webpack();
```

main2.js
```
exports.webpack = function() {
    document.write('<h2>Hello Webpack</h2>');
}
```

bundle.js
```
/******/ (function(modules) { // webpackBootstrap
/******/  這部分和上面的一樣
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = 1);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
exports.webpack = function() {
    document.write('<h2>Hello Webpack</h2>');
}
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
var main = __webpack_require__(0)
document.write('<h1>Hello World</h1>');
main.webpack();
/***/ })
/******/ ]);
```

![](https://tva1.sinaimg.cn/large/0082zybply1gc5le7z2rdj30ze09u41a.jpg)

**整體分析**
webpack在打包的時候會分析模塊間的依賴關係,對導入導出模塊相關的內容做一個替換。比方說在main1.js文件中,遇到var main = require('./main2.js')就會轉化爲var __WEBPACK_IMPORTED_MODULE_0__main__ = __webpack_require__(0)。
由於有兩個文件,所以IIFE的參數爲長度是2的數組,並且按照require的順序排列。

**程序的流程**
bundle通過__webpack_require__(__webpack_require__.s = 1)來啓動整個程序。與第一種情況不同的是,這裏首先檢查緩存中是否有ID爲1的模塊,因爲main1.js依賴於main2.js,所以在main.js中調用模塊加載函數。當執行到var main = __webpack_require__(0),會執行module[0].call(這裏的call爲了確保每個module中的this指向的是module本身),然後執行document.write('<h1>Hello World</h1>');,最後執行document.write('<h2>Hello Webpack</h2>');。至此,__webpack_require__(1)執行完畢,這是一個遞歸的過程。

### 三、兩個入口,兩個出口文件

main1包含inner1;main2包含inner1和inner2。

webpack.config.js


```
module.exports = {
  entry: {
    bundle1: './main1.js',
    bundle2: './main2.js'
  },
  output: {
    filename: '[name].js'
  }
};
```
main1.js
```
var inner1 = require('./inner1.js');
inner1.inner1();
document.write("<h1>我是main1</h1>")
```
main2.js
```
var inner1 = require('./inner1.js');
var inner2 = require('./inner2.js');
inner1.inner1();
inner2.inner2();
document.write("<h1>我是main2</h1>");
```
bundle1.js
```
/******/ (function(modules) { // webpackBootstrap
/******/ 這部分和上面的一樣
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = 2);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
exports.inner1 = function() {
    document.write('<h1>我是inner1</h1>');
}
/***/ }),
/* 1 */,
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
var inner1 = __webpack_require__(0);
inner1.inner1();
document.write("<h1>我是main1</h1>")
/***/ })
/******/ ]);
```
bundle2.js

```
/******/ (function(modules) { // webpackBootstrap
/******/ 這部分和上面的一樣
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = 3);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
exports.inner1 = function() {
    document.write('<h1>我是inner1</h1>');
}
/***/ }),
/* 1 */
/***/ (function(module, exports) {
exports.inner2 = function() {
    document.write('<h1>我是inner2</h1>');
}
/***/ }),
/* 2 */,
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
var inner1 = __webpack_require__(0);
var inner2 = __webpack_require__(1);
inner1.inner1();
inner2.inner2();
document.write("<h1>我是main2</h1>");
/***/ })
/******/ ]);
```

![](https://tva1.sinaimg.cn/large/0082zybply1gc5lgeeod0j312a0iaqa9.jpg)

整體分析
對於多入口文件的情況,分別獨立執行單個入口的情況,每個入口文件互不干擾。
從上面可以看到,兩個入口文件main1.js和main2.js的module id都是0,所以可以知道每個入口文件對應的module id都是0。又因爲每個module id都是全局唯一的,所以在main1中沒有1;在main2中沒有2。

靜態分析打包是事先生成chunk,inner1.js文件被重複包含了,如果需要消除模塊冗餘,可以通過CommonsChunkPlugin插件來對公共依賴模塊進行提取。

![](https://tva1.sinaimg.cn/large/0082zybply1gc5lhu7r58j313a0f0q61.jpg)

四、總結
- bundle.js文件是一個立即執行函數表達式,傳入參數是一個數組modules。真正執行module的是modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);。
- modules數組用來保存模塊初始化函數,裏面存儲着真正用到的模塊內容以及一些id信息。
- installedModules對象用來保存module緩存對象,方便其他模塊使用。
- __webpack_require__模塊加載函數,require時得到的對象,參數爲模塊id。
- installedModules[moduleId].exports === module.exports === __webpack_exports__
- 總的來說,webpack分析得到所有必須模塊併合並;提供讓這些模塊有序執行的環境。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章