注册
web

一次低端机 WebView 白屏的兼容之路

问题


项目:Vite4 + Vue3,APP WebView 项目


页面在 OPPO A5 手机上打不开,页面空白。


最开始是客户端在看,然后发现一个警告,大概也因为没看出什么问题,给到 Web 前端。


相关背景


为了方便描述过程的行为,先做一些相关背景的介绍。知道这些背景才能更好的了解问题的复杂。这些在解决问题的过程中始终是干扰因素,在反复调试试错的过程中才梳理总结出来,这里把它们列出来。


使用测试 App,其中有两个入口,一个是本地调试,这个地址是写在 App 里的,也就是要修改这个地址需要客户端重新出包;一个是项目的测试地址,这个地址测试可以进行配置。



修改客户端,重新出包,是很麻烦的,所以尽量避免。


项目配置了 HTTPS 支持,所以开发地址是 https 开头。但是也能启动 http 的地址。


关于项目支持 HTTPS,可以参考之前写的这篇:juejin.cn/post/732783…


之所以要支持 HTTPS 是因为 iOS WebView 只支持 HTTPS 的地址。


而安卓 App WebView 却需要 HTTP 打开,原因是安卓 WebView 反馈不支持本地地址 HTTPS 的方式。但是在本机上用 MuMu 模拟器打开 App,是能打开本地 HTTPS 地址的,之前也尝试过给安卓手机安装根证书,但是还是不行,得到以上反馈。


所以我本地开发安卓在电脑 MuMu 上调试,iOS 可以用手机调试,如果要安卓真机本地调试,需要去掉本地 HTTPS 的支持,使用 HTTP 的地址(自然,iOS 本地调试就不能同时进行了)


快速尝试


拿到问题之后,快速进行问题验证,在 OPPO A5 上,进入 APP 中,打开本地调试,用 HTTP 的方式。发现确实白屏,查看了客户端相关的日志,发现一个警告:



[INFO:CONSOLE(9)] "The key "viewport-fit" is not recognized and ignored.", source: xxx



于是修改 viewport-fit,发现并没有区别,这只是一个提示,应该没有影响。


于是采用最简代码法,排除法,用最简单的页面进行测试,看是否能正常打开,确定 WebView 没有问题。直到确定 script type="module" 引入的 main.ts 的代码没有起作用。


于是,基本上确定 Vite 的开发模式在 OPPO A5 WebView 中有问题。那不支持 ESM,就是兼容性问题?


快速查看解决方案,引入官方插件:@vitejs/plugin-legacy。但是怎么测试呢?要验证兼容性是否生效,只能验证打包构建后的代码,而不是通过本地调试进行测试。那只能发布到测试了,但这样岂不是要改一点就要发布一次,这是没办法进行的。但是第一次,还是发布一下看有没有生效。


不出意料,没那么容易解决!测试地址依然白屏。


如何调试


确定如何方便的调试是解决问题的必要条件。


几天后又开始看这个问题。


浏览器是否能打开页面?


首先在 App 中进行调试是比较麻烦的,需求启动 App,那么能否在浏览器中进行测试呢?很遗憾,期间用手机系统浏览器打开测试地址是正常的,后面打开本地地址也是正常的。所以浏览器和 App WebView 是有区别的。


启动本地服务查看构建后的页面


兼容插件只是解决打包后的构建产物,想要看打包后的效果,于是我想到将打包后的文件起一个 Web 服务,这样就可以打开打包后的页面 index.html,而且手机访问同一网络,扫码就可以打开这个页面。



  • 找了 Chrome 插件 Web Server for Chrome,发现已经不能用了
  • 找了 VS code 插件 Live Server,服务启了,但是有个报错。
  • 换用 http-server,启动服务 xxx:8080。正常打开页面,手机也能访问。

那么考虑我们的实际问题,如何在手机调试呢?将本地调试改成本地起的服务 xxx:8080,看 WebView 能否打开,这样每次修改、打包,生成新的打包后文件,刷新 WebView 就可以了。


但是前面说了,找 APP 出包很麻烦,改一个地址要出个包,费时。还有其他的办法吗?如果把本地启的服务端口改成 5173 不就不用改 App 了吗,可以直接用本地调试来进行测试,突然又想到本地调试的地址是 HTTPS,可是启动的本地服务好像没法改成 HTTPS。


