feat(style): 添加 layer 属性以避免全局样式冲突

在 StyleContextProps 和相关函数中引入 layer 属性,支持样式分层。
pull/8130/head
Xiang 2025-04-17 17:55:58 +08:00
parent aa211fd789
commit d8e6db2991
4 changed files with 78 additions and 26 deletions

View File

@ -82,6 +82,8 @@ export interface StyleContextProps {
* Please note that `linters` do not support dynamic update. * Please note that `linters` do not support dynamic update.
*/ */
linters?: Linter[]; linters?: Linter[];
/** Wrap css in a layer to avoid global style conflict */
layer?: boolean;
} }
const StyleContextKey: InjectionKey<ShallowRef<Partial<StyleContextProps>>> = const StyleContextKey: InjectionKey<ShallowRef<Partial<StyleContextProps>>> =
@ -174,6 +176,8 @@ export const styleProviderProps = () => ({
* Please note that `linters` do not support dynamic update. * Please note that `linters` do not support dynamic update.
*/ */
linters: arrayType<Linter[]>(), linters: arrayType<Linter[]>(),
/** Wrap css in a layer to avoid global style conflict */
layer: booleanType(),
}); });
export type StyleProviderProps = Partial<ExtractPropTypes<ReturnType<typeof styleProviderProps>>>; export type StyleProviderProps = Partial<ExtractPropTypes<ReturnType<typeof styleProviderProps>>>;
export const StyleProvider = withInstall( export const StyleProvider = withInstall(

View File

@ -35,6 +35,12 @@ const isClientSide = canUseDom();
const SKIP_CHECK = '_skip_check_'; const SKIP_CHECK = '_skip_check_';
const MULTI_VALUE = '_multi_value_'; const MULTI_VALUE = '_multi_value_';
export interface LayerConfig {
name: string;
dependencies?: string[];
}
export type CSSProperties = Omit<CSS.PropertiesFallback<number | string>, 'animationName'> & { export type CSSProperties = Omit<CSS.PropertiesFallback<number | string>, 'animationName'> & {
animationName?: CSS.PropertiesFallback<number | string>['animationName'] | Keyframes; animationName?: CSS.PropertiesFallback<number | string>['animationName'] | Keyframes;
}; };
@ -102,7 +108,7 @@ function injectSelectorHash(key: string, hashId: string, hashPriority?: HashPrio
export interface ParseConfig { export interface ParseConfig {
hashId?: string; hashId?: string;
hashPriority?: HashPriority; hashPriority?: HashPriority;
layer?: string; layer?: LayerConfig;
path?: string; path?: string;
transformers?: Transformer[]; transformers?: Transformer[];
linters?: Linter[]; linters?: Linter[];
@ -278,14 +284,14 @@ export const parseStyle = (
if (!root) { if (!root) {
styleStr = `{${styleStr}}`; styleStr = `{${styleStr}}`;
} else if (layer && supportLayer()) { } else if (layer && supportLayer()) {
const layerCells = layer.split(','); if (styleStr) {
const layerName = layerCells[layerCells.length - 1].trim(); styleStr = `@layer ${layer.name} {${styleStr}}`;
styleStr = `@layer ${layerName} {${styleStr}}`; }
// Order of layer if needed if (layer.dependencies) {
if (layerCells.length > 1) { effectStyle[`@layer ${layer.name}`] = layer.dependencies
// zombieJ: stylis do not support layer order, so we need to handle it manually. .map(deps => `@layer ${deps}, ${layer.name};`)
styleStr = `@layer ${layer}{%%%:%}${styleStr}`; .join('\n');
} }
} }
@ -312,7 +318,7 @@ export default function useStyleRegister(
token: any; token: any;
path: string[]; path: string[];
hashId?: string; hashId?: string;
layer?: string; layer?: LayerConfig;
nonce?: string | (() => string); nonce?: string | (() => string);
clientOnly?: boolean; clientOnly?: boolean;
/** /**
@ -328,7 +334,11 @@ export default function useStyleRegister(
const tokenKey = computed(() => info.value.token._tokenKey as string); const tokenKey = computed(() => info.value.token._tokenKey as string);
const fullPath = computed(() => [tokenKey.value, ...info.value.path]); const fullPath = computed(() => [
tokenKey.value,
...(styleContext.value.layer ? ['layer'] : []),
...info.value.path,
]);
// Check if need insert style // Check if need insert style
let isMergedClientSide = isClientSide; let isMergedClientSide = isClientSide;
@ -361,12 +371,19 @@ export default function useStyleRegister(
} }
} }
const styleObj = styleFn(); const styleObj = styleFn();
const { hashPriority, container, transformers, linters, cache } = styleContext.value; const {
hashPriority,
container,
transformers,
linters,
cache,
layer: enableLayer,
} = styleContext.value;
const [parsedStyle, effectStyle] = parseStyle(styleObj, { const [parsedStyle, effectStyle] = parseStyle(styleObj, {
hashId, hashId,
hashPriority, hashPriority,
layer, layer: enableLayer ? layer : undefined,
path: path.join('-'), path: path.join('-'),
transformers, transformers,
linters, linters,
@ -377,7 +394,7 @@ export default function useStyleRegister(
if (isMergedClientSide) { if (isMergedClientSide) {
const mergedCSSConfig: Parameters<typeof updateCSS>[2] = { const mergedCSSConfig: Parameters<typeof updateCSS>[2] = {
mark: ATTR_MARK, mark: ATTR_MARK,
prepend: 'queue', prepend: enableLayer ? false : 'queue',
attachTo: container, attachTo: container,
priority: order, priority: order,
}; };
@ -388,6 +405,30 @@ export default function useStyleRegister(
mergedCSSConfig.csp = { nonce: nonceStr }; mergedCSSConfig.csp = { nonce: nonceStr };
} }
// ================= Split Effect Style =================
// We will split effectStyle here since @layer should be at the top level
const effectLayerKeys: string[] = [];
const effectRestKeys: string[] = [];
Object.keys(effectStyle).forEach(key => {
if (key.startsWith('@layer')) {
effectLayerKeys.push(key);
} else {
effectRestKeys.push(key);
}
});
// ================= Inject Layer Style =================
// Inject layer style
effectLayerKeys.forEach(effectKey => {
updateCSS(normalizeStyle(effectStyle[effectKey]), `_layer-${effectKey}`, {
...mergedCSSConfig,
prepend: true,
});
});
// ==================== Inject Style ====================
// Inject style
const style = updateCSS(styleStr, styleId, mergedCSSConfig); const style = updateCSS(styleStr, styleId, mergedCSSConfig);
(style as any)[CSS_IN_JS_INSTANCE] = cache.instanceId; (style as any)[CSS_IN_JS_INSTANCE] = cache.instanceId;
@ -400,18 +441,14 @@ export default function useStyleRegister(
style.setAttribute(ATTR_CACHE_PATH, fullPath.value.join('|')); style.setAttribute(ATTR_CACHE_PATH, fullPath.value.join('|'));
} }
// ================ Inject Effect Style =================
// Inject client side effect style // Inject client side effect style
Object.keys(effectStyle).forEach(effectKey => { effectRestKeys.forEach(effectKey => {
if (!globalEffectStyleKeys.has(effectKey)) { updateCSS(
globalEffectStyleKeys.add(effectKey); normalizeStyle(effectStyle[effectKey]),
`_effect-${effectKey}`,
// Inject mergedCSSConfig,
updateCSS(normalizeStyle(effectStyle[effectKey]), `_effect-${effectKey}`, { );
mark: ATTR_MARK,
prepend: 'queue',
attachTo: container,
});
}
}); });
} }
@ -530,12 +567,20 @@ export function extractStyle(cache: Cache, plain = false) {
// Effect style can be reused // Effect style can be reused
if (!effectStyles[effectKey]) { if (!effectStyles[effectKey]) {
effectStyles[effectKey] = true; effectStyles[effectKey] = true;
keyStyleText += toStyleStr(
normalizeStyle(effectStyle[effectKey]), const effectStyleStr = normalizeStyle(effectStyle[effectKey]);
const effectStyleHTML = toStyleStr(
effectStyleStr,
tokenKey, tokenKey,
`_effect-${effectKey}`, `_effect-${effectKey}`,
sharedAttrs, sharedAttrs,
); );
if (effectKey.startsWith('@layer')) {
keyStyleText = effectStyleHTML + keyStyleText;
} else {
keyStyleText += effectStyleHTML;
}
} }
}); });
} }

View File

@ -12,6 +12,7 @@ const useStyle = (iconPrefixCls: Ref<string>) => {
token: token.value, token: token.value,
hashId: '', hashId: '',
path: ['ant-design-icons', iconPrefixCls.value], path: ['ant-design-icons', iconPrefixCls.value],
layer: { name: 'antd' },
})), })),
() => [ () => [
{ {

View File

@ -55,6 +55,7 @@ export default function genComponentStyleHook<ComponentName extends OverrideComp
token: token.value, token: token.value,
hashId: hashId.value, hashId: hashId.value,
path: ['Shared', rootPrefixCls.value], path: ['Shared', rootPrefixCls.value],
layer: { name: 'antd' },
}; };
}); });
// Generate style for all a tags in antd component. // Generate style for all a tags in antd component.
@ -70,6 +71,7 @@ export default function genComponentStyleHook<ComponentName extends OverrideComp
token: token.value, token: token.value,
hashId: hashId.value, hashId: hashId.value,
path: [component, prefixCls.value, iconPrefixCls.value], path: [component, prefixCls.value, iconPrefixCls.value],
layer: { name: 'antd' },
}; };
}); });
return [ return [