最近在升级 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/cssinjs
的 StyleProvider
取消默认的降权操作。
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-env
的 corejs
选项,然后在入口文件顶部加上 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/...