最新要闻
- 环球热资讯!《进击的巨人》最终季完结篇开播 前篇拿下豆瓣9.7分:改编超越原作
- 核心部件100%国产!东风新一代商乘通用氢燃料电池产品完成
- 世界热文:同档网络无敌!一加Ace 2V搭载自研的游戏云专网技术
- 代表建议试行十二年义务教育:中考定终身太早 拒绝小学初中内卷
- 一男子和峨眉山猴子练搏击互殴 当事人回应:条件反射、请勿模仿
- 精选! 《狂飙》“大嫂”高叶上手小米13:徕卡人像实拍样张美呆
- 天天时讯:3端通用!腾讯视频VIP会员年卡148元到手
- 当前快讯:董明珠称格力不看文凭只看能力:研究生到一线生产线非人才
- 济南动物园网红小黑猩猩“柒仔”去世:初步研判系突发疾病死亡
- 每日热议!奔驰4S店展车充新车 女子上门维权:被骗了、将走法律程序
- 天天快资讯丨突发!蔚来前实习生称遭同事强奸未遂 反被拒绝留用!公司:已成立调查组
- “清籁”琴
- 灭蟑螂小窍门厨房_灭蟑螂小窍门
- 【新要闻】如何去除新家的甲醛_如何去除新家的甲醛
- 环球热头条丨外星人AW620M无线鼠标上市:26000 DPI、140小时长续航
- 世界简讯:PC优化翻车!《卧龙:苍天陨落》官方道歉:将尽快推出更新补丁
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
定位解析一个因脚本劫持导致webpack动态加载异常的问题
问题描述
项目现场的前端项目在点击顶部的导航栏切换不同的模块时,会有小概率出现模块加载报错的情况:
我们的前端项目里是有基于react-loadable
做的懒加载的,上图的12.be789340.chunk.js
就是懒加载需要请求的模块。现场复现问题时出错的模块每次都可能不一样,并且出现问题的频率也挺稳定的,差不多每一二十次就会出现一次这种情况。
(资料图片)
在复现出问题时,再看到网络请求的面板:
可以看到,先是有一个正常的js文件请求,接着会再发出一个相同地址的请求但后缀带上了个从没见过的参数。并且看到在最右侧一列,第二个请求发出的地方是12.be789340.chunk.js:3
,是在上一个js文件里发出的!
看完请求面板这里,再结合控制台的(missing: xxx.js)
报错,几乎可以断定是我们的js脚本被第三方劫持了。劫持了第一个请求后将里边的内容都替换为自己的,加载完后执行的就是它们的代码,然后再重新发送一次请求,这次请求加载到的内容才是我们前端项目里真正的代码。并且还带上了参数用来标识。
webpack动态加载原理
虽然第一个js脚本的请求被劫持了,但不是接着就发送了第二个请求去加载真正的js内容了吗?为何还会报上图的错误呢。这要从webpack动态加载模块的实现说起。
懒加载模块是利用ES10的新特性import()
方法来完成的,经过webpack编译后如下:
// 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 javascriptvar 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 cachevar promise = new Promise(function(resolve, reject) {installedChunkData = installedChunks[chunkId] = [resolve, reject];});promises.push(installedChunkData[2] = promise);// start chunk loadingvar head = document.getElementsByTagName("head")[0];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);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;var error = new Error("Loading chunk " + chunkId + " failed.\n(" + errorType + ": " + realSrc + ")");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;head.appendChild(script);}}return Promise.all(promises);};
对于需要加载的模块chunkId
,流程如下:
设置
installedChunkData[chunkId]
,标记该模块正在加载。创建
<script/>
标签,并插入页面中,开始加载js脚本。加载完js脚本后会立即执行。在由webpack打包出来的chunk中,会执行
webpackJsonpCallback
函数。在该函数中,会修改installedChunks[chunkId] = 0
,并且还会执行installedChunks[chunkId]
数组中的第一个函数也就是上面那个promise
的resolve
函数,将__webpack_require__.e
函数中返回的promise
变成成功状态。webpackJsonpCallback
函数的代码如下:// install a JSONP callback for chunk loadingfunction webpackJsonpCallback(data) {var chunkIds = data[0];var moreModules = data[1];var executeModules = data[2];// add "moreModules" to the modules object,// then flag all "chunkIds" as loaded and fire callbackvar moduleId, chunkId, i = 0, resolves = [];for(;i < chunkIds.length; i++) {chunkId = chunkIds[i];if(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()();}// add entry modules from loaded chunk to deferred listdeferredModules.push.apply(deferredModules, executeModules || []);// run deferred modules when all chunks readyreturn checkDeferredModules();};
执行完后,执行
<script/>
的onload回调,也就是上面的onScriptComplete
函数。如果加载成功会判断到installedChunks[chunkId] === 0
,则无需做任何操作。否则的话,说明资源加载出错,执行reject(error)
抛出异常。
捋清了webpack动态加载chunk文件的流程,导致报错问题的真正原因也就清楚了。我们把导致问题的整个流程也梳理一遍:
- webpack的运行时 向页面中插入需要动态加载的chunk的
<script/>
标签,并添加onload
回调。 <script/>
标签发起请求,但是被拦截了并返回篡改后的代码。- 浏览器接收到篡改后的js脚本后立即执行。由于里面并不是我们前端项目中的chunk的内容,所有并不会有执行
installedChunks[chunkId] = 0
这一步。 - 第[3]步执行完后,触发
<script/>
的onload
回调。在回调函数中,因为判断到installedChunks[chunkId] !== 0
,所以reject(error)
抛出异常。 - 在篡改的代码内容中,最后还会再请求一次真正的chunk内容。而这个chunk中的代码执行后就算设置了
installedChunks[chunkId] = 0
并调用resolve()
也已经没有作用了,因为对应的promise
在前面已经被reject
掉了。
解决办法
- 使用https来加密传输的数据。对于运营商劫持的情况,用https连接就可以很大程度上解决问题。
- 对于笔者的这种情况,是由于项目现场内网环境的一些特殊原因造成的并且没法干预,只能想办法绕开:通过前文对导致报错问题流程的梳理,我们知道是因为第一个执行了篡改内容的
<script/>
提前先触发了onload
回调(即onScriptComplete
函数),才导致了webpack报错。因此我们采用的临时解决办法就是覆写Element.prototype.appendChild
方法,使得在document.head.appendChild(script)
添加<script/>
标签并且资源是属于webpack的动态加载的chunk时,就给原script.onload
的回调加上一个延时后再执行(但不要超过script.timeout
)。因为在chunk中的js代码执行时调用的webpackJsonpCallback
函数会将__webpack_require__.e
中的promise
给resolve
掉,所以onload
回调是否执行并不影响webpack动态加载的流程,回调中的代码只是处理 在出错时能够抛出异常的逻辑而已。
定位解析一个因脚本劫持导致webpack动态加载异常的问题
全球热文:python名称空间和作用域
天天实时:定位Dll加载异常的方法
环球热资讯!《进击的巨人》最终季完结篇开播 前篇拿下豆瓣9.7分:改编超越原作
核心部件100%国产!东风新一代商乘通用氢燃料电池产品完成
世界热文:同档网络无敌!一加Ace 2V搭载自研的游戏云专网技术
第五章 运输层
代表建议试行十二年义务教育:中考定终身太早 拒绝小学初中内卷
一男子和峨眉山猴子练搏击互殴 当事人回应:条件反射、请勿模仿
【全球新要闻】(数据库系统概论|王珊)第九章关系查询处理和关系优化-第二节:查询优化
轻松玩转Makefile | 企业项目级Makefile实例
精选! 《狂飙》“大嫂”高叶上手小米13:徕卡人像实拍样张美呆
天天时讯:3端通用!腾讯视频VIP会员年卡148元到手
当前快讯:董明珠称格力不看文凭只看能力:研究生到一线生产线非人才
济南动物园网红小黑猩猩“柒仔”去世:初步研判系突发疾病死亡
每日热议!奔驰4S店展车充新车 女子上门维权:被骗了、将走法律程序
天天快资讯丨突发!蔚来前实习生称遭同事强奸未遂 反被拒绝留用!公司:已成立调查组
“清籁”琴
环球观点:5-Nacos注册中心
灭蟑螂小窍门厨房_灭蟑螂小窍门
关于目录问题的总结-Python
天天即时看!实现js继承的几种方式以及他们的优缺点
今热点:AI修复图片画质和视频画质的方法
【热闻】数学建模(一):LP 问题
【新要闻】如何去除新家的甲醛_如何去除新家的甲醛
2023年2月随笔-难产的“学系列”
关注:乘风破浪,遇见最佳跨平台跨终端框架.Net Core/.Net生态 - 深入OpenAI与马斯克、微软的秘密,通过API轻松接入ChatGPT
天天热推荐:git clone的时候出现出现 fatal: unable to access 'https://github.com/...':OpenSSL
第125篇: 期约Promise基本特性
环球速递!kong和konga的安装部署
视焦点讯!腾讯云短信的使用方法
06while循环
环球热头条丨外星人AW620M无线鼠标上市:26000 DPI、140小时长续航
世界简讯:PC优化翻车!《卧龙:苍天陨落》官方道歉:将尽快推出更新补丁
环球头条:多线程全面总结
怎么注册微信公众号?
Pro版同款!荣耀Magic5至臻版影像泄露:5000万像素旗舰三摄
今日要闻!《LOL》英雄价格正式调整!全面下调
全球热议:理想L9车主实惨:白天打开星环模式灯 扣1分罚款100元?
iPhone良品率不足50%也不怕 富士康又在印度设立新工厂
小鹏汽车欲靠P7“回血”:老款清库再降3.5万、新车下周上市
降价后真香了!特斯拉中国2月销量出炉:暴涨130%
9.98万起杀疯!比亚迪王朝系列2月销量超10万台:秦PLUS贡献3成
2月新能源汽车销量榜:比亚迪一家占比近4成 第三名暴走
世界资讯:委员蒋胜男谈35岁职场危机:根源是“996” 必须改变
今日讯!中国性能车!全新领克03 TCR赛车官图发布:售价超百万
排查系统执行SQL与数据库直接执行结果不一致的问题
每日资讯:详细剖析|袋鼠云数栈前端框架Antd 3.x 升级 4.x 的踩坑之路
记录--手摸手带你撸一个拖拽效果
天天讯息:VSCode官方的配置同步方案
快报:144MB缓存立大功 AMD锐龙7000X3D内存自由:4800都稳赢i9
环球观点:AMD RX 7900 XT价格全面雪崩:沦落到RTX 4070 Ti的级别
《旷野之息》发售6周年:续作《塞尔达传说:王国之泪》发布新预告
当前视点!中国围棋第一人易主!李轩豪超越柯洁 AI立大功
环球快讯:vivo推出“手语翻译官”应用:准确率可达80%以上
网络通信——TCP “三次握手“、“四次挥手“ 详解
PHP语言在线代码运行编译工具推荐
今日报丨JavaScript 回调函数属于闭包?
全球快看:ChatGPT开放API,上来就干到最低价,可以人手一个ChatGPT了
全球新动态:Spring事务使用注意事项
【全球独家】限制儿童支付金额方便了!微信青少年模式升级:一键开启上线
三排七座!仰望U8内饰曝光:比亚迪首款百万豪车来了
世界热门:刚失败一次后 日本不放弃:新一代运载火箭尝试再度发射
热到离谱?首个冲上20℃的北方省会诞生 下周或破30℃
欧洲2035年禁售燃油车要黄?德国公然反对!意大利:我也不同意
环球精选!登月用!中国新一代载人火箭预计2027年首飞
天天即时:《狂飙》“大嫂”高叶上手小米13:女神持机美如画
全球观速讯丨火山引擎 DataTester:A/B 实验如何实现人群智能化定向?
环球快资讯丨Redis分布式锁常见坑点分析
世界今日讯!eas里客户端保存,提交里增加校验规则和必填
访问者模式
世界微动态丨网友偶遇眼镜王蛇求助 博物杂志:务必远离、打输住院打赢坐牢
世界今亮点!Vtuber因直播《霍格沃茨之遗》被骚扰 宣布毕业
天天讯息:委员建议研究生招生规模动态扩大:缓解考研难
全球聚焦:收个滴滴Offer:从小伙三面经历,看看需要学点啥?
环球热资讯!Study for Go! Chapter one - Type
环球最新:手写模拟Spring底层原理-Bean的创建与获取
速看:兰博基尼领衔 今年值得期待的7款跑车 买不起还不能看看?
女子试用期被辞退 现场给HR普法:金句频出网友点赞称解气
每日聚焦:靠ChatGPT年入百万!合法还不限学历专业:一般人我不告诉他(doge)
全球新消息丨韩系车日子不好过!起亚狮铂拓界限时优惠:降3万还给大礼包
zip文件结构
头条:与时俱进推动智慧城市建设,智慧管网监测加强城市治理能力
全球视讯!Java项目集成工作流activiti,会签
简单介绍Python中如何给字典设置默认值
播报:LG:三星QD OLED电视更容易烧屏
世界聚焦:掏耳朵怎么就这么爽!
今日视点:不只全面屏!努比亚Z50 Ultra后摄惊艳:黄金镜皇组合
男子月薪3千相亲角“反向相亲”气到大妈 大爷理解:靠颜值吃饭
每日热文:吴京+杰森斯坦森主演!《巨齿鲨2》暑期上映 国内有望同步
环球视点!ffmpeg视频上传及压缩Linux配置篇下
世界快资讯:【Avalonia】【跨平台】关于Prism项目模块化在Linux下路径问题
浙大揭秘吃鱼为什么会变聪明 网友:告诉老默 我想吃鱼了
上海消保委提醒谨慎购买威马汽车:经营异常、消极应对投诉
特斯拉将放弃稀土材料 中国公司无惧:目前没有东西替代
即时:B站两款自研游戏将上线 CEO陈睿:能挣钱的游戏只剩下两种
《生化危机4:重制版》新演示/截图 里昂拯救黑丝碍事梨
焦点日报:配置资源管理Secret和ConfigMap
环球视点!Windows故障转移群集 和 SQLServer AlwaysOn 搭建教程
(数据库系统概论|王珊)第九章关系查询处理和关系优化-第一节:查询处理