摘要:函數首先會檢查是否緩存了已加載的模塊,如果有則直接返回緩存模塊的。調用完成后,模塊標記為已加載。返回模塊的內容。細心的你一定會發現,文章到這里只介紹了對的實現,那么是如何實現的呢歡迎閱讀本系列第二篇模塊化原理。
我們都知道,webpack作為一個構建工具,解決了前端代碼缺少模塊化能力的問題。我們寫的代碼,經過webpack構建和包裝之后,能夠在瀏覽器以模塊化的方式運行。這些能力,都是因為webpack對我們的代碼進行了一層包裝,本文就以webpack生成的代碼入手,分析webpack是如何實現模塊化的。
PS: webpack的模塊不僅指js,包括css、圖片等資源都可以以模塊看待,但本文只關注js。
準備首先我們創建一個簡單入口模塊index.js和一個依賴模塊bar.js:
//index.js "use strict"; var bar = require("./bar"); function foo() { return bar.bar(); }
//bar.js "use strict"; exports.bar = function () { return 1; }
webpack配置如下:
var path = require("path"); module.exports = { entry: path.join(__dirname, "index.js"), output: { path: path.join(__dirname, "outs"), filename: "index.js" }, };
這是一個最簡單的配置,只指定了模塊入口和輸出路徑,但已經滿足了我們的要求。
在根目錄下執行webpack,得到經過webpack打包的代碼如下(去掉了不必要的注釋):
(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是如何實現這些功能的。
下面是摘取的函數內容,并添加了一些注釋:
// 1、模塊緩存對象 var installedModules = {}; // 2、webpack實現的require function __webpack_require__(moduleId) { // 3、判斷是否已緩存模塊 if(installedModules[moduleId]) { return installedModules[moduleId].exports; } // 4、緩存模塊 var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // 5、調用模塊函數 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // 6、標記模塊為已加載 module.l = true; // 7、返回module.exports return module.exports; } // 8、require第一個模塊 return __webpack_require__(__webpack_require__.s = 0);
模塊數組作為參數傳入IIFE函數后,IIFE做了一些初始化工作:
IIFE首先定義了installedModules ,這個變量被用來緩存已加載的模塊。
定義了__webpack_require__ 這個函數,函數參數為模塊的id。這個函數用來實現模塊的require。
__webpack_require__ 函數首先會檢查是否緩存了已加載的模塊,如果有則直接返回緩存模塊的exports。
如果沒有緩存,也就是第一次加載,則首先初始化模塊,并將模塊進行緩存。
然后調用模塊函數,也就是前面webpack對我們的模塊的包裝函數,將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》。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/84326.html
摘要:每一個模塊的源代碼都會被組織在一個立即執行的函數里。接下來看的生成代碼可以看到,的源代碼中關于引入的模塊的部分做了修改,因為無論是,或是風格的,都無法被解釋器直接執行,它需要依賴模塊管理系統,把這些抽象的關鍵詞具體化。 現在前端用Webpack打包JS和其它文件已經是主流了,加上Node的流行,使得前端的工程方式和后端越來越像。所有的東西都模塊化,最后統一編譯。Webpack因為版本的...
摘要:所以通常情況下當你的庫需要依賴到例如,這樣的通用模塊時,我們可以不將它打包進,而是在的配置中聲明這就是在告訴請不要將這個模塊注入編譯后的文件里,對于我源代碼里出現的任何這個模塊的語句,請將它保留。 這篇文章討論Webpack打包library時經常需要用到的一個選項external,它用于避免將一些很通用的模塊打包進你發布的library里,而是選擇把它們聲明成external的模塊,...
摘要:所以你編譯后的文件實際上應當只輸出,這就需要在配置里用來控制這樣上面的模塊加載函數會在返回值后面加一個,這樣就只返回的部分。 之前一篇文章分析了Webpack打包JS模塊的基本原理,所介紹的案例是最常見的一種情況,即多個JS模塊和一個入口模塊,打包成一個bundle文件,可以直接被瀏覽器或者其它JavaScript引擎執行,相當于直接編譯生成一個完整的可執行的文件。不過還有一種很常見的...
摘要:打包出來的代碼快照如下,注意看注釋中的時序實際上,的處理同相差無幾,只是在定義模塊和引入模塊時會去處理標識,從而兼容其在語法上的差異。 前言 隨著 Web 技術的蓬勃發展和依賴的基礎設施日益完善,前端領域逐漸從瀏覽器擴展至服務端(Node.js),桌面端(PC、Android、iOS),乃至于物聯網設備(IoT),其中 JavaScript 承載著這些應用程序的核心部分,隨著其規模化和...
閱讀 1963·2021-10-25 09:48
閱讀 2782·2021-09-22 14:59
閱讀 1755·2019-08-29 16:52
閱讀 854·2019-08-29 16:07
閱讀 2298·2019-08-29 12:38
閱讀 1751·2019-08-26 13:23
閱讀 875·2019-08-26 11:49
閱讀 3264·2019-08-26 10:56