摘要:的模塊化不僅支持和,還能通過實現模塊的動態加載。根據官方文檔,實現動態加載的方式有兩種和。如果你對如何實現和感興趣,可以查看我的前兩篇文章模塊化原理和模塊化原理。
webpack的模塊化不僅支持commonjs和es module,還能通過code splitting實現模塊的動態加載。根據wepack官方文檔,實現動態加載的方式有兩種:import和require.ensure。
那么,這篇文檔就來分析一下,webpack是如何實現code splitting的。
PS:如果你對webpack如何實現commonjs和es module感興趣,可以查看我的前兩篇文章:webpack模塊化原理-commonjs和webpack模塊化原理-ES module。
準備首先我們依然創建一個簡單入口模塊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(不指定會有默認文件名)。
在根目錄下執行webpack,得到經過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編寫的代碼編譯后的結構差別不大,都是通過IFFE的方式啟動代碼,然后使用webpack實現的require和exports實現的模塊化。
而對于code splitting的支持,區別在于這里使用__webpack_require__.e實現動態加載模塊和實現基于promise的模塊導入。
所以首先分析__webpack_require__.e函數的定義,這個函數實現了動態加載:
__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中查找是否有緩存模塊,如果緩存標識為0,則表示模塊已加載過,直接返回promise;如果緩存為數組,表示緩存正在加載中,則返回緩存的promise對象
如果沒有緩存,則創建一個promise,并將promise和resolve、reject緩存在installedChunks中
構建一個script標簽,append到head標簽中,src指向加載的模塊腳本資源,實現動態加載js腳本
添加script標簽onload、onerror 事件,如果超時或者模塊加載失敗,則會調用reject返回模塊加載失敗異常
如果模塊加載成功,則返回當前模塊promise,對應于import()
以上便是模塊加載的過程,當資源加載完成,模塊代碼開始執行,那么我們來看一下模塊代碼的結構:
webpackJsonp([0],[ /* 0 */, /* 1 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; exports.foo = function () { return 2; } /***/ }) ]);
可以看到,模塊代碼不僅被包在一個函數中(用來模擬模塊作用域),外層還被當做參數傳入webpackJsonp中。那么這個webpackJsonp函數的作用是什么呢?
其實這里的webpackJsonp類似于jsonp中的callback,作用是作為模塊加載和執行完成的回調,從而觸發import的resolve。
具體細看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()(); } };
代碼大致邏輯如下:
根據chunkIds收集對應模塊的resolve,這里的chunkIds為數組是因為require.ensure是可以實現異步加載多個模塊的,所以需要兼容
把動態模塊添加到IFFE的modules中,提供其他CMD方案使用模塊
直接調用resolve,完成整個異步加載
總結webpack通過__webpack_require__.e函數實現了動態加載,再通過webpackJsonp函數實現異步加載回調,把模塊內容以promise的方式暴露給調用方,從而實現了對code splitting的支持。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/88846.html
摘要:支持定義分割點,通過進行按需加載。若按照中做,則會造成通用模塊重復打包。下文將詳細說明。同樣是利用和來處理的。如下在中添加入口其中模塊為通用功能模塊在中對應和這樣則會打包出和兩個文件。為通用功能模塊。希望有更好方案的同學能夠不吝賜教。 什么是code splitting 首先說,code splitting指什么。我們打包時通常會生成一個大的bundle.js(或者index,看你如...
摘要:但是同時,抽離到父模塊,也意味著如果有一個懶加載的路由沒有用到模塊,但是實際上引入了父模塊,也為這也引入了的代碼。 前言 我們清楚,在 webpack 中通過CommonsChunkPlugin 可以將 entry 的入口文件中引用多次的文件抽離打包成一個公用文件,從而減少代碼重復冗余 entry: { main: ./src/main.js, ...
摘要:澄清一個共同的誤解代碼分離不僅僅是抽出公共代碼把它們放進一個共享的塊中。讓我們來使用來移除這個重復的部分。插件將會注意到我們已經將分割成一個單獨的塊。并且從我們的主中刪除了這部分。 對于大型web app來說,如果把所有的文件都打包到一個文件中是非常低效的,特別是當一些代碼塊只在某些特定的條件下被調用。webpack可以讓你的代碼庫分割成不同的塊(chucks),僅僅在需要的時候再加載...
摘要:不知大家是不是跟大雄一樣之前從未看過編譯產出的代碼。前文大雄給了一個粗陋的動態加載的方法說白了就是動態創建標簽。大雄看完至少大概知道了原來編出來的代碼是那樣執行的原來可以那么靈活的使用。 Code Splitting是webpack的一個重要特性,他允許你將代碼打包生成多個bundle。對多頁應用來說,它是必須的,因為必須要配置多個入口生成多個bundle;對于單頁應用來說,如果只打包...
摘要:前言是最引人矚目的特性之一此特性將代碼分離到不同的文件中。功能分析官網上有三種方式實現入口起點使用選項手動分離代碼。防止重復使用去重和分離。本質則是多個入口的,則在以為入口文件將多入口的切分為按切割文件通過加載。 前言 code-splitting是webpack最引人矚目的特性之一,此特性將代碼分離到不同的bundle文件中。詳細介紹官網code-split,這次實現則在筆者上次文件...
閱讀 1336·2023-04-25 23:47
閱讀 912·2021-11-23 09:51
閱讀 4432·2021-09-26 10:17
閱讀 3706·2021-09-10 11:19
閱讀 3254·2021-09-06 15:10
閱讀 3546·2019-08-30 12:49
閱讀 2421·2019-08-29 13:20
閱讀 1730·2019-08-28 18:14