探究webpack5懶加載原理

圖片描述

不廢話看看官方怎麼談5,Webpack5的新特性javascript

  • 1.使用持久化緩存提升構建性能
  • 2.使用更好的算法和默認值改進長期緩存(long-term caching)
  • 3.清理內部結構而不引入任何破壞性的變化
  • 4.引入一些breaking changes,以便儘量長的使用v5版本

光說不練假把式

安裝webpack(v5)威武版,不用用怎麼知道他有多好用?html

npm init -y 
npm i webpack@next --save-dev
能夠直接經過@next方式來安裝webpack5版本,目前版本是"^5.0.0-alpha.23"

先來看下基本結構java

├── bootstrap.js  // 手動啓動webpack
├── pack.js       // 本身實現的webpack
├── package-lock.json
├── package.json
├── README.md
├── src
│   ├── a.js     // 入口文件會引用 a.js
│   └── index.js // 打包的入口文件
└── webpack.config.js // webpack配置文件

a.js只是導出個變量而已,很是的簡單webpack

module.exports = 'webyouxuan'

index.js負責引入a.js模塊web

let webyouxuan= require('./a');
console.log(webyouxuan)

webpack.config文件算法

const path = require('path');
module.exports = {
    mode:'development',
    entry:'./src/index.js',
    output:{
        path:path.join(__dirname,'dist'), 
        filename:'main.js'
    }
}
你會發現和webpack4的配置基本沒有變化

開始打包,你會發現npx webpack 須要webpack-cli的支持,比較尷尬的是,到目前尚未與之匹配的webpack-cli,沒辦法啦,咱們只好手動啓動webpack了~~~npm

bootstrap.js 引入webpack進行打包項目json

const webpack = require('webpack');
const webpackOptions = require('./webpack.config');
// 須要將 配置文件傳入到webpack中,打包成功後咱們打印stats信息
webpack(webpackOptions,(err,stats)=>{
    if(err){
        console.log(err);
    }else{
        console.log(stats.toJson())
    }
})

看下打包出來的信息:
bootstrap

咱們須要掌握一些關鍵詞:數組

  • module:在webpack中全部文件都是模塊,一個模塊會對應一個文件,webpack會經過入口找到全部依賴的模塊
  • chunk:代碼塊,一個chunk由多個模塊組合而成
  • assets:打包出的資源,通常和chunk個數相同

查看編譯出的源碼

發現比webpack4,打包出來的結果確實少了很多!更簡潔,更容易讀懂(這裏我已把註釋刪掉)。

// 2.總體函數是個自執行函數
((modules) => { // 3.module傳入的爲全部打包後的結果
  var installedModules = {};
  function __webpack_require__(moduleId) { 
    if (installedModules[moduleId]) { // 作緩存的能夠先不理 
      return installedModules[moduleId].exports;
    }
    var module = (installedModules[moduleId] = { // 5.建立模塊,每一個模塊都有一個exports對象
      i: moduleId,
      l: false,
      exports: {}
    });
    modules[moduleId](module, module.exports, __webpack_require__); // 6.調用對應的模塊函數,將模塊exports傳入
    module.l = true;
    // 8.用戶會將結果放到module.exports對象上
    return module.exports;
  }
  function startup() {
    // 經過入口開始加載
    return __webpack_require__("./src/index.js"); // 默認返回的是 module.exports結果;
  }
  return startup(); // 4.啓動加載
})({ // 1.列出打包後的模塊
  "./src/a.js": module => {
    eval(
      "module.exports = 'webyouxuan'\n\n//# sourceURL=webpack:///./src/a.js?"
    );
  },
  "./src/index.js": (__unusedmodule, __unusedexports, __webpack_require__) => { // 7.加載其餘模塊,拿到其餘模塊的module.exports結果
    eval(
      'let webyouxuan = __webpack_require__(/*! ./a */ "./src/a.js");\nconsole.log(webyouxuan)\n\n//# sourceURL=webpack:///./src/index.js?'
    );
  }
});

總的來講不難理解,其實仍是內部實現了個___webpack_require__方法,若是看不懂就多來幾遍,看懂了發現也沒什麼。。。

這樣咱們能夠直接把main.js在html中直接引入啦~,發現是否是已經能夠打印出webyouxuan啦,順便作個廣告,關注咱們!持續推送精品文章,給你點個贊👍

懶加載

咱們在index入口文件中,採用 import語法動態導入文件

const button = document.createElement('button');
button.innerHTML = '關注 webyouxuan';
document.body.appendChild(button);
document.addEventListener('click',()=>{
    import('./a').then(data=>{
        console.log(data.default);
    })
});

再回頭看編譯的結果,貌似好像打包出來的結果有些複雜啦,不要緊!其實核心很簡單就是個jsonp加載文件。

打包出來的結果多了個src_a_js.main.js

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([
  ["src_a_js"],
  { 
    "./src/a.js": module => {
      eval(
        "module.exports = 'webyouxuan'\n\n//# sourceURL=webpack:///./src/a.js?"
      );
    }
  }
]);
最終會經過script標籤加載這個文件,加載後默認會調用 window下的 webpackJsonp的push方法

咱們來看看 main.js 發生了哪些變化,話說有代碼些小複雜,分段來看

先來看index模塊作了那些事,爲了看着方便我來把代碼整理一下

