webpack是如何实现模块化加载的方法


Posted in Javascript onNovember 06, 2019

webpack支持的模块规范有 AMD 、CommonJS、ES2015 import 等规范。不管何种规范大致可以分为同步加载和异步加载两种情况。本文将介绍webpack是如何实现模块管理和加载。

同步加载如下:

import a from './a';
console.log(a);

异步加载如下:

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

webpacks实现的启动函数,直接将入口程序module传入启动函数内,并缓存在闭包内,如下:

(function(modules){
  ......
  // 加载入口模块并导出(实现启动程序)
  return __webpack_require__(__webpack_require__.s = 0);
})({
  0: (function(module, __webpack_exports__, __webpack_require__) {
    module.exports = __webpack_require__(/*! ./src/app.js */"./src/app.js");
  })
})

webpack在实现模块管理上不管服务端还是客户端大致是一样,主要由installedChunks记录已经加载的chunk,installedModules记录已经执行过的模块,具体如下:

/**
 * module 缓存器
 * key 为 moduleId (一般为文件路径)
 * value 为 module 对象 {i: moduleId, l: false, exports: {}}
 */
var installedModules = {};
/**
 * chunks加载状态记录器
 * key 一般为 chunk 索引
 * value undefined:未加载 0:已经加载 (客户端特有 null: 准备加载 [resolve, reject]: 加载中)
 */
var installedChunks = {
  "app": 0
}

不管是服务端还是客户端同步加载的方法都一样,主要是检测installedModules中是否已经缓存有要加载的module,有则直接返回,否则就创建一个新的module,并执行返回module.exports,具体实现如下:

// 编译后的同步加载
__webpack_require__(/*! ./src/app.js */"./src/app.js");

// 加载模块的方法,即require方法
function __webpack_require__(moduleId) {
  // 检查当前的module是否已经存在缓存中
  if(installedModules[moduleId]) {
    return installedModules[moduleId].exports; // 直接返回已缓存的 module.exports
  }
  // 创建一个新的 module, 并添加到缓存中
  var module = installedModules[moduleId] = {
    i: moduleId,
    l: false, // 是否已经加载
    exports: {} // 暴露的对象
  };
  // 执行当前 module 的方法
  modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  // 标记 module 加载完成状态
  module.l = true;
  // 返回 module 暴露的 exports 对象
  return module.exports;
}

服务端的异步加载是通过node的require方法加载chunk并返回一个promises对象。所有chunk都是暴露出ids和modules对象,具体实现如下:

// 编译后的异步加载方法
__webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./c.js */ "./src/c.js"))

// chunk 0 代码如下(即0.js的代码)
exports.ids = [0];
exports.modules = {
  "./src/c.js": (function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    __webpack_exports__["default"] = (function () {
      console.log('c');
    })
  })
}

// 异步加载模块方法
__webpack_require__.e = function requireEnsure(chunkId) {
  var promises = [];
  if(installedChunks[chunkId] !== 0) {
    var chunk = require("./" + ({}[chunkId]||chunkId) + ".js");
    var moreModules = chunk.modules, chunkIds = chunk.ids;
    for(var moduleId in moreModules) {
      modules[moduleId] = moreModules[moduleId];
    }
    for(var i = 0; i < chunkIds.length; i++)
      installedChunks[chunkIds[i]] = 0;
  }
  return Promise.all(promises);
}

客户端的异步加载是通过JSONP原理进行加载资源,将chunk内容([chunkIds, modules])存到全局的webpackJsonp数组中,并改造webpackJsonp的push方法实现监听chunk加载完成事件。具体实现如下:

// 编译后的异步加载方法
__webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./c.js */ "./src/c.js"))

// chunk 0 代码如下(即0.js的代码)
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{
  "./src/c.js": (function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    __webpack_exports__["default"] = (function () {
      console.log('c');
    });
  })
}]);

// 加载成功的回调函数
function webpackJsonpCallback(data) {
  var chunkIds = data[0];
  var moreModules = data[1];
  
  // 将本次加载回来的 chunk 标记为加载完成状态,并执行回调
  var moduleId, chunkId, i = 0, resolves = [];
  for(;i < chunkIds.length; i++) {
    chunkId = chunkIds[i];
    if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
      resolves.push(installedChunks[chunkId][0]); // 将chunk成功回调添加到要执行的队列中
    }
    installedChunks[chunkId] = 0; // 将chunk标记为加载完成
  }
  // 将本次加载回来的 module 添加到全局的 modules 对象
  for(moduleId in moreModules) {
    if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
      modules[moduleId] = moreModules[moduleId];
    }
  }
  // 判断 webpackJsonp 数组原始的push方法是否存在,存在则将数据追加到webpackJsonp中
  if(parentJsonpFunction) parentJsonpFunction(data);
  // 执行所有 chunk 回调
  while(resolves.length) {
    resolves.shift()();
  }
};

