Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions action.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Action Items for @ant-design/cssinjs

## 优化 CSS 变量注入以减少冗余

### 当前问题
在 `@rc-component/util` 的 `genStyleUtils` 中新增了 `extraCssVarPrefixCls` 选项,允许为多个 prefixCls 注册 CSS 变量。当前实现需要为每个 scope 调用一次 `useCSSVarRegister`,导致:

1. **代码冗余**:多个 scope 会生成相同值的 CSS 变量
2. **性能开销**:大量 DOM 操作会带来性能问题

### 当前生成的 CSS
```css
.xxx-hashId { --btn-color: #1890ff; }
.xxx-hashId.ant-btn-compact { --btn-color: #1890ff; }
.xxx-hashId.ant-btn-large { --btn-color: #1890ff; }
```

### 建议的优化方案
支持在 `useCSSVarRegister` 的 config 中传入多个 scope,生成逗号分隔的选择器:

```css
.xxx-hashId, .xxx-hashId.ant-btn-compact, .xxx-hashId.ant-btn-large {
--btn-color: #1890ff;
}
```

### API 变更建议
```typescript
// 当前 API
useCSSVarRegister(
{ ..., scope: 'ant-btn-compact' }, // 单个 scope
tokenGenerator
);

// 建议的 API(兼容现有)
useCSSVarRegister(
{ ..., scope: 'ant-btn-compact' }, // 仍然支持单个 scope
tokenGenerator
);
useCSSVarRegister(
{ ..., scopes: ['ant-btn', 'ant-btn-compact', 'ant-btn-large'] }, // 新增支持数组的 scopes
tokenGenerator
);
```

### 需要修改的文件
- `lib/hooks/useCSSVarRegister.js` - 接受 `scopes` 数组并修改 `serializeCSSVar` 生成逻辑
- `lib/util/css-variables.js` - 修改 `serializeCSSVar` 支持多个 scope 生成逗号分隔选择器
- 对应的 TypeScript 类型定义文件

