本文针对Webpack懒加载构建和加载的原理,对构建后的源码进行分析。

一.准备工作

首先,init之后创建一个简单的webpack基本的配置,在src目录下创建两个js文件(一个主入口文件和一个非主入口文件)和一个html文件,package.json,webpack.config.js公共部分代码如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<button id="oBtn">按钮加载</button>
</body>
</html>
//入口js文件
let oBtn=document.getElementById('oBtn')
oBtn.addEventListener('click',function(){
import (/*webpackChunkName:"index1"*/'./index1.js').then((index1)=>{
console.log(index1)
})
})
//非入口文件
module.exports="我是张三"
//package.json
{
"name": "mywebpack",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"html-webpack-plugin": "4.5.0",
"webpack": "4.44.2",
"webpack-cli": "3.3.12"
}
}
//webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
devtool: 'none',
mode: 'development',
entry: './src/index.js',
output: {
filename: 'built.js',
path: path.resolve('dist')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
}

二.执行打包操作

yarn webpack执行操作打包之后生成built.js.index1.built.js,index.html,

从上文中入口js的代码可分析出 import (/*webpackChunkName:"index1"*/'./index1.js')可以实现指定的懒加载操作。

下面是打包之后的源码index.html,built.js,index1.built.js

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<button id="oBtn">按钮加载</button>
<script src="built.js"></script></body>
</html>
 //built.js
(function(modules) { // webpackBootstrap
// install a JSONP callback for chunk loading
function webpackJsonpCallback(data) {
var chunkIds = data[0];
var moreModules = data[1];
// add "moreModules" to the modules object,
// then flag all "chunkIds" as loaded and fire callback
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]);
}
installedChunks[chunkId] = 0;
}
for(moduleId in moreModules) {
if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
if(parentJsonpFunction) parentJsonpFunction(data);
while(resolves.length) {
resolves.shift()();
}
};
// The module cache
var installedModules = {};
// object to store loaded and loading chunks
// undefined = chunk not loaded, null = chunk preloaded/prefetched
// Promise = chunk loading, 0 = chunk loaded
var installedChunks = {
"main": 0
};
// script path function
function jsonpScriptSrc(chunkId) {
return __webpack_require__.p + "" + chunkId + ".built.js"
}
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// This file contains only the entry chunk.
// The chunk loading function for additional chunks
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
// JSONP chunk loading for javascript
var installedChunkData = installedChunks[chunkId];
if(installedChunkData !== 0) { // 0 means "already installed".
// a Promise means "currently loading".
if(installedChunkData) {
promises.push(installedChunkData[2]);
} else {
// setup Promise in chunk cache
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
promises.push(installedChunkData[2] = promise);
// start chunk loading
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);
// create error before stack unwound to get useful stacktrace later
var error = new Error();
onScriptComplete = function (event) {
// avoid mem leaks in 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);
};
// expose the modules object (__webpack_modules__)
__webpack_require__.m = modules;
// expose the module cache
__webpack_require__.c = installedModules;
// define getter function for harmony exports
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
// define __esModule on exports
__webpack_require__.r = function(exports) {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
// create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like require
__webpack_require__.t = function(value, mode) {
if(mode & 1) value = __webpack_require__(value);
if(mode & 8) return value;
if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
var ns = Object.create(null);
__webpack_require__.r(ns);
Object.defineProperty(ns, 'default', { enumerable: true, value: value });
if(mode & 2 && typeof value != 'string')
      for(var key in value) __webpack_require__.d(ns, key, function(key) {
       return value[key];
}.bind(null, key));
return ns;
}; // getDefaultExport function for compatibility with non-harmony modules
__webpack_require__.n = function(module) {
var getter = module && module.__esModule ?
function getDefault() { return module['default']; } :
function getModuleExports() { return module; };
__webpack_require__.d(getter, 'a', getter);
return getter;
};
// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
__webpack_require__.p = "";
__webpack_require__.oe = function(err) { console.error(err); throw err; };
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;
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
/************************************************************************/
({
"./src/index.js":
(function(module, exports, __webpack_require__) {
//入口js文件
let oBtn=document.getElementById('oBtn')
oBtn.addEventListener('click',function(){
__webpack_require__.e(/*! import() | index1 */ "index1")
.then(__webpack_require__.t.bind(null, /*! ./index1.js */ "./src/index1.js", 7))
.then((index1)=>{
console.log(index1)
})
})
})
});
//index1.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["index1"],{
"./src/index1.js":
(function(module, exports) {
module.exports="我是张三"
})
}]);

