webpack模塊化原理-ES module

上一篇文章介紹了webpack對commonjs模塊的支持(若是你還沒讀過,建議你先閱讀),這篇文章來探究一下,webpack是如何支持es模塊的。webpack

準備

咱們依然寫兩個文件,m.js文件用es模塊的方式export一個default函數和一個foo函數,index.js import該模塊,具體代碼以下:es6

// m.js
'use strict';
export default function bar () {
    return 1;
};
export function foo () {
    return 2;
}
// index.js
'use strict';
import bar, {foo} from './m';
bar();
foo();

webpack配置沒有變化,依然以index.js做爲入口:web

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

在根目錄下執行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);
})
([
(function(module, __webpack_exports__, __webpack_require__) {

    "use strict";
    Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
    /* harmony import */
    var __WEBPACK_IMPORTED_MODULE_0__m__ = __webpack_require__(1);

    Object(__WEBPACK_IMPORTED_MODULE_0__m__["a" /* default */])();
    Object(__WEBPACK_IMPORTED_MODULE_0__m__["b" /* foo */])();

}),
(function(module, __webpack_exports__, __webpack_require__) {

    "use strict";
    /* harmony export (immutable) */
    __webpack_exports__["a"] = bar;
    /* harmony export (immutable) */
    __webpack_exports__["b"] = foo;

    function bar () {
        return 1;
    };
    function foo () {
        return 2;
    }

})
]);

分析

上一篇文章已經分析過了,webpack生成的代碼是一個IIFE,這個IIFE完成一系列初始化工做後,就會經過__webpack_require__(0)啓動入口模塊。模塊化

咱們首先來看m.js模塊是如何實現es的export的,被webpack轉換後的m.js代碼以下:函數

__webpack_exports__["a"] = bar;
__webpack_exports__["b"] = foo;

function bar () {
    return 1;
};
function foo () {
    return 2;
}

其實一眼就能看出來,export default和export都被轉換成了相似於commonjs的exports.xxx,這裏也已經不區分是否是default export了,全部的export對象都是__webpack_exports__的屬性。ui

咱們繼續來看看入口模塊,被webpack轉換後的index.js代碼以下:prototype

Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
var __WEBPACK_IMPORTED_MODULE_0__module__ = __webpack_require__(1);

Object(__WEBPACK_IMPORTED_MODULE_0__m__["a" /* default */])();
Object(__WEBPACK_IMPORTED_MODULE_0__m__["b" /* foo */])();

index模塊首先經過Object.defineProperty__webpack_exports__上添加屬性__esModule ,值爲true,代表這是一個es模塊。在目前的代碼下,這個標記是沒有做用的,至於在什麼狀況下須要判斷模塊是否es模塊,後面會分析。code

而後就是經過__webpack_require__(1)導入m.js模塊,再而後經過module.xxx獲取m.js中export的對應屬性。注意這裏有一個重要的點,就是全部引入的模塊屬性都會用Object()包裝成對象,這是爲了保證像Boolean、String、Number這些基本數據類型轉換成相應的類型對象。對象

commonjs與es6 module混用

咱們前面分析的都是commonjs模塊對commonjs模塊的導入,或者es模塊對es模塊的導入,那麼若是是es模塊對commonjs模塊的導入會是什麼狀況呢,反過來又會如何呢?

其實咱們前面說到的__webpack_exports__. __esModule = true就是針對這種狀況的解決方法。

下面用具體代碼來解釋一下,首先修改m.js和index.js代碼以下:

// m.js
'use strict';
exports.foo = function () {
    return 1;
}
// index.js
'use strict';
import m from './m';
m.foo();

從新執行webpack後生成的代碼以下(只截取IIFE的參數部分):

[
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {

    "use strict";
    Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
    /* harmony import */ 
    var __WEBPACK_IMPORTED_MODULE_0__m__ = __webpack_require__(1);
    /* harmony import */ 
    var __WEBPACK_IMPORTED_MODULE_0__m___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__m__);

    __WEBPACK_IMPORTED_MODULE_0__m___default.a.foo();

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

    "use strict";
    exports.foo = function () {
        return 1;
    }

})
]

m.js轉換後的代碼跟轉換前的代碼基本沒有變化,都是用webpack提供的exports進行模塊導出。可是index.js有一點不一樣,主要是多了一行代碼:

var __WEBPACK_IMPORTED_MODULE_0__m___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__m__);

這段代碼做用是什麼呢,看一下__webpack_require__.n的定義就知道了:

// 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;
};

__webpack_require__.n會判斷module是否爲es模塊,當__esModule爲true的時候,標識module爲es模塊,那麼module.a默認返回module.default,不然返回module

具體實現則是經過 __webpack_require__.d將具體操做綁定到屬性a的getter方法上的。

那麼,當經過es模塊的方式去import一個commonjs規範的模塊時,就會把require獲得的module進行一層包裝,從而兼容兩種狀況。

至於經過commonjs去require一個es模塊的狀況,原理相同,就不過多解釋了。

結論

webpack對於es模塊的實現,也是基於本身實現的__webpack_require__ __webpack_exports__ ,裝換成相似於commonjs的形式。對於es模塊和commonjs混用的狀況,則須要經過__webpack_require__.n的形式作一層包裝來實現。

下一篇webpack模塊化原理-Code Splitting,會繼續來分析webpack是如何經過動態importmodule.ensure實現Code Splitting的。

相關文章
相關標籤/搜索