Skip to content

Commit ec07fb1

Browse files
authored
fix: apply StyleProvider nonce to CSS variable styles (#252)
* fix: apply StyleProvider nonce to CSS variable styles 修复了当通过 StyleProvider 传递 nonce 时,CSS 变量样式未获得 nonce 属性的问题。 Changes: - 在 StyleContextProps 中添加 nonce 配置支持 - 在 useCacheToken 中应用 nonce 到 CSS var 样式注入 - 在 useCSSVarRegister 中应用 nonce 到 CSS var 样式注入 - 为 StyleProvider nonce 功能添加测试用例 用户现在可以一致地在所有样式注入点使用 StyleProvider 配置 nonce。 改进建议(未来优化): - 提取 nonce 处理逻辑为共享函数以减少代码重复 - 当前测试已覆盖 useCacheToken → useCSSVarRegister 的调用路径 * refactor: extract nonce handling logic into shared function 提取了 useCacheToken 和 useCSSVarRegister 中重复的 nonce 处理逻辑为 mergeCSSConfig 共享函数,减少代码重复并提高可维护性。 Changes: - 在 util/index.ts 中添加 mergeCSSConfig 工具函数 - useCacheToken 使用 mergeCSSConfig 代替重复代码 - useCSSVarRegister 使用 mergeCSSConfig 代替重复代码 * fix: correct mergeCSSConfig generic type constraint 修复了 mergeCSSConfig 的泛型约束,使其支持 Options | undefined 类型,而不是只接受 Record<string, any>。 Changes: - 移除 mergeCSSConfig 的泛型约束 `T extends Record<string, any>` - 改为泛型 `T` 以支持任何类型
1 parent 103922e commit ec07fb1

5 files changed

Lines changed: 73 additions & 15 deletions

File tree

src/StyleContext.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,11 @@ export interface StyleContextProps {
7878
linters?: Linter[];
7979
/** Wrap css in a layer to avoid global style conflict */
8080
layer?: boolean;
81-
8281
/** Hardcode here since transformer not support take effect on serialize currently */
8382
autoPrefix?: boolean;
83+
84+
/** Nonce for CSP (Content Security Policy) */
85+
nonce?: string | (() => string);
8486
}
8587

8688
const StyleContext = React.createContext<StyleContextProps>({

src/hooks/useCSSVarRegister.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import StyleContext, {
55
ATTR_TOKEN,
66
CSS_IN_JS_INSTANCE,
77
} from '../StyleContext';
8-
import { isClientSide, toStyleStr } from '../util';
8+
import { isClientSide, mergeCSSConfig, toStyleStr } from '../util';
99
import type { TokenWithCSSVar } from '../util/css-variables';
1010
import { transformToken } from '../util/css-variables';
1111
import type { ExtractStyle } from './useGlobalCache';
@@ -39,6 +39,7 @@ const useCSSVarRegister = <V, T extends Record<string, V>>(
3939
cache: { instanceId },
4040
container,
4141
hashPriority,
42+
nonce,
4243
} = useContext(StyleContext);
4344
const { _tokenKey: tokenKey } = token;
4445

@@ -70,12 +71,17 @@ const useCSSVarRegister = <V, T extends Record<string, V>>(
7071
if (!cssVarsStr) {
7172
return;
7273
}
73-
const style = updateCSS(cssVarsStr, styleId, {
74-
mark: ATTR_MARK,
75-
prepend: 'queue',
76-
attachTo: container,
77-
priority: -999,
78-
});
74+
const mergedCSSConfig = mergeCSSConfig<Parameters<typeof updateCSS>[2]>(
75+
{
76+
mark: ATTR_MARK,
77+
prepend: 'queue',
78+
attachTo: container,
79+
priority: -999,
80+
},
81+
nonce,
82+
);
83+
84+
const style = updateCSS(cssVarsStr, styleId, mergedCSSConfig);
7985

8086
(style as any)[CSS_IN_JS_INSTANCE] = instanceId;
8187

src/hooks/useCacheToken.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import StyleContext, {
77
CSS_IN_JS_INSTANCE,
88
} from '../StyleContext';
99
import type Theme from '../theme/Theme';
10-
import { flattenToken, memoResult, token2key, toStyleStr } from '../util';
10+
import { flattenToken, memoResult, mergeCSSConfig, token2key, toStyleStr } from '../util';
1111
import { transformToken } from '../util/css-variables';
1212
import type { ExtractStyle } from './useGlobalCache';
1313
import useGlobalCache from './useGlobalCache';
@@ -161,6 +161,7 @@ export default function useCacheToken<
161161
cache: { instanceId },
162162
container,
163163
hashPriority,
164+
nonce,
164165
} = useContext(StyleContext);
165166
const {
166167
salt = '',
@@ -219,12 +220,17 @@ export default function useCacheToken<
219220
if (!cssVarsStr) {
220221
return;
221222
}
222-
const style = updateCSS(cssVarsStr, hash(`css-var-${themeKey}`), {
223-
mark: ATTR_MARK,
224-
prepend: 'queue',
225-
attachTo: container,
226-
priority: -999,
227-
});
223+
const mergedCSSConfig = mergeCSSConfig<Parameters<typeof updateCSS>[2]>(
224+
{
225+
mark: ATTR_MARK,
226+
prepend: 'queue',
227+
attachTo: container,
228+
priority: -999,
229+
},
230+
nonce,
231+
);
232+
233+
const style = updateCSS(cssVarsStr, hash(`css-var-${themeKey}`), mergedCSSConfig);
228234

229235
(style as any)[CSS_IN_JS_INSTANCE] = instanceId;
230236

src/util/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,28 @@ export function supportLogicProps(): boolean {
155155

156156
export const isClientSide = canUseDom();
157157

158+
/**
159+
* Merge CSS injection configuration with nonce support
160+
*/
161+
/**
162+
* Merge CSS injection configuration with nonce support
163+
*/
164+
export function mergeCSSConfig<T>(
165+
config: T,
166+
nonce?: string | (() => string),
167+
): T {
168+
if (!nonce) {
169+
return config;
170+
}
171+
172+
const mergedConfig = { ...config };
173+
const nonceStr = typeof nonce === 'function' ? nonce() : nonce;
174+
if (nonceStr) {
175+
(mergedConfig as any).csp = { nonce: nonceStr };
176+
}
177+
178+
return mergedConfig;
179+
}
158180
export function unit(num: string | number) {
159181
if (typeof num === 'number') {
160182
return `${num}px`;

tests/index.spec.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,28 @@ describe('csssinjs', () => {
507507
test('function', () => 'bamboo');
508508
});
509509

510+
describe('StyleProvider nonce for CSS var', () => {
511+
function testWithStyleProvider(name: string, nonce: string | (() => string)) {
512+
it(name, () => {
513+
const { unmount } = render(
514+
<StyleProvider cache={createCache()} nonce={nonce}>
515+
<Box />
516+
</StyleProvider>,
517+
);
518+
519+
const styles = Array.from(document.head.querySelectorAll('style'));
520+
// Box 组件使用 useCacheToken 注册 CSS var,应该有 nonce
521+
const cssVarStyle = styles.find(s => s.innerHTML.includes('--primary-color'));
522+
expect(cssVarStyle).toBeDefined();
523+
expect(cssVarStyle?.nonce).toBe('bamboo');
524+
// unmount 后样式清理行为取决于 cache 配置
525+
});
526+
}
527+
528+
testWithStyleProvider('string', 'bamboo');
529+
testWithStyleProvider('function', () => 'bamboo');
530+
});
531+
510532
it('should not insert style with different instanceId', () => {
511533
const genDemoStyle = (token: DerivativeToken): CSSInterpolation => ({
512534
div: {

0 commit comments

Comments
 (0)