Webpack-异步加载(懒加载)实现原理

328 阅读2分钟

Webpack中懒加载的方式

1.第一种:import()

import('./a').then((data) => {
  console.log(data);
});

2.第二种:require.ensure()

const home = resolve => {
  require.ensure(['./home.vue'], () => {
    resolve(require('./home.vue'))
  }, 'home')
}

const routes = [
  {
    path: '/',
    component: home
  }
]

分析

  1. webpack版本4.43.0(公司用的这个版本)

构建项目

  • package.json
{
  "name": "webpack-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack --config ./webpack.config.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "webpack": "^5.76.2",
    "webpack-cli": "^5.0.1"
  },
  "devDependencies": {}
}

  • webpack.config.js
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
}
  • a.js
const a = 'a模块';
export default a;
  • index.js
import('./a').then((data) => {
  console.log(data);
});

代码

  1. 有删减只剩下觉得有用的部分

index.js被编译后

__webpack_require__
    .e(/*! import() */ 0)
    .then(__webpack_require__.bind(null, /*! ./a */ \"./a.js\"))
    .then((data) => {console.log(data);});
  • 将import()编译成了__webpack_require__.e函数

webpack_require.e函数

  1. 疑问点:这里只是将resolve 和 reject 全部存入了 installedChunks,并没有执行resolve
// 加载过的js
var installedChunks = {
  "main": 0
}

__webpack_require__.e = function requireEnsure(chunkId) {
  var promises = [];

  var installedChunkData = installedChunks[ chunkId ];
  /**
   * 等于0代表加载过了直接传入一个空数组
   * 等于promises代表正在加载,继续传入Promise.all
   * 等于null代表还未加载过
   */
  if (installedChunkData !== 0) {

    if (installedChunkData) {
      promises.push(installedChunkData[ 2 ]);
    } else {
      var promise = new Promise(function (resolve, reject) {
        // 将resolve, reject放入installedChunks的对应chunkId中
        installedChunkData = installedChunks[ chunkId ] = [ resolve, reject ];
      })

      promises.push(installedChunkData[ 2 ] = promise);
      // 创建script标签-jsonp
      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);

      var error = new Error();
      // 加载完成后
      onScriptComplete = function (event) {
        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;
            error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
            error.name = 'ChunkLoadError';
            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);
};

0.bundle.js

  1. 这里调用了window["webpackJsonp"].push()函数
  2. push()执行完后最后会回到onScriptComplete清除定时器
(window[ "webpackJsonp" ] = window[ "webpackJsonp" ] || []).push([ [ 0 ], {
  "./a.js":
    (function (module, __webpack_exports__, __webpack_require__) {
      "use strict";
      eval("__webpack_require__.r(__webpack_exports__);\nconst a = 'a模块';\n/* harmony default export */ __webpack_exports__[\"default\"] = (a);\n\n//# sourceURL=webpack:///./a.js?");
    })

} ]);

window["webpackJsonp"].push()

  1. installedChunks\[chunkId] 置为0
function webpackJsonpCallback(data) {
  var chunkIds = data[ 0 ];
  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 ]) {
      // 保存resolve
      resolves.push(installedChunks[ chunkId ][ 0 ]);
    }
    //  把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);

  // 执行resolve
  while (resolves.length) {
    resolves.shift()();
  }
};

var jsonpArray = window[ "webpackJsonp" ] = window[ "webpackJsonp" ] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();

总结

  • Webpack的懒加载就是依赖于Jsonp方式直接引入+Promise来实现的
  • 最后说一句,第一次写文章如果有错误请@一下,会立马改正的,谢谢啦🙏