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 相关文章推荐
JSON语法五大要素图文介绍
Dec 04 Javascript
制作jquery遮罩层效果导航菜单代码分享
Dec 25 Javascript
JS使用oumousemove和oumouseout动态改变图片显示的方法
Mar 31 Javascript
jQuery插件Tmpl的简单使用方法
Apr 27 Javascript
js+css实现的圆角边框TAB选项卡滑动门代码分享(2款)
Aug 26 Javascript
纯javascript实现分页(两种方法)
Aug 26 Javascript
javascript表达式和运算符详解
Feb 07 Javascript
JS沙箱模式实例分析
Sep 04 Javascript
D3.js实现拓扑图的示例代码
Jun 30 Javascript
基于Vue全局组件与局部组件的区别说明
Aug 11 Javascript
vue点击按钮实现简单页面的切换
Sep 08 Javascript
四十九个javascript小知识实用技巧
Nov 20 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
第七节--类的静态成员
2006/11/16 PHP
PHP将session信息存储到数据库的类实例
2015/03/04 PHP
使用php自动备份数据库表的实现方法
2017/07/28 PHP
判断是否输入完毕再激活提交按钮
2006/06/26 Javascript
学习YUI.Ext 第六天--关于树TreePanel(Part 1)
2007/03/10 Javascript
JavaScript中函数声明优先于变量声明的实例分析
2012/03/01 Javascript
js 单击式的下拉菜单效果实例
2013/08/13 Javascript
javascript解析json数据的3种方式
2014/05/08 Javascript
javascript二维数组转置实例
2015/01/22 Javascript
javascript中substring()、substr()、slice()的区别
2015/08/30 Javascript
基于AngularJS实现页面滚动到底自动加载数据的功能
2015/10/16 Javascript
基于javascript实现窗口抖动效果
2016/01/03 Javascript
jQuery EasyUI之DataGrid使用实例详解
2016/01/04 Javascript
jQuery form插件之formDdata参数校验表单及验证后提交
2016/01/23 Javascript
基于BootStrap Metronic开发框架经验小结【一】框架总览及菜单模块的处理
2016/05/12 Javascript
d3.js实现简单的网络拓扑图实例代码
2016/11/06 Javascript
微信小程序 支付功能实现PHP实例详解
2017/05/12 Javascript
js+html获取系统当前时间
2017/11/10 Javascript
微信小程序6位或多位验证码密码输入框功能的实现代码
2018/05/29 Javascript
[01:06:19]DOTA2-DPC中国联赛定级赛 LBZS vs SAG BO3第二场 1月8日
2021/03/11 DOTA
python通过邮件服务器端口发送邮件的方法
2015/04/30 Python
全面了解Python环境配置及项目建立
2016/06/30 Python
Python实现通过文件路径获取文件hash值的方法
2017/04/29 Python
Python yield与实现方法代码分析
2018/02/06 Python
Python 实现文件读写、坐标寻址、查找替换功能
2019/09/11 Python
python实现的按要求生成手机号功能示例
2019/10/08 Python
django Layui界面点击弹出对话框并请求逻辑生成分页的动态表格实例
2020/05/12 Python
python与idea的集成的实现
2020/11/20 Python
html5教程实现Photoshop渐变色效果
2013/12/04 HTML / CSS
英国探险旅游专家:Explore
2018/12/20 全球购物
集世界奢侈品和设计师品牌的意大利精品买手店:Tessabit
2019/08/17 全球购物
高一学生期末评语
2014/04/25 职场文书
公司员工安全协议书
2014/11/21 职场文书
教师培训简讯
2015/07/20 职场文书
Windows server 2003卸载和安装IIS的图文教程
2022/07/15 Servers