通过测试地址增加本地调试入口


又想到 App 中的本地调试入口本应该做成一个公共页面,里面放上很多可能的入口。这样只需要 APP 改一次,之后想要什么入口,可以自己添加。改这个调试入口还是需要 App 改动,还是麻烦。我可以在项目中增加一个页面 debug.html(因为项目是多页面应用),这样我增加页面\增加调试入口,发布一下测试就生效了,在测试入口就能看到,这样更快。于是做了一个公共页面。


Vite preview


而且突然想到根本不需要自己起一个服务,Vite 项目,Vite preview 就是把打包后的页面启动服务。地址是:xxx:4173。


修改测试地址为本地预览


然而,OPPO A5 WebView 本来就是打不开我们的系统,那么 WebView 打不开测试地址自然也就没法打开我的本地预览了。但是,测试地址是可以配置的,所以为了快速调试,让测试配置了我的本地预览地址 xxx:4173。


这样,终于在 APP WebView 中打开了我本地预览的页面。


如何查看 App WebView 的日志


手机连接电脑,adb 日志:


image1-2.png


看起来这几个报错是正常的,报错信息也说了:



vite: loading legacy chunks, syntax error above and the same error below should be ignored



但是页面没有加载,不知道 WebView 打开页面和页面加载之间发生了什么。


Vite 兼容插件的原理


这期间,反复详细理解原理,是否是插件的使用不对。


用一句话说就是,Vite 兼容插件让构建打包的产物多了传统版本的 chunk 和对应 ES 语言特性的 polyfill,来支持传统浏览器的运行。兼容版的 chunk 只会在传统浏览器中运行。它是如何做到的呢?




  • 通过 script type="module" 中的代码,判断当前浏览器是否是现代浏览器。如果是,设置全局变量:window.__vite_is_modern_browser = true。判断的依据是:

    • import.meta.url;
    • import("_").catch(() => 1);
    • async function* g() { }


  • 通过 script type="module",如果是现代浏览器,直接退出;如果不是,加载兼容文件:
  • 通过 script type="nomodule",加载兼容 polyfill 文件;
  • 通过 script type="nomodule",加载兼容入口文件;

传统浏览器不执行 type="module" 的代码,执行 type="nomodule" 的代码。


现代浏览器执行 type="module" 的代码,不执行 type="nomodule" 的代码。


为什么需要 type="module" 的代码?这里是针对浏览器支持 ESM 却不支持以上 3 个语法的情况,仍然使用兼容模式。


详细可以看参考文章,以及查看打包构建产物。


除了知乎那篇文章,我几乎翻遍了搜到的 vite 兼容 空白 白屏 相关的文章,参考相关的配置。这个插件就是很常规的使用,几乎没有看到有任何特殊的配置或处理。就是生成了兼容的代码,低版本浏览器就能使用而已,似乎没人碰到过我的问题。


尝试解决


前面说了,用手机系统浏览器打开页面,竟然正常。怀疑是不是 WebView 的问题。


WebView 的内核版本


借了几个低端机型,几个安卓 5.x 6.x 的系统,结果手机浏览器都能正常打开。


打印 console.log(navigator.appVersion),WebView 中:


