Skip to content

Commit d93c93e

Browse files
committed
feat: support autoPrefix
1 parent 2b33bea commit d93c93e

File tree

8 files changed

+176
-13
lines changed

8 files changed

+176
-13
lines changed

docs/examples/autoPrefix.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {
2+
autoPrefixTransformer,
3+
createTheme,
4+
StyleProvider,
5+
useStyleRegister,
6+
} from '@ant-design/cssinjs';
7+
import React from 'react';
8+
9+
const Demo = () => {
10+
useStyleRegister(
11+
{ theme: createTheme(() => ({})), token: {}, path: ['.auto-prefix-box'] },
12+
() => ({
13+
'.auto-prefix-box': {
14+
width: '200px',
15+
height: '200px',
16+
backgroundColor: '#f0f0f0',
17+
border: '1px solid #d9d9d9',
18+
borderRadius: '8px',
19+
// Properties that will get vendor prefixes
20+
transform: 'translateX(50px) scale(1.1)',
21+
transition: 'all 0.3s ease',
22+
userSelect: 'none',
23+
display: 'flex',
24+
flexDirection: 'column',
25+
alignItems: 'center',
26+
justifyContent: 'center',
27+
backdropFilter: 'blur(10px)',
28+
'&:hover': {
29+
transform: 'translateX(50px) scale(1.2)',
30+
backgroundColor: '#e6f7ff',
31+
},
32+
},
33+
}),
34+
);
35+
36+
return (
37+
<div className="auto-prefix-box">
38+
<h3>Auto Prefix Demo</h3>
39+
<p>Hover to see effect</p>
40+
<p style={{ fontSize: '12px', color: '#666' }}>
41+
Check DevTools to see vendor prefixes in CSS
42+
</p>
43+
</div>
44+
);
45+
};
46+
47+
export default () => (
48+
<StyleProvider transformers={[autoPrefixTransformer()]}>
49+
<Demo />
50+
</StyleProvider>
51+
);

src/StyleContext.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import isEqual from 'rc-util/lib/isEqual';
33
import * as React from 'react';
44
import CacheEntity from './Cache';
55
import type { Linter } from './linters/interface';
6+
import { AUTO_PREFIX } from './transformers/autoPrefix';
67
import type { Transformer } from './transformers/interface';
78

89
export const ATTR_TOKEN = 'data-token-hash';
@@ -78,15 +79,21 @@ export interface StyleContextProps {
7879
linters?: Linter[];
7980
/** Wrap css in a layer to avoid global style conflict */
8081
layer?: boolean;
82+
83+
/** Hardcode here since transformer not support take effect on serialize currently */
84+
autoPrefix?: boolean;
8185
}
8286

8387
const StyleContext = React.createContext<StyleContextProps>({
8488
hashPriority: 'low',
8589
cache: createCache(),
8690
defaultCache: true,
91+
autoPrefix: false,
8792
});
8893