// 加载完成监听方法的实现
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]);
var parentJsonpFunction = oldJsonpFunction;

// 异步加载模块方法
__webpack_require__.e = function requireEnsure(chunkId) {
  var promises = [];
  var installedChunkData = installedChunks[chunkId];
  if(installedChunkData !== 0) { // 0 时表示已经安装完成
    if(installedChunkData) { // 加载中
      promises.push(installedChunkData[2]);
    } else {
      // 创建一个回调的Promise,并将Promise缓存到installedChunks中
      var promise = new Promise(function(resolve, reject) {
        installedChunkData = installedChunks[chunkId] = [resolve, reject];
      });
      promises.push(installedChunkData[2] = promise);
      
      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) { // 加载完成回调
        // 避免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;
            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);
};

更多可以查看编译后的代码 客户端、服务端

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
实现只能输入数字的input不用replace方法
Sep 12 Javascript
jQuery把表单元素变为json对象
Nov 06 Javascript
判断文件是否正在被使用的JS代码
Dec 21 Javascript
jQuery实现的一个tab切换效果内部还嵌有切换
Aug 10 Javascript
12306验证码破解思路分享
Mar 25 Javascript
基于Vue.js实现简单搜索框
Mar 26 Javascript
node.js发送邮件email的方法详解
Jan 06 Javascript
JavaScript中数组Array.sort()排序方法详解
Mar 01 Javascript
vue-router重定向不刷新问题的解决
Jun 25 Javascript
vuedraggable+element ui实现页面控件拖拽排序效果
Jul 29 Javascript
详解React项目如何修改打包地址(编译输出文件地址)
Mar 21 Javascript
webpack-mvc 传统多页面组件化开发详解
May 07 Javascript
node读写Excel操作实例分析
Nov 06 #Javascript
详解vue页面首次加载缓慢原因及解决方案
Nov 06 #Javascript
electron 安装,调试,打包的具体使用
Nov 06 #Javascript
weui中的picker使用js进行动态绑定数据问题
Nov 06 #Javascript
在vue中阻止浏览器后退的实例
Nov 06 #Javascript
vue 关闭浏览器窗口的时候,清空localStorage的数据示例
Nov 06 #Javascript
vue项目强制清除页面缓存的例子
Nov 06 #Javascript
You might like
LotusPhp笔记之:基于ObjectUtil组件的使用分析
2013/05/06 PHP
解决Laravel5.2 Auth认证退出失效的问题
2019/10/14 PHP
JavaScript 无符号右移赋值操作
2009/04/17 Javascript
基于jquery的获取mouse坐标插件的实现代码
2010/04/01 Javascript
Jquery进度条插件 Progress Bar小问题解决
2011/07/12 Javascript
Node.js中使用事件发射器模式实现事件绑定详解
2014/08/15 Javascript
jquery的总体架构分析及实现示例详解
2014/11/08 Javascript
jQuery中队列queue()函数的实例教程
2016/05/03 Javascript
Javascript中构造函数要注意的一些坑
2017/01/23 Javascript
js仿微博动态栏功能
2017/02/22 Javascript
js实现各浏览器全屏代码实例
2018/07/03 Javascript
详解Vue Elementui中的Tag与页面其它元素相互交互的两三事
2018/09/25 Javascript
ES6 Map结构的应用实例分析
2019/06/26 Javascript
详细教你微信公众号正文页SVG交互开发技巧
2019/07/25 Javascript
详解Vue.js 作用域、slot用法(单个slot、具名slot)
2019/10/15 Javascript
vue把输入框的内容添加到页面的实例讲解
2019/11/11 Javascript
element表格翻页第2页从1开始编号(后端从0开始分页)
2019/12/10 Javascript
JS运算符优先级与表达式示例详解
2020/09/04 Javascript
tensorflow构建BP神经网络的方法
2018/03/12 Python
Python中创建二维数组
2018/10/17 Python
python中嵌套函数的实操步骤
2019/02/27 Python
python常用数据重复项处理方法
2019/11/22 Python
python实现在线翻译功能
2020/03/03 Python
Python语法垃圾回收机制原理解析
2020/03/25 Python
Python可视化工具如何实现动态图表
2020/10/23 Python
详解Canvas实用库Fabric.js使用手册
2019/01/07 HTML / CSS
乌克兰品牌化妆品和香水在线商店:Bomond
2020/01/14 全球购物
建筑实习自我鉴定
2013/10/18 职场文书
初中生期末考试的自我评价
2013/12/17 职场文书
面临毕业的毕业生自荐书范文
2014/02/05 职场文书
中医学专业自荐信范文
2014/04/01 职场文书
地球一小时宣传标语
2014/06/24 职场文书
门面房租房协议书
2014/08/20 职场文书
2014年群众路线教育实践活动整改措施
2014/09/24 职场文书
js实现上传图片到服务器
2021/04/11 Javascript
Arthas排查Kubernetes中应用频繁挂掉重启异常
2022/02/28 MySQL