浅谈 Antd v5 的样式兼容问题

最近在升级 antd@v5 的时候,发现组件的类名上都加了 css-dev-only-do-not-override-1e3x2xa 选择器,然后内部样式都用这个选择器设置,并且还用到了 :where 选择器:

那么这个选择器作用是什么呢?翻了下 antd 官方的 discussion,给大家解读一下。

首先 css-dev-only-hash 的作用是为了避免应用中存在多个 antd 实例导致样式冲突问题。例如微前端场景,两个微应用分别引入不同版本的 antd,这两个 antd 实例的 hash 是不一样的,因此 A 应用的 antd 样式不会影响 B 应用的 antd 样式,因为他们的哈希类名是不一样的。

另外 css-dev-only-do-not-override- 前缀只是一个提示,不建议开发者直接用类名覆盖样式(但实际上是可以覆盖的,下面会说明),其在生产环境下会转为 css- 前缀。

但是用哈希类名的方式限定 antd 样式的 scope,显然会提升 antd 选择器的优先级,导致外部难以覆盖样式。该问题的解就是用 :where 选择器,该选择器是零优先级的,因此可以像 v4 那样直接覆盖样式。

问题在于,这里 :where 选择器存在兼容性问题,Chrome 88 以上,且 PostCSS 不支持 polyfill。

参考了 antd 文档,官方建议的方案是用 @ant-design/cssinjsStyleProvider 取消默认的降权操作。

tsx 复制代码
import React from 'react';
import { StyleProvider } from '@ant-design/cssinjs';

// `hashPriority` 默认为 `low`,配置为 `high` 后,
// 会移除 `:where` 选择器封装
export default () => (
  <StyleProvider hashPriority="high">
    <MyApp />
  </StyleProvider>
);

切换后,样式将从 :where 切换为类选择器:

css 复制代码
--  :where(.css-bAMboO).ant-btn {
++  .css-bAMboO.ant-btn {
      color: #fff;
    }

注意,关闭 :where 降权后,绝大多数覆盖 antd 样式的代码,由于选择器权重问题都会失效,因此需要手动调整这些样式的优先级,例如手动加上 !important。个人理解 :where 选择器应该只是官方提供的一种 workaround,以便 v4 版本可以流畅升级 v5。

除了默认启用 :where 选择器的问题,官方文档还提到 v5 使用了 CSS 逻辑属性。参考 MDN 文档,CSS 逻辑属性有很多,彼此兼容性都不一样。以官方文档提到的 inset 属性为例,需要 Chrome 87 以上:

因此 CSS 逻辑属性也需要降级兼容:

tsx 复制代码
import React from 'react';
import { StyleProvider, legacyLogicalPropertiesTransformer } from '@ant-design/cssinjs';

// `transformers` 提供预处理功能将样式进行转换
export default () => (
  <StyleProvider transformers={[legacyLogicalPropertiesTransformer]}>
    <MyApp />
  </StyleProvider>
);

切换后,样式将降级 CSS 逻辑属性:

css 复制代码
.ant-modal-root {
-- inset: 0;
++ top: 0;
++ right: 0;
++ bottom: 0;
++ left: 0;
}

总结一下,antd v4 到 v5 属于 Breaking Change,不建议存量业务工程升级。升级也要做好心理准备,要么存在样式兼容问题,无法兼容 Chrome 88 以下浏览器(antd 的组件会全都挂掉,无法正常用),要么就手动处理一大堆样式覆盖不生效问题。官方 discussion 也有很多人建议不要升级 v5,因为 cssinjs 很多特性现在还不稳定,例如在 Next.js 13 里面 SSR 注水会报错等。对于新工程可以尝试,但是覆盖 antd 内部样式暂时没有好的解决方案。

最后顺带提一个和 antd 无关的问题,看到有同事开发的业务工程,vite 打包都不做兼容处理,例如:

ts 复制代码
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
	plugins: [react()],
	build: {
		// 完全没有兼容处理。。
		outDir: 'build'
	},
	resolve: {
		alias: {
			'@': resolve(__dirname, './src')
		}
	}
})

假如不做兼容会有什么问题?大家都知道,Vite 在生产环境下默认 esbuild minify,而 esbuild 不仅会做压缩,而且还会在 target 配置允许的范围内做一些语法转换(例如把低版本语法转为高版本),尽可能减小 bundle 体积。最关键的是,esbuild 的 target 默认 esnext,这种情况下,即使你用 Babel、PostCSS 等工具做语法转换,也很难保证 esbuild 不会给你转换成高版本语法(因为代码压缩总是最后一步)。

The default target is esnext which means that by default, esbuild will assume all of the latest JavaScript and CSS features are supported.

一种解决方案是给 esbuild 手动设置 target

ts 复制代码
build: {
	// 注意两个 target 都要设置
	target: "es2015",
	cssTarget: "chrome61"
}

由于 esbuild 最低只支持 es2015,如果需要支持更低版本浏览器,则可以改用 terser 压缩:

ts 复制代码
build: {
	minify: "terser"
}

另外,以上只是解决语法兼容问题,esbuild 不提供 api 兼容方案。即使你在业务代码中不用高版本 api,也很难保证第三方库不出现高版本 api,在低版本浏览器上肯定就是线上 bug 了。这种情况下,需要我们自行配置 @babel/preset-envcorejs 选项,然后在入口文件顶部加上 import "core-js/stable";

这就是为啥不建议 Vite 打包业务工程、只建议打包组件库的原因。业务工程配置更加复杂,而且很多都是 VIte 本身不提供的,需要开发者对工程化知识有深入理解。

参考

github.com/ant-design/... github.com/ant-design/... ant.design/docs/react/... developer.mozilla.org/zh-CN/docs/... developer.mozilla.org/zh-CN/docs/...

相关推荐
小曲曲40 分钟前
接口上传视频和oss直传视频到阿里云组件
javascript·阿里云·音视频
学不会•2 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
EasyNTS3 小时前
H.264/H.265播放器EasyPlayer.js视频流媒体播放器关于websocket1006的异常断连
javascript·h.265·h.264
活宝小娜4 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点4 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow4 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o4 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā5 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
沉默璇年7 小时前
react中useMemo的使用场景
前端·react.js·前端框架