chore: update cssinjs
parent
2b8f2adcbf
commit
d0f7c34497
|
@ -1,16 +1,20 @@
|
||||||
export type KeyType = string | number;
|
export type KeyType = string | number;
|
||||||
type ValueType = [number, any]; // [times, realValue]
|
type ValueType = [number, any]; // [times, realValue]
|
||||||
|
const SPLIT = '%';
|
||||||
class Entity {
|
class Entity {
|
||||||
|
instanceId: string;
|
||||||
|
constructor(instanceId: string) {
|
||||||
|
this.instanceId = instanceId;
|
||||||
|
}
|
||||||
/** @private Internal cache map. Do not access this directly */
|
/** @private Internal cache map. Do not access this directly */
|
||||||
cache = new Map<string, ValueType>();
|
cache = new Map<string, ValueType>();
|
||||||
|
|
||||||
get(keys: KeyType[] | string): ValueType | null {
|
get(keys: KeyType[] | string): ValueType | null {
|
||||||
return this.cache.get(Array.isArray(keys) ? keys.join('%') : keys) || null;
|
return this.cache.get(Array.isArray(keys) ? keys.join(SPLIT) : keys) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(keys: KeyType[] | string, valueFn: (origin: ValueType | null) => ValueType | null) {
|
update(keys: KeyType[] | string, valueFn: (origin: ValueType | null) => ValueType | null) {
|
||||||
const path = Array.isArray(keys) ? keys.join('%') : keys;
|
const path = Array.isArray(keys) ? keys.join(SPLIT) : keys;
|
||||||
const prevValue = this.cache.get(path)!;
|
const prevValue = this.cache.get(path)!;
|
||||||
const nextValue = valueFn(prevValue);
|
const nextValue = valueFn(prevValue);
|
||||||
|
|
||||||
|
|
|
@ -7,20 +7,22 @@ import { arrayType, booleanType, objectType, someType, stringType, withInstall }
|
||||||
import initDefaultProps from '../props-util/initDefaultProps';
|
import initDefaultProps from '../props-util/initDefaultProps';
|
||||||
export const ATTR_TOKEN = 'data-token-hash';
|
export const ATTR_TOKEN = 'data-token-hash';
|
||||||
export const ATTR_MARK = 'data-css-hash';
|
export const ATTR_MARK = 'data-css-hash';
|
||||||
export const ATTR_DEV_CACHE_PATH = 'data-dev-cache-path';
|
export const ATTR_CACHE_PATH = 'data-cache-path';
|
||||||
|
|
||||||
// Mark css-in-js instance in style element
|
// Mark css-in-js instance in style element
|
||||||
export const CSS_IN_JS_INSTANCE = '__cssinjs_instance__';
|
export const CSS_IN_JS_INSTANCE = '__cssinjs_instance__';
|
||||||
export const CSS_IN_JS_INSTANCE_ID = Math.random().toString(12).slice(2);
|
|
||||||
|
|
||||||
export function createCache() {
|
export function createCache() {
|
||||||
|
const cssinjsInstanceId = Math.random().toString(12).slice(2);
|
||||||
|
|
||||||
|
// Tricky SSR: Move all inline style to the head.
|
||||||
|
// PS: We do not recommend tricky mode.
|
||||||
if (typeof document !== 'undefined' && document.head && document.body) {
|
if (typeof document !== 'undefined' && document.head && document.body) {
|
||||||
const styles = document.body.querySelectorAll(`style[${ATTR_MARK}]`) || [];
|
const styles = document.body.querySelectorAll(`style[${ATTR_MARK}]`) || [];
|
||||||
const { firstChild } = document.head;
|
const { firstChild } = document.head;
|
||||||
|
|
||||||
Array.from(styles).forEach(style => {
|
Array.from(styles).forEach(style => {
|
||||||
(style as any)[CSS_IN_JS_INSTANCE] =
|
(style as any)[CSS_IN_JS_INSTANCE] = (style as any)[CSS_IN_JS_INSTANCE] || cssinjsInstanceId;
|
||||||
(style as any)[CSS_IN_JS_INSTANCE] || CSS_IN_JS_INSTANCE_ID;
|
|
||||||
|
|
||||||
// Not force move if no head
|
// Not force move if no head
|
||||||
document.head.insertBefore(style, firstChild);
|
document.head.insertBefore(style, firstChild);
|
||||||
|
@ -31,7 +33,7 @@ export function createCache() {
|
||||||
Array.from(document.querySelectorAll(`style[${ATTR_MARK}]`)).forEach(style => {
|
Array.from(document.querySelectorAll(`style[${ATTR_MARK}]`)).forEach(style => {
|
||||||
const hash = style.getAttribute(ATTR_MARK)!;
|
const hash = style.getAttribute(ATTR_MARK)!;
|
||||||
if (styleHash[hash]) {
|
if (styleHash[hash]) {
|
||||||
if ((style as any)[CSS_IN_JS_INSTANCE] === CSS_IN_JS_INSTANCE_ID) {
|
if ((style as any)[CSS_IN_JS_INSTANCE] === cssinjsInstanceId) {
|
||||||
style.parentNode?.removeChild(style);
|
style.parentNode?.removeChild(style);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -40,7 +42,7 @@ export function createCache() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CacheEntity();
|
return new CacheEntity(cssinjsInstanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HashPriority = 'low' | 'high';
|
export type HashPriority = 'low' | 'high';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import hash from '@emotion/hash';
|
import hash from '@emotion/hash';
|
||||||
import { ATTR_TOKEN, CSS_IN_JS_INSTANCE, CSS_IN_JS_INSTANCE_ID } from '../StyleContext';
|
import { ATTR_TOKEN, CSS_IN_JS_INSTANCE, useStyleInject } from '../StyleContext';
|
||||||
import type Theme from '../theme/Theme';
|
import type Theme from '../theme/Theme';
|
||||||
import useGlobalCache from './useGlobalCache';
|
import useGlobalCache from './useGlobalCache';
|
||||||
import { flattenToken, token2key } from '../util';
|
import { flattenToken, token2key } from '../util';
|
||||||
|
@ -12,7 +12,7 @@ const EMPTY_OVERRIDE = {};
|
||||||
// This helps developer not to do style override directly on the hash id.
|
// 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 hashPrefix = process.env.NODE_ENV !== 'production' ? 'css-dev-only-do-not-override' : 'css';
|
||||||
|
|
||||||
export interface Option<DerivativeToken> {
|
export interface Option<DerivativeToken, DesignToken> {
|
||||||
/**
|
/**
|
||||||
* Generate token with salt.
|
* Generate token with salt.
|
||||||
* This is used to generate different hashId even same derivative token for different version.
|
* This is used to generate different hashId even same derivative token for different version.
|
||||||
|
@ -30,6 +30,18 @@ export interface Option<DerivativeToken> {
|
||||||
* It's ok to useMemo outside but this has better cache strategy.
|
* It's ok to useMemo outside but this has better cache strategy.
|
||||||
*/
|
*/
|
||||||
formatToken?: (mergedToken: any) => DerivativeToken;
|
formatToken?: (mergedToken: any) => DerivativeToken;
|
||||||
|
/**
|
||||||
|
* Get final token with origin token, override token and theme.
|
||||||
|
* The parameters do not contain formatToken since it's passed by user.
|
||||||
|
* @param origin The original token.
|
||||||
|
* @param override Extra tokens to override.
|
||||||
|
* @param theme Theme instance. Could get derivative token by `theme.getDerivativeToken`
|
||||||
|
*/
|
||||||
|
getComputedToken?: (
|
||||||
|
origin: DesignToken,
|
||||||
|
override: object,
|
||||||
|
theme: Theme<any, any>,
|
||||||
|
) => DerivativeToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenKeys = new Map<string, number>();
|
const tokenKeys = new Map<string, number>();
|
||||||
|
@ -37,20 +49,22 @@ function recordCleanToken(tokenKey: string) {
|
||||||
tokenKeys.set(tokenKey, (tokenKeys.get(tokenKey) || 0) + 1);
|
tokenKeys.set(tokenKey, (tokenKeys.get(tokenKey) || 0) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeStyleTags(key: string) {
|
function removeStyleTags(key: string, instanceId: string) {
|
||||||
if (typeof document !== 'undefined') {
|
if (typeof document !== 'undefined') {
|
||||||
const styles = document.querySelectorAll(`style[${ATTR_TOKEN}="${key}"]`);
|
const styles = document.querySelectorAll(`style[${ATTR_TOKEN}="${key}"]`);
|
||||||
|
|
||||||
styles.forEach(style => {
|
styles.forEach(style => {
|
||||||
if ((style as any)[CSS_IN_JS_INSTANCE] === CSS_IN_JS_INSTANCE_ID) {
|
if ((style as any)[CSS_IN_JS_INSTANCE] === instanceId) {
|
||||||
style.parentNode?.removeChild(style);
|
style.parentNode?.removeChild(style);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TOKEN_THRESHOLD = 0;
|
||||||
|
|
||||||
// Remove will check current keys first
|
// Remove will check current keys first
|
||||||
function cleanTokenStyle(tokenKey: string) {
|
function cleanTokenStyle(tokenKey: string, instanceId: string) {
|
||||||
tokenKeys.set(tokenKey, (tokenKeys.get(tokenKey) || 0) - 1);
|
tokenKeys.set(tokenKey, (tokenKeys.get(tokenKey) || 0) - 1);
|
||||||
|
|
||||||
const tokenKeyList = Array.from(tokenKeys.keys());
|
const tokenKeyList = Array.from(tokenKeys.keys());
|
||||||
|
@ -60,14 +74,37 @@ function cleanTokenStyle(tokenKey: string) {
|
||||||
return count <= 0;
|
return count <= 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (cleanableKeyList.length < tokenKeyList.length) {
|
// Should keep tokens under threshold for not to insert style too often
|
||||||
|
if (tokenKeyList.length - cleanableKeyList.length > TOKEN_THRESHOLD) {
|
||||||
cleanableKeyList.forEach(key => {
|
cleanableKeyList.forEach(key => {
|
||||||
removeStyleTags(key);
|
removeStyleTags(key, instanceId);
|
||||||
tokenKeys.delete(key);
|
tokenKeys.delete(key);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getComputedToken = <DerivativeToken = object, DesignToken = DerivativeToken>(
|
||||||
|
originToken: DesignToken,
|
||||||
|
overrideToken: object,
|
||||||
|
theme: Theme<any, any>,
|
||||||
|
format?: (token: DesignToken) => DerivativeToken,
|
||||||
|
) => {
|
||||||
|
const derivativeToken = theme.getDerivativeToken(originToken);
|
||||||
|
|
||||||
|
// Merge with override
|
||||||
|
let mergedDerivativeToken = {
|
||||||
|
...derivativeToken,
|
||||||
|
...overrideToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format if needed
|
||||||
|
if (format) {
|
||||||
|
mergedDerivativeToken = format(mergedDerivativeToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedDerivativeToken;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache theme derivative token as global shared one
|
* Cache theme derivative token as global shared one
|
||||||
* @param theme Theme entity
|
* @param theme Theme entity
|
||||||
|
@ -78,8 +115,10 @@ function cleanTokenStyle(tokenKey: string) {
|
||||||
export default function useCacheToken<DerivativeToken = object, DesignToken = DerivativeToken>(
|
export default function useCacheToken<DerivativeToken = object, DesignToken = DerivativeToken>(
|
||||||
theme: Ref<Theme<any, any>>,
|
theme: Ref<Theme<any, any>>,
|
||||||
tokens: Ref<Partial<DesignToken>[]>,
|
tokens: Ref<Partial<DesignToken>[]>,
|
||||||
option: Ref<Option<DerivativeToken>> = ref({}),
|
option: Ref<Option<DerivativeToken, DesignToken>> = ref({}),
|
||||||
) {
|
) {
|
||||||
|
const style = useStyleInject();
|
||||||
|
|
||||||
// Basic - We do basic cache here
|
// Basic - We do basic cache here
|
||||||
const mergedToken = computed(() => Object.assign({}, ...tokens.value));
|
const mergedToken = computed(() => Object.assign({}, ...tokens.value));
|
||||||
const tokenStr = computed(() => flattenToken(mergedToken.value));
|
const tokenStr = computed(() => flattenToken(mergedToken.value));
|
||||||
|
@ -94,19 +133,15 @@ export default function useCacheToken<DerivativeToken = object, DesignToken = De
|
||||||
overrideTokenStr.value,
|
overrideTokenStr.value,
|
||||||
]),
|
]),
|
||||||
() => {
|
() => {
|
||||||
const { salt = '', override = EMPTY_OVERRIDE, formatToken } = option.value;
|
const {
|
||||||
const derivativeToken = theme.value.getDerivativeToken(mergedToken.value);
|
salt = '',
|
||||||
|
override = EMPTY_OVERRIDE,
|
||||||
// Merge with override
|
formatToken,
|
||||||
let mergedDerivativeToken = {
|
getComputedToken: compute,
|
||||||
...derivativeToken,
|
} = option.value;
|
||||||
...override,
|
const mergedDerivativeToken = compute
|
||||||
};
|
? compute(mergedToken.value, override, theme.value)
|
||||||
|
: getComputedToken(mergedToken.value, override, theme.value, formatToken);
|
||||||
// Format if needed
|
|
||||||
if (formatToken) {
|
|
||||||
mergedDerivativeToken = formatToken(mergedDerivativeToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optimize for `useStyleRegister` performance
|
// Optimize for `useStyleRegister` performance
|
||||||
const tokenKey = token2key(mergedDerivativeToken, salt);
|
const tokenKey = token2key(mergedDerivativeToken, salt);
|
||||||
|
@ -120,7 +155,7 @@ export default function useCacheToken<DerivativeToken = object, DesignToken = De
|
||||||
},
|
},
|
||||||
cache => {
|
cache => {
|
||||||
// Remove token will remove all related style
|
// Remove token will remove all related style
|
||||||
cleanTokenStyle(cache[0]._tokenKey);
|
cleanTokenStyle(cache[0]._tokenKey, style.value?.cache.instanceId);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,8 @@ if (
|
||||||
process.env.NODE_ENV !== 'production' &&
|
process.env.NODE_ENV !== 'production' &&
|
||||||
typeof module !== 'undefined' &&
|
typeof module !== 'undefined' &&
|
||||||
module &&
|
module &&
|
||||||
(module as any).hot
|
(module as any).hot &&
|
||||||
|
typeof window !== 'undefined'
|
||||||
) {
|
) {
|
||||||
const win = window as any;
|
const win = window as any;
|
||||||
if (typeof win.webpackHotUpdate === 'function') {
|
if (typeof win.webpackHotUpdate === 'function') {
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
import canUseDom from '../../../../_util/canUseDom';
|
||||||
|
import { ATTR_MARK } from '../../StyleContext';
|
||||||
|
|
||||||
|
export const ATTR_CACHE_MAP = 'data-ant-cssinjs-cache-path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This marks style from the css file.
|
||||||
|
* Which means not exist in `<style />` tag.
|
||||||
|
*/
|
||||||
|
export const CSS_FILE_STYLE = '_FILE_STYLE__';
|
||||||
|
|
||||||
|
export function serialize(cachePathMap: Record<string, string>) {
|
||||||
|
return Object.keys(cachePathMap)
|
||||||
|
.map(path => {
|
||||||
|
const hash = cachePathMap[path];
|
||||||
|
return `${path}:${hash}`;
|
||||||
|
})
|
||||||
|
.join(';');
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachePathMap: Record<string, string>;
|
||||||
|
let fromCSSFile = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private Test usage only. Can save remove if no need.
|
||||||
|
*/
|
||||||
|
export function reset(mockCache?: Record<string, string>, fromFile = true) {
|
||||||
|
cachePathMap = mockCache!;
|
||||||
|
fromCSSFile = fromFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepare() {
|
||||||
|
if (!cachePathMap) {
|
||||||
|
cachePathMap = {};
|
||||||
|
|
||||||
|
if (canUseDom()) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = ATTR_CACHE_MAP;
|
||||||
|
div.style.position = 'fixed';
|
||||||
|
div.style.visibility = 'hidden';
|
||||||
|
div.style.top = '-9999px';
|
||||||
|
document.body.appendChild(div);
|
||||||
|
|
||||||
|
let content = getComputedStyle(div).content || '';
|
||||||
|
content = content.replace(/^"/, '').replace(/"$/, '');
|
||||||
|
|
||||||
|
// Fill data
|
||||||
|
content.split(';').forEach(item => {
|
||||||
|
const [path, hash] = item.split(':');
|
||||||
|
cachePathMap[path] = hash;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove inline record style
|
||||||
|
const inlineMapStyle = document.querySelector(`style[${ATTR_CACHE_MAP}]`);
|
||||||
|
if (inlineMapStyle) {
|
||||||
|
fromCSSFile = false;
|
||||||
|
inlineMapStyle.parentNode?.removeChild(inlineMapStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.removeChild(div);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function existPath(path: string) {
|
||||||
|
prepare();
|
||||||
|
|
||||||
|
return !!cachePathMap[path];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStyleAndHash(path: string): [style: string | null, hash: string] {
|
||||||
|
const hash = cachePathMap[path];
|
||||||
|
let styleStr: string | null = null;
|
||||||
|
|
||||||
|
if (hash && canUseDom()) {
|
||||||
|
if (fromCSSFile) {
|
||||||
|
styleStr = CSS_FILE_STYLE;
|
||||||
|
} else {
|
||||||
|
const style = document.querySelector(`style[${ATTR_MARK}="${cachePathMap[path]}"]`);
|
||||||
|
|
||||||
|
if (style) {
|
||||||
|
styleStr = style.innerHTML;
|
||||||
|
} else {
|
||||||
|
// Clean up since not exist anymore
|
||||||
|
delete cachePathMap[path];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [styleStr, hash];
|
||||||
|
}
|
|
@ -3,27 +3,33 @@ import type * as CSS from 'csstype';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import unitless from '@emotion/unitless';
|
import unitless from '@emotion/unitless';
|
||||||
import { compile, serialize, stringify } from 'stylis';
|
import { compile, serialize, stringify } from 'stylis';
|
||||||
import type { Theme, Transformer } from '..';
|
import type { Theme, Transformer } from '../..';
|
||||||
import type Cache from '../Cache';
|
import type Cache from '../../Cache';
|
||||||
import type Keyframes from '../Keyframes';
|
import type Keyframes from '../../Keyframes';
|
||||||
import type { Linter } from '../linters';
|
import type { Linter } from '../../linters';
|
||||||
import { contentQuotesLinter, hashedAnimationLinter } from '../linters';
|
import { contentQuotesLinter, hashedAnimationLinter } from '../../linters';
|
||||||
import type { HashPriority } from '../StyleContext';
|
import type { HashPriority } from '../../StyleContext';
|
||||||
import {
|
import {
|
||||||
useStyleInject,
|
useStyleInject,
|
||||||
ATTR_DEV_CACHE_PATH,
|
ATTR_CACHE_PATH,
|
||||||
ATTR_MARK,
|
ATTR_MARK,
|
||||||
ATTR_TOKEN,
|
ATTR_TOKEN,
|
||||||
CSS_IN_JS_INSTANCE,
|
CSS_IN_JS_INSTANCE,
|
||||||
CSS_IN_JS_INSTANCE_ID,
|
} from '../../StyleContext';
|
||||||
} from '../StyleContext';
|
import { supportLayer } from '../../util';
|
||||||
import { supportLayer } from '../util';
|
import useGlobalCache from '../useGlobalCache';
|
||||||
import useGlobalCache from './useGlobalCache';
|
import { removeCSS, updateCSS } from '../../../../vc-util/Dom/dynamicCSS';
|
||||||
import canUseDom from '../../canUseDom';
|
|
||||||
import { removeCSS, updateCSS } from '../../../vc-util/Dom/dynamicCSS';
|
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import type { VueNode } from '../../type';
|
import type { VueNode } from '../../../type';
|
||||||
|
import canUseDom from '../../../../_util/canUseDom';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ATTR_CACHE_MAP,
|
||||||
|
existPath,
|
||||||
|
getStyleAndHash,
|
||||||
|
serialize as serializeCacheMap,
|
||||||
|
} from './cacheMapUtil';
|
||||||
|
|
||||||
const isClientSide = canUseDom();
|
const isClientSide = canUseDom();
|
||||||
|
|
||||||
|
@ -60,7 +66,7 @@ export interface CSSObject extends CSSPropertiesWithMultiValues, CSSPseudos, CSS
|
||||||
// == Parser ==
|
// == Parser ==
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Preprocessor style content to browser support one
|
// Preprocessor style content to browser support one
|
||||||
export function normalizeStyle(styleStr: string) {
|
export function normalizeStyle(styleStr: string): string {
|
||||||
const serialized = serialize(compile(styleStr), stringify);
|
const serialized = serialize(compile(styleStr), stringify);
|
||||||
return serialized.replace(/\{%%%\:[^;];}/g, ';');
|
return serialized.replace(/\{%%%\:[^;];}/g, ';');
|
||||||
}
|
}
|
||||||
|
@ -307,6 +313,14 @@ export default function useStyleRegister(
|
||||||
path: string[];
|
path: string[];
|
||||||
hashId?: string;
|
hashId?: string;
|
||||||
layer?: string;
|
layer?: string;
|
||||||
|
nonce?: string | (() => string);
|
||||||
|
clientOnly?: boolean;
|
||||||
|
/**
|
||||||
|
* Tell cssinjs the insert order of style.
|
||||||
|
* It's useful when you need to insert style
|
||||||
|
* before other style to overwrite for the same selector priority.
|
||||||
|
*/
|
||||||
|
order?: number;
|
||||||
}>,
|
}>,
|
||||||
styleFn: () => CSSInterpolation,
|
styleFn: () => CSSInterpolation,
|
||||||
) {
|
) {
|
||||||
|
@ -323,14 +337,32 @@ export default function useStyleRegister(
|
||||||
}
|
}
|
||||||
|
|
||||||
// const [cacheStyle[0], cacheStyle[1], cacheStyle[2]]
|
// const [cacheStyle[0], cacheStyle[1], cacheStyle[2]]
|
||||||
useGlobalCache(
|
useGlobalCache<
|
||||||
|
[
|
||||||
|
styleStr: string,
|
||||||
|
tokenKey: string,
|
||||||
|
styleId: string,
|
||||||
|
effectStyle: Record<string, string>,
|
||||||
|
clientOnly: boolean | undefined,
|
||||||
|
order: number,
|
||||||
|
]
|
||||||
|
>(
|
||||||
'style',
|
'style',
|
||||||
fullPath,
|
fullPath,
|
||||||
// Create cache if needed
|
// Create cache if needed
|
||||||
() => {
|
() => {
|
||||||
|
const { path, hashId, layer, nonce, clientOnly, order = 0 } = info.value;
|
||||||
|
const cachePath = fullPath.value.join('|');
|
||||||
|
// Get style from SSR inline style directly
|
||||||
|
if (existPath(cachePath)) {
|
||||||
|
const [inlineCacheStyleStr, styleHash] = getStyleAndHash(cachePath);
|
||||||
|
if (inlineCacheStyleStr) {
|
||||||
|
return [inlineCacheStyleStr, tokenKey.value, styleHash, {}, clientOnly, order];
|
||||||
|
}
|
||||||
|
}
|
||||||
const styleObj = styleFn();
|
const styleObj = styleFn();
|
||||||
const { hashPriority, container, transformers, linters } = styleContext.value;
|
const { hashPriority, container, transformers, linters, cache } = styleContext.value;
|
||||||
const { path, hashId, layer } = info.value;
|
|
||||||
const [parsedStyle, effectStyle] = parseStyle(styleObj, {
|
const [parsedStyle, effectStyle] = parseStyle(styleObj, {
|
||||||
hashId,
|
hashId,
|
||||||
hashPriority,
|
hashPriority,
|
||||||
|
@ -343,20 +375,29 @@ export default function useStyleRegister(
|
||||||
const styleId = uniqueHash(fullPath.value, styleStr);
|
const styleId = uniqueHash(fullPath.value, styleStr);
|
||||||
|
|
||||||
if (isMergedClientSide) {
|
if (isMergedClientSide) {
|
||||||
const style = updateCSS(styleStr, styleId, {
|
const mergedCSSConfig: Parameters<typeof updateCSS>[2] = {
|
||||||
mark: ATTR_MARK,
|
mark: ATTR_MARK,
|
||||||
prepend: 'queue',
|
prepend: 'queue',
|
||||||
attachTo: container,
|
attachTo: container,
|
||||||
});
|
priority: order,
|
||||||
|
};
|
||||||
|
|
||||||
(style as any)[CSS_IN_JS_INSTANCE] = CSS_IN_JS_INSTANCE_ID;
|
const nonceStr = typeof nonce === 'function' ? nonce() : nonce;
|
||||||
|
|
||||||
|
if (nonceStr) {
|
||||||
|
mergedCSSConfig.csp = { nonce: nonceStr };
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = updateCSS(styleStr, styleId, mergedCSSConfig);
|
||||||
|
|
||||||
|
(style as any)[CSS_IN_JS_INSTANCE] = cache.instanceId;
|
||||||
|
|
||||||
// Used for `useCacheToken` to remove on batch when token removed
|
// Used for `useCacheToken` to remove on batch when token removed
|
||||||
style.setAttribute(ATTR_TOKEN, tokenKey.value);
|
style.setAttribute(ATTR_TOKEN, tokenKey.value);
|
||||||
|
|
||||||
// Dev usage to find which cache path made this easily
|
// Dev usage to find which cache path made this easily
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
style.setAttribute(ATTR_DEV_CACHE_PATH, fullPath.value.join('|'));
|
style.setAttribute(ATTR_CACHE_PATH, fullPath.value.join('|'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject client side effect style
|
// Inject client side effect style
|
||||||
|
@ -374,7 +415,7 @@ export default function useStyleRegister(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return [styleStr, tokenKey.value, styleId];
|
return [styleStr, tokenKey.value, styleId, effectStyle, clientOnly, order];
|
||||||
},
|
},
|
||||||
// Remove cache if no need
|
// Remove cache if no need
|
||||||
([, , styleId], fromHMR) => {
|
([, , styleId], fromHMR) => {
|
||||||
|
@ -414,20 +455,112 @@ export default function useStyleRegister(
|
||||||
// == SSR ==
|
// == SSR ==
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
export function extractStyle(cache: Cache, plain = false) {
|
export function extractStyle(cache: Cache, plain = false) {
|
||||||
// prefix with `style` is used for `useStyleRegister` to cache style context
|
const matchPrefix = `style%`;
|
||||||
const styleKeys = Array.from(cache.cache.keys()).filter(key => key.startsWith('style%'));
|
|
||||||
|
|
||||||
// const tokenStyles: Record<string, string[]> = {};
|
// prefix with `style` is used for `useStyleRegister` to cache style context
|
||||||
|
const styleKeys = Array.from(cache.cache.keys()).filter(key => key.startsWith(matchPrefix));
|
||||||
|
|
||||||
|
// Common effect styles like animation
|
||||||
|
const effectStyles: Record<string, boolean> = {};
|
||||||
|
|
||||||
|
// Mapping of cachePath to style hash
|
||||||
|
const cachePathMap: Record<string, string> = {};
|
||||||
|
|
||||||
let styleText = '';
|
let styleText = '';
|
||||||
|
|
||||||
styleKeys.forEach(key => {
|
function toStyleStr(
|
||||||
const [styleStr, tokenKey, styleId]: [string, string, string] = cache.cache.get(key)![1];
|
style: string,
|
||||||
|
tokenKey?: string,
|
||||||
|
styleId?: string,
|
||||||
|
customizeAttrs: Record<string, string> = {},
|
||||||
|
) {
|
||||||
|
const attrs: Record<string, string | undefined> = {
|
||||||
|
...customizeAttrs,
|
||||||
|
[ATTR_TOKEN]: tokenKey,
|
||||||
|
[ATTR_MARK]: styleId,
|
||||||
|
};
|
||||||
|
|
||||||
styleText += plain
|
const attrStr = Object.keys(attrs)
|
||||||
? styleStr
|
.map(attr => {
|
||||||
: `<style ${ATTR_TOKEN}="${tokenKey}" ${ATTR_MARK}="${styleId}">${styleStr}</style>`;
|
const val = attrs[attr];
|
||||||
|
return val ? `${attr}="${val}"` : null;
|
||||||
|
})
|
||||||
|
.filter(v => v)
|
||||||
|
.join(' ');
|
||||||
|
|
||||||
|
return plain ? style : `<style ${attrStr}>${style}</style>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== Fill Style ======================
|
||||||
|
type OrderStyle = [order: number, style: string];
|
||||||
|
|
||||||
|
const orderStyles: OrderStyle[] = styleKeys
|
||||||
|
.map(key => {
|
||||||
|
const cachePath = key.slice(matchPrefix.length).replace(/%/g, '|');
|
||||||
|
|
||||||
|
const [styleStr, tokenKey, styleId, effectStyle, clientOnly, order]: [
|
||||||
|
string,
|
||||||
|
string,
|
||||||
|
string,
|
||||||
|
Record<string, string>,
|
||||||
|
boolean,
|
||||||
|
number,
|
||||||
|
] = cache.cache.get(key)![1];
|
||||||
|
|
||||||
|
// Skip client only style
|
||||||
|
if (clientOnly) {
|
||||||
|
return null! as OrderStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== Style ======================
|
||||||
|
// Used for vc-util
|
||||||
|
const sharedAttrs = {
|
||||||
|
'data-vc-order': 'prependQueue',
|
||||||
|
'data-vc-priority': `${order}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
let keyStyleText = toStyleStr(styleStr, tokenKey, styleId, sharedAttrs);
|
||||||
|
|
||||||
|
// Save cache path with hash mapping
|
||||||
|
cachePathMap[cachePath] = styleId;
|
||||||
|
|
||||||
|
// =============== Create effect style ===============
|
||||||
|
if (effectStyle) {
|
||||||
|
Object.keys(effectStyle).forEach(effectKey => {
|
||||||
|
// Effect style can be reused
|
||||||
|
if (!effectStyles[effectKey]) {
|
||||||
|
effectStyles[effectKey] = true;
|
||||||
|
keyStyleText += toStyleStr(
|
||||||
|
normalizeStyle(effectStyle[effectKey]),
|
||||||
|
tokenKey,
|
||||||
|
`_effect-${effectKey}`,
|
||||||
|
sharedAttrs,
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const ret: OrderStyle = [order, keyStyleText];
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
})
|
||||||
|
.filter(o => o);
|
||||||
|
|
||||||
|
orderStyles
|
||||||
|
.sort((o1, o2) => o1[0] - o2[0])
|
||||||
|
.forEach(([, style]) => {
|
||||||
|
styleText += style;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== Fill Cache Path ====================
|
||||||
|
styleText += toStyleStr(
|
||||||
|
`.${ATTR_CACHE_MAP}{content:"${serializeCacheMap(cachePathMap)}";}`,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
[ATTR_CACHE_MAP]: ATTR_CACHE_MAP,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return styleText;
|
return styleText;
|
||||||
}
|
}
|
|
@ -11,6 +11,8 @@ import { createTheme, Theme } from './theme';
|
||||||
import type { Transformer } from './transformers/interface';
|
import type { Transformer } from './transformers/interface';
|
||||||
import legacyLogicalPropertiesTransformer from './transformers/legacyLogicalProperties';
|
import legacyLogicalPropertiesTransformer from './transformers/legacyLogicalProperties';
|
||||||
import px2remTransformer from './transformers/px2rem';
|
import px2remTransformer from './transformers/px2rem';
|
||||||
|
import { supportLogicProps, supportWhere } from './util';
|
||||||
|
|
||||||
const cssinjs = {
|
const cssinjs = {
|
||||||
Theme,
|
Theme,
|
||||||
createTheme,
|
createTheme,
|
||||||
|
@ -68,4 +70,8 @@ export type {
|
||||||
StyleProviderProps,
|
StyleProviderProps,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const _experimental = {
|
||||||
|
supportModernCSS: () => supportWhere() && supportLogicProps(),
|
||||||
|
};
|
||||||
|
|
||||||
export default cssinjs;
|
export default cssinjs;
|
||||||
|
|
|
@ -2,17 +2,30 @@ import hash from '@emotion/hash';
|
||||||
import { removeCSS, updateCSS } from '../../vc-util/Dom/dynamicCSS';
|
import { removeCSS, updateCSS } from '../../vc-util/Dom/dynamicCSS';
|
||||||
import canUseDom from '../canUseDom';
|
import canUseDom from '../canUseDom';
|
||||||
|
|
||||||
|
import { Theme } from './theme';
|
||||||
|
|
||||||
|
// Create a cache here to avoid always loop generate
|
||||||
|
const flattenTokenCache = new WeakMap<any, string>();
|
||||||
|
|
||||||
export function flattenToken(token: any) {
|
export function flattenToken(token: any) {
|
||||||
let str = '';
|
let str = flattenTokenCache.get(token) || '';
|
||||||
|
|
||||||
|
if (!str) {
|
||||||
Object.keys(token).forEach(key => {
|
Object.keys(token).forEach(key => {
|
||||||
const value = token[key];
|
const value = token[key];
|
||||||
str += key;
|
str += key;
|
||||||
if (value && typeof value === 'object') {
|
if (value instanceof Theme) {
|
||||||
|
str += value.id;
|
||||||
|
} else if (value && typeof value === 'object') {
|
||||||
str += flattenToken(value);
|
str += flattenToken(value);
|
||||||
} else {
|
} else {
|
||||||
str += value;
|
str += value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Put in cache
|
||||||
|
flattenTokenCache.set(token, str);
|
||||||
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,12 +36,18 @@ export function token2key(token: any, salt: string): string {
|
||||||
return hash(`${salt}_${flattenToken(token)}`);
|
return hash(`${salt}_${flattenToken(token)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const layerKey = `layer-${Date.now()}-${Math.random()}`.replace(/\./g, '');
|
const randomSelectorKey = `random-${Date.now()}-${Math.random()}`.replace(/\./g, '');
|
||||||
const layerWidth = '903px';
|
|
||||||
|
|
||||||
function supportSelector(styleStr: string, handleElement?: (ele: HTMLElement) => void): boolean {
|
// Magic `content` for detect selector support
|
||||||
|
const checkContent = '_bAmBoO_';
|
||||||
|
|
||||||
|
function supportSelector(
|
||||||
|
styleStr: string,
|
||||||
|
handleElement: (ele: HTMLElement) => void,
|
||||||
|
supportCheck?: (ele: HTMLElement) => boolean,
|
||||||
|
): boolean {
|
||||||
if (canUseDom()) {
|
if (canUseDom()) {
|
||||||
updateCSS(styleStr, layerKey);
|
updateCSS(styleStr, randomSelectorKey);
|
||||||
|
|
||||||
const ele = document.createElement('div');
|
const ele = document.createElement('div');
|
||||||
ele.style.position = 'fixed';
|
ele.style.position = 'fixed';
|
||||||
|
@ -42,10 +61,12 @@ function supportSelector(styleStr: string, handleElement?: (ele: HTMLElement) =>
|
||||||
ele.style.zIndex = '9999999';
|
ele.style.zIndex = '9999999';
|
||||||
}
|
}
|
||||||
|
|
||||||
const support = getComputedStyle(ele).width === layerWidth;
|
const support = supportCheck
|
||||||
|
? supportCheck(ele)
|
||||||
|
: getComputedStyle(ele).content?.includes(checkContent);
|
||||||
|
|
||||||
ele.parentNode?.removeChild(ele);
|
ele.parentNode?.removeChild(ele);
|
||||||
removeCSS(layerKey);
|
removeCSS(randomSelectorKey);
|
||||||
|
|
||||||
return support;
|
return support;
|
||||||
}
|
}
|
||||||
|
@ -57,12 +78,41 @@ let canLayer: boolean | undefined = undefined;
|
||||||
export function supportLayer(): boolean {
|
export function supportLayer(): boolean {
|
||||||
if (canLayer === undefined) {
|
if (canLayer === undefined) {
|
||||||
canLayer = supportSelector(
|
canLayer = supportSelector(
|
||||||
`@layer ${layerKey} { .${layerKey} { width: ${layerWidth}!important; } }`,
|
`@layer ${randomSelectorKey} { .${randomSelectorKey} { content: "${checkContent}"!important; } }`,
|
||||||
ele => {
|
ele => {
|
||||||
ele.className = layerKey;
|
ele.className = randomSelectorKey;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return canLayer!;
|
return canLayer!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let canWhere: boolean | undefined = undefined;
|
||||||
|
export function supportWhere(): boolean {
|
||||||
|
if (canWhere === undefined) {
|
||||||
|
canWhere = supportSelector(
|
||||||
|
`:where(.${randomSelectorKey}) { content: "${checkContent}"!important; }`,
|
||||||
|
ele => {
|
||||||
|
ele.className = randomSelectorKey;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canWhere!;
|
||||||
|
}
|
||||||
|
|
||||||
|
let canLogic: boolean | undefined = undefined;
|
||||||
|
export function supportLogicProps(): boolean {
|
||||||
|
if (canLogic === undefined) {
|
||||||
|
canLogic = supportSelector(
|
||||||
|
`.${randomSelectorKey} { inset-block: 93px !important; }`,
|
||||||
|
ele => {
|
||||||
|
ele.className = randomSelectorKey;
|
||||||
|
},
|
||||||
|
ele => getComputedStyle(ele).bottom === '93px',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canLogic!;
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ interface Options {
|
||||||
csp?: { nonce?: string };
|
csp?: { nonce?: string };
|
||||||
prepend?: Prepend;
|
prepend?: Prepend;
|
||||||
mark?: string;
|
mark?: string;
|
||||||
|
priority?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMark({ mark }: Options = {}) {
|
function getMark({ mark }: Options = {}) {
|
||||||
|
|
Loading…
Reference in New Issue