From 396b47192bc62f58bb4f5447f06dd5396da2e675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?= =?UTF-8?q?=E5=8D=9C?= Date: Tue, 2 Apr 2024 14:24:14 +0800 Subject: [PATCH] feat: Support layer config (#179) * feat: support layer * test: fix layer test * chore: opt cache --- docs/examples/layer.less | 3 -- docs/examples/layer.tsx | 57 ++++++++++++++------------ src/StyleContext.tsx | 2 + src/hooks/useStyleRegister.tsx | 75 ++++++++++++++++++++++++++-------- tests/util.spec.tsx | 32 ++++++++------- 5 files changed, 109 insertions(+), 60 deletions(-) delete mode 100644 docs/examples/layer.less diff --git a/docs/examples/layer.less b/docs/examples/layer.less deleted file mode 100644 index 03597d36..00000000 --- a/docs/examples/layer.less +++ /dev/null @@ -1,3 +0,0 @@ -a { - color: green; -} diff --git a/docs/examples/layer.tsx b/docs/examples/layer.tsx index 653009b9..cca53476 100644 --- a/docs/examples/layer.tsx +++ b/docs/examples/layer.tsx @@ -1,38 +1,24 @@ +import { StyleProvider, Theme, useStyleRegister } from '@ant-design/cssinjs'; import classNames from 'classnames'; import React from 'react'; -import './layer.less'; -import { useStyleRegister, Theme } from '@ant-design/cssinjs'; const theme = new Theme([() => ({})]); const Div = ({ className, ...rest }: React.HTMLAttributes) => { - // Shared - useStyleRegister( - { - theme, - token: { _tokenKey: 'test' }, - path: ['shared'], - layer: 'shared', - }, - () => ({ - 'html body .layer-div': { - color: 'rgba(0,0,0,0.65)', - }, - }), - ); - // Layer useStyleRegister( { theme, token: { _tokenKey: 'test' }, path: ['layer'], - layer: 'shared, layer', + layer: { + name: 'layer', + dependencies: ['shared'], + }, }, () => ({ '.layer-div': { - // color: 'blue', - color: 'pink', + color: 'blue', a: { color: 'orange', @@ -46,16 +32,35 @@ const Div = ({ className, ...rest }: React.HTMLAttributes) => { }), ); + // Shared + useStyleRegister( + { + theme, + token: { _tokenKey: 'test' }, + path: ['shared'], + layer: { + name: 'shared', + }, + }, + () => ({ + 'html body .layer-div': { + color: 'green', + }, + }), + ); + return
; }; export default function App() { return ( -
- Layer: blue & `a` orange. User: `a` green -
- A simple link -
-
+ +
+ Text should be pink: +
+ A simple link +
+
+
); } diff --git a/src/StyleContext.tsx b/src/StyleContext.tsx index a51dbc3b..923155b4 100644 --- a/src/StyleContext.tsx +++ b/src/StyleContext.tsx @@ -77,6 +77,8 @@ export interface StyleContextProps { * Please note that `linters` do not support dynamic update. */ linters?: Linter[]; + /** Wrap css in a layer to avoid global style conflict */ + layer?: boolean; } const StyleContext = React.createContext({ diff --git a/src/hooks/useStyleRegister.tsx b/src/hooks/useStyleRegister.tsx index d2474a9d..240c56d6 100644 --- a/src/hooks/useStyleRegister.tsx +++ b/src/hooks/useStyleRegister.tsx @@ -16,7 +16,7 @@ import StyleContext, { ATTR_TOKEN, CSS_IN_JS_INSTANCE, } from '../StyleContext'; -import { isClientSide, supportLayer, toStyleStr } from '../util'; +import { isClientSide, toStyleStr } from '../util'; import { CSS_FILE_STYLE, existPath, @@ -28,6 +28,11 @@ import useGlobalCache from './useGlobalCache'; const SKIP_CHECK = '_skip_check_'; const MULTI_VALUE = '_multi_value_'; +export interface LayerConfig { + name: string; + dependencies?: string[]; +} + export type CSSProperties = Omit< CSS.PropertiesFallback, 'animationName' @@ -123,7 +128,7 @@ function injectSelectorHash( export interface ParseConfig { hashId?: string; hashPriority?: HashPriority; - layer?: string; + layer?: LayerConfig; path?: string; transformers?: Transformer[]; linters?: Linter[]; @@ -324,15 +329,13 @@ export const parseStyle = ( if (!root) { styleStr = `{${styleStr}}`; - } else if (layer && supportLayer()) { - const layerCells = layer.split(','); - const layerName = layerCells[layerCells.length - 1].trim(); - styleStr = `@layer ${layerName} {${styleStr}}`; - - // Order of layer if needed - if (layerCells.length > 1) { - // zombieJ: stylis do not support layer order, so we need to handle it manually. - styleStr = `@layer ${layer}{%%%:%}${styleStr}`; + } else if (layer) { + styleStr = `@layer ${layer.name} {${styleStr}}`; + + if (layer.dependencies) { + effectStyle[`@layer ${layer.name}`] = layer.dependencies + .map((deps) => `@layer ${deps}, ${layer.name};`) + .join('\n'); } } @@ -370,7 +373,7 @@ export default function useStyleRegister( token: any; path: string[]; hashId?: string; - layer?: string; + layer?: LayerConfig; nonce?: string | (() => string); clientOnly?: boolean; /** @@ -393,10 +396,15 @@ export default function useStyleRegister( transformers, linters, cache, + layer: enableLayer, } = React.useContext(StyleContext); const tokenKey = token._tokenKey as string; - const fullPath = [tokenKey, ...path]; + const fullPath = [tokenKey]; + if (enableLayer) { + fullPath.push('layer'); + } + fullPath.push(...path); // Check if need insert style let isMergedClientSide = isClientSide; @@ -432,7 +440,7 @@ export default function useStyleRegister( const [parsedStyle, effectStyle] = parseStyle(styleObj, { hashId, hashPriority, - layer, + layer: enableLayer ? layer : undefined, path: path.join('-'), transformers, linters, @@ -467,6 +475,31 @@ export default function useStyleRegister( 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, + ); + }); + + // ==================== Inject Style ==================== + // Inject style const style = updateCSS(styleStr, styleId, mergedCSSConfig); (style as any)[CSS_IN_JS_INSTANCE] = cache.instanceId; @@ -479,8 +512,9 @@ export default function useStyleRegister( style.setAttribute(ATTR_CACHE_PATH, fullPath.join('|')); } + // ================ Inject Effect Style ================= // Inject client side effect style - Object.keys(effectStyle).forEach((effectKey) => { + effectRestKeys.forEach((effectKey) => { updateCSS( normalizeStyle(effectStyle[effectKey]), `_effect-${effectKey}`, @@ -539,13 +573,14 @@ export const extract: ExtractStyle = ( let keyStyleText = styleStr; - // ====================== Style ====================== + // ====================== Share ====================== // Used for rc-util const sharedAttrs = { 'data-rc-order': 'prependQueue', 'data-rc-priority': `${order}`, }; + // ====================== Style ====================== keyStyleText = toStyleStr(styleStr, tokenKey, styleId, sharedAttrs, plain); // =============== Create effect style =============== @@ -555,13 +590,19 @@ export const extract: ExtractStyle = ( if (!effectStyles[effectKey]) { effectStyles[effectKey] = true; const effectStyleStr = normalizeStyle(effectStyle[effectKey]); - keyStyleText += toStyleStr( + const effectStyleHTML = toStyleStr( effectStyleStr, tokenKey, `_effect-${effectKey}`, sharedAttrs, plain, ); + + if (effectKey.startsWith('@layer')) { + keyStyleText = effectStyleHTML + keyStyleText; + } else { + keyStyleText += effectStyleHTML; + } } }); } diff --git a/tests/util.spec.tsx b/tests/util.spec.tsx index 0065ba03..1b9f0daa 100644 --- a/tests/util.spec.tsx +++ b/tests/util.spec.tsx @@ -74,29 +74,33 @@ describe('util', () => { }, }, ], - { hashId: 'hashed', layer: 'test-layer' }, + { hashId: 'hashed', layer: { name: 'test-layer' } }, ); expect(str).toEqual('@layer test-layer {p.hashed{color:red;}}'); }); it('order', () => { - const str = normalizeStyle( - parseStyle( - [ - { - p: { - color: 'red', - }, + const parsedStyle = parseStyle( + [ + { + p: { + color: 'red', }, - ], - { hashId: 'hashed', layer: 'shared, test-layer' }, - )[0], + }, + ], + { + hashId: 'hashed', + layer: { name: 'test-layer', dependencies: ['shared'] }, + }, ); - expect(str).toEqual( - '@layer shared,test-layer;@layer test-layer{p.hashed{color:red;}}', - ); + const str = normalizeStyle(parsedStyle[0]); + + expect(str).toEqual('@layer test-layer{p.hashed{color:red;}}'); + expect(parsedStyle[1]).toEqual({ + '@layer test-layer': '@layer shared, test-layer;', + }); }); it('raw order', () => {