浅谈 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/...

相关推荐
什么鬼昵称22 分钟前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色39 分钟前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2341 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河1 小时前
CSS总结
前端·css
NiNg_1_2341 小时前
Vue3 Pinia持久化存储
开发语言·javascript·ecmascript
读心悦1 小时前
如何在 Axios 中封装事件中心EventEmitter
javascript·http
BigYe程普1 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
神之王楠2 小时前
如何通过js加载css和html
javascript·css·html
余生H2 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍2 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发