三.打包之后代码块解析

__webpack_require__.e  实现jsonp加载内容 利用promise来实现异步加载操作

__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
//判断installedChunks是否存在对应id的值
var installedChunkData = installedChunks[chunkId];
      //0 表示已经加载 promise 表示正在加载 undefined 表示没有加载
if(installedChunkData !== 0) {
// a Promise means "currently loading".
if(installedChunkData) {
          //把完整的promise push到数组中  
promises.push(installedChunkData[2]);
} else {
// 不存在创建promise
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
          //把完整的promise push进数组
promises.push(installedChunkData[2] = promise);
// 创建script标签
var script = document.createElement('script');
var onScriptComplete;
script.charset = 'utf-8';
script.timeout = 120;
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
          //设置src
script.src = jsonpScriptSrc(chunkId);
var error = new Error();
onScriptComplete = function (event) {
// avoid mem leaks in 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);
};

该方法主要判断chunkId对应的模块是否已经加载了如果已经加载了,就不再重新加载;

如果模块没有被加载过,但模块处于正在被加载的过程,不再重复加载,直接将加载模块的promise返回。

如果模块没有被加载过,也不处于加载过程,就创建一个promise,并将resolve、reject、promise构成的数组存储在上边说过的installedChunks缓存对象属性中。然后创建一个script标签加载对应的文件,加载超时时间是2分钟。如果script

文件加载失败,触发reject(对应源码中:chunk[1](error),chunk[1]就是上边缓存的数组的第二个元素reject),并将installedChunks缓存对象中对应key的值设置为undefined,标识其没有被加载。

//定义变量存放数组 window里面是否有webpackJsonp的方法,如果没有设置为[]数组
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
//oldJsonpFunction保存旧的jsonpArray
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
//重新定义push方法
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
var parentJsonpFunction = oldJsonpFunction;

上述代码主要是定义了一个全局变量jsonpArray,并且重新定义了push方法,改变量为一个数组,该数组变量的原生push方法被复写为webpackJsonpCallback方法,该方法是懒加载实现的一个核心方法,作用是把子模块调用自定义的push方法

(webpackJsonpCallback)添加进去。

//该push方法实际上就是webpackJsonpCallback方法
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["index1"],{
"./src/index1.js":
(function(module, exports) {
module.exports="我是张三"
})
}]);

webpackJsonpCallback函数分析

function webpackJsonpCallback(data) {
//获取需要被加载的模块id
var chunkIds = data[0];
//获取需要被动态加载的模块依赖
var moreModules = data[1];
var moduleId, chunkId, i = 0, resolves = [];
//循环判断 chunkIds 里对应的模块内容是否已经完成了加载
for(;i < chunkIds.length; i++) {
chunkId = chunkIds[i];
//判断installedChunks[chunkId]是否存在,并且installedChunks的对象属性中有chunkId的值(正在加载中.)
if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
//把resolve放进数组表明已加载完成
resolves.push(installedChunks[chunkId][0]);
}
//将chunkId对应的值设置为0 表明已经加载过
installedChunks[chunkId] = 0;
}
//对模块进行合并
for(moduleId in moreModules) {
if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
//加载后续内容
if(parentJsonpFunction) parentJsonpFunction(data);
//如果存在调用resolves方法,执行后续的then操作
while(resolves.length) {
resolves.shift()();
}
};

定义webpackJsonpCallback 实现:合并模块定义,改变 promise 状态执行后续行为,

