成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

webpack Code Splitting淺析

Amos / 2549人閱讀

摘要:不知大家是不是跟大雄一樣之前從未看過編譯產(chǎn)出的代碼。前文大雄給了一個(gè)粗陋的動態(tài)加載的方法說白了就是動態(tài)創(chuàng)建標(biāo)簽。大雄看完至少大概知道了原來編出來的代碼是那樣執(zhí)行的原來可以那么靈活的使用。

Code Splitting是webpack的一個(gè)重要特性,他允許你將代碼打包生成多個(gè)bundle。對多頁應(yīng)用來說,它是必須的,因?yàn)楸仨氁渲枚鄠€(gè)入口生成多個(gè)bundle;對于單頁應(yīng)用來說,如果只打包成一個(gè)bundle可能體積很大,導(dǎo)致無法利用瀏覽器并行下載的能力,且白屏?xí)r間長,也會導(dǎo)致下載很多可能用不到的代碼,每次上線用戶都得下載全部代碼,Code Splitting能夠?qū)⒋a分割,實(shí)現(xiàn)按需加載或并行加載多個(gè)bundle,可利用并發(fā)下載能力,減少首次訪問白屏?xí)r間,可以只上線必要的文件。
三種Code Splitting方式

webpack提供了三種方式來切割代碼,分別是:

多entry方式

公共提取

動態(tài)加載

本文將簡單介紹多entry方式和公共提取方式,重點(diǎn)介紹的是動態(tài)加載。這幾種方式可以根據(jù)需要組合起來使用。這里是官方文檔,中文 英文

多entry方式

這種方式就是指定多個(gè)打包入口,從入口開始將所有依賴打包進(jìn)一個(gè)bundle,每個(gè)入口打包成一個(gè)bundle。此方式特別適合多頁應(yīng)用,我們可以每個(gè)頁面指定一個(gè)入口,從而每個(gè)頁面生成一個(gè)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")
  }
};

上邊的配置最終將生成兩個(gè)bundle, 即page1.bundle.js和page2.bundle.js。

公共提取

這種方式將公共模塊提取出來生成一個(gè)bundle,公共模塊意味著有可能有很多地方使用,可能導(dǎo)致每個(gè)生成的bundle都包含公共模塊打包生成的代碼,造成浪費(fèi),將公共模塊提取出來多帶帶生成一個(gè)bundle可有效解決這個(gè)問題。這里貼一個(gè)官方文檔給出的配置示例:

  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")
    },
    // 關(guān)鍵
    optimization: {
      splitChunks: {
        chunks: "all"
      }
    }
  };

這個(gè)示例中index.js和another-module.js中都import了loadsh,如果不配置optimization,將生成兩個(gè)bundle, 兩個(gè)bundle都包含loadsh的代碼。配置optimization后,loadsh代碼被多帶帶提取到一個(gè)vendors~another~index.bundle.js。

動態(tài)加載

動態(tài)加載的含義就是講代碼打包成多個(gè)bundle, 需要用到哪個(gè)bundle時(shí)在加載他。這樣做的好處是可以讓用戶下載需要用到的代碼,避免無用代碼下載。確定是操作體驗(yàn)可能變差,因?yàn)椴僮髦罂赡苓€有一個(gè)下載代碼的過程。關(guān)于動態(tài)加載,后面詳解。

實(shí)現(xiàn)一個(gè)簡單的動態(tài)加載

動態(tài)加載就是要實(shí)現(xiàn)可以在代碼里邊去加載其他js,這個(gè)太簡單了,新建script標(biāo)簽插入dom就可以了,如下:

function loadScript(url) {
    const script = document.createElement("script");
    script.src = url;
    document.head.appendChild(script);
}

只需要在需要加載某個(gè)js時(shí)調(diào)用即可,例如需要點(diǎn)擊按鈕時(shí)加載js可能就如下邊這樣。

btn.onClick = function() {
    console.log("1");
    loadScript("http://abc.com/a.js");
}

看上去非常簡單,事實(shí)上webpack也是這么做的,但是他的處理更加通用和精細(xì)。

webpack動態(tài)加載 webpak打包出來的代碼怎么執(zhí)行

現(xiàn)有一個(gè)文件test2.js, 其中代碼為

console.log("1")

