摘要:函數(shù)首先會(huì)檢查是否緩存了已加載的模塊,如果有則直接返回緩存模塊的。調(diào)用完成后,模塊標(biāo)記為已加載。返回模塊的內(nèi)容。細(xì)心的你一定會(huì)發(fā)現(xiàn),文章到這里只介紹了對(duì)的實(shí)現(xiàn),那么是如何實(shí)現(xiàn)的呢歡迎閱讀本系列第二篇模塊化原理。
我們都知道,webpack作為一個(gè)構(gòu)建工具,解決了前端代碼缺少模塊化能力的問題。我們寫的代碼,經(jīng)過webpack構(gòu)建和包裝之后,能夠在瀏覽器以模塊化的方式運(yùn)行。這些能力,都是因?yàn)閣ebpack對(duì)我們的代碼進(jìn)行了一層包裝,本文就以webpack生成的代碼入手,分析webpack是如何實(shí)現(xiàn)模塊化的。
PS: webpack的模塊不僅指js,包括css、圖片等資源都可以以模塊看待,但本文只關(guān)注js。
準(zhǔn)備首先我們創(chuàng)建一個(gè)簡單入口模塊index.js和一個(gè)依賴模塊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" }, };
這是一個(gè)最簡單的配置,只指定了模塊入口和輸出路徑,但已經(jīng)滿足了我們的要求。
在根目錄下執(zhí)行webpack,得到經(jīng)過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打包的代碼,整體可以簡化成下面的結(jié)構(gòu):
(function (modules) {/* 省略函數(shù)內(nèi)容 */}) ([ function (module, exports, __webpack_require__) { /* 模塊index.js的代碼 */ }, function (module, exports, __webpack_require__) { /* 模塊bar.js的代碼 */ } ]);
可以看到,整個(gè)打包生成的代碼是一個(gè)IIFE(立即執(zhí)行函數(shù)),函數(shù)內(nèi)容我們待會(huì)看,我們先來分析函數(shù)的參數(shù)。
函數(shù)參數(shù)是我們寫的各個(gè)模塊組成的數(shù)組,只不過我們的代碼,被webpack包裝在了一個(gè)函數(shù)的內(nèi)部,也就是說我們的模塊,在這里就是一個(gè)函數(shù)。為什么要這樣做,是因?yàn)闉g覽器本身不支持模塊化,那么webpack就用函數(shù)作用域來hack模塊化的效果。
如果你debug過node代碼,你會(huì)發(fā)現(xiàn)一樣的hack方式,node中的模塊也是函數(shù),跟模塊相關(guān)的參數(shù)exports、require,或者其他參數(shù)__filename和__dirname等都是通過函數(shù)傳值作為模塊中的變量,模塊與外部模塊的訪問就是通過這些參數(shù)進(jìn)行的,當(dāng)然這對(duì)開發(fā)者來說是透明的。
同樣的方式,webpack也控制了模塊的module、exports和require,那么我們就看看webpack是如何實(shí)現(xiàn)這些功能的。
下面是摘取的函數(shù)內(nèi)容,并添加了一些注釋:
// 1、模塊緩存對(duì)象 var installedModules = {}; // 2、webpack實(shí)現(xiàn)的require function __webpack_require__(moduleId) { // 3、判斷是否已緩存模塊 if(installedModules[moduleId]) { return installedModules[moduleId].exports; } // 4、緩存模塊 var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // 5、調(diào)用模塊函數(shù) modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // 6、標(biāo)記模塊為已加載 module.l = true; // 7、返回module.exports return module.exports; } // 8、require第一個(gè)模塊 return __webpack_require__(__webpack_require__.s = 0);
模塊數(shù)組作為參數(shù)傳入IIFE函數(shù)后,IIFE做了一些初始化工作:
IIFE首先定義了installedModules ,這個(gè)變量被用來緩存已加載的模塊。
定義了__webpack_require__ 這個(gè)函數(shù),函數(shù)參數(shù)為模塊的id。這個(gè)函數(shù)用來實(shí)現(xiàn)模塊的require。
__webpack_require__ 函數(shù)首先會(huì)檢查是否緩存了已加載的模塊,如果有則直接返回緩存模塊的exports。
如果沒有緩存,也就是第一次加載,則首先初始化模塊,并將模塊進(jìn)行緩存。
然后調(diào)用模塊函數(shù),也就是前面webpack對(duì)我們的模塊的包裝函數(shù),將module、module.exports和__webpack_require__作為參數(shù)傳入。注意這里做了一個(gè)動(dòng)態(tài)綁定,將模塊函數(shù)的調(diào)用對(duì)象綁定為module.exports,這是為了保證在模塊中的this指向當(dāng)前模塊。
調(diào)用完成后,模塊標(biāo)記為已加載。
返回模塊exports的內(nèi)容。
利用前面定義的__webpack_require__ 函數(shù),require第0個(gè)模塊,也就是入口模塊。
require入口模塊時(shí),入口模塊會(huì)收到收到三個(gè)參數(shù),下面是入口模塊代碼:
function(module, exports, __webpack_require__) { "use strict"; var bar = __webpack_require__(1); bar.bar(); }
webpack傳入的第一個(gè)參數(shù)module是當(dāng)前緩存的模塊,包含當(dāng)前模塊的信息和exports;第二個(gè)參數(shù)exports是module.exports的引用,這也符合commonjs的規(guī)范;第三個(gè)__webpack_require__ 則是require的實(shí)現(xiàn)。
在我們的模塊中,就可以對(duì)外使用module.exports或exports進(jìn)行導(dǎo)出,使用__webpack_require__導(dǎo)入需要的模塊,代碼跟commonjs完全一樣。
這樣,就完成了對(duì)第一個(gè)模塊的require,然后第一個(gè)模塊會(huì)根據(jù)自己對(duì)其他模塊的require,依次加載其他模塊,最終形成一個(gè)依賴網(wǎng)狀結(jié)構(gòu)。webpack管理著這些模塊的緩存,如果一個(gè)模塊被require多次,那么只會(huì)有一次加載過程,而返回的是緩存的內(nèi)容,這也是commonjs的規(guī)范。
結(jié)論到這里,webpack就hack了commonjs代碼。
原理還是很簡單的,其實(shí)就是實(shí)現(xiàn)exports和require,然后自動(dòng)加載入口模塊,控制緩存模塊,that"s all。
細(xì)心的你一定會(huì)發(fā)現(xiàn),文章到這里只介紹了webpack對(duì)commonjs的實(shí)現(xiàn),那么ES6 module是如何實(shí)現(xiàn)的呢?
歡迎閱讀本系列第二篇《webpack模塊化原理-ES6 module》。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/84326.html
摘要:每一個(gè)模塊的源代碼都會(huì)被組織在一個(gè)立即執(zhí)行的函數(shù)里。接下來看的生成代碼可以看到,的源代碼中關(guān)于引入的模塊的部分做了修改,因?yàn)闊o論是,或是風(fēng)格的,都無法被解釋器直接執(zhí)行,它需要依賴模塊管理系統(tǒng),把這些抽象的關(guān)鍵詞具體化。 現(xiàn)在前端用Webpack打包JS和其它文件已經(jīng)是主流了,加上Node的流行,使得前端的工程方式和后端越來越像。所有的東西都模塊化,最后統(tǒng)一編譯。Webpack因?yàn)榘姹镜?..
摘要:所以通常情況下當(dāng)你的庫需要依賴到例如,這樣的通用模塊時(shí),我們可以不將它打包進(jìn),而是在的配置中聲明這就是在告訴請(qǐng)不要將這個(gè)模塊注入編譯后的文件里,對(duì)于我源代碼里出現(xiàn)的任何這個(gè)模塊的語句,請(qǐng)將它保留。 這篇文章討論Webpack打包library時(shí)經(jīng)常需要用到的一個(gè)選項(xiàng)external,它用于避免將一些很通用的模塊打包進(jìn)你發(fā)布的library里,而是選擇把它們聲明成external的模塊,...
摘要:所以你編譯后的文件實(shí)際上應(yīng)當(dāng)只輸出,這就需要在配置里用來控制這樣上面的模塊加載函數(shù)會(huì)在返回值后面加一個(gè),這樣就只返回的部分。 之前一篇文章分析了Webpack打包JS模塊的基本原理,所介紹的案例是最常見的一種情況,即多個(gè)JS模塊和一個(gè)入口模塊,打包成一個(gè)bundle文件,可以直接被瀏覽器或者其它JavaScript引擎執(zhí)行,相當(dāng)于直接編譯生成一個(gè)完整的可執(zhí)行的文件。不過還有一種很常見的...
摘要:打包出來的代碼快照如下,注意看注釋中的時(shí)序?qū)嶋H上,的處理同相差無幾,只是在定義模塊和引入模塊時(shí)會(huì)去處理標(biāo)識(shí),從而兼容其在語法上的差異。 前言 隨著 Web 技術(shù)的蓬勃發(fā)展和依賴的基礎(chǔ)設(shè)施日益完善,前端領(lǐng)域逐漸從瀏覽器擴(kuò)展至服務(wù)端(Node.js),桌面端(PC、Android、iOS),乃至于物聯(lián)網(wǎng)設(shè)備(IoT),其中 JavaScript 承載著這些應(yīng)用程序的核心部分,隨著其規(guī)模化和...
閱讀 2023·2021-10-25 09:48
閱讀 2893·2021-09-22 14:59
閱讀 1819·2019-08-29 16:52
閱讀 934·2019-08-29 16:07
閱讀 2381·2019-08-29 12:38
閱讀 1848·2019-08-26 13:23
閱讀 939·2019-08-26 11:49
閱讀 3352·2019-08-26 10:56