### 注意事项
1. 保持向后兼容,`scope` 仍然可用
2. 这属于性能优化,不是破坏性变更
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@
"typescript": "^5.1.6"
},
"dependencies": {
"@ant-design/cssinjs": "^2.0.1",
"@ant-design/cssinjs": "2.1.0",
"@babel/runtime": "^7.23.2",
"@rc-component/util": "^1.4.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
}
}
73 changes: 46 additions & 27 deletions src/util/genStyleUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,13 @@ export type CSSVarRegisterProps = {

type GetResetStylesConfig = {
prefix: ReturnType<UsePrefix>;
csp: ReturnType<UseCSP>
csp: ReturnType<UseCSP>;
};

export type GetResetStyles<AliasToken extends TokenType> = (token: AliasToken, config?: GetResetStylesConfig) => CSSInterpolation;
export type GetResetStyles<AliasToken extends TokenType> = (
token: AliasToken,
config?: GetResetStylesConfig,
) => CSSInterpolation;

export type GetCompUnitless<CompTokenMap extends TokenMap, AliasToken extends TokenType> = <
C extends TokenMapKey<CompTokenMap>,
Expand Down Expand Up @@ -160,6 +163,18 @@ function genStyleUtils<
* @default true
*/
injectStyle?: boolean;
/**
* Extra prefixCls to inject CSS variables.
* 为额外的 prefixCls 注入 CSS 变量(不注入样式)。
*
* @example
* ```typescript
* {
* extraCssVarPrefixCls: ['my-comp-compact', 'my-comp-large']
* }
* ```
*/
extraCssVarPrefixCls?: string[];
},
) {
const componentName = Array.isArray(component) ? component[0] : component;
Expand Down Expand Up @@ -197,7 +212,9 @@ function genStyleUtils<

return (prefixCls: string, rootCls: string = prefixCls) => {
const hashId = useStyle(prefixCls, rootCls);
const cssVarCls = useCSSVar(rootCls);
const cssVarCls = useCSSVar(
options?.extraCssVarPrefixCls ? [rootCls, ...options.extraCssVarPrefixCls] : rootCls,
);

return [hashId, cssVarCls] as const;
};
Expand All @@ -208,7 +225,7 @@ function genStyleUtils<
getDefaultToken: GetDefaultToken<CompTokenMap, AliasToken, C> | undefined,
options: {
unitless?: Partial<Record<ComponentTokenKey<CompTokenMap, AliasToken, C>, boolean>>;
ignore?: Partial<Record<keyof AliasToken, boolean>>
ignore?: Partial<Record<keyof AliasToken, boolean>>;
deprecatedTokens?: [
ComponentTokenKey<CompTokenMap, AliasToken, C>,
ComponentTokenKey<CompTokenMap, AliasToken, C>,
Expand All @@ -219,9 +236,32 @@ function genStyleUtils<
) {
const { unitless: compUnitless, prefixToken, ignore } = options;

return (rootCls: string) => {
return (rootCls: string | string[]) => {
const { cssVar, realToken } = useToken();

const tokenGenerator = () => {
const defaultToken = getDefaultComponentToken<CompTokenMap, AliasToken, C>(
component,
realToken,
getDefaultToken,
);
const componentToken = getComponentToken<CompTokenMap, AliasToken, C>(
component,
realToken,
defaultToken,
{
deprecatedTokens: options?.deprecatedTokens,
},
);
if (defaultToken) {
Object.keys(defaultToken).forEach((key) => {
componentToken[prefixToken(key)] = componentToken[key];
delete componentToken[key];
});
}
return componentToken;
};

useCSSVarRegister(
{
path: [component],
Expand All @@ -232,28 +272,7 @@ function genStyleUtils<
token: realToken,
scope: rootCls,
},
() => {
const defaultToken = getDefaultComponentToken<CompTokenMap, AliasToken, C>(
component,
realToken,
getDefaultToken,
);
const componentToken = getComponentToken<CompTokenMap, AliasToken, C>(
component,
realToken,
defaultToken,
{
deprecatedTokens: options?.deprecatedTokens,
},
);
if (defaultToken) {
Object.keys(defaultToken).forEach((key) => {
componentToken[prefixToken(key)] = componentToken[key];
delete componentToken[key];
});
}
return componentToken;
},
tokenGenerator,
);

return cssVar?.key;
Expand Down
96 changes: 96 additions & 0 deletions tests/extraCssVarPrefixCls.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React from 'react';
import { render } from '@testing-library/react';
import { createCache, StyleProvider } from '@ant-design/cssinjs';
import { genStyleUtils } from '../src';

interface TestTokenMap {
TestComponent: {
colorPrimary?: string;
fontSize?: number;
};
}

describe('extraCssVarPrefixCls', () => {
const mockConfig = {
usePrefix: jest.fn().mockReturnValue({
rootPrefixCls: 'ant',
iconPrefixCls: 'anticon',
}),
useToken: jest.fn().mockReturnValue({
theme: {
id: 'test',
} as any,
realToken: {
colorPrimary: '#1890ff',
fontSize: 14,
TestComponent: {
colorPrimary: '#ff0000',
fontSize: 16,
},
},
hashId: 'css-dev-only-do-not-override-abc123',
token: {
colorPrimary: '#1890ff',
fontSize: 14,
TestComponent: {
colorPrimary: '#ff0000',
fontSize: 16,
},
},
cssVar: {
prefix: 'ant',
key: 'test',
},
}),
useCSP: jest.fn().mockReturnValue({ nonce: 'nonce' }),
getResetStyles: jest.fn().mockReturnValue([]),
layer: {
name: 'test',
dependencies: ['parent'],
},
};

const { genStyleHooks } = genStyleUtils<TestTokenMap, any, any>(mockConfig);

beforeEach(() => {
document.head.innerHTML = '';
});

it('should inject CSS vars for extraCssVarPrefixCls', () => {
const hooks = genStyleHooks(
'TestComponent',
(token) => ({
[`${token.componentCls}`]: {
color: token.colorPrimary,
fontSize: token.fontSize,
},
}),
() => ({
colorPrimary: '#ff0000',
fontSize: 16,
}),
{
extraCssVarPrefixCls: ['custom-a', 'custom-b'],
},
);

const TestComponent = () => {
const [hashId, cssVarCls] = hooks('test-prefix');
const className = [hashId, cssVarCls].filter(Boolean).join(' ');
return <div className={className}>{hashId}</div>;
};

render(
<StyleProvider cache={createCache()}>
<TestComponent />
</StyleProvider>,
);

const totalStyle = Array.from(document.querySelectorAll('style'))
.map((el) => el.textContent)
.join('\n');

expect(totalStyle).toContain('.custom-a');
expect(totalStyle).toContain('.custom-b');
});
});
18 changes: 8 additions & 10 deletions tests/genStyleUtils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('genStyleUtils', () => {
const component = 'TestComponent';
const styleFn = jest.fn();
const getDefaultToken = {
mockCompToken: 'mock'
mockCompToken: 'mock',
};
const hooks = genStyleHooks(component, styleFn, getDefaultToken);

Expand Down Expand Up @@ -158,16 +158,14 @@ describe('genStyleUtils', () => {
zeroRuntime: true,
}),
usePrefix,
}
const { genComponentStyleHook: gen } = genStyleUtils<
TestCompTokenMap,
object,
object
>(config);
};
const { genComponentStyleHook: gen } = genStyleUtils<TestCompTokenMap, object, object>(
config,
);

const styleFn = jest.fn();
const getDefaultToken = jest.fn();
const useStyle = gen('TestComponent', styleFn, getDefaultToken)
const useStyle = gen('TestComponent', styleFn, getDefaultToken);

const TestComponent: React.FC<SubStyleComponentProps> = ({ prefixCls, rootCls }) => {
useStyle(prefixCls, rootCls);
Expand All @@ -177,6 +175,6 @@ describe('genStyleUtils', () => {
const { getByTestId } = render(<TestComponent prefixCls="test-prefix" rootCls="test-root" />);
expect(getByTestId('test-component')).toHaveTextContent('Test');
expect(usePrefix).not.toHaveBeenCalled();
})
})
});
});
});