5.0 (Linux; Android 8.1.0; PBAT00 Build/OPM1.171019.026; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36 uniweb/ma75 uniweb-apk-version/0.6.0 uniweb-script-version/0.6.0 uniweb-channel/netease Unisdk/2.1 NetType/wifi os/android27 ngwebview/4.1 package_name/com.netease.sky udid/944046b939d510b1 webview_orbit/1.2(1)


而手机浏览器版本为 Chrome 90,其他手机有 Chrome70。总之,OPPO A5 WebView 的内核 Chrome 版本较低。


Vite 文档对于构建生产版本浏览器兼容性的介绍:


用于生产环境的构建包会假设目标浏览器支持现代 JavaScript 语法。默认情况下,Vite 的目标是能够 支持原生 ESM script 标签支持原生 ESM 动态导入import.meta 的浏览器


原生 ESM script 标签的支持:



原生 ESM 动态导入的支持:



import.meta 的支持:



所以,原来 Chrome 62 支持 ESM,但是不支持其他 2 个。通过日志,也可以知道不支持兼容插件尝试的 3 个语法,因为打印了那句警告,来自 module 的代码位置,window.__vite_is_modern_browser 不为 true。



从 Android 4.4 开始,系统 WebView 使用 Chrome 内核。


手机系统浏览器内核和系统 WebView 不一样,手机系统的 WebView 也可能不是安卓默认的。


兼容生效了吗?


但还是不知道低版本的浏览器兼容性是否生效,当前我们只能确定 Chrome 62 的 WebView 中兼容有问题,那是否在浏览器中就正常呢?或者更低的版本兼容是否生效?(毕竟不支持 ESM 的浏览器版本的代码执行又不一样)


target 配置不对?


target 配置的是目标浏览器,针对这些浏览器生成对应的兼容代码,期间我一直调整 target 的配置,使用 .broswerlistrc 文件配置,target 直接配置,参考不同的配置方案,确保包含了 Chrome 62。


又是如何调试?


想要下载安卓 Chrome 62 进行测试,但是搜了一圈也没找到。


后来想到不一定要手机浏览器进行测试,Chrome 也行。这里面有点思维上的转换,之前我测试只能通过 WebView 进行调试,因为浏览器上没有问题。现在确定了是浏览器 Chrome 版本的问题,那么我们还是可以通过 PC 浏览器进行测试。


于是范围更大一些,找到了 Chrome 的所有历史版本,不得不说,Chrome 提供的下载真是太有用了,对于测试兼容性非常有帮助! 而且我所担心的覆盖现有浏览器版本的问题完全不存在,下载之后直接运行。


安装 Chrome 62,打开页面,果然空白。终于在浏览器复现,确定就是兼容的问题,而不是 WebView 的问题。安装安卓版本的 Chrome 62,也同样复现。


下载 win 的 Chrome 62,虽然在 refs 里找到同版本的记录:xxx。但是没找到同版本的下载,不过也都是 62,应该没问题。下打开页面,打开控制台,和在 adb 中看到的报错一样,只是这里是红色的:



安装更低版本的 Chrome,同样复现,说明不支持 ESM 的兼容也出现问题。同时可以看到那句提示没有了:



过程当然也没那么顺利,下载 Chrome 的过程中,Chrome 62 直接可以运行,下载 Chrome 59 却没法打开。于是又下载了 Chrome 55,mini exe 文件,可以直接打开。


报错到底要不要处理?


通过 adb日志可以看到报错,也可以从打包后的代码看到:对于语法报错是可以忽略的,因为那是预期中的行为。可是之后的代码为什么没执行了呢?


回到现在的问题,这个报错不是不需要处理吗?但是加载了兼容的 js,页面却没有渲染元素。此时隐隐觉得报错可能还是要处理,至少可能最后一个报错有点问题?


但是这个报错实在难以查看,之前我把它当作和前两个报错一样的来源。现在只剩这个报错了,问题是这是打包压缩后的代码,完全不知道真正的问题是什么。


通过请教网友,做了一些尝试:


通过对插件配置:renderModernChunks: false,只生成兼容代码,依然报错。


通过修改 Vite 配置:build.minify: false 不压缩代码,尝试查看报错位置。新的报错:



升级 Vite。新的报错:



所以每次的报错都不一样,越来越奇怪。不过看起来似乎是同一个原因导致的。


在构建源码中调试


通过在构建后的源码中打印,其中 excute 函数中,有两个参数 exports module,但是在其中使用 module.meta 报错,说明其他文件在使用这个方法是并没有传参。看起来像是模块规范的问题(commonJS 和 ES Module)。


ChatGPT


在这期间,也在 ChatGPT 搜素方法:




就尝试了一下 format: 'es',顺便看到有个配置 compact: true,好像也是压缩,就顺手改成 false,这样全部不要压缩,方便看报错。


结果竟然 OK 了,页面打开,没有报错!


是这个配置生效的吗?通过排除,发现竟然是 compact 的原因。这个配置不是 Vite 本身的,是 Vite 使用的 rollup 的配置:




果然是插件冲突的结果。


再搜素 execute,已经没有带参数了:



再次感叹 Webpack 配置工程师



build.sourcemap


后来想到开启 sourcemap 来定位报错的原始文件位置,未开启:




开启 sourcemap:




如果在打包过程中对代码进行了混淆或压缩,可能会导致 Source Map 无法准确映射到原始代码位置。


这就完了?


中午去吃个饭,下午回来,本以为打包发布验证一下就完了,结果测试地址能打开页面了,却和我在电脑浏览器上看到的一样,没有正常加载页面。晴天霹雳!


image21-2.png


说明一下:我们这个项目是 App Hybrid 应用,Web 前端和服务端的通信通过客户端中转,所以在非客户端环境是拿不到服务端的数据的,联调测试都是在 App 中进行的。


但为什么前面一直用浏览器测试呢?虽然数据获取不到,但是如果页面加载出来了,说明打包代码是没问题,所以在前面页面显示了背景图等元素,id="app"中有了内容以后,就是兼容版本的 js 正常执行了。但是再说一次,真实的环境是 WebView,最终的结果还是要看 WebView。


目前在 App 中还是没有显示完整的网页加载过程,又陷入了困境。页面已经没有了报错(除了 Vite 那个可以被忽略的),难道生成的兼容 js 有问题? 如果是具体的兼容代码有问题,那整个项目的代码又如何排查呢?


虽然没有头绪,但是隐隐觉得应该再坚持一下。继续分析,如果已经显示了页面,兼容的 js 已经执行了,那么数据接口请求了吗?


于是联系服务端,查看服务器日志,确定页面加载没有请求接口,说明请求接口的代码没执行。我用高版本 Chrome 查看 Preview(之前没做这一步),果然除了看到页面元素,还进行了请求尝试。于是又运用排除法,一步步打印首页加载的过程。


在这之前,又是如何调试的问题,我们把测试入口改为我本地的 preview 地址。


加入打印日志,本地 build,Preview,打印了日志信息,果然接口请求的部分未执行。排查发现是之前调起 WebView 导航的代码出问题,可能是 jsBridge 还未加载。


但是为什么其他设备没有出现这个问题呢,可能是设备性能较差。于是把相关代码放到 jsBridge 加载完成后执行,修复了这样一个隐藏的 bug。


但是为什么不报错呢??这真的是很不好的体验,之前 Vue router 不报错的问题也类似。


总结


同样,我们再回头看那个最初的报错:



vite: loading legacy chunks, syntax error above and the same error below should be ignored



上面的报错、项目相同的报错可以被忽略。很容易让人忽略了报错,明确提到了下面的报错,但是 Vite 打包中下面没有出现同样的报错了(我目前的打包和其他文章中提到的不一样,比如知乎文章提到的代码);而且相同的报错到底是什么相同,同样是语法报错而已啊。


这句提示值得商榷。


function __vite_legacy_guard() {
import.meta.url;
import("_").catch(() => 1);
async function* g() {};
};

主要是因为出问题的恰恰就是中间的版本,Chrome 62,Vite 让它报错又能正常运行。遇到这种情况少之又少,从权衡上来说,好像也没有问题


本质上,只是在解决一个打包后的文件报错的问题,问题是一开始并没有定位到这个问题,其次是打包后的报错仍然难以定位具体错误位置。然后这其中还涉及到项目自身环境的各种干扰。


几点感悟:



  • 坚持不懈,这是解决问题的唯一原因。
  • 总结熟练调试很重要,要快速找到方便调试的方法。
  • 没有报错是开发的一大痛点。
  • 针对当前的问题更深入的分析原因,更广泛的尝试。
  • 多用 ChatGPT,ChatGPT 的强大在于它没有弱点,没有缺项。

说明


通过这个案例,希望能给大家一点解决问题的启发。是遇到类似的问题时:



  • 了解相关的问题
  • 熟悉相关的概念
  • 学习解决问题的方法
  • 学习调试的方法
  • 坚持的重要性

参考


【原理揭秘】Vite 是怎么兼容老旧浏览器的?你以为仅仅依靠 Babel?


juejin.cn/post/723953…


Chromium History Versions Download ↓


作者:choreau
来源:juejin.cn/post/7386493910820667418

0 个评论

要回复文章请先登录注册