摘要:的模塊化不僅支持和,還能通過實(shí)現(xiàn)模塊的動態(tài)加載。根據(jù)官方文檔,實(shí)現(xiàn)動態(tài)加載的方式有兩種和。如果你對如何實(shí)現(xiàn)和感興趣,可以查看我的前兩篇文章模塊化原理和模塊化原理。
webpack的模塊化不僅支持commonjs和es module,還能通過code splitting實(shí)現(xiàn)模塊的動態(tài)加載。根據(jù)wepack官方文檔,實(shí)現(xiàn)動態(tài)加載的方式有兩種:import和require.ensure。
那么,這篇文檔就來分析一下,webpack是如何實(shí)現(xiàn)code splitting的。
PS:如果你對webpack如何實(shí)現(xiàn)commonjs和es module感興趣,可以查看我的前兩篇文章:webpack模塊化原理-commonjs和webpack模塊化原理-ES module。
準(zhǔn)備首先我們依然創(chuàng)建一個簡單入口模塊index.js和兩個依賴模塊foo.js和bar.js:
// index.js "use strict"; import(/* webpackChunkName: "foo" */ "./foo").then(foo => { console.log(foo()); }) import(/* webpackChunkName: "bar" */ "./bar").then(bar => { console.log(bar()); })
// foo.js "use strict"; exports.foo = function () { return 2; }
// 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", chunkFilename: "[name].bundle.js" }, };
這是一個最簡單的配置,指定了模塊入口和打包文件輸出路徑,值得注意的是,這次還指定了分離模塊的文件名[name].bundle.js(不指定會有默認(rèn)文件名)。
在根目錄下執(zhí)行webpack,得到經(jīng)過webpack打包的代碼如下(去掉了不必要的注釋):
(function(modules) { // webpackBootstrap // install a JSONP callback for chunk loading var parentJsonpFunction = window["webpackJsonp"]; window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) { // add "moreModules" to the modules object, // then flag all "chunkIds" as loaded and fire callback var moduleId, chunkId, i = 0, resolves = [], result; for(;i < chunkIds.length; i++) { chunkId = chunkIds[i]; if(installedChunks[chunkId]) { resolves.push(installedChunks[chunkId][0]); } installedChunks[chunkId] = 0; } for(moduleId in moreModules) { if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; } } if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules); while(resolves.length) { resolves.shift()(); } }; // The module cache var installedModules = {}; // objects to store loaded and loading chunks var installedChunks = { 2: 0 }; // 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; } // This file contains only the entry chunk. // The chunk loading function for additional chunks __webpack_require__.e = function requireEnsure(chunkId) { var installedChunkData = installedChunks[chunkId]; if(installedChunkData === 0) { return new Promise(function(resolve) { resolve(); }); } // a Promise means "currently loading". if(installedChunkData) { return installedChunkData[2]; } // setup Promise in chunk cache var promise = new Promise(function(resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; }); installedChunkData[2] = promise; // start chunk loading var head = document.getElementsByTagName("head")[0]; var script = document.createElement("script"); script.type = "text/javascript"; script.charset = "utf-8"; script.async = true; script.timeout = 120000; if (__webpack_require__.nc) { script.setAttribute("nonce", __webpack_require__.nc); } script.src = __webpack_require__.p + "" + ({"0":"foo","1":"bar"}[chunkId]||chunkId) + ".bundle.js"; var timeout = setTimeout(onScriptComplete, 120000); script.onerror = script.onload = onScriptComplete; function onScriptComplete() { // avoid mem leaks in IE. script.onerror = script.onload = null; clearTimeout(timeout); var chunk = installedChunks[chunkId]; if(chunk !== 0) { if(chunk) { chunk[1](new Error("Loading chunk " + chunkId + " failed.")); } installedChunks[chunkId] = undefined; } }; head.appendChild(script); return promise; }; // 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 = ""; // on error function for async loading __webpack_require__.oe = function(err) { console.error(err); throw err; }; // Load entry module and return exports return __webpack_require__(__webpack_require__.s = 0); }) ([ (function(module, exports, __webpack_require__) { "use strict"; __webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 1)).then(foo => { console.log(foo()); }) __webpack_require__.e/* import() */(1).then(__webpack_require__.bind(null, 2)).then(bar => { console.log(bar()); }) }) ]);分析
編譯后的代碼,整體跟前兩篇文章中使用commonjs和es6 module編寫的代碼編譯后的結(jié)構(gòu)差別不大,都是通過IFFE的方式啟動代碼,然后使用webpack實(shí)現(xiàn)的require和exports實(shí)現(xiàn)的模塊化。
而對于code splitting的支持,區(qū)別在于這里使用__webpack_require__.e實(shí)現(xiàn)動態(tài)加載模塊和實(shí)現(xiàn)基于promise的模塊導(dǎo)入。
所以首先分析__webpack_require__.e函數(shù)的定義,這個函數(shù)實(shí)現(xiàn)了動態(tài)加載:
__webpack_require__.e = function requireEnsure(chunkId) { // 1、緩存查找 var installedChunkData = installedChunks[chunkId]; if(installedChunkData === 0) { return new Promise(function(resolve) { resolve(); }); } if(installedChunkData) { return installedChunkData[2]; } // 2、緩存模塊 var promise = new Promise(function(resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; }); installedChunkData[2] = promise; // 3、加載模塊 var head = document.getElementsByTagName("head")[0]; var script = document.createElement("script"); script.type = "text/javascript"; script.charset = "utf-8"; script.async = true; script.timeout = 120000; if (__webpack_require__.nc) { script.setAttribute("nonce", __webpack_require__.nc); } script.src = __webpack_require__.p + "" + ({"0":"foo"}[chunkId]||chunkId) + ".bundle.js"; // 4、異常處理 var timeout = setTimeout(onScriptComplete, 120000); script.onerror = script.onload = onScriptComplete; function onScriptComplete() { // avoid mem leaks in IE. script.onerror = script.onload = null; clearTimeout(timeout); var chunk = installedChunks[chunkId]; if(chunk !== 0) { if(chunk) { chunk[1](new Error("Loading chunk " + chunkId + " failed.")); } installedChunks[chunkId] = undefined; } }; head.appendChild(script); // 5、返回promise return promise; };
代碼大致邏輯如下:
緩存查找:從緩存installedChunks中查找是否有緩存模塊,如果緩存標(biāo)識為0,則表示模塊已加載過,直接返回promise;如果緩存為數(shù)組,表示緩存正在加載中,則返回緩存的promise對象
如果沒有緩存,則創(chuàng)建一個promise,并將promise和resolve、reject緩存在installedChunks中
構(gòu)建一個script標(biāo)簽,append到head標(biāo)簽中,src指向加載的模塊腳本資源,實(shí)現(xiàn)動態(tài)加載js腳本
添加script標(biāo)簽onload、onerror 事件,如果超時或者模塊加載失敗,則會調(diào)用reject返回模塊加載失敗異常
如果模塊加載成功,則返回當(dāng)前模塊promise,對應(yīng)于import()
以上便是模塊加載的過程,當(dāng)資源加載完成,模塊代碼開始執(zhí)行,那么我們來看一下模塊代碼的結(jié)構(gòu):
webpackJsonp([0],[ /* 0 */, /* 1 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; exports.foo = function () { return 2; } /***/ }) ]);
可以看到,模塊代碼不僅被包在一個函數(shù)中(用來模擬模塊作用域),外層還被當(dāng)做參數(shù)傳入webpackJsonp中。那么這個webpackJsonp函數(shù)的作用是什么呢?
其實(shí)這里的webpackJsonp類似于jsonp中的callback,作用是作為模塊加載和執(zhí)行完成的回調(diào),從而觸發(fā)import的resolve。
具體細(xì)看webpackJsonp代碼來分析:
window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) { var moduleId, chunkId, i = 0, resolves = [], result; // 1、收集模塊resolve for(;i < chunkIds.length; i++) { chunkId = chunkIds[i]; if(installedChunks[chunkId]) { resolves.push(installedChunks[chunkId][0]); } installedChunks[chunkId] = 0; } // 2、copy模塊到modules for(moduleId in moreModules) { if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; } } if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules); // 3、resolve import while(resolves.length) { resolves.shift()(); } };
代碼大致邏輯如下:
根據(jù)chunkIds收集對應(yīng)模塊的resolve,這里的chunkIds為數(shù)組是因?yàn)?b>require.ensure是可以實(shí)現(xiàn)異步加載多個模塊的,所以需要兼容
把動態(tài)模塊添加到IFFE的modules中,提供其他CMD方案使用模塊
直接調(diào)用resolve,完成整個異步加載
總結(jié)webpack通過__webpack_require__.e函數(shù)實(shí)現(xiàn)了動態(tài)加載,再通過webpackJsonp函數(shù)實(shí)現(xiàn)異步加載回調(diào),把模塊內(nèi)容以promise的方式暴露給調(diào)用方,從而實(shí)現(xiàn)了對code splitting的支持。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/88846.html
摘要:支持定義分割點(diǎn),通過進(jìn)行按需加載。若按照中做,則會造成通用模塊重復(fù)打包。下文將詳細(xì)說明。同樣是利用和來處理的。如下在中添加入口其中模塊為通用功能模塊在中對應(yīng)和這樣則會打包出和兩個文件。為通用功能模塊。希望有更好方案的同學(xué)能夠不吝賜教。 什么是code splitting 首先說,code splitting指什么。我們打包時通常會生成一個大的bundle.js(或者index,看你如...
摘要:但是同時,抽離到父模塊,也意味著如果有一個懶加載的路由沒有用到模塊,但是實(shí)際上引入了父模塊,也為這也引入了的代碼。 前言 我們清楚,在 webpack 中通過CommonsChunkPlugin 可以將 entry 的入口文件中引用多次的文件抽離打包成一個公用文件,從而減少代碼重復(fù)冗余 entry: { main: ./src/main.js, ...
摘要:澄清一個共同的誤解代碼分離不僅僅是抽出公共代碼把它們放進(jìn)一個共享的塊中。讓我們來使用來移除這個重復(fù)的部分。插件將會注意到我們已經(jīng)將分割成一個單獨(dú)的塊。并且從我們的主中刪除了這部分。 對于大型web app來說,如果把所有的文件都打包到一個文件中是非常低效的,特別是當(dāng)一些代碼塊只在某些特定的條件下被調(diào)用。webpack可以讓你的代碼庫分割成不同的塊(chucks),僅僅在需要的時候再加載...
摘要:不知大家是不是跟大雄一樣之前從未看過編譯產(chǎn)出的代碼。前文大雄給了一個粗陋的動態(tài)加載的方法說白了就是動態(tài)創(chuàng)建標(biāo)簽。大雄看完至少大概知道了原來編出來的代碼是那樣執(zhí)行的原來可以那么靈活的使用。 Code Splitting是webpack的一個重要特性,他允許你將代碼打包生成多個bundle。對多頁應(yīng)用來說,它是必須的,因?yàn)楸仨氁渲枚鄠€入口生成多個bundle;對于單頁應(yīng)用來說,如果只打包...
摘要:前言是最引人矚目的特性之一此特性將代碼分離到不同的文件中。功能分析官網(wǎng)上有三種方式實(shí)現(xiàn)入口起點(diǎn)使用選項(xiàng)手動分離代碼。防止重復(fù)使用去重和分離。本質(zhì)則是多個入口的,則在以為入口文件將多入口的切分為按切割文件通過加載。 前言 code-splitting是webpack最引人矚目的特性之一,此特性將代碼分離到不同的bundle文件中。詳細(xì)介紹官網(wǎng)code-split,這次實(shí)現(xiàn)則在筆者上次文件...
閱讀 1351·2023-04-25 23:47
閱讀 929·2021-11-23 09:51
閱讀 4481·2021-09-26 10:17
閱讀 3729·2021-09-10 11:19
閱讀 3268·2021-09-06 15:10
閱讀 3556·2019-08-30 12:49
閱讀 2436·2019-08-29 13:20
閱讀 1743·2019-08-28 18:14