本文针对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拿到值

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

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

 

 

webpack4.X源码解析之懒加载的更多相关文章

  1. mybatis源码解析之Configuration加载(四)

    概述 上一篇文章,我们主要讲了datasource的相关内容,那么<environments>标签下的内容就看的差不多了,今天就来看一下在拿到transationManager和datas ...

  2. mybatis源码解析之Configuration加载(五)

    概述 前面几篇文章主要看了mybatis配置文件configuation.xml中<setting>,<environments>标签的加载,接下来看一下mapper标签的解析 ...

  3. mybatis源码解析之Configuration加载(三)

    概述 上一篇我们主要分析了下<environments>标签下面,transactionManager的配置,上问最后还有个遗留问题:就是在设置事物管理器的时候有个autocommit的变 ...

  4. mybatis源码解析之Configuration加载(二)

    概述 上一篇我们讲了configuation.xml中几个标签的解析,例如<properties>,<typeAlises>,<settings>等,今天我们来介绍 ...

  5. mybatis源码解析之Configuration加载(一)

    概要 上一篇,我们主要搭建了一个简单的环境,这边我们主要来分析下mybatis是如何来加载它的配置文件Configuration.xml的. 分析 public class App { public ...

  6. Spring源码解析-配置文件的加载

    spring是一个很有名的java开源框架,作为一名javaer还是有必要了解spring的设计原理和机制,beans.core.context作为spring的三个核心组件.而三个组件中最重要的就是 ...

  7. 从SpringBoot源码分析 配置文件的加载原理和优先级

    本文从SpringBoot源码分析 配置文件的加载原理和配置文件的优先级     跟入源码之前,先提一个问题:   SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数( ...

  8. 05 flask源码剖析之配置加载

    05 Flask源码之:配置加载 目录 05 Flask源码之:配置加载 1.加载配置文件 2.app.config源码分析 3.from_object源码分析 4. 总结 1.加载配置文件 from ...

  9. 五、spring源码阅读之ClassPathXmlApplicationContext加载beanFactory

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml&q ...

随机推荐

  1. MySql(四)SQL注入

    MySql(四)SQL注入 一.SQL注入简介 1.1 SQL注入流程 1.2 SQL注入的产生过程 1.2.1 构造动态字符串 转义字符处理不当 类型处理不当 查询语句组装不当 错误处理不当 多个提 ...

  2. 通过Portainer统一管理不同服务器的Docker

    通过Portainer统一管理不同服务器的Docker 一.可视化管理工具Portainer的安装 二.跨服务器管理Docker 2.1开启2375监听端口 2.2Portainer配置远程管理 一. ...

  3. GeoMesa 环境搭建

    GeoMesa 环境搭建 版本 虚拟机安装 os centos7 Centos安装 CentOS安装Jdk并配置环境变量 hadoop.hbase环境部署 geomesa_hbase部署 geoser ...

  4. linux shell 判断空字符串的几种方法!

    在书写linux shell 脚本我们经常会遇到,对一个字符串是否为空进行判断,下面我对几种常用的方法进行了一个总结: 1.-z判断 -z string True if the length of s ...

  5. 1.二层常用技术-STP

    1.STP定义: STP(Spanning Tree Protocol)是生成树协议的英文缩写.STP在IEEE 802.1D文档中定义,该协议的原理是按照树的结构来构造网络拓扑,消除网络中的环路,避 ...

  6. Codeforces Round #594 (Div. 2) D1 - The World Is Just a Programming Task

    思路:枚举换的位置i,j 然后我们要先判断改序列能否完全匹配 如果可以 那我们就需要把差值最大的位置换过来 然后直接判断就行

  7. 2020 ICPC Universidad Nacional de Colombia Programming Contest

    2020 ICPC Universidad Nacional de Colombia Programming Contest A. Approach 三分 显然答案可以三分,注意\(eps\)还有两条 ...

  8. HttpServletResponse的学习

    关于Response对象的一些方法和属性可以查看官方文档:https://javaee.github.io/javaee-spec/javadocs/ 比如里面定义了许多常量: 这些都是服务器向浏览器 ...

  9. poj3252 Round Numbers (数位dp)

    Description The cows, as you know, have no fingers or thumbs and thus are unable to play Scissors, P ...

  10. Codeforces Round #345 (Div. 1) A. Watchmen (数学,map)

    题意:给你\(n\)个点,求这\(n\)个点中,曼哈顿距离和欧几里得距离相等的点对数. 题解: 不难发现,当两个点的曼哈顿距离等于欧几里得距离的时候它们的横坐标或者纵坐标至少有一个相同,可以在纸上画一 ...