参数data为一个数组。有两个元素:第一个元素是要懒加载文件中所有模块的chunkId组成的数组;第二个参数是一个对象,对象的属性和值分别是要加载模块的moduleId和模块代码函数,

该函数的作用:

(1)遍历参数中的chunkId:

判断installedChunks缓存变量中对应chunkId的属性值:如果是真,说明模块正在加载,因为从上边分析中可以知道,installedChunks[chunkId]只有一种情况是真,那就是在对应的模块正在加载时,会将加载模块创建的promise组合

一个数组[resolve, reject, proimise]赋值给installedChunks[chunkId]。将resolve存入resolves变量中。将installedChunks中对应的chunkId置为0,该模块标识为已经被加载过。

(2)遍历参数中模块属性

将模块代码函数存储到modules中,该modules是入口文件built.js中自执行函数的参数。

(3)调用parentJsonpFunction(原生push方法)加载模块后续内容

(4)遍历resolves,执行所有promise的resolve

__webpack_require__.t方法解析

__webpack_require__.t = function(value, mode) {
if(mode & 1) value = __webpack_require__(value);
if(mode & 8) return value;
if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
var ns = Object.create(null);
__webpack_require__.r(ns);
Object.defineProperty(ns, 'default', { enumerable: true, value: value });
if(mode & 2 && typeof value != 'string')
for(var key in value)
__webpack_require__.d(ns, key, function(key) {
return value[key];
}.bind(null, key));
return ns;
};

__webpack_require__.t方法为懒加载导出页面的最后一道工序,它主要是做了这么几件事情

*1 接收两个参数,一个是value一般用于表示被加载模块id,第二个值mode是一个二进制数值

*2 t方法内部做的第一件事情就是调用自定义的require方法加载value对应的模块导出,重新赋值给value

*3 当获取到value值之后 余下的84 2 ns都是对当前的内容进行加工处理,然后返回使用

*4 当mode & 8成立直接将返回(比如0111 1000 不成立)(commonjs规范)

*5 当mode & 4 成立时直接将value返回 (esmodule)

*6 如果上述条件都不成立,还是继续处理value,定义一个ns {}

* 6-1 如果拿到的value是一个可以直接使用的内容,例如是一个字符串,将它挂载到ns的default属性上

* 6-2 如果不是一个简单数据类型调用d方法添加getter属性外部可以通过ns.name拿到值

/**********************************************************************************************************************************************************************************************************************************************************************************/

快乐很简单,就是春天的鲜花,夏天的绿荫,秋天的野果,冬天的漫天飞雪。

 

 

最新文章

  1. 2017预防bug的重要性
  2. 每天一个 Linux 命令(18):locate 命令
  3. PHP语言基础(标记、注释、变量、数组、常量、函数)
  4. C++中使用接口
  5. sublime3 注册码
  6. HTTPConnection与HTTPClient的区别
  7. objective-c在Xcode中@property相关参数的解释
  8. LREM key count value
  9. (转) 新手入门:C/C++中的结构体
  10. Repository在DDD中的应用
  11. 无法删除MySql数据库,报错1010 error dropping
  12. STL中关于map和set的四个问题?
  13. 学习java接口知识
  14. appium 与 selenium python解决python &#39;WebElement&#39; object does not support indexing 报错问题问题
  15. mongoose 连接数据库操作
  16. SSIS使用事务回滚
  17. 关于Unity中网格导航与寻路
  18. 20145315何佳蕾《网络对抗》MSF基础应用
  19. linux的客户端安装步骤配置
  20. Python和MySQL数据库交互PyMySQL

热门文章

  1. Spring Boot 整合 Freemarker
  2. Java 跨域 Json字符转类对象
  3. JYM虚拟机性能监控与故障处理工具
  4. (4)Linux常用的运维平台和工具
  5. nginx教程&lt;二&gt;(高可用)
  6. Maven三种打包方式jar war pom
  7. Educational Codeforces Round 17
  8. Codeforces Global Round 8 D. AND, OR and square sum(位运算)
  9. poj3757 Training little cats
  10. c#小灶——9.算术运算符