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 相关文章推荐
Jquery 快速构建可拖曳的购物车DragDrop
Nov 30 Javascript
Eclipse去除js(JavaScript)验证错误
Feb 11 Javascript
将页面table内容与样式另存成excel文件的方法
Aug 05 Javascript
js实现的星星评分功能函数
Dec 09 Javascript
Angular实现form自动布局
Jan 28 Javascript
BootStrap Fileinput初始化时的一些参数
Dec 30 Javascript
在 Angular2 中实现自定义校验指令(确认密码)的方法
Jan 23 Javascript
谈谈JavaScript数组常用方法总结
Jan 24 Javascript
详解EasyUi控件中的Datagrid
Aug 23 Javascript
使用nvm管理不同版本的node与npm的方法
Oct 31 Javascript
原生js实现表格循环滚动
Nov 24 Javascript
JavaScript最完整的深浅拷贝实现方式详解
Feb 28 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
一个用于网络的工具函数库
2006/10/09 PHP
php数组函数序列之asort() - 对数组的元素值进行升序排序,保持索引关系
2011/11/02 PHP
二进制交叉权限微型php类分享
2014/02/07 PHP
PHP 如何获取二维数组中某个key的集合
2014/06/03 PHP
php版阿里大于(阿里大鱼)短信发送实例详解
2016/11/30 PHP
TP5框架使用QueryList采集框架爬小说操作示例
2020/03/26 PHP
javascript中巧用“闭包”实现程序的暂停执行功能
2007/04/04 Javascript
node.js中的buffer.Buffer.isEncoding方法使用说明
2014/12/14 Javascript
javascript实现rgb颜色转换成16进制格式
2015/07/10 Javascript
第三篇Bootstrap网格基础
2016/06/21 Javascript
webpack常用配置项配置文件介绍
2016/11/07 Javascript
Iphone手机、安卓手机浏览器控制默认缩放大小的方法总结(附代码)
2017/08/18 Javascript
利用angular、react和vue实现相同的面试题组件
2018/02/19 Javascript
在vue项目中,使用axios跨域处理
2018/03/07 Javascript
layer.open 按钮的点击事件关闭方法
2018/08/17 Javascript
vue中使用gojs/jointjs的示例代码
2018/08/24 Javascript
如何让node运行es6模块文件及其原理详解
2018/12/11 Javascript
JavaScript对象字面量和构造函数原理与用法详解
2020/04/18 Javascript
使用Python装饰器在Django框架下去除冗余代码的教程
2015/04/16 Python
python3实现ftp服务功能(服务端 For Linux)
2017/03/24 Python
Python计算时间间隔(精确到微妙)的代码实例
2019/02/26 Python
python调用并链接MATLAB脚本详解
2019/07/05 Python
PyQt5的QWebEngineView使用示例
2020/10/20 Python
美国玩具公司:U.S.Toy
2018/05/19 全球购物
优衣库台湾官网:UNIQLO台湾
2019/02/01 全球购物
美国家用和厨房电器销售网站:Appliances Connection
2020/01/24 全球购物
大学新生军训感言
2014/02/25 职场文书
道路建设实施方案
2014/03/18 职场文书
垃圾桶标语
2014/06/24 职场文书
企业读书活动总结
2014/06/30 职场文书
乡镇干部党的群众路线教育实践活动个人对照检查材料
2014/09/24 职场文书
2014县政府领导班子三严三实对照检查材料思想汇报
2014/09/26 职场文书
2015年领班工作总结
2015/04/29 职场文书
工资证明格式模板
2015/06/12 职场文书
导游词之鲁迅祖居
2019/10/17 职场文书
javascript之Object.assign()的痛点分析
2022/03/03 Javascript