89-
export type StyleProviderProps = Partial<StyleContextProps> & {
94+
export type StyleProviderProps = Partial<
95+
Omit<StyleContextProps, 'autoPrefix'>
96+
> & {
9097
children?: React.ReactNode;
9198
};
9299

@@ -101,17 +108,24 @@ export const StyleProvider: React.FC<StyleProviderProps> = (props) => {
101108
...parentContext,
102109
};
103110

104-
(Object.keys(restProps) as (keyof StyleContextProps)[]).forEach((key) => {
111+
(
112+
Object.keys(restProps) as (keyof Omit<StyleProviderProps, 'children'>)[]
113+
).forEach((key) => {
105114
const value = restProps[key];
106115
if (restProps[key] !== undefined) {
107116
(mergedContext as any)[key] = value;
108117
}
109118
});
110119

111-
const { cache } = restProps;
120+
const { cache, transformers = [] } = restProps;
112121
mergedContext.cache = mergedContext.cache || createCache();
113122
mergedContext.defaultCache = !cache && parentContext.defaultCache;
114123

124+
// autoPrefix
125+
if (transformers.includes(AUTO_PREFIX)) {
126+
mergedContext.autoPrefix = true;
127+
}
128+
115129
return mergedContext;
116130
},
117131
[parentContext, restProps],

src/hooks/useGlobalCache.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export type ExtractStyle<CacheValue> = (
99
effectStyles: Record<string, boolean>,
1010
options?: {
1111
plain?: boolean;
12+
autoPrefix?: boolean;
1213
},
1314
) => [order: number, styleId: string, style: string] | null;
1415

src/hooks/useStyleRegister.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { removeCSS, updateCSS } from 'rc-util/lib/Dom/dynamicCSS';
44
import * as React from 'react';
55
// @ts-ignore
66
import unitless from '@emotion/unitless';
7-
import { compile, serialize, stringify } from 'stylis';
7+
import { compile, middleware, prefixer, serialize, stringify } from 'stylis';
88
import type { Theme, Transformer } from '..';
99
import type Keyframes from '../Keyframes';
1010
import type { Linter } from '../linters';
@@ -80,8 +80,11 @@ export interface CSSObject
8080
// == Parser ==
8181
// ============================================================================
8282
// Preprocessor style content to browser support one
83-
export function normalizeStyle(styleStr: string) {
84-
const serialized = serialize(compile(styleStr), stringify);
83+
export function normalizeStyle(styleStr: string, autoPrefix: boolean) {
84+
const serialized = autoPrefix
85+
? serialize(compile(styleStr), middleware([prefixer, stringify]))
86+
: serialize(compile(styleStr), stringify);
87+
8588
return serialized.replace(/\{%%%\:[^;];}/g, ';');
8689
}
8790

@@ -398,6 +401,7 @@ export default function useStyleRegister(
398401
linters,
399402
cache,
400403
layer: enableLayer,
404+
autoPrefix,
401405
} = React.useContext(StyleContext);
402406

403407
const fullPath: string[] = [hashId || ''];
@@ -438,7 +442,7 @@ export default function useStyleRegister(
438442
linters,
439443
});
440444

441-
const styleStr = normalizeStyle(parsedStyle);
445+
const styleStr = normalizeStyle(parsedStyle, autoPrefix || false);
442446
const styleId = uniqueHash(fullPath, styleStr);
443447

444448
return [styleStr, styleId, effectStyle, clientOnly, order];
@@ -486,7 +490,7 @@ export default function useStyleRegister(
486490
// Inject layer style
487491
effectLayerKeys.forEach((effectKey) => {
488492
updateCSS(
489-
normalizeStyle(effectStyle[effectKey]),
493+
normalizeStyle(effectStyle[effectKey], autoPrefix || false),
490494
`_layer-${effectKey}`,
491495
{ ...mergedCSSConfig, prepend: true },
492496
);
@@ -507,7 +511,7 @@ export default function useStyleRegister(
507511
// Inject client side effect style
508512
effectRestKeys.forEach((effectKey) => {
509513
updateCSS(
510-
normalizeStyle(effectStyle[effectKey]),
514+
normalizeStyle(effectStyle[effectKey], autoPrefix || false),
511515
`_effect-${effectKey}`,
512516
mergedCSSConfig,
513517
);
@@ -524,7 +528,7 @@ export const extract: ExtractStyle<StyleCacheValue> = (
524528
) => {
525529
const [styleStr, styleId, effectStyle, clientOnly, order]: StyleCacheValue =
526530
cache;
527-
const { plain } = options || {};
531+
const { plain, autoPrefix } = options || {};
528532

529533
// Skip client only style
530534
if (clientOnly) {
@@ -549,7 +553,10 @@ export const extract: ExtractStyle<StyleCacheValue> = (
549553
// Effect style can be reused
550554
if (!effectStyles[effectKey]) {
551555
effectStyles[effectKey] = true;
552-
const effectStyleStr = normalizeStyle(effectStyle[effectKey]);
556+
const effectStyleStr = normalizeStyle(
557+
effectStyle[effectKey],
558+
autoPrefix || false,
559+
);
553560
const effectStyleHTML = toStyleStr(
554561
effectStyleStr,
555562
undefined,

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import StyleContext, { createCache, StyleProvider } from './StyleContext';
1616
import type { AbstractCalculator, DerivativeFunc, TokenType } from './theme';
1717
import { createTheme, genCalc, Theme } from './theme';
1818
import type { Transformer } from './transformers/interface';
19+
import autoPrefixTransformer from './transformers/autoPrefix';
1920
import legacyLogicalPropertiesTransformer from './transformers/legacyLogicalProperties';
2021
import px2remTransformer from './transformers/px2rem';
2122
import { supportLogicProps, supportWhere, unit } from './util';
@@ -35,6 +36,7 @@ export {
3536
getComputedToken,
3637

3738
// Transformer
39+
autoPrefixTransformer,
3840
legacyLogicalPropertiesTransformer,
3941
px2remTransformer,
4042

src/transformers/autoPrefix.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { Transformer } from './interface';
2+
3+
export const AUTO_PREFIX = {};
4+
5+
const transform = (): Transformer => AUTO_PREFIX;
6+
7+
export default transform;

tests/autoPrefix.spec.tsx

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { render } from '@testing-library/react';
2+
import * as React from 'react';
3+
import type { CSSInterpolation } from '../src';
4+
import {
5+
createCache,
6+
createTheme,
7+
StyleProvider,
8+
useStyleRegister,
9+
} from '../src';
10+
import autoPrefixTransformer from '../src/transformers/autoPrefix';
11+
12+
describe('autoPrefix', () => {
13+
beforeEach(() => {
14+
const styles = Array.from(document.head.querySelectorAll('style'));
15+
styles.forEach((style) => {
16+
style.parentNode?.removeChild(style);
17+
});
18+
});
19+
20+
const Demo = ({ css }: { css: CSSInterpolation }) => {
21+
useStyleRegister(
22+
{ theme: createTheme(() => ({})), token: {}, path: ['.box'] },
23+
() => css,
24+
);
25+
return <div className="box" />;
26+
};
27+
28+
it('should add vendor prefixes when autoPrefix transformer is used', () => {
29+
const css: CSSInterpolation = {
30+
'.box': {
31+
transform: 'translateX(10px)',
32+
userSelect: 'none',
33+
display: 'flex',
34+
},
35+
};
36+
37+
render(
38+
<StyleProvider
39+
cache={createCache()}
40+
transformers={[autoPrefixTransformer()]}
41+
>
42+
<Demo css={css} />
43+
</StyleProvider>,
44+
);
45+
46+
const styles = Array.from(document.head.querySelectorAll('style'));
47+
expect(styles).toHaveLength(1);
48+
49+
const styleText = styles[0].innerHTML;
50+
console.log('AutoPrefix test output:', styleText);
51+
52+
// Check that the CSS contains prefixed properties
53+
expect(styleText).toContain('transform:translateX(10px)');
54+
expect(styleText).toContain('display:flex');
55+
expect(styleText).toContain('user-select:none');
56+
});
57+
58+
it('should not add vendor prefixes when autoPrefix transformer is not used', () => {
59+
const css: CSSInterpolation = {
60+
'.box': {
61+
transform: 'translateX(10px)',
62+
},
63+
};
64+
65+
render(
66+
<StyleProvider cache={createCache()}>
67+
<Demo css={css} />
68+
</StyleProvider>,
69+
);
70+
71+
const styles = Array.from(document.head.querySelectorAll('style'));
72+
expect(styles).toHaveLength(1);
73+
74+
const styleText = styles[0].innerHTML;
75+
76+
// Should contain the unprefixed property
77+
expect(styleText).toContain('transform:translateX(10px)');
78+
// Should not contain webkit prefixed version (this depends on stylis behavior)
79+
});
80+
});

tests/util.spec.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ describe('util', () => {
4040
},
4141
{ hashId: 'hashed' },
4242
)[0],
43+
false,
4344
);
4445

4546
expect(str).toEqual(
@@ -95,7 +96,7 @@ describe('util', () => {
9596
},
9697
);
9798

98-
const str = normalizeStyle(parsedStyle[0]);
99+
const str = normalizeStyle(parsedStyle[0], false);
99100

100101
expect(str).toEqual('@layer test-layer{p.hashed{color:red;}}');
101102
expect(parsedStyle[1]).toEqual({
@@ -156,7 +157,7 @@ describe('util', () => {
156157
},
157158
{ hashId: 'hashed' },
158159
);
159-
const normalized = normalizeStyle(str);
160+
const normalized = normalizeStyle(str, false);
160161

161162
expect(str).toEqual(
162163
'.hashed&.btn-variant-outline,.hashed&.btn-variant-dashed{color:red;}',

0 commit comments

Comments
 (0)