摘要:不知大家是不是跟大雄一樣之前從未看過編譯產出的代碼。前文大雄給了一個粗陋的動態加載的方法說白了就是動態創建標簽。大雄看完至少大概知道了原來編出來的代碼是那樣執行的原來可以那么靈活的使用。
Code Splitting是webpack的一個重要特性,他允許你將代碼打包生成多個bundle。對多頁應用來說,它是必須的,因為必須要配置多個入口生成多個bundle;對于單頁應用來說,如果只打包成一個bundle可能體積很大,導致無法利用瀏覽器并行下載的能力,且白屏時間長,也會導致下載很多可能用不到的代碼,每次上線用戶都得下載全部代碼,Code Splitting能夠將代碼分割,實現按需加載或并行加載多個bundle,可利用并發下載能力,減少首次訪問白屏時間,可以只上線必要的文件。三種Code Splitting方式
webpack提供了三種方式來切割代碼,分別是:
多entry方式
公共提取
動態加載
本文將簡單介紹多entry方式和公共提取方式,重點介紹的是動態加載。這幾種方式可以根據需要組合起來使用。這里是官方文檔,中文 英文
多entry方式這種方式就是指定多個打包入口,從入口開始將所有依賴打包進一個bundle,每個入口打包成一個bundle。此方式特別適合多頁應用,我們可以每個頁面指定一個入口,從而每個頁面生成一個js。此方式的核心配置代碼如下:
const path = require("path"); module.exports = { mode: "development", entry: { page1: "./src/page1.js", page2: "./src/page2.js" }, output: { filename: "[name].bundle.js", path: path.resolve(__dirname, "dist") } };
上邊的配置最終將生成兩個bundle, 即page1.bundle.js和page2.bundle.js。
公共提取這種方式將公共模塊提取出來生成一個bundle,公共模塊意味著有可能有很多地方使用,可能導致每個生成的bundle都包含公共模塊打包生成的代碼,造成浪費,將公共模塊提取出來多帶帶生成一個bundle可有效解決這個問題。這里貼一個官方文檔給出的配置示例:
const path = require("path"); module.exports = { mode: "development", entry: { index: "./src/index.js", another: "./src/another-module.js" }, output: { filename: "[name].bundle.js", path: path.resolve(__dirname, "dist") }, // 關鍵 optimization: { splitChunks: { chunks: "all" } } };
這個示例中index.js和another-module.js中都import了loadsh,如果不配置optimization,將生成兩個bundle, 兩個bundle都包含loadsh的代碼。配置optimization后,loadsh代碼被多帶帶提取到一個vendors~another~index.bundle.js。
動態加載動態加載的含義就是講代碼打包成多個bundle, 需要用到哪個bundle時在加載他。這樣做的好處是可以讓用戶下載需要用到的代碼,避免無用代碼下載。確定是操作體驗可能變差,因為操作之后可能還有一個下載代碼的過程。關于動態加載,后面詳解。
實現一個簡單的動態加載動態加載就是要實現可以在代碼里邊去加載其他js,這個太簡單了,新建script標簽插入dom就可以了,如下:
function loadScript(url) { const script = document.createElement("script"); script.src = url; document.head.appendChild(script); }
只需要在需要加載某個js時調用即可,例如需要點擊按鈕時加載js可能就如下邊這樣。
btn.onClick = function() { console.log("1"); loadScript("http://abc.com/a.js"); }
看上去非常簡單,事實上webpack也是這么做的,但是他的處理更加通用和精細。
webpack動態加載 webpak打包出來的代碼怎么執行現有一個文件test2.js, 其中代碼為
console.log("1")
此文件通過webpack打包后輸出如下,刪除了部分代碼,完整版可自己嘗試編譯一個,也可查看web-test(這個項目是基于react,express,webpack的用于web相關實驗的項目,里邊使用了code splitting方案來基于路由拆分代碼,與code splitting相關的實驗放在test-code-split分支)。
(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; } return __webpack_require__(__webpack_require__.s = "./test2.js"); }) ({ "./test2.js": (function (module, exports, __webpack_require__) { "use strict"; eval(" console.log("1"); //# sourceURL=webpack:///./test2.js?"); }) });
不知大家是不是跟大雄一樣之前從未看過webpack編譯產出的代碼。其實看一下還是挺有趣的,原來我們的代碼是放在eval中執行的。細看下這段代碼,其實并不復雜。他是一個自執行函數,參數是一個對象,key是模塊id(moduleId), value是函數,這個函數是里邊是執行我們寫的代碼,在自執行函數體內是直接調用了一個__webpack_require__,參數就是入口moduleId, __webpack_require__方法里值執行給定模塊id對應的函數,核心代碼是modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);。
上面是沒有import命令的情況,對于有import命令的情況,產出和上邊類似,只是自執行函數的參數有變化。例如:
// 入口文件test2.js import "./b.js" console.log("1") // b.js console.log("b")
這段代碼產出的自執行函數里邊的參數如下:
// 自執行函數里邊的參數 { "./b.js": (function (module, exports, __webpack_require__) { "use strict"; eval(" console.log("b"); //# sourceURL=webpack:///./b.js?"); }), "./test2.js": (function (module, exports, __webpack_require__) { "use strict"; eval(" __webpack_require__(/*! ./b.js */ "./b.js"); console.log("1"); //# sourceURL=webpack:///./test2.js?"); }) }
./test2.js這個moduleId對應的函數的eval里邊調用了__webpack_require__方法,為了看起來方便,將eval中的字符串拿出來,如下
__webpack_require__("./b.js"); console.log("1");
原來import命令在webpack中就是被轉換成了__webpack_require__的調用。太奇妙了,但是話說為啥模塊里邊為啥要用eval來執行我們寫的代碼,大雄還是比較困惑的。
webpack動態code splitting方案經過一番鋪墊,終于到主題了,即webpack是如何實現動態加載的。前文大雄給了一個粗陋的動態加載的方法--loadScript, 說白了就是動態創建script標簽。webpack中也是類似的,只是他做了一些細節處理。本文只介紹主流程,具體實現細節大家可以自己編譯產出一份代碼進行研究。
首先需要介紹在webpack中如何使用code splitting,非常簡單,就像下邊這樣
import("lodash").then(_ => { // Do something with lodash (a.k.a "_")... });
我們使用了一個import()方法, 這個import方法經過webpack打包后類似于前文提到的loadScript, 大家可以參看下邊的代碼:
__webpack_require__.e = function requireEnsure(chunkId) { var promises = []; // JSONP chunk loading for javascript var installedChunkData = installedChunks[chunkId]; if(installedChunkData !== 0) { // 0 means "already installed". // a Promise means "currently loading". if(installedChunkData) { promises.push(installedChunkData[2]); } else { // setup Promise in chunk cache var promise = new Promise(function(resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; }); promises.push(installedChunkData[2] = promise); // start chunk loading var script = document.createElement("script"); var onScriptComplete; script.charset = "utf-8"; script.timeout = 120; if (__webpack_require__.nc) { script.setAttribute("nonce", __webpack_require__.nc); } script.src = jsonpScriptSrc(chunkId); onScriptComplete = function (event) { // avoid mem leaks in IE. script.onerror = script.onload = null; clearTimeout(timeout); var chunk = installedChunks[chunkId]; if(chunk !== 0) { if(chunk) { var errorType = event && (event.type === "load" ? "missing" : event.type); var realSrc = event && event.target && event.target.src; var error = new Error("Loading chunk " + chunkId + " failed. (" + errorType + ": " + realSrc + ")"); error.type = errorType; error.request = realSrc; chunk[1](error); } installedChunks[chunkId] = undefined; } }; var timeout = setTimeout(function(){ onScriptComplete({ type: "timeout", target: script }); }, 120000); script.onerror = script.onload = onScriptComplete; document.head.appendChild(script); } } return Promise.all(promises); };
是不是非常熟悉,代碼中也調用了document.createElement("script")來創建script標簽,最后插入到head里。這段代碼所做的就是動態加載js,加載失敗時reject,加載成功resolve,這里并不能看到resolve的情況,resolve是在拆分出去的代碼里調用一個全局函數實現的。拆分出的js如下:
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{ /***/ "./b.js": /*!**************!* !*** ./b.js ***! **************/ /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { "use strict"; eval(" console.log("b"); //# sourceURL=webpack:///./b.js?"); /***/ }) }]);
在webpackJsonp方法里調用了對應的resolve,具體如下:
function webpackJsonpCallback(data) { var chunkIds = data[0]; var moreModules = data[1]; // add "moreModules" to the modules object, // then flag all "chunkIds" as loaded and fire callback var moduleId, chunkId, i = 0, resolves = []; 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(data); while(resolves.length) { resolves.shift()(); } };
這里的掛到全局的webpackJsonp是個數組,其push方法被改為webpackJsonpCallback方法的數組。所以每次在執行webpackJsonp時實際是在調用webpackJsonpCallback方法。
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); jsonpArray.push = webpackJsonpCallback; jsonpArray = jsonpArray.slice(); for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i])
總結起來,webpack的動態加載流程大致如下:
本文對webpack打包出的代碼的結構和執行過程作了簡單分析,介紹了webpack中code splitting的幾種方式,重點分析了一下動態加載的流程。分析的不一定完全正確,大家可以自己使用webpack打包產出代碼進行研究,一定會有所收獲。大雄看完至少大概知道了原來webpack編出來的代碼是那樣執行的、Promise原來可以那么靈活的使用。
大雄在學習web開發或在項目中遇到問題時經常需要做一些實驗, 在react出了什么新的特性時也常常通過做實驗來了解一下. 最開始常常直接在公司的項目做實驗, 直接拉個test分支就開搞, 這樣做有如下缺點:
在公司的項目去做實驗本身就是一件不好的事情
公司的項目里邊只有前端的部分, 想要做接口有關的實驗不方便. 例如想測試跨域的響應頭Access-Control-Allow-Origin就得再啟一個web服務器
實驗過的東西零散, 過一段時間想查找卻找不到了
基于以上原因, 特搭建了個基于react,webpack,express的用于web開發相關實驗的項目web-test.歡迎使用。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/103975.html
摘要:澄清一個共同的誤解代碼分離不僅僅是抽出公共代碼把它們放進一個共享的塊中。讓我們來使用來移除這個重復的部分。插件將會注意到我們已經將分割成一個單獨的塊。并且從我們的主中刪除了這部分。 對于大型web app來說,如果把所有的文件都打包到一個文件中是非常低效的,特別是當一些代碼塊只在某些特定的條件下被調用。webpack可以讓你的代碼庫分割成不同的塊(chucks),僅僅在需要的時候再加載...
摘要:支持定義分割點,通過進行按需加載。若按照中做,則會造成通用模塊重復打包。下文將詳細說明。同樣是利用和來處理的。如下在中添加入口其中模塊為通用功能模塊在中對應和這樣則會打包出和兩個文件。為通用功能模塊。希望有更好方案的同學能夠不吝賜教。 什么是code splitting 首先說,code splitting指什么。我們打包時通常會生成一個大的bundle.js(或者index,看你如...
摘要:但是同時,抽離到父模塊,也意味著如果有一個懶加載的路由沒有用到模塊,但是實際上引入了父模塊,也為這也引入了的代碼。 前言 我們清楚,在 webpack 中通過CommonsChunkPlugin 可以將 entry 的入口文件中引用多次的文件抽離打包成一個公用文件,從而減少代碼重復冗余 entry: { main: ./src/main.js, ...
摘要:瀏覽器需要重新下載打包后的文件,即使文件的絕大部分都沒有變化。分離并且以來命名新的入口能夠緩和當前的問題。現在運行綁定的檢查結果是只是被綁定到這個綁定文件中。 分離庫代碼Code Splitting - Libraries 這個在webpack2.x中文網已存在,點擊這里 讓我們想一個簡單的應用——momentjs,他是一個事件格式化的庫。安裝moment. npm install -...
摘要:前言是最引人矚目的特性之一此特性將代碼分離到不同的文件中。功能分析官網上有三種方式實現入口起點使用選項手動分離代碼。防止重復使用去重和分離。本質則是多個入口的,則在以為入口文件將多入口的切分為按切割文件通過加載。 前言 code-splitting是webpack最引人矚目的特性之一,此特性將代碼分離到不同的bundle文件中。詳細介紹官網code-split,這次實現則在筆者上次文件...
閱讀 917·2021-11-24 09:38
閱讀 925·2021-11-23 09:51
閱讀 2939·2021-11-16 11:44
閱讀 1762·2021-09-22 15:52
閱讀 1626·2021-09-10 11:20
閱讀 1361·2019-08-30 13:47
閱讀 1292·2019-08-29 12:36
閱讀 3293·2019-08-26 10:43