从白屏问题重学模块机制

背景

公司App使用的是跨平台技术H5+原生混合开发,双方通信的协议是Jsbridge。
为了获取用户行为以及跟踪产品在用户端的使用情况,并且能自动监控到App的所有H5页面,由Native引入了隔壁部门研发的前端监控SDK。
由于被任命为【推动隔壁部门完善监控SDK事项】负责人,于是在后续使用过程不断结合实际情况,发现SDK存在问题并进行梳理和方案输出,但是由于对方不开放SDK源码,只能把问题和方案告知对方后进行推动和跟进。
在某个版本修复了部分问题并扩展了一些新功能后,推动Native对SDK进行升级,并通过邮件通知各组进行验证,一切正常,但是隔壁部门上报某个页面白屏了。后隔壁部门一顿操作总算解决了问题,但是给出的理由是同时引入两个监控SDK文件冲突导致的问题。后面我对他给的原因进行分析,发现逻辑串不起来,也就不相信了,于是自己研究了起来。

现象

经不断测试,发现Vue项目均正常,而Backbone.js + Require.js项目页面安卓一直白屏,ios偶现白屏。经调试,发现报错 Mismatched anonymous define() module 的问题。

定位之旅

1. 从报错信息入手

image.png
image.png

根据提示信息访问官网错误信息页面:https://requirejs.org/docs/errors.html#mismatch,官网给出的解释如下:
If you manually code a script tag in HTML to load a script with an anonymous define() call, this error can occur.
If you use the loader plugins or anonymous modules (modules that call define() with no string ID) but do not use the RequireJS optimizer to combine files together, this error can occur.

2. 理解官网解释

  • anonymous defines 解释
define(function() {
    return { helloWorld: function() { console.log('hello world!') } };
})
  • define with string id 解释
define('moduleOne',function() {
    return { helloWorld: function() { console.log('hello world!') } };
})
  • call define() with no string ID 解释
define(function() {
    return { helloWorld: function() { console.log('hello world!') } };
})

3. 分析定位

通过第2步了解到报错原因可能是调用了define函数,而且传入的第一个参数不是字符串导致的。于是,搜代码定位到监控SDK文件采用UMD打包模式,里面有一句关键代码

typeof define === 'function' && define.amd ? define(factory) : factory()

为了验证猜想,我把define函数调用删除后,重新运行代码,果然页面渲染出来了,也就是问题就在这里。

4. 解决方案

  • 方案一
    这也是隔壁部门实施的方案,就是SDK直接不用UMD模式,改成IIFE模式,只能感叹真简单粗暴!至于为什么能想到,后面给答案。
  • 方案二
    让SDK优先于require.js下载执行,理由后面给出,但是因为当前项目属于老破大的屎山项目,而且领导对页面性能也有要求,所以不太适合。
  • 方案三
    使用require.js方式加载SDK,而不是用script标签方式引入,这应该是最合理的方案,但是猜测目前维护性能监控SDK的人太年轻,根本不懂requirejs是啥,怎么用,所以没想到这个方案(其实我没研究之前也不知道,因为之前我也没接触过Backbone.js + Require.js项目,也没研究过Require.js)。示例代码

      require(['http://some.domain.dom/path/to/papm.js'],
    function (papm) {});
  • 方案四
    网上所谓奇淫技巧,本质跟方案二是一样的,即加载SDK时不受AMD模式影响,相关代码参考

      
              
      

5. 快问快答

  1. 为什么升级前没问题,升级后有问题?

    • 因为升级前就是IIFE模式,这也是为什么他们最终能解决而且用的是IIFE的原因。
  2. AMD模式和requirejs到底怎么个冲突法?怎么定位到的?

    • 一句话,撸requirejs源码。
      具体分析如下:

      当先加载requirejs,后加载UMD模式SDK时,
      执行requirejs会依次调用req({})和req(cfg),
      每次调用req首先会调用intakeDefines函数,它会做2件事,

      • 将globalDefQueue队列任务放到defQueue队列,
      • 检查defQueue队列成员合法性;

      接着会调用context.nextTick启动一个定时器,回调函数也是intakeDefines函数;
      而执行UMD模式SDK时,调用define(fn)会触发requirejs源码define定义,即把fn函数push进globalDefQueue;
      而当context.nextTick回调函数intakeDefines在这之后从宏任务队列取出执行的话,当检查defQueue队列成员合法性时,由于 define(fn) 第一参数为 function而不是string,所以 name = null,则触发错误"Mismatched anonymous define() module: "。

    • 执行流程代码可简化如下

      req({}) -> context.nextTick(cb1)
      req(cfg) -> context.nextTick(cb2)
      next script tag run -> define(fn) -> globalDefQueue.push(fn);
      cb1() -> check fn -> "Mismatched anonymous define() module: "
      cb2()
    • 浏览器运行日志也验证此流程
      从白屏问题重学模块机制_第1张图片
  • 为什么安卓大概率白屏,ios偶现白屏?

    • 为了验证 requirejs里的nextTick回调函数 和 通过script标签加载SDK哪个先执行问题,写了一个测试demo页面scriptOrSettimeout.html。通过测试(不断刷新),当 requirejs 和 SDK 都是通过 src 加载时,且 requirejs 在前面,chrome 和 safari 浏览器都有报错概率,特别还和 settimeout 设置的延时有关,如设 1 和 5,结果也不大一样;也和 nextTick 和 define()中间代码执行时间有关系;所以因为执行顺序存在不确定性,所以白屏也是概率出现。
    • 白屏时候浏览器日志截图请看上一张图
    • 没有白屏时候的浏览器日志截图
      截屏2023-12-19 下午11.54.24.png

你可能感兴趣的:(2023-年度总结bug模块化)