webpack打包bundle檔案解析

wdapp發表於2020-02-22

一、一個入口,一個檔案

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();
/***/ })
/******/ ]);
複製程式碼

webpack打包bundle檔案解析

整體分析 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('

Hello World

');,最後執行document.write('

Hello Webpack

');。至此,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>");
/***/ })
/******/ ]);
複製程式碼

webpack打包bundle檔案解析

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

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

webpack打包bundle檔案解析

四、總結

  • 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分析得到所有必須模組併合並;提供讓這些模組有序執行的環境。

相關文章