diff --git a/.dumi/theme/locales/en-US.json b/.dumi/theme/locales/en-US.json index bd1fa14132..f3a0cd5f5e 100644 --- a/.dumi/theme/locales/en-US.json +++ b/.dumi/theme/locales/en-US.json @@ -5,10 +5,6 @@ "app.theme.switch.compact": "Compact theme", "app.header.search": "Search...", "app.header.menu.documentation": "Docs", - "app.header.menu.development": "Development", - "app.header.menu.components": "Components", - "app.header.menu.spec": "Design", - "app.header.menu.resource": "Resources", "app.header.menu.more": "More", "app.header.menu.mobile": "Mobile", "app.header.menu.pro.v4": "Ant Design Pro", diff --git a/.dumi/theme/locales/zh-CN.json b/.dumi/theme/locales/zh-CN.json index efd572abca..bfeb9c11a4 100644 --- a/.dumi/theme/locales/zh-CN.json +++ b/.dumi/theme/locales/zh-CN.json @@ -5,10 +5,6 @@ "app.theme.switch.compact": "紧凑主题", "app.header.search": "全文本搜索...", "app.header.menu.documentation": "文档", - "app.header.menu.development": "研发", - "app.header.menu.components": "组件", - "app.header.menu.spec": "设计", - "app.header.menu.resource": "资源", "app.header.menu.more": "更多", "app.header.menu.mobile": "移动版", "app.header.menu.pro.v4": "Ant Design Pro", diff --git a/.dumi/theme/slots/Header/Navigation.tsx b/.dumi/theme/slots/Header/Navigation.tsx index bb373fd115..9ff027040a 100644 --- a/.dumi/theme/slots/Header/Navigation.tsx +++ b/.dumi/theme/slots/Header/Navigation.tsx @@ -1,15 +1,35 @@ import * as React from 'react'; import classNames from 'classnames'; -import { Link, useLocation, FormattedMessage } from 'dumi'; -import type { MenuProps } from 'antd'; +import { Link, useLocation, FormattedMessage, useFullSidebarData } from 'dumi'; +import { MenuProps, Tooltip } from 'antd'; import { MenuOutlined } from '@ant-design/icons'; import { Menu } from 'antd'; import { getEcosystemGroup } from './More'; import * as utils from '../../utils'; import type { SharedProps } from './interface'; import useSiteToken from '../../../hooks/useSiteToken'; +import useLocale from '../../../hooks/useLocale'; import { css } from '@emotion/react'; +// ============================= Theme ============================= +const locales = { + cn: { + design: '设计', + development: '研发', + components: '组件', + resources: '资源', + blog: '博客', + }, + en: { + design: 'Design', + development: 'Development', + components: 'Components', + resources: 'Resources', + blog: 'Blog', + }, +}; + +// ============================= Style ============================= const useStyle = () => { const { token } = useSiteToken(); @@ -99,6 +119,10 @@ export default ({ onDirectionChange, }: NavigationProps) => { const { pathname, search } = useLocation(); + const [locale] = useLocale(locales); + + const sidebarData = useFullSidebarData(); + const blogList = sidebarData['/docs/blog']?.[0]?.children || []; const style = useStyle(); @@ -160,7 +184,7 @@ export default ({ { label: ( - + {locale.design} ), key: 'docs/spec', @@ -168,7 +192,7 @@ export default ({ { label: ( - + {locale.development} ), key: 'docs/react', @@ -176,15 +200,25 @@ export default ({ { label: ( - + {locale.components} ), key: 'components', }, + blogList.length + ? { + label: ( + + {locale.blog} + + ), + key: 'docs/blog', + } + : null, { label: ( - + {locale.resources} ), key: 'docs/resources', @@ -192,9 +226,14 @@ export default ({ showTechUIButton ? { label: ( - - TechUI - + + + + + ), key: 'tech-ui', } diff --git a/.dumi/theme/slots/Header/SwitchBtn.tsx b/.dumi/theme/slots/Header/SwitchBtn.tsx new file mode 100644 index 0000000000..b21bd8a6ff --- /dev/null +++ b/.dumi/theme/slots/Header/SwitchBtn.tsx @@ -0,0 +1,128 @@ +import * as React from 'react'; +import { Button, Tooltip } from 'antd'; +import useSharedStyle from './style'; +import useSiteToken from '../../../hooks/useSiteToken'; +import { css } from '@emotion/react'; + +export interface LangBtnProps { + label1: React.ReactNode; + label2: React.ReactNode; + tooltip1?: React.ReactNode; + tooltip2?: React.ReactNode; + value: 1 | 2; + pure?: boolean; + onClick?: React.MouseEventHandler; +} + +const useStyle = () => { + const { token } = useSiteToken(); + const { controlHeightSM } = token; + + return { + btn: css` + padding: 0 !important; + width: ${controlHeightSM}px; + height: ${controlHeightSM}px; + display: inline-flex; + align-items: center; + justify-content: center; + + img { + width: 1em; + height: 1em; + } + `, + }; +}; + +export default function LangBtn({ + label1, + label2, + tooltip1, + tooltip2, + value, + pure, + onClick, +}: LangBtnProps) { + const { token } = useSiteToken(); + const style = useStyle(); + const sharedStyle = useSharedStyle(); + + let label1Style: React.CSSProperties; + let label2Style: React.CSSProperties; + + const iconStyle: React.CSSProperties = { + position: 'absolute', + fontSize: '1em', + lineHeight: 1, + border: `1px solid ${token.colorText}`, + color: token.colorText, + }; + + const fontStyle: React.CSSProperties = { + left: '-5%', + top: 0, + zIndex: 1, + background: token.colorText, + color: token.colorTextLightSolid, + transformOrigin: '0 0', + transform: `scale(0.7)`, + }; + const backStyle: React.CSSProperties = { + right: '-5%', + bottom: 0, + zIndex: 0, + transformOrigin: '100% 100%', + transform: `scale(0.5)`, + }; + + if (value === 1) { + label1Style = fontStyle; + label2Style = backStyle; + } else { + label1Style = backStyle; + label2Style = fontStyle; + } + + let node = ( + + ); + + if (tooltip1 || tooltip2) { + node = {node}; + } + + return node; +} diff --git a/.dumi/theme/slots/Header/index.tsx b/.dumi/theme/slots/Header/index.tsx index cc3344dec2..bfda6d2356 100644 --- a/.dumi/theme/slots/Header/index.tsx +++ b/.dumi/theme/slots/Header/index.tsx @@ -19,6 +19,8 @@ import { useLocation, useNavigate } from 'dumi'; import { ClassNames, css } from '@emotion/react'; import useSiteToken from '../../../hooks/useSiteToken'; import useLocale from '../../../hooks/useLocale'; +import SwitchBtn from './SwitchBtn'; +import useSharedStyle from './style'; const RESPONSIVE_XS = 1120; const RESPONSIVE_SM = 1200; @@ -55,8 +57,8 @@ const useStyle = () => { } .nav-search-wrapper { - flex: auto; display: flex; + flex: auto; } .dumi-default-search-bar { @@ -121,10 +123,6 @@ const useStyle = () => { } } `, - headerButton: css` - color: ${token.colorText}; - border-color: ${token.colorBorder}; - `, popoverMenu: { width: 300, @@ -216,6 +214,7 @@ const Header: React.FC = (props) => { const navigate = useNavigate(); const style = useStyle(); + const sharedStyle = useSharedStyle(); const handleHideMenu = useCallback(() => { setHeaderState((prev) => ({ ...prev, menuVisible: false })); @@ -415,17 +414,29 @@ const Header: React.FC = (props) => { {versionOptions} , - , - , + value={direction === 'rtl' ? 2 : 1} + label1={ + + } + tooltip1="LTR" + label2={ + + } + tooltip2="RTL" + pure + />, , , ]; diff --git a/.dumi/theme/slots/Header/style.tsx b/.dumi/theme/slots/Header/style.tsx new file mode 100644 index 0000000000..d13567cd35 --- /dev/null +++ b/.dumi/theme/slots/Header/style.tsx @@ -0,0 +1,15 @@ +import { css } from '@emotion/react'; +import useSiteToken from '../../../hooks/useSiteToken'; + +const useSharedStyle = () => { + const { token } = useSiteToken(); + + return { + headerButton: css` + color: ${token.colorText}; + border-color: ${token.colorBorder}; + `, + }; +}; + +export default useSharedStyle; diff --git a/.dumi/theme/utils.tsx b/.dumi/theme/utils.tsx index f24df6305f..08d845e33e 100644 --- a/.dumi/theme/utils.tsx +++ b/.dumi/theme/utils.tsx @@ -139,6 +139,7 @@ export function getLocalizedPathname( fullPath = pathname.replace(/\/$/, '-cn/'); } else { fullPath = `${pathname}-cn`; + fullPath = fullPath.replace(/(-cn)+/, '-cn'); } if (hash) { diff --git a/docs/blog/css-in-js.en-US.md b/docs/blog/css-in-js.en-US.md new file mode 100644 index 0000000000..2e01935688 --- /dev/null +++ b/docs/blog/css-in-js.en-US.md @@ -0,0 +1,26 @@ +--- +order: 0 +title: Component-level CSS-in-JS +--- + +On November 18, 2022, we released Ant Design 5.0. At the same time, Ant Design's unique CSS-in-JS solution was brought into everyone's view. Through this solution, Ant Design achieves higher performance than other CSS-in-JS libraries, but at the cost of sacrificing its flexibility for free use in applications. So we call it a "component-level" CSS-in-JS solution. + +## Dilemma of CSS-in-JS + +In CSS-in-JS, hash is used to confirm whether a style has been inserted. The way to calculate the hash is usually to convert a complete css into a hash value. For example, in emotion, we can see such a style tag by checking the elements on the page. The hash value corresponding to such a style tag is unique:
![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*X5tDQ5VIpcoAAAAAAAAAAAAADrJ8AQ/original)
In this way, you can find a problem that CSS-in-JS has been criticized for a long time. What we write when coding is not the final css. So every time we need to serialize to get the css and calculate the hash again. If your page or component has a very complex or a large amount of CSS-in-JS code, and even the style will follow the component's props change, then this performance issue becomes non-negligible.
To solve this problem, each CSS-in-JS library will have its own way to deal with it. Let’s take a look at Ant Design’s solution. + +## Hash + +In fact, it is not difficult for us to find that the problem lies in the process of serializing css. How about reducing the times of serializing css by caching? For application-level CSS-in-JS, it is difficult for us to find a suitable key for cache. But if it is a component library, the final style is relatively stable.
According to the style structure we determined from v4 and previous versions, the style of each component will not change under the same theme variable and the same version. Conversely, the style may change only if the theme variable is modified, or the version of antd is changed.
From this we get a very simple way to calculate the hash:
![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*XuVYRJ_27Q0AAAAAAAAAAAAADrJ8AQ/original)
We will apply the **same** **hash** to all antd components. In this way, when using the antd component, we only perform hash calculations on the current version and theme variables. Version can be obtained directly from `package.json`, and theme variables can be obtained directly from context. So we don't need to serialize css again and again to get a stable hash, and the performance is improved finally. + +## Cache for Components + +In the above way, we have taken the first step of "component level" CSS-in-JS, but this is not enough. Since it is "component level", we can also optimize it again with components.
In Ant Design, the style of a component is usually complete. That is to say, no matter what variant the component has, its style exist in the whole component style. In this way, we can draw a conclusion again: the props of antd components will not affect the component style.
This is very important. In the application-level CSS-in-JS solution, since props may affect the component style, it is inevitable that the component style will be regenerated during the rendering phase. No matter how to optimize this point, it cannot be ignored. Now that we have adopted a "component-level" solution, this problem can be easily solved: do style caching for components.
![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*yZMNSYVtxnAAAAAAAAAAAAAADrJ8AQ/original)
In the case of the same hash, no matter how many times the same component is used and rendered, the style will only be generated once at the first mount, and will hit the cache for the rest of the time. This is the second insurance for "component level" CSS-in-JS solutions. + +## Benchmark + +At the release of Ant Design 5.0, we simply made a benchmark, and here are some supplementary instructions:
![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*upmYSqZ5FwsAAAAAAAAAAAAADrJ8AQ/original)
The benchmark is based on generating a very long unchanging style to test the performance of basic usage of the three libraries. It can be seen that under the "component level" usage scenario of Ant Design, @ant-design/cssinjs has a performance advantage whether it is the first rendering or the second rendering. Since styled has certain optimizations when dealing with stable styles, the performance of secondary rendering in this benchmark is better, but it will still be affected by recalculation like emotion when props participate in style calculation. + +## Limitation + +In the above comparison, it cannot be said that antd is definitely better than styled and emotion, but in the component-level usage scenarios, we have made corresponding optimizations to obtain performance advantages. Conversely, due to the limitation of "component level", antd's CSS-in-JS solution is not suitable for construction applications.
Due to the special hash calculation method and component cache, when applying antd's CSS-in-JS solution, developers must provide stable hash and unique component names by themselves. For applications, automatic hash capabilities such as css modules are more needed. At the same time, caching a large number of components in the application also requires additional management costs. Once an error occurs, it is difficult to troubleshoot. Therefore, we recommend using the "component-level" CSS-in-JS solution in component libraries. diff --git a/docs/blog/css-in-js.zh-CN.md b/docs/blog/css-in-js.zh-CN.md new file mode 100644 index 0000000000..aa490ad2e3 --- /dev/null +++ b/docs/blog/css-in-js.zh-CN.md @@ -0,0 +1,28 @@ +--- +order: 0 +title: 组件级别的 CSS-in-JS +--- + +- 2022-11-25 + +在 2022 年 11 月 18 日,我们发布了 Ant Design 5.0 的正式版本,同时带入大家视野中的还有 Ant Design 独特的 CSS-in-JS 方案。通过这个方案,Ant Design 获得了相较于其他 CSS-in-JS 库更高的性能,但代价则是牺牲了其在应用中自由使用的灵活性。所以我们把它称为“组件级”的 CSS-in-JS 方案。 + +## Dilemma of CSS-in-JS + +在 CSS-in-JS 中,hash 会用于确认一段 style 是否已经插入。而计算 hash 的方法通常是将一段完整的 css 转换为 hash 值。比如在 emotion 中,我们检查页面中的元素就可以看到这样的 style 标签,这样的 style 标签对应的 hash 值每一段都是不一样的:
![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*X5tDQ5VIpcoAAAAAAAAAAAAADrJ8AQ/original)
如此便可以引入一个 CSS-in-JS 被诟病已久的问题:我们在编写代码时写的并不是最终的 css,所以每次都需要重新序列化得到 css 后再次计算 hash,这就在每次渲染组件时带来了而外的开销。如果你的页面或者组件带有非常复杂或者大量的 CSS-in-JS 代码,甚至样式会跟随组件的 props 变化,那么这个性能消耗便变得不可忽视。
针对这个问题,各个 CSS-in-JS 库会有自己的应对方式,这里就先不做赘述,让我们来看一看 Ant Design 的方案。 + +## 计算 hash + +其实我们不难发现,问题其实在于序列化 css 的过程。如果通过缓存的方法去减少序列化 css 的次数呢?对于应用级的 CSS-in-JS 来说,我们很难去找到一个很合适的 key 去确认缓存。但是如果是组件库的话,最终得到的样式则是比较稳定的。根据我们从 v4 及之前版本确定下来的样式结构,每一个组件的样式在相同的主题变量和相同的版本下是不会改变的。反过来说,只有修改了主题变量,或者改变了 antd 的版本,样式才可能会变化。由此我们得到了一个非常简单的计算 hash 的方法:
![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*XuVYRJ_27Q0AAAAAAAAAAAAADrJ8AQ/original)
我们会对所有的 antd 组件应用**相同的** **hash**。如此一来,使用 antd 组件时,我们只会对当前的版本和主题变量进行 hash 计算,而前者可以直接由 `package.json`中得到,后者可以直接从 context 中得到,所以我们并不需要进行繁重的序列化 css 的操作,就可以得到稳定的 hash,从而大幅地减少性能消耗。 + +## 组件缓存 + +通过上述的方式,我们迈出了“组件级” CSS-in-JS 的第一步,但是这还不够。既然是“组件级”,那我们也可以针对组件再次进行优化。
在 Ant Design 中,一个组件的样式通常来说是“完整”的,也就是说不管这个组件有什么样的变体,他的样式会一并存在于组件样式中。如此我们可以再次得到一个结论:antd 组件的 props **不会**影响组件样式。这是非常重要的一点,在应用级的 CSS-in-JS 方案中,由于存在 props 影响组件样式的可能,所以不可避免地会在渲染阶段重新生成组件样式,不管如何去优化这一点仍无法忽视。既然我们采用了“组件级”的方案,那么这个问题就可以很轻松地解决:对组件做样式缓存。
![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*yZMNSYVtxnAAAAAAAAAAAAAADrJ8AQ/original)
在 hash 相同的情况下,同一个组件无论使用了多少次、渲染了多少次,样式永远只会在第一次 mount 时生成一次,剩下的时间里都会命中缓存,这便是“组件级” CSS-in-JS 方案的第二重保险。 + +## Benchmark + +在 Ant Design 5.0 的发布会上,我们简单地做了一次 benchmark,在这里可以做一些补充说明:
![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*upmYSqZ5FwsAAAAAAAAAAAAADrJ8AQ/original)
这个 benchmark 的成立条件是产生一段非常长的不会变更的样式,以此来测试这三个库的基本用法的性能。可以看出在 Ant Design 的“组件级”使用场景下,无论是初次渲染还是二次渲染,antd 都拥有性能上的优势。由于 styled 在处理稳定的样式时有一定优化,所以这个 benchmark 中二次渲染的性能较好,但在有 props 参与样式计算时仍会和 emotion 一样受到重新计算的影响。 + +## “组件级”的局限 + +在上述的对比中,其实并不能说 antd 一定优于 styled 和 emotion,而是在 antd 的组件级使用场景下,我们做了相应的优化以取得了性能上的优势。反过来说,由于“组件级”的局限性,antd 的 CSS-in-JS 方案并不能适用于日常构建应用。
由于特殊的 hash 计算方法和组件缓存,在套用 antd 的 CSS-in-JS 方案时,开发者必须自己提供稳定的 hash 和独特的组件名。对于应用来说,像 css module 这样的自动 hash 的能力反而是更为需要的,同时对应用中海量的组件进行缓存也需要额外的管理成本,一旦出错问题是很难排查的。因此我们更加推荐在组件库中使用“组件级”的 CSS-in-JS 方案。