浅谈 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 分钟前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
我要洋人死43 分钟前
导航栏及下拉菜单的实现
前端·css·css3
川石课堂软件测试1 小时前
性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台
运维·javascript·深度学习·jmeter·docker·容器·grafana
科技探秘人1 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人1 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR1 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai1 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
problc1 小时前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter