异步加载的原理、作用

原理

  • 简单写一个demo看看打包后结果
js 复制代码
// index.js
import('./async.js').then(module => console.log(module, 'module'))
// async.js
export default 'async--'

打包后产物(部分一)

js 复制代码
(() => {
  var __webpack_modules__ = {
    './src/async.js': ((module, exports, __webpack_require__) => {
        eval("__webpack_require__.e('src_async_js').then(__webpack_require__.bind(__webpack_require__, './src/async.js')).then(module => console.log(module))")
    })
    // 由此可见 import()会变成调用__webpack_required__.e(path1).then(() => __webpack_require__.bind(__webpack_required__, path2))
  }
  // 缓存所有modules 包括async类型
  var __webpack_module_cache__ = {}
  function __webpack_require__(moduleId) {
    var cachedModule = __webpack_module_cache__[moduleId]
    if (cachedModule !== undefined) {
      return cachedModule.exports
    }
    var module = __webpack_module_cache__[moduleId] = {
      i: moduleId,
      l: false,
      exports: {}
    }
    // 执行函数
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__)
    return module.exports
  }
})()
  • 总结这部分,就是根据moduleId(也就是路径如./src/index.js)判断是否有缓存,有直接返回,没有则执行编译好的函数并将__webpack_require__也传入,供递归调用。还会有一个外部变量记录module的缓存。
  • 执行到这里会去找__webpack_require__.e函数

打包后产物(部分二)

js 复制代码
__webpack_require__.f.j = (chunkId, promises) => {
  // JSONP chunk loading for javascript
  var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined
  if (installedChunkData !== 0) { // 0 表示已经加载过
    if (installedChunkData) { // 存在表示正在加载
      promises.push(installedChunkData[2])
    } else {
      if (true) {
        var promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]))
        promises.push(installedChunkData[2] = promise)

        // 拿到script脚本src的需要用的完整路径
        var url = __webpack_require__.p + __webpack_require__.u(chunkId)
        var error = new Error()
        var loadingEnded = (event) => {
          if (__webpack_require__.o(installedChunks, chunkId)) {
            installedChunkData = installedChunks[chunkId]
            if (installedChunkData !== 0) installedChunks[chunkId] = undefined
            if (installedChunkData) {
              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
              // 报错则reject掉这个promise
              installedChunkData[1](error)
            }
          }
        }
        // 创建script元素开始加载
        __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId)
      }
    }
  }
}

(() => {
  __webpack_require__.f = {}
  __webpack_require__.e = (chunkId) => {
    return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
      __webpack_require__.f[key](chunkId, promises) // 调用__webpack_require__.f.j函数
      return promises
    }, []))
  }

})();
  • 总结这部分,webpack_require.e函数会根据外部变量installedChunks以及chunkId判断是否加载过,0表示已经加载完成,存在promise表示正在加载中,undefined表示没有加载过
  • 对于没有加载过的创建一个promise,并且创建一个script元素,将src赋值给当前异步chunk的完整url,会注册回调函数,成功则resolve,失败或报错则reject

打包产物(部分三)

js 复制代码
// src_async.js 开始执行
(self["webpackChunkwebpack_test"] = self["webpackChunkwebpack_test"] || []).push([["src_async_js"],{
    "./src/async.js":
    ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
        eval('...')
    }]);
 
// 接着执行这部分代码
var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
  var [chunkIds, moreModules, runtime] = data
  var moduleId, chunkId, i = 0
  if (chunkIds.some((id) => (installedChunks[id] !== 0))) {
    for (moduleId in moreModules) {
      if (__webpack_require__.o(moreModules, moduleId)) {
        // 异步chunk也缓存
        __webpack_require__.m[moduleId] = moreModules[moduleId]
      }
    }
  }
  if (parentChunkLoadingFunction) parentChunkLoadingFunction(data)
  for (; i < chunkIds.length; i++) {
    chunkId = chunkIds[i]
    if (__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
      installedChunks[chunkId][0]() // 加载成功调用resolve

    }
    installedChunks[chunkId] = 0 // 标记为加载完成
  }
}
  • 打包后异步导入的单独生产一个chunk,该chunk调用的push被重写了。
  • 执行到这里就说明加载完成可以resolve了(如果存在promise),同时也对异步的chunk进行缓存。
  • reoslve后接下来继续执行.then()方法调用__webpack_require__加载异步模块。

作用

  • 减少包体积,会单独提成一个chunk
  • 并非立即加载,当使用的时候才会去加载
相关推荐
孤水寒月1 小时前
给自己网站增加一个免费的AI助手,纯HTML
前端·人工智能·html
CoderLiu1 小时前
用这个MCP,只给大模型一个figma链接就能直接导出图片,还能自动压缩上传?
前端·llm·mcp
伍哥的传说1 小时前
鸿蒙系统(HarmonyOS)应用开发之实现电子签名效果
开发语言·前端·华为·harmonyos·鸿蒙·鸿蒙系统
海的诗篇_2 小时前
前端开发面试题总结-原生小程序部分
前端·javascript·面试·小程序·vue·html
uncleTom6662 小时前
前端地图可视化的新宠儿:Cesium 地图封装实践
前端
lemonzoey2 小时前
无缝集成 gemini-cli 的 vscode 插件:shenma
前端·人工智能
老家的回忆2 小时前
jsPDF和html2canvas生成pdf,组件用的elementplus,亲测30多页,20s实现
前端·vue.js·pdf·html2canvas·jspdf
半点寒12W2 小时前
uniapp全局状态管理实现方案
前端
Vertira2 小时前
pdf 合并 python实现(已解决)
前端·python·pdf
PeterJXL3 小时前
Chrome 下载文件时总是提示“已阻止不安全的下载”的解决方案
前端·chrome·安全