likes
comments
collection
share

記一次 WebView 的兼容問題(白屏的兼容之路二)背景 Vite5 + Vue3 項目,主要在 App WebVie

作者站长头像
站长
· 阅读数 35

背景

Vite5 + Vue3 項目,主要在 App WebView 中使用。

使用了 @vitejs/plugin-legacy 處理低版本瀏覽器的兼容問題。

問題

最初的問題是,在小米 Mix3 手機上,沒顯示頁面中的菜單。而且有報錯,我想既然控制台有報錯,那還不容易解決?

初次嘗試解決

按照慣例,測試環境沒辦法在頁面中顯示調試工具,所以嘗試在本地環境復現。但是問題來了,本地頁面在這個手機WebView 上竟然打不開。一開始我懷疑是 WebView 對於本地頁面 http 或者 https 的支持問題。

通過 Android Studio adb 的日誌能夠看到測試地址的報錯信息:

"TypeError: Cannot read property 'includes' of undefined"

也就是程序中有一處 .includes 的語法報錯,只要找到報錯位置,應該很快就能知道報錯的原因。

那麼如何找到報錯位置呢?

想到之前用過的代理的方法,訪問測試地址,實際上代理到本地地址。後來發現不行,本地本來就打不開。代理解決的是無法訪問本地地址的問題。

分析

系統(頁面)有一個菜單,菜單每一項導航一個活動頁面。菜單中包含當前活動的信息。菜單是通過接口數據返回進行顯示的,同時數據使用全局狀態管理。

仔細觀察問題的表現:

  1. 頁面打開時,菜單有加載,然後又隱藏消失了。
  2. 而且,刷新頁面,有時最終又能顯示菜單。
  3. 顯示菜單時,點擊菜單項切換頁面,有的頁面也能顯示菜單。

1 說明接口有調用,接口沒有問題。但是 2、3 有點複雜,必須到找到報錯原因才好分析。

搜索 .includes,Menu.vue 中有幾處,根據對項目的熟悉,我大概能判斷報錯位置在這個頁面。

又是兼容性問題,白屏!

回到前面我想要本地調試的問題,那麼來看一下為什麼 WebView 打不開我的本地頁面?(只有這個手機)。

還是使用最簡頁面法,當頁面刪除 <script type="module"> 時,頁面打開了。

所以是這個 WebView 不支持 ESM script!

這真是奇怪了,這個手機看著沒有太老舊,打印一下 WebView 的版本信息,Android 9,內核 Chrome 72。

Chrome 72 完全支持啊,參考:caniuse.com/es6-module

那麼測試地址為什麼能打開頁面呢?因為測試頁面使用了兼容插件生成的兼容版本 js(猜錯了!)

奇怪的 WebView

如果 WebView 雖然版本是 Chrome 72,但是因為某些未知的原因,實際上不支持 Chrome 72 已經支持的特性,那麼會應用 Vite 兼容插件生成的兼容代碼。

但是調試會發現,此 WebView 會被當成現代瀏覽器,因為它能夠處理:

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

如何調試

既然搞清楚本地打不開的原因,那麼就可以參考上一篇:一次低端机 WebView 白屏的兼容之路 的經驗。

調試的方法是:使用 preview 查看構建後的頁面。

缺點有 2 個:報錯仍然不能確定準確位置、每一次修改需要構建才能生效。

好在終於可以調試了,這個問題因為我熟悉代碼邏輯、確定大概的報錯位置。所以很快確定了報錯位置。

查到报错原因

報錯的代碼如下:

menuItem.value.includes(xxx)

菜單項數據:

[
  {
    isCompleted: true,
    label: 'A',
    value: 'A',
  },
  {
    isCompleted: true,
    label: 'B',
    value: 'B',
  },
  ...other
]

Menu.vue 中的 menuData 为 computed 数据,来自全局状态 menuStore。

不能直接打印出 menuStore,不知道是否是 vConsole 的问题。

在 computed 中打印 menuData 和 menuStore,菜单数据为:

[
  {
    isCompleted: true,
    label: 'A',
    value: 'A',
  },
  {
    isCompleted: true,
  },
  ...other
]

除了第一项,其他项都只有一个属性,因此才导致那个报错。

虽然知道了报错的原因,但是并没有完全知道。知道某个位置报错,不知道为什么会出现那样的数据。

新的问题表现

