1
This commit is contained in:
+155
@@ -0,0 +1,155 @@
|
||||
import hash from '@emotion/hash';
|
||||
import { updateCSS } from "@rc-component/util/es/Dom/dynamicCSS";
|
||||
import { useContext } from 'react';
|
||||
import StyleContext, { ATTR_MARK, ATTR_TOKEN, CSS_IN_JS_INSTANCE } from "../StyleContext";
|
||||
import { flattenToken, injectCSPNonce, memoResult, token2key, toStyleStr } from "../util";
|
||||
import { transformToken } from "../util/css-variables";
|
||||
import useGlobalCache from "./useGlobalCache";
|
||||
const EMPTY_OVERRIDE = {};
|
||||
|
||||
// Generate different prefix to make user selector break in production env.
|
||||
// This helps developer not to do style override directly on the hash id.
|
||||
const hashPrefix = process.env.NODE_ENV !== 'production' ? 'css-dev-only-do-not-override' : 'css';
|
||||
const tokenKeys = new Map();
|
||||
function recordCleanToken(tokenKey) {
|
||||
tokenKeys.set(tokenKey, (tokenKeys.get(tokenKey) || 0) + 1);
|
||||
}
|
||||
function removeStyleTags(key, instanceId) {
|
||||
if (typeof document !== 'undefined') {
|
||||
const styles = document.querySelectorAll(`style[${ATTR_TOKEN}="${key}"]`);
|
||||
styles.forEach(style => {
|
||||
if (style[CSS_IN_JS_INSTANCE] === instanceId) {
|
||||
style.parentNode?.removeChild(style);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
const TOKEN_THRESHOLD = -1;
|
||||
|
||||
// Remove will check current keys first
|
||||
function cleanTokenStyle(tokenKey, instanceId) {
|
||||
tokenKeys.set(tokenKey, (tokenKeys.get(tokenKey) || 0) - 1);
|
||||
const cleanableKeyList = new Set();
|
||||
tokenKeys.forEach((value, key) => {
|
||||
if (value <= 0) cleanableKeyList.add(key);
|
||||
});
|
||||
|
||||
// Should keep tokens under threshold for not to insert style too often
|
||||
if (tokenKeys.size - cleanableKeyList.size > TOKEN_THRESHOLD) {
|
||||
cleanableKeyList.forEach(key => {
|
||||
removeStyleTags(key, instanceId);
|
||||
tokenKeys.delete(key);
|
||||
});
|
||||
}
|
||||
}
|
||||
export const getComputedToken = (originToken, overrideToken, theme, format) => {
|
||||
const derivativeToken = theme.getDerivativeToken(originToken);
|
||||
|
||||
// Merge with override
|
||||
let mergedDerivativeToken = {
|
||||
...derivativeToken,
|
||||
...overrideToken
|
||||
};
|
||||
|
||||
// Format if needed
|
||||
if (format) {
|
||||
mergedDerivativeToken = format(mergedDerivativeToken);
|
||||
}
|
||||
return mergedDerivativeToken;
|
||||
};
|
||||
export const TOKEN_PREFIX = 'token';
|
||||
/**
|
||||
* Cache theme derivative token as global shared one
|
||||
* @param theme Theme entity
|
||||
* @param tokens List of tokens, used for cache. Please do not dynamic generate object directly
|
||||
* @param option Additional config
|
||||
* @returns Call Theme.getDerivativeToken(tokenObject) to get token
|
||||
*/
|
||||
export default function useCacheToken(theme, tokens, option) {
|
||||
const {
|
||||
cache: {
|
||||
instanceId
|
||||
},
|
||||
container,
|
||||
hashPriority
|
||||
} = useContext(StyleContext);
|
||||
const {
|
||||
salt = '',
|
||||
override = EMPTY_OVERRIDE,
|
||||
formatToken,
|
||||
getComputedToken: compute,
|
||||
cssVar,
|
||||
nonce
|
||||
} = option;
|
||||
|
||||
// Basic - We do basic cache here
|
||||
const mergedToken = memoResult(() => Object.assign({}, ...tokens), tokens);
|
||||
const tokenStr = flattenToken(mergedToken);
|
||||
const overrideTokenStr = flattenToken(override);
|
||||
const cssVarStr = flattenToken(cssVar);
|
||||
const cachedToken = useGlobalCache(TOKEN_PREFIX, [salt, theme.id, tokenStr, overrideTokenStr, cssVarStr], () => {
|
||||
const mergedDerivativeToken = compute ? compute(mergedToken, override, theme) : getComputedToken(mergedToken, override, theme, formatToken);
|
||||
const actualToken = {
|
||||
...mergedDerivativeToken
|
||||
};
|
||||
|
||||
// Optimize for `useStyleRegister` performance
|
||||
const mergedSalt = `${salt}_${cssVar.prefix}`;
|
||||
const hashId = hash(mergedSalt);
|
||||
const hashCls = `${hashPrefix}-${hashId}`;
|
||||
actualToken._tokenKey = token2key(actualToken, mergedSalt);
|
||||
|
||||
// Replace token value with css variables
|
||||
const [tokenWithCssVar, cssVarsStr] = transformToken(mergedDerivativeToken, cssVar.key, {
|
||||
prefix: cssVar.prefix,
|
||||
ignore: cssVar.ignore,
|
||||
unitless: cssVar.unitless,
|
||||
preserve: cssVar.preserve,
|
||||
hashPriority,
|
||||
hashCls: cssVar.hashed ? hashCls : undefined
|
||||
});
|
||||
tokenWithCssVar._hashId = hashId;
|
||||
recordCleanToken(cssVar.key);
|
||||
return [tokenWithCssVar, hashCls, actualToken, cssVarsStr, cssVar.key];
|
||||
}, ([,,,, themeKey]) => {
|
||||
// Remove token will remove all related style
|
||||
cleanTokenStyle(themeKey, instanceId);
|
||||
}, ([,,, cssVarsStr, themeKey]) => {
|
||||
if (!cssVarsStr) {
|
||||
return;
|
||||
}
|
||||
let mergedCSSConfig = {
|
||||
mark: ATTR_MARK,
|
||||
prepend: 'queue',
|
||||
attachTo: container,
|
||||
priority: -999
|
||||
};
|
||||
mergedCSSConfig = injectCSPNonce(mergedCSSConfig, nonce);
|
||||
const style = updateCSS(cssVarsStr, hash(`css-var-${themeKey}`), mergedCSSConfig);
|
||||
style[CSS_IN_JS_INSTANCE] = instanceId;
|
||||
|
||||
// Used for `useCacheToken` to remove on batch when token removed
|
||||
style.setAttribute(ATTR_TOKEN, themeKey);
|
||||
});
|
||||
return cachedToken;
|
||||
}
|
||||
export const extract = (cache, effectStyles, options) => {
|
||||
const [,, realToken, styleStr, cssVarKey] = cache;
|
||||
const {
|
||||
plain
|
||||
} = options || {};
|
||||
if (!styleStr) {
|
||||
return null;
|
||||
}
|
||||
const styleId = realToken._tokenKey;
|
||||
const order = -999;
|
||||
|
||||
// ====================== Style ======================
|
||||
// Used for @rc-component/util
|
||||
const sharedAttrs = {
|
||||
'data-rc-order': 'prependQueue',
|
||||
'data-rc-priority': `${order}`
|
||||
};
|
||||
const styleText = toStyleStr(styleStr, cssVarKey, styleId, sharedAttrs, plain);
|
||||
return [order, styleId, styleText];
|
||||
};
|
||||
Reference in New Issue
Block a user