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 相关文章推荐
表单(FORM)的一些实用效果代码
Mar 25 Javascript
javascript cookies操作集合
Apr 12 Javascript
js 动态文字滚动的例子
Jan 17 Javascript
jquery图片上下tab切换效果
Mar 18 Javascript
JS简单实现文件上传实例代码(无需插件)
Nov 15 Javascript
微信小程序 window_x64环境搭建
Sep 30 Javascript
简单几步实现返回顶部效果
Dec 05 Javascript
JavaScript闭包和回调详解
Aug 09 Javascript
React + webpack 环境配置的方法步骤
Sep 07 Javascript
Vue组件开发技巧总结
Mar 04 Javascript
Node.js Event Loop各阶段讲解
Mar 08 Javascript
vue 表单输入框不支持focus及blur事件的解决方案
Nov 17 Vue.js
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/12/06 PHP
PHP 基于文件头的文件类型验证类函数
2012/05/01 PHP
php metaphone()函数的定义和用法
2016/05/15 PHP
php使用json-schema模块实现json校验示例
2019/09/28 PHP
JavaScript 异步调用框架 (Part 2 - 用例设计)
2009/08/03 Javascript
Jquery中getJSON在asp.net中的使用说明
2011/03/10 Javascript
JavaScript 盒模型 尺寸深入理解
2012/12/31 Javascript
下拉列表select 由左边框移动到右边示例
2013/12/04 Javascript
BootStrap 图标icon符号图标glyphicons不正常显示的快速解决办法
2016/12/08 Javascript
jQuery实现 上升、下降、删除、添加一行代码
2017/03/06 Javascript
vue教程之toast弹框全局调用示例详解
2020/08/24 Javascript
详解Angular中通过$location获取地址栏的参数
2018/08/02 Javascript
详解Require.js与Sea.js的区别
2018/08/05 Javascript
优雅的将ElementUI表格变身成树形表格的方法步骤
2019/04/11 Javascript
使用vue实现多规格选择实例(SKU)
2019/08/23 Javascript
让 python 命令行也可以自动补全
2014/11/30 Python
Python 批量合并多个txt文件的实例讲解
2018/05/08 Python
使用python实现快速搭建简易的FTP服务器
2018/09/12 Python
Python2和Python3的共存和切换使用
2019/04/12 Python
详解python路径拼接os.path.join()函数的用法
2019/10/09 Python
Python 矩阵转置的几种方法小结
2019/12/02 Python
Python对Tornado请求与响应的数据处理
2020/02/12 Python
Django 实现将图片转为Base64,然后使用json传输
2020/03/27 Python
python3中datetime库,time库以及pandas中的时间函数区别与详解
2020/04/16 Python
安装多个版本的TensorFlow的方法步骤
2020/04/21 Python
如何在python中执行另一个py文件
2020/04/30 Python
CSS3实现头像旋转效果
2017/03/13 HTML / CSS
详解HTML5中download属性的应用
2015/08/06 HTML / CSS
高性能钓鱼服装:Huk Gear
2019/02/20 全球购物
JavaScript实现页面动态验证码的实现示例
2021/03/23 Javascript
电子商务个人职业生涯规划范文
2014/02/12 职场文书
中学优秀班主任事迹材料
2014/05/01 职场文书
平面设计师岗位职责
2014/09/18 职场文书
2014年卫生监督工作总结
2014/12/09 职场文书
吧主申请感言怎么写
2015/08/03 职场文书
JavaScript小技巧带你提升你的代码技能
2021/09/15 Javascript