webpack模塊化原理-commonjs

咱們都知道,webpack做爲一個構建工具,解決了前端代碼缺乏模塊化能力的問題。咱們寫的代碼,通過webpack構建和包裝以後,可以在瀏覽器以模塊化的方式運行。這些能力,都是由於webpack對咱們的代碼進行了一層包裝,本文就以webpack生成的代碼入手,分析webpack是如何實現模塊化的。css

PS: webpack的模塊不只指js,包括css、圖片等資源均可以以模塊看待,但本文只關注js。前端

準備

首先咱們建立一個簡單入口模塊index.js和一個依賴模塊bar.js:node

//index.js
'use strict';
var bar = require('./bar');
function foo() {
    return bar.bar();
}
//bar.js
'use strict';
exports.bar = function () {
    return 1;
}

webpack配置以下:webpack

var path = require("path");
module.exports = {
    entry: path.join(__dirname, 'index.js'),
    output: {
        path: path.join(__dirname, 'outs'),
        filename: 'index.js'
    },
};

這是一個最簡單的配置,只指定了模塊入口和輸出路徑,但已經知足了咱們的要求。web

在根目錄下執行webpack,獲得通過webpack打包的代碼以下(去掉了沒必要要的註釋):segmentfault

(function(modules) { // webpackBootstrap
    // The module cache
    var installedModules = {};
    // The require function
    function __webpack_require__(moduleId) {
        // Check if module is in cache
        if(installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        // Create a new module (and put it into the cache)
        var module = installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}
        };
        // Execute the module function
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
        // Flag the module as loaded
        module.l = true;
        // Return the exports of the module
        return module.exports;
    }
    // expose the modules object (__webpack_modules__)
    __webpack_require__.m = modules;
    // expose the module cache
    __webpack_require__.c = installedModules;
    // define getter function for harmony exports
    __webpack_require__.d = function(exports, name, getter) {
        if(!__webpack_require__.o(exports, name)) {
            Object.defineProperty(exports, name, {
                configurable: false,
                enumerable: true,
                get: getter
            });
        }
    };
    // getDefaultExport function for compatibility with non-harmony modules
    __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_public_path__
    __webpack_require__.p = "";
    // Load entry module and return exports
    return __webpack_require__(__webpack_require__.s = 0);
})
/************************************************************************/
([
/* 0 */
(function(module, exports, __webpack_require__) {

"use strict";

var bar = __webpack_require__(1);
bar.bar();

}),
/* 1 */
(function(module, exports, __webpack_require__) {

"use strict";

exports.bar = function () {
    return 1;
}

})
]);

分析

上面webpack打包的代碼,總體能夠簡化成下面的結構:數組

(function (modules) {/* 省略函數內容 */})
([
function (module, exports, __webpack_require__) {
    /* 模塊index.js的代碼 */
},
function (module, exports, __webpack_require__) {
    /* 模塊bar.js的代碼 */
}
]);

能夠看到,整個打包生成的代碼是一個IIFE(當即執行函數),函數內容咱們待會看,咱們先來分析函數的參數。瀏覽器

函數參數是咱們寫的各個模塊組成的數組,只不過咱們的代碼,被webpack包裝在了一個函數的內部,也就是說咱們的模塊,在這裏就是一個函數。爲何要這樣作,是由於瀏覽器自己不支持模塊化,那麼webpack就用函數做用域來hack模塊化的效果。緩存

若是你debug過node代碼,你會發現同樣的hack方式,node中的模塊也是函數,跟模塊相關的參數exportsrequire,或者其餘參數__filename__dirname等都是經過函數傳值做爲模塊中的變量,模塊與外部模塊的訪問就是經過這些參數進行的,固然這對開發者來講是透明的。模塊化

一樣的方式,webpack也控制了模塊的moduleexportsrequire,那麼咱們就看看webpack是如何實現這些功能的。

下面是摘取的函數內容,並添加了一些註釋:

// 一、模塊緩存對象
var installedModules = {};
// 二、webpack實現的require
function __webpack_require__(moduleId) {
    // 三、判斷是否已緩存模塊
    if(installedModules[moduleId]) {
        return installedModules[moduleId].exports;
    }
    // 四、緩存模塊
    var module = installedModules[moduleId] = {
        i: moduleId,
        l: false,
        exports: {}
    };
    // 五、調用模塊函數
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    // 六、標記模塊爲已加載
    module.l = true;
    // 七、返回module.exports
    return module.exports;
}
// 八、require第一個模塊
return __webpack_require__(__webpack_require__.s = 0);

模塊數組做爲參數傳入IIFE函數後,IIFE作了一些初始化工做:

  1. IIFE首先定義了installedModules ,這個變量被用來緩存已加載的模塊。
  2. 定義了__webpack_require__ 這個函數,函數參數爲模塊的id。這個函數用來實現模塊的require。
  3. __webpack_require__ 函數首先會檢查是否緩存了已加載的模塊,若是有則直接返回緩存模塊的exports
  4. 若是沒有緩存,也就是第一次加載,則首先初始化模塊,並將模塊進行緩存。
  5. 而後調用模塊函數,也就是前面webpack對咱們的模塊的包裝函數,將modulemodule.exports__webpack_require__做爲參數傳入。注意這裏作了一個動態綁定,將模塊函數的調用對象綁定爲module.exports,這是爲了保證在模塊中的this指向當前模塊。
  6. 調用完成後,模塊標記爲已加載。
  7. 返回模塊exports的內容。
  8. 利用前面定義的__webpack_require__ 函數,require第0個模塊,也就是入口模塊。

require入口模塊時,入口模塊會收到收到三個參數,下面是入口模塊代碼:

function(module, exports, __webpack_require__) {
    "use strict";
    var bar = __webpack_require__(1);
    bar.bar();
}

webpack傳入的第一個參數module是當前緩存的模塊,包含當前模塊的信息和exports;第二個參數exportsmodule.exports的引用,這也符合commonjs的規範;第三個__webpack_require__ 則是require的實現。

在咱們的模塊中,就能夠對外使用module.exportsexports進行導出,使用__webpack_require__導入須要的模塊,代碼跟commonjs徹底同樣。

這樣,就完成了對第一個模塊的require,而後第一個模塊會根據本身對其餘模塊的require,依次加載其餘模塊,最終造成一個依賴網狀結構。webpack管理着這些模塊的緩存,若是一個模塊被require屢次,那麼只會有一次加載過程,而返回的是緩存的內容,這也是commonjs的規範。

結論

到這裏,webpack就hack了commonjs代碼。

原理仍是很簡單的,其實就是實現exportsrequire,而後自動加載入口模塊,控制緩存模塊,that's all。

細心的你必定會發現,文章到這裏只介紹了webpack對commonjs的實現,那麼ES6 module是如何實現的呢?
歡迎閱讀本系列第二篇《webpack模塊化原理-ES6 module》

相關文章
相關標籤/搜索