18.重学webpack——webpack动态加载原理/webpack异步加载原理/webpack动态解析import()原理

  • Post author:
  • Post category:其他






【重学webpack系列——webpack5.0】


1-15节主要讲webpack的使用,当然,建议结合

《webpack学完这些就够了》

一起学习。

从16节开始,专攻webpack原理,只有深入原理,才能学到webpack设计的精髓,从而将技术点运用到实际项目中。

可以

点击上方专栏订阅

哦。

以下是本节正文:



import()

对于webpack来说,是一个天然的分割点,也就是说,webpack碰到import()会对import()进行解析。怎么解析的,过程主要是:


面试题:webpack解析import()的原理/webpack懒加载原理/webpack异步加载原理

:

  1. 首先,

    webpack

    遇到

    import方法

    时,会将其当成一个代码分割点,也就是说碰到

    import方法

    了,那么就去解析

    import方法

  2. 然后,

    import

    引用的文件,

    webpack

    会将其编译成一个

    jsonp

    ,也就是一个自执行函数,然后函数内部是引用的文件的内容,因为到时候是通过

    jsonp

    的方法去加载的。

  3. 具体就是,

    import

    引用文件,会先调用

    require.ensure

    方法(打包的结果来看叫

    require.e

    ),这个方法主要是构造一个

    promise

    ,会将

    resolve



    reject



    promise

    放到一个数组中,将

    promise

    放到一个队列中。

  4. 然后,调用

    require.load

    (打包结果来看叫

    require.l

    )方法,这个方法主要是创建一个

    jsonp

    ,也就是创建一个

    script

    标签,标签的

    url

    就是文件加载地址,然后塞到

    document.head

    中,一塞进去,就会加载该文件了。

  5. 加载完,就去执行这段

    jsonp

    ,主要就是把

    moduleId



    module

    内容存到modules数组中,然后再去走

    webpack

    内置的

    require


  6. webpack

    内置的

    require

    ,主要是先判断缓存,这个

    moduleId

    是否缓存过了,如果缓存过了,就直接返回。如果没有缓存,再继续往下走,也就是加载

    module

    内容,然后最终内容会挂在都

    module,exports

    上,返回

    module.exports

    就返回了引用文件的最终执行结果。

  • 入口文件
import(/* webpackChunkName: "title"*/'./title').then(result => {
  console.log(result);
})
  • 依赖文件
module.exports = "title"
  • webpack编译结果及分析(缩减过后)
var modules = {}
var cache = {}

function require(moduleId) {
  var cachedModule = cache[moduleId];
  if (cachedModule) {
    return cachedModule.exports; // 最终返回的都是module.exports
  }
  var module= cache[moduleId] = {
    exports: {}
  }
  modules[moduleId](module, module.exports, require);
  return module.exports;
}
// 定义查找代码块的方法
require.find = {};
// 通过JSONP一部加载指定的代码块
require.ensure = (chunkId) => {
  let promises = [];
  require.find.jsonp(chunkId, promises); // 在jsonp中会创建一个promise,并且添加到promises数组中
  return Promise.all(promises);
}
require.publicPath = ''; // 资源文件的访问路径,默认是空字符串,值从webpack.config.js的output的publicPath中去找
require.unionFileName = (chunkId) => { // 统一文件名
  return "" + chunkId + ".js"; // title.js
}
require.load = (url) => {
  let script = document.createElement('script');
  script.src = url;
  document.head.appendChild(script); //一旦append之后,浏览器会立刻请求脚本
}
// 已经安装或者加载好的或者加载中的chunk
var installedChunks = {
  "main": 0, // key为“main”表示主入口文件,0表示ok,也就是已经加载就绪
  // "title": [resolve, reject, promise]
}
require.find.jsonp = (chunkId, promises) => {
  var installedChunkData;
  let promise = new Promise((resolve, reject) => {
    installedChunkData = installedChunks[chunkId] = [resolve, reject];
    /*
      这个相当于installedChunks值变成了
      {
        "main": 0,
        "title": [resolve, reject, promise]
      }
    */
  })
  installedChunkData[2] = promise;
  promises.push(promise);
  var url = require.publicPath + require.unionFileName(chunkId); //title.js
  require.load(url); // 常见script,然后塞入document.head,然后会自动加载该文件
}
var webpackJsonpCallback = ([chunkIds, moreModules]) => {
  var resolves = [];
  for (let i = 0; i < chunkIds.length; i++) {
    let chunkId = chunkIds[i];
    resolves.push(installedChunks[chunkId][0]);//把chunk对应的promise的resolve方法添加到resolves数组里去
    installedChunks[chunkId][0] = 0; // 表示已经添加完成
  }
  for (let moduleId in moreModules) {
    modules[moduleId] = moreModules[moduleId];
  }
  while(resolves.length){
    resolves.shift()(); // 让promise的resolve执行,让promise成功
  }
}
// 由于打包好了依赖文件title.js是被
var chunkLoadingGlobal = self["webpackChunk_1_webpack_bundle"] = self["webpackChunk_1_webpack_bundle"] || [];
// 重写数组的push方法
chunkLoadingGlobal.push = webpackJsonpCallback;
require.ensure("title") // 先加载代码块,这里的参数"title"就是魔法字符串中的webpackChunkName的值title
  .then(require.bind(require, "./src/title.js")) // require模块
  .then(result => { // 获取结果
    console.log(result);
  })