此文件通過webpack打包后輸出如下,刪除了部分代碼,完整版可自己嘗試編譯一個(gè),也可查看web-test(這個(gè)項(xiàng)目是基于react,express,webpack的用于web相關(guān)實(shí)驗(yàn)的項(xiàng)目,里邊使用了code splitting方案來基于路由拆分代碼,與code splitting相關(guān)的實(shí)驗(yàn)放在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編譯產(chǎn)出的代碼。其實(shí)看一下還是挺有趣的,原來我們的代碼是放在eval中執(zhí)行的。細(xì)看下這段代碼,其實(shí)并不復(fù)雜。他是一個(gè)自執(zhí)行函數(shù),參數(shù)是一個(gè)對象,key是模塊id(moduleId), value是函數(shù),這個(gè)函數(shù)是里邊是執(zhí)行我們寫的代碼,在自執(zhí)行函數(shù)體內(nèi)是直接調(diào)用了一個(gè)__webpack_require__,參數(shù)就是入口moduleId, __webpack_require__方法里值執(zhí)行給定模塊id對應(yīng)的函數(shù),核心代碼是modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);。

上面是沒有import命令的情況,對于有import命令的情況,產(chǎn)出和上邊類似,只是自執(zhí)行函數(shù)的參數(shù)有變化。例如:

// 入口文件test2.js
import "./b.js"
console.log("1")
// b.js
console.log("b")

這段代碼產(chǎn)出的自執(zhí)行函數(shù)里邊的參數(shù)如下:

// 自執(zhí)行函數(shù)里邊的參數(shù)
{

  "./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這個(gè)moduleId對應(yīng)的函數(shù)的eval里邊調(diào)用了__webpack_require__方法,為了看起來方便,將eval中的字符串拿出來,如下

__webpack_require__("./b.js");
console.log("1");

原來import命令在webpack中就是被轉(zhuǎn)換成了__webpack_require__的調(diào)用。太奇妙了,但是話說為啥模塊里邊為啥要用eval來執(zhí)行我們寫的代碼,大雄還是比較困惑的。

webpack動態(tài)code splitting方案

經(jīng)過一番鋪墊,終于到主題了,即webpack是如何實(shí)現(xiàn)動態(tài)加載的。前文大雄給了一個(gè)粗陋的動態(tài)加載的方法--loadScript, 說白了就是動態(tài)創(chuàng)建script標(biāo)簽。webpack中也是類似的,只是他做了一些細(xì)節(jié)處理。本文只介紹主流程,具體實(shí)現(xiàn)細(xì)節(jié)大家可以自己編譯產(chǎn)出一份代碼進(jìn)行研究。

首先需要介紹在webpack中如何使用code splitting,非常簡單,就像下邊這樣

import("lodash").then(_ => {
    // Do something with lodash (a.k.a "_")...
  });

我們使用了一個(gè)import()方法, 這個(gè)import方法經(jīng)過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);
};

是不是非常熟悉,代碼中也調(diào)用了document.createElement("script")來創(chuàng)建script標(biāo)簽,最后插入到head里。這段代碼所做的就是動態(tài)加載js,加載失敗時(shí)reject,加載成功resolve,這里并不能看到resolve的情況,resolve是在拆分出去的代碼里調(diào)用一個(gè)全局函數(shù)實(shí)現(xiàn)的。拆分出的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方法里調(diào)用了對應(yīng)的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是個(gè)數(shù)組,其push方法被改為webpackJsonpCallback方法的數(shù)組。所以每次在執(zhí)行webpackJsonp時(shí)實(shí)際是在調(diào)用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])

總結(jié)起來,webpack的動態(tài)加載流程大致如下:

總結(jié)

本文對webpack打包出的代碼的結(jié)構(gòu)和執(zhí)行過程作了簡單分析,介紹了webpack中code splitting的幾種方式,重點(diǎn)分析了一下動態(tài)加載的流程。分析的不一定完全正確,大家可以自己使用webpack打包產(chǎn)出代碼進(jìn)行研究,一定會有所收獲。大雄看完至少大概知道了原來webpack編出來的代碼是那樣執(zhí)行的、Promise原來可以那么靈活的使用。

大雄在學(xué)習(xí)web開發(fā)或在項(xiàng)目中遇到問題時(shí)經(jīng)常需要做一些實(shí)驗(yàn), 在react出了什么新的特性時(shí)也常常通過做實(shí)驗(yàn)來了解一下. 最開始常常直接在公司的項(xiàng)目做實(shí)驗(yàn), 直接拉個(gè)test分支就開搞, 這樣做有如下缺點(diǎn):

