Skip to content

Commit d0f7c34

Browse files
committed
chore: update cssinjs
1 parent 2b8f2ad commit d0f7c34

File tree

9 files changed

+404
-81
lines changed

9 files changed

+404
-81
lines changed

Diff for: components/_util/cssinjs/Cache.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
export type KeyType = string | number;
22
type ValueType = [number, any]; // [times, realValue]
3-
3+
const SPLIT = '%';
44
class Entity {
5+
instanceId: string;
6+
constructor(instanceId: string) {
7+
this.instanceId = instanceId;
8+
}
59
/** @private Internal cache map. Do not access this directly */
610
cache = new Map<string, ValueType>();
711

812
get(keys: KeyType[] | string): ValueType | null {
9-
return this.cache.get(Array.isArray(keys) ? keys.join('%') : keys) || null;
13+
return this.cache.get(Array.isArray(keys) ? keys.join(SPLIT) : keys) || null;
1014
}
1115

1216
update(keys: KeyType[] | string, valueFn: (origin: ValueType | null) => ValueType | null) {
13-
const path = Array.isArray(keys) ? keys.join('%') : keys;
17+
const path = Array.isArray(keys) ? keys.join(SPLIT) : keys;
1418
const prevValue = this.cache.get(path)!;
1519
const nextValue = valueFn(prevValue);
1620

Diff for: components/_util/cssinjs/StyleContext.tsx

+8-6
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,22 @@ import { arrayType, booleanType, objectType, someType, stringType, withInstall }
77
import initDefaultProps from '../props-util/initDefaultProps';
88
export const ATTR_TOKEN = 'data-token-hash';
99
export const ATTR_MARK = 'data-css-hash';
10-
export const ATTR_DEV_CACHE_PATH = 'data-dev-cache-path';
10+
export const ATTR_CACHE_PATH = 'data-cache-path';
1111

1212
// Mark css-in-js instance in style element
1313
export const CSS_IN_JS_INSTANCE = '__cssinjs_instance__';
14-
export const CSS_IN_JS_INSTANCE_ID = Math.random().toString(12).slice(2);
1514

1615
export function createCache() {
16+
const cssinjsInstanceId = Math.random().toString(12).slice(2);
17+
18+
// Tricky SSR: Move all inline style to the head.
19+
// PS: We do not recommend tricky mode.
1720
if (typeof document !== 'undefined' && document.head && document.body) {
1821
const styles = document.body.querySelectorAll(`style[${ATTR_MARK}]`) || [];
1922
const { firstChild } = document.head;
2023

2124
Array.from(styles).forEach(style => {
22-
(style as any)[CSS_IN_JS_INSTANCE] =
23-
(style as any)[CSS_IN_JS_INSTANCE] || CSS_IN_JS_INSTANCE_ID;
25+
(style as any)[CSS_IN_JS_INSTANCE] = (style as any)[CSS_IN_JS_INSTANCE] || cssinjsInstanceId;
2426

2527
// Not force move if no head
2628
document.head.insertBefore(style, firstChild);
@@ -31,7 +33,7 @@ export function createCache() {
3133
Array.from(document.querySelectorAll(`style[${ATTR_MARK}]`)).forEach(style => {
3234
const hash = style.getAttribute(ATTR_MARK)!;
3335
if (styleHash[hash]) {
34-
if ((style as any)[CSS_IN_JS_INSTANCE] === CSS_IN_JS_INSTANCE_ID) {
36+
if ((style as any)[CSS_IN_JS_INSTANCE] === cssinjsInstanceId) {
3537
style.parentNode?.removeChild(style);
3638
}
3739
} else {
@@ -40,7 +42,7 @@ export function createCache() {
4042
});
4143
}
4244

43-
return new CacheEntity();
45+
return new CacheEntity(cssinjsInstanceId);
4446
}
4547

4648
export type HashPriority = 'low' | 'high';

Diff for: components/_util/cssinjs/hooks/useCacheToken.tsx

+57-22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import hash from '@emotion/hash';
2-
import { ATTR_TOKEN, CSS_IN_JS_INSTANCE, CSS_IN_JS_INSTANCE_ID } from '../StyleContext';
2+
import { ATTR_TOKEN, CSS_IN_JS_INSTANCE, useStyleInject } from '../StyleContext';
33
import type Theme from '../theme/Theme';
44
import useGlobalCache from './useGlobalCache';
55
import { flattenToken, token2key } from '../util';
@@ -12,7 +12,7 @@ const EMPTY_OVERRIDE = {};
1212
// This helps developer not to do style override directly on the hash id.
1313
const hashPrefix = process.env.NODE_ENV !== 'production' ? 'css-dev-only-do-not-override' : 'css';
1414

15-
export interface Option<DerivativeToken> {
15+
export interface Option<DerivativeToken, DesignToken> {
1616
/**
1717
* Generate token with salt.
1818
* This is used to generate different hashId even same derivative token for different version.
@@ -30,27 +30,41 @@ export interface Option<DerivativeToken> {
3030
* It's ok to useMemo outside but this has better cache strategy.
3131
*/
3232
formatToken?: (mergedToken: any) => DerivativeToken;
33+
/**
34+
* Get final token with origin token, override token and theme.
35+
* The parameters do not contain formatToken since it's passed by user.
36+
* @param origin The original token.
37+
* @param override Extra tokens to override.
38+
* @param theme Theme instance. Could get derivative token by `theme.getDerivativeToken`
39+
*/
40+
getComputedToken?: (
41+
origin: DesignToken,
42+
override: object,
43+
theme: Theme<any, any>,
44+
) => DerivativeToken;
3345
}
3446

3547
const tokenKeys = new Map<string, number>();
3648
function recordCleanToken(tokenKey: string) {
3749
tokenKeys.set(tokenKey, (tokenKeys.get(tokenKey) || 0) + 1);
3850
}
3951

40-
function removeStyleTags(key: string) {
52+
function removeStyleTags(key: string, instanceId: string) {
4153
if (typeof document !== 'undefined') {
4254
const styles = document.querySelectorAll(`style[${ATTR_TOKEN}="${key}"]`);
4355

4456
styles.forEach(style => {
45-
if ((style as any)[CSS_IN_JS_INSTANCE] === CSS_IN_JS_INSTANCE_ID) {
57+
if ((style as any)[CSS_IN_JS_INSTANCE] === instanceId) {
4658
style.parentNode?.removeChild(style);
4759
}
4860
});
4961
}
5062
}
5163

64+
const TOKEN_THRESHOLD = 0;
65+
5266
// Remove will check current keys first
53-
function cleanTokenStyle(tokenKey: string) {
67+
function cleanTokenStyle(tokenKey: string, instanceId: string) {
5468
tokenKeys.set(tokenKey, (tokenKeys.get(tokenKey) || 0) - 1);
5569

5670
const tokenKeyList = Array.from(tokenKeys.keys());
@@ -60,14 +74,37 @@ function cleanTokenStyle(tokenKey: string) {
6074
return count <= 0;
6175
});
6276

63-
if (cleanableKeyList.length < tokenKeyList.length) {
77+
// Should keep tokens under threshold for not to insert style too often
78+
if (tokenKeyList.length - cleanableKeyList.length > TOKEN_THRESHOLD) {
6479
cleanableKeyList.forEach(key => {
65-
removeStyleTags(key);
80+
removeStyleTags(key, instanceId);
6681
tokenKeys.delete(key);
6782
});
6883
}
6984
}
7085

86+
export const getComputedToken = <DerivativeToken = object, DesignToken = DerivativeToken>(
87+
originToken: DesignToken,
88+
overrideToken: object,
89+
theme: Theme<any, any>,
90+
format?: (token: DesignToken) => DerivativeToken,
91+
) => {
92+
const derivativeToken = theme.getDerivativeToken(originToken);
93+
94+
// Merge with override
95+
let mergedDerivativeToken = {
96+
...derivativeToken,
97+
...overrideToken,
98+
};
99+
100+
// Format if needed
101+
if (format) {
102+
mergedDerivativeToken = format(mergedDerivativeToken);
103+
}
104+
105+
return mergedDerivativeToken;
106+
};
107+
71108
/**
72109
* Cache theme derivative token as global shared one
73110
* @param theme Theme entity
@@ -78,8 +115,10 @@ function cleanTokenStyle(tokenKey: string) {
78115
export default function useCacheToken<DerivativeToken = object, DesignToken = DerivativeToken>(
79116
theme: Ref<Theme<any, any>>,
80117
tokens: Ref<Partial<DesignToken>[]>,
81-
option: Ref<Option<DerivativeToken>> = ref({}),
118+
option: Ref<Option<DerivativeToken, DesignToken>> = ref({}),
82119
) {
120+
const style = useStyleInject();
121+
83122
// Basic - We do basic cache here
84123
const mergedToken = computed(() => Object.assign({}, ...tokens.value));
85124
const tokenStr = computed(() => flattenToken(mergedToken.value));
@@ -94,19 +133,15 @@ export default function useCacheToken<DerivativeToken = object, DesignToken = De
94133
overrideTokenStr.value,
95134
]),
96135
() => {
97-
const { salt = '', override = EMPTY_OVERRIDE, formatToken } = option.value;
98-
const derivativeToken = theme.value.getDerivativeToken(mergedToken.value);
99-
100-
// Merge with override
101-
let mergedDerivativeToken = {
102-
...derivativeToken,
103-
...override,
104-
};
105-
106-
// Format if needed
107-
if (formatToken) {
108-
mergedDerivativeToken = formatToken(mergedDerivativeToken);
109-
}
136+
const {
137+
salt = '',
138+
override = EMPTY_OVERRIDE,
139+
formatToken,
140+
getComputedToken: compute,
141+
} = option.value;
142+
const mergedDerivativeToken = compute
143+
? compute(mergedToken.value, override, theme.value)
144+
: getComputedToken(mergedToken.value, override, theme.value, formatToken);
110145

111146
// Optimize for `useStyleRegister` performance
112147
const tokenKey = token2key(mergedDerivativeToken, salt);
@@ -120,7 +155,7 @@ export default function useCacheToken<DerivativeToken = object, DesignToken = De
120155
},
121156
cache => {
122157
// Remove token will remove all related style
123-
cleanTokenStyle(cache[0]._tokenKey);
158+
cleanTokenStyle(cache[0]._tokenKey, style.value?.cache.instanceId);
124159
},
125160
);
126161

Diff for: components/_util/cssinjs/hooks/useHMR.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ if (
1616
process.env.NODE_ENV !== 'production' &&
1717
typeof module !== 'undefined' &&
1818
module &&
19-
(module as any).hot
19+
(module as any).hot &&
20+
typeof window !== 'undefined'
2021
) {
2122
const win = window as any;
2223
if (typeof win.webpackHotUpdate === 'function') {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import canUseDom from '../../../../_util/canUseDom';
2+
import { ATTR_MARK } from '../../StyleContext';
3+
4+
export const ATTR_CACHE_MAP = 'data-ant-cssinjs-cache-path';
5+
6+
/**
7+
* This marks style from the css file.
8+
* Which means not exist in `<style />` tag.
9+
*/
10+
export const CSS_FILE_STYLE = '_FILE_STYLE__';
11+
12+
export function serialize(cachePathMap: Record<string, string>) {
13+
return Object.keys(cachePathMap)
14+
.map(path => {
15+
const hash = cachePathMap[path];
16+
return `${path}:${hash}`;
17+
})
18+
.join(';');
19+
}
20+
21+
let cachePathMap: Record<string, string>;
22+
let fromCSSFile = true;
23+
24+
/**
25+
* @private Test usage only. Can save remove if no need.
26+
*/
27+
export function reset(mockCache?: Record<string, string>, fromFile = true) {
28+
cachePathMap = mockCache!;
29+
fromCSSFile = fromFile;
30+
}
31+
32+
export function prepare() {
33+
if (!cachePathMap) {
34+
cachePathMap = {};
35+
36+
if (canUseDom()) {
37+
const div = document.createElement('div');
38+
div.className = ATTR_CACHE_MAP;
39+
div.style.position = 'fixed';
40+
div.style.visibility = 'hidden';
41+
div.style.top = '-9999px';
42+
document.body.appendChild(div);
43+
44+
let content = getComputedStyle(div).content || '';
45+
content = content.replace(/^"/, '').replace(/"$/, '');
46+
47+
// Fill data
48+
content.split(';').forEach(item => {
49+
const [path, hash] = item.split(':');
50+
cachePathMap[path] = hash;
51+
});
52+
53+
// Remove inline record style
54+
const inlineMapStyle = document.querySelector(`style[${ATTR_CACHE_MAP}]`);
55+
if (inlineMapStyle) {
56+
fromCSSFile = false;
57+
inlineMapStyle.parentNode?.removeChild(inlineMapStyle);
58+
}
59+
60+
document.body.removeChild(div);
61+
}
62+
}
63+
}
64+
65+
export function existPath(path: string) {
66+
prepare();
67+
68+
return !!cachePathMap[path];
69+
}
70+
71+
export function getStyleAndHash(path: string): [style: string | null, hash: string] {
72+
const hash = cachePathMap[path];
73+
let styleStr: string | null = null;
74+
75+
if (hash && canUseDom()) {
76+
if (fromCSSFile) {
77+
styleStr = CSS_FILE_STYLE;
78+
} else {
79+
const style = document.querySelector(`style[${ATTR_MARK}="${cachePathMap[path]}"]`);
80+
81+
if (style) {
82+
styleStr = style.innerHTML;
83+
} else {
84+
// Clean up since not exist anymore
85+
delete cachePathMap[path];
86+
}
87+
}
88+
}
89+
90+
return [styleStr, hash];
91+
}

0 commit comments

Comments
 (0)