咱們都知道,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中的模塊也是函數,跟模塊相關的參數exports
、require
,或者其餘參數__filename
和__dirname
等都是經過函數傳值做爲模塊中的變量,模塊與外部模塊的訪問就是經過這些參數進行的,固然這對開發者來講是透明的。模塊化
一樣的方式,webpack也控制了模塊的module
、exports
和require
,那麼咱們就看看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作了一些初始化工做:
installedModules
,這個變量被用來緩存已加載的模塊。__webpack_require__
這個函數,函數參數爲模塊的id。這個函數用來實現模塊的require。__webpack_require__
函數首先會檢查是否緩存了已加載的模塊,若是有則直接返回緩存模塊的exports
。module
、module.exports
和__webpack_require__
做爲參數傳入。注意這裏作了一個動態綁定,將模塊函數的調用對象綁定爲module.exports
,這是爲了保證在模塊中的this指向當前模塊。exports
的內容。__webpack_require__
函數,require第0個模塊,也就是入口模塊。require入口模塊時,入口模塊會收到收到三個參數,下面是入口模塊代碼:
function(module, exports, __webpack_require__) { "use strict"; var bar = __webpack_require__(1); bar.bar(); }
webpack傳入的第一個參數module
是當前緩存的模塊,包含當前模塊的信息和exports
;第二個參數exports
是module.exports
的引用,這也符合commonjs的規範;第三個__webpack_require__
則是require
的實現。
在咱們的模塊中,就能夠對外使用module.exports
或exports
進行導出,使用__webpack_require__
導入須要的模塊,代碼跟commonjs徹底同樣。
這樣,就完成了對第一個模塊的require,而後第一個模塊會根據本身對其餘模塊的require,依次加載其餘模塊,最終造成一個依賴網狀結構。webpack管理着這些模塊的緩存,若是一個模塊被require屢次,那麼只會有一次加載過程,而返回的是緩存的內容,這也是commonjs的規範。
到這裏,webpack就hack了commonjs代碼。
原理仍是很簡單的,其實就是實現exports
和require
,而後自動加載入口模塊,控制緩存模塊,that's all。
細心的你必定會發現,文章到這裏只介紹了webpack對commonjs的實現,那麼ES6 module是如何實現的呢?
歡迎閱讀本系列第二篇《webpack模塊化原理-ES6 module》。