在公司的項(xiàng)目去做實(shí)驗(yàn)本身就是一件不好的事情

公司的項(xiàng)目里邊只有前端的部分, 想要做接口有關(guān)的實(shí)驗(yàn)不方便. 例如想測試跨域的響應(yīng)頭Access-Control-Allow-Origin就得再啟一個(gè)web服務(wù)器

實(shí)驗(yàn)過的東西零散, 過一段時(shí)間想查找卻找不到了

基于以上原因, 特搭建了個(gè)基于react,webpack,express的用于web開發(fā)相關(guān)實(shí)驗(yàn)的項(xiàng)目web-test.歡迎使用。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/103975.html

相關(guān)文章

  • 翻譯webpack3.5.5 - code splitting - 上半部分

    摘要:澄清一個(gè)共同的誤解代碼分離不僅僅是抽出公共代碼把它們放進(jìn)一個(gè)共享的塊中。讓我們來使用來移除這個(gè)重復(fù)的部分。插件將會注意到我們已經(jīng)將分割成一個(gè)單獨(dú)的塊。并且從我們的主中刪除了這部分。 對于大型web app來說,如果把所有的文件都打包到一個(gè)文件中是非常低效的,特別是當(dāng)一些代碼塊只在某些特定的條件下被調(diào)用。webpack可以讓你的代碼庫分割成不同的塊(chucks),僅僅在需要的時(shí)候再加載...

    Bryan 評論0 收藏0
  • webpack學(xué)習(xí)(四)— code splitting

    摘要:支持定義分割點(diǎn),通過進(jìn)行按需加載。若按照中做,則會造成通用模塊重復(fù)打包。下文將詳細(xì)說明。同樣是利用和來處理的。如下在中添加入口其中模塊為通用功能模塊在中對應(yīng)和這樣則會打包出和兩個(gè)文件。為通用功能模塊。希望有更好方案的同學(xué)能夠不吝賜教。 什么是code splitting 首先說,code splitting指什么。我們打包時(shí)通常會生成一個(gè)大的bundle.js(或者index,看你如...

    lsxiao 評論0 收藏0
  • 代碼分割與懶加載情況下(code-splitting+lazyload)抽離懶加載模塊的公用模塊代碼

    摘要:但是同時(shí),抽離到父模塊,也意味著如果有一個(gè)懶加載的路由沒有用到模塊,但是實(shí)際上引入了父模塊,也為這也引入了的代碼。 前言 我們清楚,在 webpack 中通過CommonsChunkPlugin 可以將 entry 的入口文件中引用多次的文件抽離打包成一個(gè)公用文件,從而減少代碼重復(fù)冗余 entry: { main: ./src/main.js, ...

    zebrayoung 評論0 收藏0
  • webpack2.x 中文文檔 翻譯 之 分離庫代碼Code Splitting - Librari

    摘要:瀏覽器需要重新下載打包后的文件,即使文件的絕大部分都沒有變化。分離并且以來命名新的入口能夠緩和當(dāng)前的問題。現(xiàn)在運(yùn)行綁定的檢查結(jié)果是只是被綁定到這個(gè)綁定文件中。 分離庫代碼Code Splitting - Libraries 這個(gè)在webpack2.x中文網(wǎng)已存在,點(diǎn)擊這里 讓我們想一個(gè)簡單的應(yīng)用——momentjs,他是一個(gè)事件格式化的庫。安裝moment. npm install -...

    elva 評論0 收藏0
  • webpack源碼分析之二:code-splitting

    摘要:前言是最引人矚目的特性之一此特性將代碼分離到不同的文件中。功能分析官網(wǎng)上有三種方式實(shí)現(xiàn)入口起點(diǎn)使用選項(xiàng)手動分離代碼。防止重復(fù)使用去重和分離。本質(zhì)則是多個(gè)入口的,則在以為入口文件將多入口的切分為按切割文件通過加載。 前言 code-splitting是webpack最引人矚目的特性之一,此特性將代碼分離到不同的bundle文件中。詳細(xì)介紹官網(wǎng)code-split,這次實(shí)現(xiàn)則在筆者上次文件...

    wudengzan 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<