diff --git a/packages/x/components/theme/context.ts b/packages/x/components/theme/context.ts index f6b972baf9..ca9ecc9b94 100644 --- a/packages/x/components/theme/context.ts +++ b/packages/x/components/theme/context.ts @@ -35,5 +35,5 @@ export interface DesignTokenProviderProps { *); * ``` */ - // zeroRuntime?: boolean; + zeroRuntime?: boolean; } diff --git a/packages/x/components/theme/genStyleUtils.ts b/packages/x/components/theme/genStyleUtils.ts index 2ee7dcc7e9..ac12bb7de6 100644 --- a/packages/x/components/theme/genStyleUtils.ts +++ b/packages/x/components/theme/genStyleUtils.ts @@ -17,8 +17,8 @@ export const { genStyleHooks, genComponentStyleHook, genSubStyleComponent } = ge }; }, useToken: () => { - const [theme, realToken, hashId, token, cssVar] = useInternalToken(); - return { theme, realToken, hashId, token, cssVar }; + const [theme, realToken, hashId, token, cssVar, zeroRuntime] = useInternalToken(); + return { theme, realToken, hashId, token, cssVar, zeroRuntime }; }, useCSP: () => { const { csp } = useXProviderContext(); diff --git a/packages/x/components/theme/useToken.ts b/packages/x/components/theme/useToken.ts index 50e9dc786a..03b6f60f3b 100644 --- a/packages/x/components/theme/useToken.ts +++ b/packages/x/components/theme/useToken.ts @@ -100,10 +100,11 @@ export const getComputedToken = ( }; export function useInternalToken(): [ theme: Theme, - token: GlobalToken, - hashId: string, realToken: GlobalToken, + hashId: string, + token: GlobalToken, cssVar?: DesignTokenProviderProps['cssVar'], + zeroRuntime?: boolean, ] { const { token: rootDesignToken, @@ -111,6 +112,7 @@ export function useInternalToken(): [ theme, override, cssVar: ctxCssVar, + zeroRuntime, } = React.useContext(antdTheme._internalContext); const cssVar = { prefix: ctxCssVar?.prefix ?? 'ant', @@ -134,7 +136,14 @@ export function useInternalToken(): [ }, }, ); - return [mergedTheme as Theme, realToken, hashed ? hashId : '', token, cssVar]; + return [ + mergedTheme as Theme, + realToken, + hashed ? hashId : '', + token, + cssVar, + !!zeroRuntime, + ]; } export default function useToken() { diff --git a/packages/x/components/x-provider/__tests__/zeroRuntime.test.tsx b/packages/x/components/x-provider/__tests__/zeroRuntime.test.tsx new file mode 100644 index 0000000000..b5cf28e9eb --- /dev/null +++ b/packages/x/components/x-provider/__tests__/zeroRuntime.test.tsx @@ -0,0 +1,80 @@ +import { createCache, StyleProvider } from '@ant-design/cssinjs'; +import React from 'react'; +import { render } from '../../../tests/utils'; +import { Bubble, Sender } from '../../index'; +import XProvider from '../index'; + +/** + * Helper to find style tags that contain actual component styles (layout, visual) + * as opposed to CSS variable declarations only. + * + * CSS variable styles only contain `--ant-*` custom property declarations. + * Component styles contain actual CSS properties like `display`, `position`, `padding`, etc. + */ +function findComponentStyles(selector: string): HTMLStyleElement | undefined { + const styleList = Array.from(document.head.querySelectorAll('style')); + return styleList.find((style) => { + const html = style.innerHTML; + if (!html.includes(selector)) return false; + // Strip all CSS variable declarations to check if real properties remain + const withoutVars = html.replace(/--[\w-]+:[^;]+;/g, ''); + // If only variable declarations existed, stripping them leaves no property declarations + return /[\w-]+:/.test(withoutVars); + }); +} + +describe('XProvider.zeroRuntime', () => { + beforeEach(() => { + document.head.innerHTML = ''; + }); + + it('should inject component styles at runtime by default', () => { + render( + + + + + , + ); + + expect(findComponentStyles('.ant-bubble')).toBeTruthy(); + }); + + it('should skip component style injection when zeroRuntime is true', () => { + render( + + + + + , + ); + + expect(findComponentStyles('.ant-bubble')).toBeFalsy(); + }); + + it('should skip component style injection for multiple components', () => { + render( + + + + + + , + ); + + expect(findComponentStyles('.ant-bubble')).toBeFalsy(); + expect(findComponentStyles('.ant-sender')).toBeFalsy(); + }); + + it('should still render component DOM when zeroRuntime is true', () => { + const { container } = render( + + + + + , + ); + + expect(container.querySelector('.ant-bubble')).toBeTruthy(); + }); +});