Webpack--动态 import 原理及源码分析
前言
在平时的开发中,我们经常使用 import()实现代码分割和懒加载。在低版本的浏览器中并不支持动态 import(),那 webpack 是如何实现 import() polyfill 的?
原理分析
我们先来看看下面的 demo
function component() {const btn = document.createElement("button");btn.onclick = () => {import("./a.js").then((res) => {console.log("动态加载a.js..", res);});};btn.innerHTML = "Button";return btn;
}document.body.appendChild(component());
点击按钮,动态加载 a.js脚本,查看浏览器网络请求可以发现,a.js请求返回的内容如下:

简单看,实际上返回的就是下面这个东西:
(self["webpackChunkwebpack_demo"] =self["webpackChunkwebpack_demo"] || []).push([["src_a_js"],{"./src/a.js": () => {},},
]);
从上面可以看出 3 点信息:
-
1.webpackChunkwebpack_demo 是挂到全局 window 对象上的属性
-
2.webpackChunkwebpack_demo 是个数组
-
3.webpackChunkwebpack_demo 有个 push 方法,用于添加动态的模块。当
a.js脚本请求成功后,这个方法会自动执行。
再来看看 main.js 返回的内容

仔细观察,动态 import 经过 webpack 编译后,变成了下面的一坨东西:
__webpack_require__.e("src_a_js").then(__webpack_require__.bind(__webpack_require__, "./src/a.js")).then((res) => {console.log("动态加载a.js..", res);});
上面代码中,__webpack_require__ 用于执行模块,比如上面我们通过webpackChunkwebpack_demo.push添加的模块,里面的./src/a.js函数就是在__webpack_require__里面执行的。
__webpack_require__.e函数就是用来动态加载远程脚本。因此,从上面的代码中我们可以看出:
-
首先 webpack 将动态 import 编译成
__webpack_require__.e函数 -
__webpack_require__.e函数加载远程的脚本,加载完成后调用__webpack_require__函数 -
__webpack_require__函数负责调用远程脚本返回来的模块,获取脚本里面导出的对象并返回
源码分析及实现
如何动态加载远程模块
在开始之前,我们先来看下如何使用 script 标签加载远程模块
var inProgress = {};
// url: "http://localhost:8080/src_a_js.main.js"
// done: 加载完成的回调
const loadScript = (url, done) => {if (inProgress[url]) {inProgress[url].push(done);return;}const script = document.createElement("script");script.charset = "utf-8";script.src = url;inProgress[url] = [done];var onScriptComplete = (prev, event) => {var doneFns = inProgress[url];delete inProgress[url];script.parentNode && script.parentNode.removeChild(script);doneFns && doneFns.forEach((fn) => fn(event));if (prev) return prev(event);};script.onload = onScriptComplete.bind(null, script.onload);document.head.appendChild(script);
};
loadScript(url, done) 函数比较简单,就是通过创建 script 标签加载远程脚本,加载完成后执行 done 回调。inProgress用于避免多次创建 script 标签。比如我们多次调用loadScript('http://localhost:8080/src_a_js.main.js', done)时,应该只创建一次 script 标签,不需要每次都创建。这也是为什么我们调用多次 import('a.js'),浏览器 network 请求只看到家在一次脚本的原因
实际上,这就是 webpack 用于加载远程模块的极简版本。
__webpack_require__.e 函数的实现
首先我们使用installedChunks对象保存动态加载的模块。key 是 chunkId
// 存储已经加载和正在加载的chunks,此对象存储的是动态import的chunk,对象的key是chunkId,值为
// 以下几种:
// undefined: chunk not loaded
// null: chunk preloaded/prefetched
// [resolve, reject, Promise]: chunk loading
// 0: chunk loaded
var installedChunks = {main: 0,
};
由于 import() 返回的是一个 promise,然后import()经过 webpack 编译后就是一个__webpack_require__.e函数,因此可以得出__webpack_require__.e返回的也是一个 promise,如下所示:
const scriptUrl = document.currentScript.src.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/");__webpack_require__.e = (chunkId) => {return Promise.resolve(ensureChunk(chunkId, promises));
};const ensureChunk = (chunkId) => {var installedChunkData = installedChunks[chunkId];if (installedChunkData === 0) return;let promise;// 1.如果多次调用了__webpack_require__.e函数,即多次调用import('a.js')加载相同的模块,只要第一次的加载还没完成,就直接使用第一次的Promiseif (installedChunkData) {promise = installedChunkData[2];} else {promise = new Promise((resolve, reject) => {// 2.注意,此时的resolve,reject还没执行installedChunkData = installedChunks[chunkId] = [resolve, reject];});installedChunkData[2] = promise; //3. 此时的installedChunkData 为[resolve, reject, promise]var url = scriptUrl + chunkId;var error = new Error();// 4.在script标签加载完成或者加载失败后执行loadingEnded方法var loadingEnded = (event) => {if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId)) {installedChunkData = installedChunks[chunkId];if (installedChunkData !== 0) installedChunks[chunkId] = undefined;if (installedChunkData) {console.log("加载失败.....");installedChunkData[1](error); // 5.执行上面的reject,那resolve在哪里执行呢?}}};loadScript(url, loadingEnded, "chunk-" + chunkId, chunkId);}return promise;
};
__webpack_require__.e的主要逻辑在ensureChunk方法中,注意该方法里面的第 1 到第 5 个注释。这个方法创建一个 promise,并调用loadScript方法加载动态模块。需要特别主要的是,返回的 promise 的 resolve 方法并不是在 script 标签加载完成后改变。如果脚本加载错误或者超时,会在 loadingEnded 方法里调用 promise 的 reject 方法。实际上,promise 的 resolve 方法是在脚本请求完成后,在 self["webpackChunkwebpack_demo"].push()执行的时候调用的
如何执行远程模块?
远程模块是通过self["webpackChunkwebpack_demo"].push()函数执行的
前面我们提到,a.js请求返回的内容是一个self["webpackChunkwebpack_demo"].push()函数。当请求完成,会自动执行这个函数。实际上,这就是一个 jsonp 的回调方式。该方法的实现如下:
var webpackJsonpCallback = (data) => {var [chunkIds, moreModules] = data;var moduleId,chunkId,i = 0;for (moduleId in moreModules) {// 1.__webpack_require__.m存储的是所有的模块,包括静态模块和动态模块__webpack_require__.m[moduleId] = moreModules[moduleId];}for (; i < chunkIds.length; i++) {chunkId = chunkIds[i];if (installedChunks[chunkId]) {// 2.调用ensureChunk方法生成的promise的resolve回调installedChunks[chunkId][0]();}// 3.将该模块标记为0,表示已经加载过installedChunks[chunkId] = 0;}
};self["webpackChunkwebpack_demo"] = [];
self["webpackChunkwebpack_demo"].push = webpackJsonpCallback.bind(null);
所有通过import()加载的模块,经过 webpack 编译后,都会被 self["webpackChunkwebpack_demo"].push()包裹。
总结
在 webpack 构建编译阶段,import()会被编译成类似__webpack_require__.e("src_a_js").then(__webpack_require__.bind(__webpack_require__, "./src/a.js"))的调用方式
__webpack_require__.e("src_a_js").then(__webpack_require__.bind(__webpack_require__, "./src/a.js")).then((res) => {console.log("动态加载a.js..", res);});
__webpack_require__.e()方法会创建一个 script 标签用于请求脚本,方法执行完返回一个 promise,此时的 promise 状态还没改变。
script 标签被添加到 document.head 后,触发浏览器网络请求。请求成功后,动态的脚本会自动执行,此时self["webpackChunkwebpack_demo"].push()方法执行,将动态的模块添加到__webpack_require__.m属性中。同时调用 promise 的 resolve 方法改变状态,模块加载完成。
脚本执行完成后,最后执行 script 标签的 onload 回调。onload 回调主要是用于处理脚本加载失败或者超时的场景,并调用 promise 的 reject 回调,表示脚本加载失败
相关文章:
Webpack--动态 import 原理及源码分析
前言 在平时的开发中,我们经常使用 import()实现代码分割和懒加载。在低版本的浏览器中并不支持动态 import(),那 webpack 是如何实现 import() polyfill 的? 原理分析 我们先来看看下面的 demo function component() {const btn docume…...
创新无处不在的便利体验——基于智能视频和语音技术的安防监控系统EasyCVR
随着科技的迅猛发展,基于智能视频和语音技术的EasyCVR智能安防监控系统正以惊人的速度改变我们的生活。EasyCVR通过结合先进的视频分析、人工智能和大数据技术,为用户提供了更加智能、便利的安全保护体验,大大提升了安全性和便利性。本文将介…...
强化IP地址管理措施:确保网络安全与高效性
IP地址管理是网络安全和性能管理的关键组成部分。有效的IP地址管理可以帮助企业确保网络的可用性、安全性和高效性。本文将介绍一些强化IP地址管理的关键措施,以帮助企业提高其网络的安全性和效率。 1. IP地址规划 良好的IP地址规划是强化IP地址管理的基础。它涉及…...
Power Automate-创建审批流
提前在SharePoint上创建好对应的表 在创建中选择自动化云端流 选择当创建项时触发 选择站点和列表,再点击添加新步骤 搜索审批,点击进入 操作里的选项区别: 1)创建审批:创建一个审批任务 2)等待审批&…...
商越科技:渗透测试保障平台安全,推动线上采购高效运转
商越科技是数字化采购解决方案提供商,在同赛道企业中始终保持前列。商越科技通过自主研发的智能采购中台、SaaS应用及运营服务等为企业搭建专属的互联网采购平台,帮助企业实现采购数字化以及智能化转型,提高工作效率、降低采购成本。 打造数字…...
Java10新增特性
特性列表 Java 10是Java的一个主要版本更新,引入了许多新功能和改进。以下是一些Java 10的新增特性: 局部变量类型推断:Java 10引入了局部变量类型推断,允许开发者使用关键字"var"来声明局部变量,而无需指定…...
Hive 知识点八股文记录 ——(一)特性
Hive通俗的特性 结构化数据文件变为数据库表sql查询功能sql语句转化为MR运行建立在hadoop的数据仓库基础架构使用hadoop的HDFS存储文件实时性较差(应用于海量数据)存储、计算能力容易拓展(源于Hadoop) 支持这些特性的架构 CLI&…...
如何使用PHP替换回车为br
1、使用PHP内置的nl2br()函数 nl2br()函数是PHP内置的函数,可以将任何字符串中的回车符(\n)替换为HTML中的换行符(br)。具体使用方法如下: $string "这里有一个\n换行符"; $string nl2br($str…...
Unity 场景优化策略
Unity 场景优化策略 GPU instancing 使用GPU Instancing可以将多个网格相同、材质相同、材质属性可以不同的物体合并为一个批次,从而减少Draw Calls的次数。这可以提高性能和渲染效率。 GPU instancing可用于绘制在场景中多次出现的几何体,例如树木或…...
Wireshark在Windows上安装后报错怎么办?
Wireshark是一个非常好的网络抓包分析工具,有了他可以轻松解决网络问题,大家有没有使用过呢? 在生产环境使用过的朋友是否各种windows系统安装时遇到各种问题?比如说缺少某某文件,我们经常的做法是找个DLL放在System32…...
【Proteus仿真】【51单片机】水质监测报警系统设计
文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真51单片机控制器,使用按键、LED、蜂鸣器、LCD1602、PCF8591 ADC、PH传感器、浑浊度传感器、DS18B20温度传感器、继电器模块等。 主要功能: 系统运行后&…...
TensorFlow2.0教程3-CNN
` 文章目录 基础CNN网络读取数据卷积层池化层全连接层模型配置模型训练CNN变体网络简单的深度网络添加了其它功能层的深度卷积NIN网络文本卷积基础CNN网络 读取数据 import numpy as np import tensorflow as tf import tensorflow.keras as keras import tensorflow.keras.la…...
flink1.18.0 sql-client报错
报错 Flink SQL> > > select * from t1; [ERROR] Could not execute SQL statement. Reason: java.lang.ClassNotFoundException: org.apache.kafka.clients.consumer.OffsetResetStrategy 解决 注意 一定要重启flink服务 否则还会报错: Flink SQL> select *…...
基于ssm的校园快递物流管理系统(java+jsp+ssm+javabean+mysql+tomcat)
博主24h在线,想要源码文档部署视频直接私聊,9.9拿走! 基于javawebmysql的ssm校园快递物流管理系统(javajspssmjavabeanmysqltomcat) 运行环境: Java≥8、MySQL≥5.7、Tomcat≥8 开发工具: eclipse/idea/myeclipse/s…...
C++:this指针和构造与析构的运用
目录 一,this指针 二,构造函数 三,析构函数 四,析构与构造的调用 一,this指针 首先,我们先观察以下类: #include <iostream> using namespace std; class Date { public: void In…...
通用工作站设计方案 :807-ORI-S3R500 -多路PCIe3.0的单CPU通用工作站
ORI-S3R500 -多路PCIe3.0的单CPU通用工作站 (研华工业计算机IPC-610,IPC940 升级款) 一、机箱功能和技术指标: 系统 系统型号 ORI-SR500 主板支持 EEB(12*13)/CEB(12*10.5)/ATX(12*9.6)/Mi cro ATX 前置硬盘 最大支持2个3.5寸1个2.5寸SATA …...
机器学习写代码时遇到的问题(23.11.9)
AttributeError: module ‘backend_interagg‘ has no attribute ‘FigureCanvas‘ 导包的时候改一下 import matplotlib matplotlib.use(TkAgg) import matplotlib.pyplot as plt UserWarning: Glyph 27425 (\N{CJK UNIFIED IDEOGRAPH-6B21}) missing from current font. …...
C#学习系列之事件
C#学习系列之事件 前言事件发布者和订阅者事件触发和注册事件声明事件订阅事件触发使用 总结 前言 基础学习。 事件 发布者和订阅者 发布者:通知某件事情发生的。 订阅者:对某件事情关注的。 事件触发和注册 触发:事件发生就通知所有关…...
list部分接口模拟实现(c++)
List list简介list基本框架list构造函数list_node结构体的默认构造list类的默认构造 push_back()iteartor迭代器迭代器里面的其他接口const迭代器通过模板参数实现复用operator->() insert()erase()clear()析构函数迭代器区间构造拷贝构造operator() list简介 - list可以在…...
数据结构(C语言) 实验-栈与字符串
删除子串 字符串采用带头结点的链表存储,设计算法函数void delstring(linkstring s, int i,int len) 在字符串s中删除从第i个位置开始,长度为len的子串。 void delstring(linkstring s, int i, int len) {linkstring p,q,r;int cnt 1;p s->next;wh…...
AI宏观因子模型:强美元与高利率预期共振下,黄金价格出现2%回撤机制解析
摘要:本文通过构建多因子宏观定价模型,结合利率路径预测、美元指数动态权重调整以及能源通胀传导机制,分析在强美元与高利率预期共振背景下,黄金价格阶段性回调的驱动逻辑与市场行为特征。一、价格动量模型触发回撤,黄…...
CSS 悬停箭头跳动问题的根源与稳定解决方案
按钮悬停时通过 :after 伪元素显示 font awesome 箭头,但鼠标移出瞬间箭头会短暂下移再消失——这是因 :hover 中意外触发 display: flex 导致布局重排所致,只需统一基础态与悬停态的 display 行为并强化垂直对齐即可彻底消除抖动。 按钮悬停时通过…...
GAN训练稳定性与DCGAN架构最佳实践
1. GAN训练稳定性挑战与核心解决思路生成对抗网络(GAN)的训练过程本质上是一个动态博弈系统,由生成器(Generator)和判别器(Discriminator)两个神经网络相互对抗、共同进化。这种特殊的架构设计带来了令人惊叹的生成能力,同时也造成…...
模力方舟:本土化AI开发平台的崛起与HuggingFace的中国挑战
在AI技术快速发展的今天,模型平台的选择已成为决定开发效率与商业价值的关键因素。全球知名的HuggingFace平台虽然拥有庞大的模型资源库,但在中国市场的本土化适配、算力支持与工程化落地方面正面临严峻挑战。与此同时,依托Gitee开源生态成长…...
86253
825747...
STC8单片机驱动ESP-01S联网实战:从AT指令到GET请求获取苏宁时间(附完整源码)
STC8单片机与ESP-01S物联网开发实战:从AT指令到云端数据获取 在嵌入式物联网开发中,如何让传统单片机快速接入互联网一直是个热门话题。STC8作为国内广泛使用的51内核单片机,与ESP-01S WiFi模块的组合,为开发者提供了一种经济高效…...
MySQL触发器在主从架构下的表现_MySQL触发器主从同步策略
触发器在从库默认不执行,主从复制仅同步binlog事件而非触发器逻辑,从库需手动创建且受read_only和DEFINER权限限制;可靠替代方案是应用层双写、binlog监听或定时对账。触发器在从库上默认不执行MySQL主从复制只同步 binlog 事件,不…...
60美元AI眼镜TikTok爆卖160万!中国品牌正在改写智能穿戴格局
在跨境电商的世界里,真正的爆款往往不是那些堆砌高端配置的“黑科技”,而是能够用极致性价比让前沿科技走进普通人生活的产品。最近,TikTok美区数码产品榜单上出现了一匹令人瞩目的黑马——一款售价仅60美元左右的AI智能眼镜,在一…...
AVM环视拼接效果总不满意?可能是鸟瞰图微调没做好(附Blender可视化调整实战)
AVM环视拼接效果优化:鸟瞰图微调与Blender可视化实战 环视监控系统(AVM)已成为现代智能汽车的标配功能,但许多工程师在完成基础拼接后常遇到一个尴尬局面——系统"能用"却不够"好用"。鸟瞰图视角生硬、拼接缝…...
PyTorch对抗训练超快
💓 博客主页:瑕疵的CSDN主页 📝 Gitee主页:瑕疵的gitee主页 ⏩ 文章专栏:《热点资讯》 PyTorch对抗训练的超快实现:从理论到实践的效率革命目录PyTorch对抗训练的超快实现:从理论到实践的效率革…...
