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

相关推荐
程序员shen16161112 分钟前
抖音短视频saas矩阵源码系统开发所需掌握的技术
java·前端·数据库·python·算法
Ling_suu41 分钟前
SpringBoot3——Web开发
java·服务器·前端
Yvemil71 小时前
《开启微服务之旅:Spring Boot Web开发》(二)
前端·spring boot·微服务
hanglove_lucky1 小时前
本地摄像头视频流在html中打开
前端·后端·html
维李设论1 小时前
Node.js的Web服务在Nacos中的实践
前端·spring cloud·微服务·eureka·nacos·node.js·express
2401_857600951 小时前
基于 SSM 框架 Vue 电脑测评系统:赋能电脑品质鉴定
前端·javascript·vue.js
天之涯上上1 小时前
Pinia 是一个专为 Vue.js 3 设计的状态管理库
前端·javascript·vue.js
@大迁世界1 小时前
摆脱 `<div>`!7 种更语义化的 HTML 标签替代方案
前端·html
高山我梦口香糖2 小时前
[react] <NavLink>自带激活属性
前端·javascript·react.js
撸码到无法自拔2 小时前
React:组件、状态与事件处理的完整指南
前端·javascript·react.js·前端框架·ecmascript