期间,测试站更新。进入首页,问题没有出现了。发现是首页加载的活动变了,是另外一个活动页面。

一开始以为是某个活动页面的问题,而不是菜单的问题。

后来发现,点菜单切换其他页面,又出现问题。

数据异常的分析

全局状态菜单数据 menuData 的更新只有一次,为什么会被改变为这种形式?后来才发现,因为实在没预料到这个问题的原因,所以才忽略了。menuData 的更新有两种:

function updateMenuData(data) {
  menuData.value = data
}

function updateMenuDataIsCompleted(activity, isCompleted) {
  menuData.value = menuData.value.map(menuItem => {
    return {
      ...menuItem,
      isCompleted: menuItem.value === activity ? true : menuItem.isCompleted
    }
  })
}

updateMenuData 为直接更新整个菜单数据,updateMenuDataIsCompleted 为更新某个菜单项的 isCompleted 属性。

猛然意识到其他项只有 isCompleted 属性,莫不就是 updateMenuDataIsCompleted 操作引起的?除非展开运算符... 有问题。

果然,不使用...而是把所有属性写出来就没有报错了。

对问题的解释

问:为什么页面加载过程中,菜单出现又消失?

系统的加载过程中,会请求菜单数据,从而展示菜单。具体的活动页面在数据请求完成之后会进行 updateMenuDataIsCompleted,出现问题,导致菜单显示异常。

问:为什么不断刷新,有时又能最终显示菜单?

活动页面的数据请求有时间间隔限制,前端的处理是请求节流、全局状态存储活动数据并本地持久化。当刷新页面时,如果在时间间隔内,接口并不会请求,所以 updateMenuDataIsCompleted 方法也不会调用,就不会报错。

问:为什么切换其他菜单时,有时菜单能够显示?

有的页面,并没有 updateMenuDataIsCompleted 操作,所以打开那个页面时,不会导致报错影响菜单的显示。这也是新的问题表现中测试站更新后为什么初始进入系统没有异常的原因,因为初始打开的活动没有更新 isCompleted 操作。

语法兼容性

developer.mozilla.org/zh-CN/docs/…

記一次 WebView 的兼容問題(白屏的兼容之路二)背景 Vite5 + Vue3 項目,主要在 App WebVie

修復

如何转换展开运算符?当然是使用 babel,Vite 中如何应用?

使用 babel 转换语法,代码如下:

import babel from '@rollup/plugin-babel'

plugins: [
  vue(),
  babel({
    babelHelpers: 'bundled',
    presets: [['@babel/preset-env', { targets: '> 0.01%, not dead' }]],
    plugins: ['@babel/plugin-transform-spread'],
    extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue'],
    include: ['src/**/*'],
    exclude: ['**/node_modules/**', '**/*.scss', '**/*.css'],
  }),
  // 其他配置
]

后续

之后在另一个需求中发现一个奇怪的 bug,某些图片显示不出来,而且只在某一个测试站如此,生产和其他测试站、本地都是正常的。

大概是最近的修改造成的,但如果本地都是正常的,又如何调试呢?这里就要注意了,这正是 Vite 的问题之一,Vite 的开发环境和打包后的生产环境是不一样的。所以开发环境正常只是开发环境。打包一下,用预览查看,果然复现了问题。

那么,到底是什么原因呢,开发环境能正常读取图片,build 之后无法读取。检查一下打包后的文件,确实没有。因为是最近的修改才出现问题,通过排除法,定位到这一次的修改,只要删除以上 babel 的配置,问题就得到解决。

主要就是 babel 的配置和 Vite 打包配置冲突了,冲突导致的问题是某个层级(/src/assets/images/xxx/xxx/)的图片未被打包。如何解决呢?尝试了调整插件加载顺序,设置 include exclude 范围等等,查找了很久,才发现问题所在。

babel 的配置应该在 rollup 的配置中:

build: {
  target: 'es2015',
  rollupOptions: {
    plugins: [
      babel({
        presets: [['@babel/preset-env', { targets: '> 0.01%, not dead' }]],
        extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue'],
        include: ['src/**/*'],
        exclude: [
          '**/node_modules/**',
          'src/assets/**',
          '**/*.scss',
          '**/*.css',
        ],
      }),
    ],
  },
},
转载自:https://juejin.cn/post/7419907042132918298
评论
请登录