本文针对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. (一)Spring-Boot-操作-Redis

    Spring-Boot-操作-Redis 1.Spring Data Redis 1.1 引入依赖 1.2 配置 Redis 信息 1.3 使用 2.Spring Cache 2.1 引入依赖 2.2 ...

  2. size_t 、wchar_t和 ptrdiff_t

    size_t在C语言中就有了. 它是一种"整型"类型,里面保存的是一个整数,就像int, long那样.这种整数用来记录一个大小(size).size_t的全称应该是size ty ...

  3. 常用的正则表达式@java后台

    package Regex; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @作者 Goofy * @邮件 ...

  4. 从官方文档中探索MySQL分页的几种方式及分页优化

    概览 相比于Oracle,SQL Server 等数据库,MySQL分页的方式简单得多了,官方自带了分页语法 limit 语句: select * from test_t LIMIT {[offset ...

  5. 人均年薪50万以上,docker到底是什么?为什么这么火?

    为什么要使用Docker? 场景一:公司双十一买了一堆服务器,技术总监让你给它们一个个都配置上JDK.Mysql.Redis等软件环境. 你心里小声嘀咕:"这总监不讲武德!"然后你 ...

  6. validate插件

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  7. Codeforces Round #682 (Div. 2)【ABCD】

    比赛链接:https://codeforces.com/contest/1438 A. Specific Tastes of Andre 题意 构造一个任意连续子数组元素之和为子数组长度倍数的数组. ...

  8. zjnu1726 STOGOVI (lca)

    Description Mirko is playing with stacks. In the beginning of the game, he has an empty stack denote ...

  9. EGADS介绍(二)--时序模型和异常检测模型算法的核心思想

    EDADS系统包含了众多的时序模型和异常检测模型,这些模型的处理会输入很多参数,若仅使用默认的参数,那么时序模型预测的准确率将无法提高,异常检测模型的误报率也无法降低,甚至针对某些时间序列这些模型将无 ...

  10. Chapter Zero 0.1.3 其他单元设备以及运作流程

    其他单元设备 五大单元中的控制单元.算数逻辑段元都被整合到CPU的封装中, 但其实系统单元中,不止有CPU(控制单元.算数逻辑单元), 计算机单元还有哪些? 系统单元:系统单元包括CPU.主存储器(内 ...