"./src/index.js":
 ((__unusedmodule, __unusedexports, __webpack_require__) => {
eval(`
    const button = document.createElement('button');
    button.innerHTML = '關注 webyouxuan';
    document.body.appendChild(button);
    document.addEventListener('click',()=>{
       __webpack_require__.e("src_a_js").then(
          __webpack_require__.t.bind(__webpack_require__, "./src/a.js", 7)).then(data=>{
            console.log(data.default);
          })
     })
    `);
 })

這裏出現了兩個方法 __webpack_require__.e__webpack_require__.t

__webpack_require__.e方法看似是用來加載文件的,我們來找一找

__webpack_require__.f = {};
__webpack_require__.e = (chunkId) => { // chunkId => src_a_js動態加載的模塊
    return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
        __webpack_require__.f[key](chunkId, promises); // 調用j方法 將數組傳入
        return promises;
    }, []));
};


var installedChunks = {
    "main": 0 // 默認main已經加載完成
};
// f方法上有個j屬性
__webpack_require__.f.j = (chunkId, promises) => {
    var installedChunkData = Object.prototype.hasOwnProperty.call(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
    if(installedChunkData !== 0) {  // 默認installedChunks確定沒有要加載的模塊
        if(installedChunkData) {
            promises.push(installedChunkData[2]);
        } else {
            if(true) { // 建立一個promise 把當前的promise 成功失敗保存到 installedChunks
                var promise = new Promise((resolve, reject) => {
                    installedChunkData = installedChunks[chunkId] = [resolve, reject];
                });
                // installedChunks[src_a_js] = [resolve,reject,promise]
                promises.push(installedChunkData[2] = promise);
                // 這句的意思是看是否配置publicPath,配置了就加個前綴
                var url = __webpack_require__.p + __webpack_require__.u(chunkId);
                // 1)建立script標籤
                var script = document.createElement('script');
                var onScriptComplete;
                script.charset = 'utf-8';
                script.timeout = 120;
                script.src = url; // 2)開始加載這個文件
                var error = new Error();
                onScriptComplete = function (event) { //  完成工做
                    script.onerror = script.onload = null;
                    clearTimeout(timeout);
                    var reportError = loadingEnded();
                    if(reportError) {
                        var errorType = event && (event.type === 'load' ? 'missing' : event.type);
                        var realSrc = event && event.target && event.target.src;
                        error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
                        error.name = 'ChunkLoadError';
                        error.type = errorType;
                        error.request = realSrc;
                        reportError(error);
                    }
                };
                var timeout = setTimeout(function(){ // 超時工做
                    onScriptComplete({ type: 'timeout', target: script });
                }, 120000);
                script.onerror = script.onload = onScriptComplete;
                document.head.appendChild(script); // 3)將標籤插入到頁面
            } else installedChunks[chunkId] = 0;

        }
    }
};
雖然代碼量比較多,其實核心就幹了一件事 : 建立script標籤,文件加載回來那確定就會調用push方法咯!

先跳過這段看下面的

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([
  ["src_a_js"],
  { 
    "./src/a.js": module => {
      eval(
        "module.exports = 'webyouxuan'\n\n//# sourceURL=webpack:///./src/a.js?"
      );
    }
  }
]);
function webpackJsonpCallback(data) { // 3) 文件加載後會調用此方法
    var chunkIds = data[0]; // data是什麼來着,你看看src_a_js怎麼寫的你就知道了 看上面! ["src_a_js"]
    var moreModules = data[1]; // 獲取新增的模塊

    var moduleId, chunkId, i = 0, resolves = [];
    for(;i < chunkIds.length; i++) {
        chunkId = chunkIds[i];
        if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
            // installedChunks[src_a_js] = [resolve,reject,promise] 這個是上面作的
            // 很好理解 其實就是取到剛纔放入的promise的resolve方法
            resolves.push(installedChunks[chunkId][0]);
        }
        installedChunks[chunkId] = 0; // 模塊加載完成
    }
    for(moduleId in moreModules) { // 將新增模塊與默認的模塊進行合併 也是就是modules模塊,這樣modules中就多了動態加載的模塊
        if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
            __webpack_require__.m[moduleId] = moreModules[moduleId];
        }
    }
    if(runtime) runtime(__webpack_require__);
    if(parentJsonpFunction) parentJsonpFunction(data);

    while(resolves.length) { // 調用promise的resolve方法,這樣e方法就調用完成了
        resolves.shift()();
    }
};

var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; // 1) window["webpackJsonp"]等於一個數組
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback; // 2) 重寫了數組的push方法
var parentJsonpFunction = oldJsonpFunction;

加載的文件會調用 webpackJsonpCallback方法,內部就是將新增的模塊合併到modules上,而且讓__webpack_require__.e完成

__webpack_require__.t = function(value, mode) { // t方法其實很簡單就是
    if(mode & 1) value = this(value); // 就是調用__webpack_require__加載最新的模塊
};

這樣用戶就能夠拿到新增的模塊結果啦~~~,源碼雖難,可是多看幾遍總會有收穫!

到此咱們就將webpack5的懶加載功能整個過了一遍,其實思路和webpack4的懶加載同樣呢~,不過不得不說webpack5打包出來的代碼更加簡潔啦! 期待webpack5正式發版!!!

圖片描述

相關文章
相關標籤/搜索