.2-淺析webpack源碼之打包后文件


  先不進源碼,分析一下打包后的文件,來一張圖:

  

  首先創建兩個JS文件,內容如下:

// config.js
module.exports = {
    entry: './input.js',
    output: {
        filename: 'output.js'
    }
}
// input.js
console.log('input')

  分別為配置文件和入口JS文件,內容弄個簡單的。

  接下來在當前目錄執行webpack --config config.js,會輸出一個output.js,簡化后內容如下:

(function(modules) { // webpackBootstrap
    // 模塊緩存對象
    var installedModules = {};

    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')
    })
]);

  可以看到,這是一個IIFE,可以利用閉包來對模塊進行緩存以及其余便利性的功能。

  整個JS可以分為三塊:

1、傳入包含所有模塊的數組,每一個模塊有唯一的標識ID

2、模塊輸出函數

3、在函數上掛載多個屬性

 

__webpack_require__
  
  函數代碼如下:
// 傳入入口文件的模塊ID
function __webpack_require__(moduleId) {
    // 檢測緩存
    if (installedModules[moduleId]) {
        return installedModules[moduleId].exports;
    }
    // 無緩存時創建新的模塊
    // i => 模塊ID
    // l => 是否被加載
    // exports => 輸出內容
    var module = installedModules[moduleId] = {
        i: moduleId,
        l: false,
        exports: {}
    };
    // 執行模塊函數
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    // 標記模塊已被加載
    module.l = true;
    // 返回模塊輸出
    return module.exports;
}

  入口文件的模塊ID會當成參數傳入該函數,首先會進行緩存檢測,沒找到會生成一個新的模塊對象,然后執行入口模塊,將該模塊標記為已加載后返回對應的exports。

  由於被打包的文件內容十分簡單,所以只會執行console語句。

 

__webpack_require__屬性掛載

 

  由於函數也是對象,所以可以在上面添加屬性,具體代碼如下:

// 模塊數組
__webpack_require__.m = modules;
// 模塊緩存
__webpack_require__.c = installedModules;
// 屬性判斷
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
// 如果不存在對應屬性 定義輸出的getter
__webpack_require__.d = function(exports, name, getter) {
    if (!__webpack_require__.o(exports, name)) {
        Object.defineProperty(exports, name, {
            configurable: false,
            enumerable: true,
            get: getter
        });
    }
};
// 默認模塊
__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;
};
// __webpack_public_path__
__webpack_require__.p = "";

  掛載了6個屬性,模塊組、緩存對象、屬性檢測方法、getter定義、默認模塊、公共路徑.

  這里的getter稍微解釋一下,module的引入模塊有兩種輸出模式,一種是webpack定義的module.exports,另一種是ES定義的export default,這里就是判斷是哪一種方式。默認情況下當然是module.exports獲取模塊輸出內容。但是用ES的方式,模塊輸出會被包裹在一個default對象,此時需要用module['default']來獲取。

 

  接下來弄復雜一些再觀察,配置文件不變,改變入口JS並添加一個依賴JS:

// require,js
module.exports = function require() {
    console.log('require');
}
// input.js
import rq from "./require.js"
rq();

  簡單的實現一個JS加載另外一個JS,接下來執行打包。

  打包后主處理函數不會變,傳入的模塊數組發生了變化,代碼如下:

(function(modules) {
    // ...
    return __webpack_require__(__webpack_require__.s = 0);
})
([
    /* 0 */
    (function(module, __webpack_exports__, __webpack_require__) {
        "use strict";
        Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
        /* harmony import */
        var __WEBPACK_IMPORTED_MODULE_0__require_js__ = __webpack_require__(1);
        /* harmony import */
        var __WEBPACK_IMPORTED_MODULE_0__require_js___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__require_js__);
        __WEBPACK_IMPORTED_MODULE_0__require_js___default()()
    }),
    /* 1 */
    (function(module, exports) {
        module.exports = function require() {
            console.log('require');
        }
    })
]);

  這里的模塊數組出現了2個,但是入口的JS仍然只有1個,ID為0,被依賴的ID為1。

  可以注意到,module、exports分別對應函數的2個參數,而在主處理函數中exports就是輸出的模塊內容,所以這就是為什么打包函數都是通過module.exports來輸出模塊內容。

  這里仔細看一下ID為0的入口模塊:

/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    // 定義該模塊的exports的__esModule屬性為true
    // 該屬性影響__webpack_require__.n的輸出
    Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
    // 獲取ID為1的模塊
    var __WEBPACK_IMPORTED_MODULE_0__require_js__ = __webpack_require__(1);
    // 這里傳入了模塊1並調用了__webpack_require__.n
    // 模塊1並沒有被標記__esModule
    // 返回函數function getModuleExports() { return module; };
    var __WEBPACK_IMPORTED_MODULE_0__require_js___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__require_js__);
    // 注意這里的module並不是模塊的module對象 而是傳入函數n的參數
    // 所以這里的調用相當於__webpack_require__(1)() => (function require() {console.log('require');})()
    __WEBPACK_IMPORTED_MODULE_0__require_js___default()()
}),

  由於案例比較簡單,所以雖然變量名很長,但是也非常好懂,引入依賴JS后,判斷模塊的輸出模式,然后執行輸出代碼。

 

  暫時先寫這么多。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM