我是如何把 Vite + Vue3 + Wujie 工程兼容到 Chrome 49 的

我是如何把 Vite + Vue3 + Wujie 工程兼容到 Chrome 49 的

技术博客 admin 540 浏览

前言

公司的系统卖出去了,终于看到回头钱了,这下不用担心被裁员了。
哈哈哈哈~~~~!
产品:等等,先别高兴,用户说得兼容他们使用的浏览器。
开发:额--,什么浏览器,不会是 ie 吧。
产品:不是,不是,ie 那么古老了谁还用啊,他们用的是谷歌浏览器。
开发:嘿嘿,谷歌浏览器,那不简简单单。他们用的是哪个版本?
产品:49 版本。
开发:(⊙o⊙)…,有点低啊,用户能升级下版本吗?
产品:不能!
开发:那先试试吧!

于是我去网上下载了一个 Chrome 49 浏览器,输入系统网址,看看效果怎么样。
好嘛,直接白屏。看看浏览器报错是不支持 import 语法,这 49 属实是低啊,import 都不支持,import 语法得在 61 版本才支持

得,咱们一步步来兼容吧。

兼容过程

我们的工程稍微有点复杂,是微前端架构的,
主应用采用 Vite + Vue3 + Element-plus 书写
子应用使用 Vite + React + ant Design 书写
微前端采用 wujie 框架
所以兼容就围绕着这些来做了

@vitejs/plugin-legacy

首先既然使用的是 Vite,那么@vitejs/plugin-legacy 这个官方兼容插件肯定得第一个用上
安装:

shell
复制代码
pnpm i @vitejs/plugin-legacy -D

使用:
vite.config.ts 文件配置:

javascript
复制代码
import legacy from '@vitejs/plugin-legacy'; export default defineConfig(() => { return { plugins: [ legacy({ // 只生成兼容文件 renderModernChunks: false, }), ] } });

再来配置下需要兼容的浏览器:
项目根目录下新建 .browserslistrc文件,内容如下:

复制代码
chrome >= 49

好的,至此@vitejs/plugin-legacy 配置完成。

postcss-preset-env

配置完了 js 兼容,接下来就要配置 css 兼容了。
说到 css 兼容,那就不得不提 postcss 和 postcss-preset-env了。
postcss-preset-env可将现代 CSS 转换为大多数浏览器可以理解的内容,并根据目标浏览器或运行时环境确定所需的 polyfill。
安装:

shell
复制代码
pnpm i postcss postcss-preset-env -D

在 vite.config.ts 中使用,Vite 默认支持 postcss,所以可以直接在 vite 的配置文件中配置 postcss

diff
复制代码
import legacy from '@vitejs/plugin-legacy'; + import poscssPresetEnv from 'postcss-preset-env'; export default defineConfig(() => { return { plugins: [ legacy({ // 只生成兼容文件 renderModernChunks: false, }), ], + css: { + postcss: { + plugins: [ + poscssPresetEnv(), + ], + }, + }, } });

不出意外的话,此时错乱的 css 应该都回到了它本来的地方。

element-plus 的兼容

如果你认为这就完事的话,那你就太天真了,实际进到系统中你会发现,element-plus 的很多组件样式依然不正确,查看它的兼容列表发现:

系统使用的是 v2.5.6 版本的 element-plus,从上图可以看出,它只兼容到 Chrome 85
这就意味着 element-plus 兼容到 Chrome 49 将是个很大的挑战。

挑战一:输入框、选择框、日期选择等组件都没有了外边框

以输入框为例,输入框的边框使用的是 box-shadow 实现的,而它这其中使用了 css 变量,如下:

但是经过查看 can i use 的内容:chrome 49 是支持 css 变量和 box-shadow 的

所以,这里猜测是 Chrome 49 支持单独的 css 变量,例如:

shell
复制代码
color: var(--bg-primary);

而不支持属性的一部分是 css 变量的情况,例如上边的 box-shadow:

css
复制代码
box-shadow: 0 0 0 1px var(--safeis-color-black7) inset;

解决方案:既然不支持属性的一部分为 css 变量,那就不使用 css 变量,而是使用 sass 的变量,同时保持 sass 变量和 css 变量一致,这样在低版本浏览器中就可以识别该样式了。

sass
复制代码
$--el-border-color: #dde1e7; .el-select__wrapper { box-shadow: 0 0 0 1px $--el-border-color inset; }

挑战二:输入框在输入中文时会将拼音也带入

在低版本浏览器中,在使用中文输入时,按下确定键就会将拼音和中文一同带入输入框,极大的影响了使用体验,不过不使用中文输入法还行,英文和数字没有问题,但这依然是无法忍受的。

通过查看 element-plus 的 issue ,发现有人提出了相关问题:github.com/element-plu…

其中也给出了产生该问题的原因:

经过本人的验证,确实存在该问题,但是很遗憾,截止到本文发表,该问题依然没有一个官方的解决方案,当然,这个问题只会在低版本浏览器中出现,element-plus 本身就不支持低版本浏览器,不解决也无可厚非。
经过本人测试,至少要在 chrome 75 版本以上才不会有该问题。
庆幸的是,textarea 没有该问题,所以可以使用 textarea 替换 input,完成输入展示。
但是使用textarea 替换 input 会增加输入框使用的复杂度,以后在每次使用 input 框时都需要做一层低版本浏览器的判断 ,对于开发来说是非常不方便且很容易出问题。
所以,寻找一种既不改变现有的开发模式又能兼容低版本浏览器的方案是非常有必要的。
解决思路:
问题发生在 compositionend 事件中,所以我们可以使用代理模式全局注册 compositionend 事件,并在其中判断发生该事件的标签为 input 框,在延时后对输入框的内容做过滤处理,过滤掉多余的拼音。
过滤拼音是一个难点,因为无法确定到底是拼音还是正常英文,所以这里采用一个巧妙的方法来解决。

  • compositionstart 事件中记录当前输入框的初始值 initialInputValue
  • compositionend 事件中,虽然 event.target.value 返回的是拼音,但是 event.data 返回的是拼音确认后的中文 chineseCharacter,所以我们可以在确保当前输入框值已经更新后,使用 initialInputValue + chineseCharacter 替换掉输入框的内容,从而过滤掉拼音
  • 在给输入框重新赋值后,因为是手动修改的输入框值,所以 vue 是无法感知的,也就造成了数据视图不一致的问题,所以在修改完输入框内容后,我们需要手动触发 input 事件,让 vue 更新值。

修改方案我也同步贴在了 issue 下。希望能帮助到给更多的人。
代码如下:

javascript
复制代码
let initialInputValue = ''; // 记录 compositionstart 事件时的输入框初始值 function handleCompositionStart(event: CompositionEvent) { initialInputValue = event.target?.value; } // 处理 compositionend 事件 function handleCompositionEnd(event: CompositionEvent) { // 延时处理,确保输入框值已更新 setTimeout(() => { const chineseCharacter = event.data; // 获取输入的汉字 // 更新输入框的值 event.target.value = initialInputValue + chineseCharacter; // 手动触发 input 事件以更新组件状态 const inputEvent = new Event('input', { bubbles: true }); event.target.dispatchEvent(inputEvent); }); } document.addEventListener('compositionend', event => { if (event.target?.tagName === 'INPUT') { handleCompositionEnd(event); } }); document.addEventListener('compositionstart', event => { if (event.target?.tagName === 'INPUT') { handleCompositionStart(event); } });

挑战三:下拉框宽度丢失、下拉筛选框中输入内容无法自动撑开等自动调整宽度的问题


当我遇到这些问题的时候,下意识的认为又是哪个 css 属性不兼容导致的,通过给下拉框设置 width: 100%; 解决了一部分问题,但是筛选框输入框内容无法撑开的问题确实一直没能通过修改 css 实现兼容。
无奈之下,我只好去查看下拉选择框的源码,发现源码中会监听 dom 元素的变化,从而更新元素的宽度,

useResizeObserver使用的是 vueuse 的 api,再去看下useResizeObserver的源码:该 api 使用的是浏览器的原生 api ResizeObserver来实现的,而如果当前浏览器不支持该 api 则什么都不做。而该 api 只有在 chrome 64 以上才兼容。

至此,问题根源已经找到,就是当前浏览器不支持ResizeObserver导致的,那么社区找找是否有该 api 的 polyfill
很庆幸,社区确实有该 api 的 polyfill,就是 resize-observer-polyfill,安装兼容即可:

javascript
复制代码
pnpm i resize-observer-polyfill -S
javascript
复制代码
if (!window.ResizeObserver) { window.ResizeObserver = ResizeObserver; }

兼容后问题就迎刃而解了。
当然,不光是这个 api 会导致问题,目前发现的是事件中的 event.composedPath也存在问题,所以同样需要 path-composedpath-polyfill来做兼容。

wujie 的兼容

做完上述的兼容,基本页面是可以正常查看了,但是还是会存在一些小的问题,主要是新版的 css 属性不支持的问题,比如:gap 等,不过这些都可以通过使用支持的 css 属性来做降级兼容,问题不是很大
但是最大的问题来了,子应用都是白屏,解决该问题确实花费了很多的时间,下面给大家说说我的解决过程:

确定是否可兼容

因为项目是使用 wujie 微前端框架做的,现在子应用白屏,有两个可能的原因:

  • 子应用本身不支持 Chrome 49
  • wujie 不支持 Chrome 49

针对这两个可能的原因,分别做了以下两个实验:

  • 因为子应用是基于 antv/g6 实现的一张多功能网状图,所以我启动了一个新项目,该项目只有最基本的一张示例网图,然后用上以上全部的兼容手段,在 chrome49 中验证是否可以正常运行,实验表明:该网图可以正常运行,所以 antv/g6 是没有兼容问题的
  • 再启动一个 wujie 应用,加载最简单的单 html 文件,判断 wujie 是否可以正常运行,实验表明:wujie 也是可以在 chrome49 下正常运行的

既然两个可能的原因都被排除了,那还能是什么原因呢?只能从报错入手了:

控制台只有一条报错信息,翻译过来就是无法读取 getAttribute属性,在打包之后的文件中全局查找,看看哪里使用了这个属性,最后确定在子应用的 html 文件中:

这段代码是 @vitejs/plugin-legacy 生成的兼容 import 语法的代码。就是使用 System 去加载文件,这个加载的文件获取的是当前 script 标签上的 data-src 属性,既然已经有了明确的加载文件路径,那不妨修改下这个加载文件的获取方式,直接使用文件名:如下:

html
复制代码
<script crossorigin> System.import('https://tracker-online.oss-cn-beijing.aliyuncs.com/scripts/index-legacy.69c5e0aa.js'); </script>

在测试环境验证发现是可以正常加载子应用的,很不错哦。

解决方案

既然确定了问题就出在这一行代码,那是否有方法可以将这行代码改成直接引用文件名呢?答案是肯定的
wujie 为了应对各种各样的可能问题,它开放了插件功能,无界的插件体系主要是方便用户在运行时去修改子应用代码从而避免去改动仓库代码,其中 html-loader 就是运行时对子应用的 html 文本进行修改,使用这个插件钩子,我们可以强行将 html 文本修改成所需的样子,代码如下:

javascript
复制代码
const plugins = [ { /** * 在vite使用vite-legacy-plugin兼容旧代码时,需要将生成的如下兼容代码: * <script crossorigin id="vite-legacy-entry" data-src="https://tracker-online.oss-cn-beijing.aliyuncs.com/scripts/index-legacy.54783046.js"> * System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src')); * </script> * * 替换为如下兼容代码 * <script crossorigin id="vite-legacy-entry" data-src="https://tracker-online.oss-cn-beijing.aliyuncs.com/scripts/index-legacy.54783046.js"> * System.import('https://tracker-online.oss-cn-beijing.aliyuncs.com/scripts/index-legacy.54783046.js'); * </script> * * 因为wujie无法获取降级情况下使用getElementById获取的script标签元素 */ htmlLoader: (htmlText: string) => { // 是否支持shadowDom const wujieSupport = window.Proxy && window.CustomElementRegistry; if (import.meta.env.VITE_MODE === 'dev' || wujieSupport) { return htmlText; } // 匹配带有 data-src 属性的 <script> 标签的正则表达式 const scriptTagWithDataSrcRegex = /<script[^>]*data-src="([^"]+)"[^>]*>([\s\S]*?)<\/script>/g; // 替换 <script> 标签内容的函数 const updatedHtmlText = htmlText.replace(scriptTagWithDataSrcRegex, function (match, p1) { const newScriptContent = `System.import("${p1}");`; return match.replace(/>[\s\S]*?<\/script>/, `>${newScriptContent}</script>`); }); return updatedHtmlText; }, }, ]; export default plugins;

虽然问题解决了,但是我依然很好奇为什么使用 document.getElementById('vite-legacy-entry')获取的值是 null 呢?
本着知其然,也要知其所以然的原则,我只能去源码里边看看到底这块是怎么回事。

wujie 将子应用的js注入主应用同域的iframe中运行,
在高版本浏览器中采用webcomponent来实现页面的样式隔离,无界会创建一个wujie自定义元素,然后将子应用的完整结构渲染在内部,
在低版本浏览器中会使用一个新的 iframe 代替webcomponent 来实现页面样式的隔离。
所以在低版本浏览器中,wujie 将子应用的 js 抽离出来在 js 沙箱 (同域 iframe)中运行,而将 dom 渲染在另一个 css 沙箱(新的 iframe)中,此 css 沙箱中没有 js。

js 沙箱,只有 script 标签,没有 dom 结构:

CSS 沙箱,只有 dom 和 css,没有 script 标签:

子应用的实例instance在iframe内运行,dom在主应用容器下的webcomponent内或新的 iframe 内,通过代理 iframe的document到webcomponent(新的 iframe),可以实现两者的互联,而问题就出在这个互联上,wujie 在代理 document.getElementById这个 api 时,是将作用域限制在了 css 沙箱内,也就是没有 script 标签的 iframe 中,而 document.getElementById('vite-legacy-entry')获取的恰恰是 script 标签,自然获取不到,故而报错。

考虑到 wujie 搭配 @vitejs/plugin-legacy 在低版本浏览器中肯定会出问题,而这也将大大影响用户使用 wujie 的体验,所以我果断向 wujie 项目提了一个 pr,很幸运也被合并了

这样,大家就可以继续开心的使用 wujie 和@vitejs/plugin-legacy 搭配了。关联 pr :github.com/Tencent/wuj…

总结

至此,vite + Vue3 + wujie + React 项目兼容 chrome49 的工作算是完成了,其中很多问题单纯通过世面上的主流兼容方案是无法实现的,需要通过阅读源码,找到问题所在,对症兼容。
希望本文对你后续做浏览器兼容有一定帮助。

源文:我是如何把 Vite + Vue3 + Wujie 工程兼容到 Chrome 49 的

如有侵权请联系站点删除!

技术合作服务热线,欢迎来电咨询!