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 相关文章推荐
基于jquery1.4.2的仿flash超炫焦点图播放效果
Apr 20 Javascript
jquery1.4.2 for Visual studio 2010 模板文件
Jul 14 Javascript
JavaScript实现鼠标点击后层展开效果的方法
May 13 Javascript
基于JavaScript实现鼠标悬浮弹出跟随鼠标移动的带箭头的信息层
Jan 18 Javascript
几种经典排序算法的JS实现方法
Mar 25 Javascript
js实现开启密码大写提示
Dec 21 Javascript
windows下vue.js开发环境搭建教程
Mar 20 Javascript
详解从angular-cli:1.0.0-beta.28.3升级到@angular/cli:1.0.0
May 22 Javascript
create-react-app构建项目慢的解决方法
Mar 14 Javascript
Vue.js 表单控件操作小结
Mar 29 Javascript
Vue 监听元素前后变化值实例
Jul 29 Javascript
js基于div丝滑实现贝塞尔曲线
Sep 23 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
坏狼php学习 计数器实例代码
2008/06/15 PHP
Yii把CGridView文本框换成下拉框的方法
2014/12/03 PHP
yii2中使用Active Record模式的方法
2016/01/09 PHP
thinkPHP中多维数组的遍历方法
2016/01/09 PHP
PHP 布尔值的自增与自减的实现方法
2018/05/03 PHP
Laravel框架Blade模板简介及模板继承用法分析
2019/12/03 PHP
jquery中ajax学习笔记4
2011/10/16 Javascript
javascript实现验证IP地址等相关信息代码
2015/05/10 Javascript
深入理解MVC中的时间js格式化
2016/05/19 Javascript
详解JavaScript中数组的reduce方法
2016/12/02 Javascript
微信小程序 两种滑动方式(横向滑动,竖向滑动)详细及实例代码
2017/01/13 Javascript
nodejs个人博客开发第七步 后台登陆
2017/04/12 NodeJs
vue keep-alive请求数据的方法示例
2018/05/16 Javascript
小程序多图列表实现性能优化的方法步骤
2019/05/28 Javascript
Vue 2.0 侦听器 watch属性代码详解
2019/06/19 Javascript
基于axios 的responseType类型的设置方法
2019/10/29 Javascript
vue实现图片按比例缩放问题操作
2020/08/11 Javascript
原生js实现下拉框选择组件
2021/01/20 Javascript
[00:52]黑暗之门更新 新英雄孽主驾临DOTA2
2016/08/24 DOTA
Python使用装饰器进行django开发实例代码
2018/02/06 Python
python自动分箱,计算woe,iv的实例代码
2019/11/22 Python
python已协程方式处理任务实现过程
2019/12/27 Python
Python求凸包及多边形面积教程
2020/04/12 Python
python+requests接口压力测试500次,查看响应时间的实例
2020/04/30 Python
使用html5 canvas绘制圆环动效
2019/06/03 HTML / CSS
大学生毕业自我评价范文分享
2013/11/11 职场文书
大学生学业生涯规划
2014/01/05 职场文书
个人授权委托书
2014/04/03 职场文书
导师工作推荐信范文
2014/05/17 职场文书
计算机毕业生自荐信
2014/06/12 职场文书
大学生学习面向未来的赶考思想汇报
2014/09/12 职场文书
民主生活会对照检查材料(统计局)
2014/09/21 职场文书
2014年学生会干事工作总结
2014/11/07 职场文书
教你利用Nginx 服务搭建子域环境提升二维地图加载性能的步骤
2021/09/25 Servers
详解Python中*args和**kwargs的使用
2022/04/07 Python
JS实现简单九宫格抽奖
2022/06/28 Javascript