diff --git a/src/Cache.ts b/src/Cache.ts index c0e1b6d..0ebd273 100644 --- a/src/Cache.ts +++ b/src/Cache.ts @@ -8,6 +8,9 @@ export function pathKey(keys: KeyType[]) { return keys.join(SPLIT); } +/** Record update id for extract static style order. */ +let updateId = 0; + class Entity { instanceId: string; constructor(instanceId: string) { @@ -17,6 +20,9 @@ class Entity { /** @private Internal cache map. Do not access this directly */ cache = new Map(); + /** @private Record update times for each key */ + updateTimes = new Map(); + get(keys: KeyType[]): ValueType | null { return this.opGet(pathKey(keys)); } @@ -43,8 +49,11 @@ class Entity { if (nextValue === null) { this.cache.delete(keyPathStr); + this.updateTimes.delete(keyPathStr); } else { this.cache.set(keyPathStr, nextValue); + this.updateTimes.set(keyPathStr, updateId); + updateId += 1; } } } diff --git a/src/extractStyle.ts b/src/extractStyle.ts index fa462b2..e664362 100644 --- a/src/extractStyle.ts +++ b/src/extractStyle.ts @@ -59,7 +59,7 @@ export default function extractStyle( let styleText = ''; styleKeys - .map<[number, string] | null>((key) => { + .map<[order: number, style: string, updateTime: number] | null>((key) => { const cachePath = key.replace(matchPrefixRegexp, '').replace(/%/g, '|'); const [prefix] = key.split('%'); const extractFn = ExtractStyleFns[prefix as keyof typeof ExtractStyleFns]; @@ -69,14 +69,20 @@ export default function extractStyle( if (!extractedStyle) { return null; } + const updateTime = cache.updateTimes.get(key) || 0; const [order, styleId, styleStr] = extractedStyle; if (key.startsWith('style')) { cachePathMap[cachePath] = styleId; } - return [order, styleStr]; + return [order, styleStr, updateTime]; }) .filter(isNotNull) - .sort(([o1], [o2]) => o1 - o2) + .sort(([o1, , u1], [o2, , u2]) => { + if (o1 !== o2) { + return o1 - o2; + } + return u1 - u2; + }) .forEach(([, style]) => { styleText += style; }); diff --git a/tests/server.spec.tsx b/tests/server.spec.tsx index 826a8ce..eaf74db 100644 --- a/tests/server.spec.tsx +++ b/tests/server.spec.tsx @@ -345,4 +345,74 @@ describe('SSR', () => { const pureStyle = extractStyle(cache, true); expect(pureStyle).toMatchSnapshot(); }); + + it('extract with order', () => { + // Create 3 components without specified order: A, C, B + const A = () => { + const [token] = useCacheToken(theme, [baseToken], { + cssVar: { key: 'css-var-test' }, + }); + useStyleRegister({ theme, token, path: ['a'] }, () => ({ + '.a': { backgroundColor: token.primaryColor }, + })); + return
; + }; + const C = () => { + const [token] = useCacheToken(theme, [baseToken], { + cssVar: { key: 'css-var-test' }, + }); + useStyleRegister({ theme, token, path: ['c'] }, () => ({ + '.c': { backgroundColor: token.primaryColor }, + })); + return
; + }; + const B = () => { + const [token] = useCacheToken(theme, [baseToken], { + cssVar: { key: 'css-var-test' }, + }); + useStyleRegister({ theme, token, path: ['b'] }, () => ({ + '.b': { backgroundColor: token.primaryColor }, + })); + return
; + }; + + function testOrder( + node1: React.ReactElement, + node2: React.ReactElement, + node3: React.ReactElement, + componentMarks: string[], + ) { + const cache = createCache(); + + renderToString( + + {node1} + {node2} + {node3} + , + ); + + const plainStyle = extractStyle(cache, true); + const index1 = plainStyle.indexOf(`.${componentMarks[0]}{`); + const index2 = plainStyle.indexOf(`.${componentMarks[1]}{`); + const index3 = plainStyle.indexOf(`.${componentMarks[2]}{`); + + expect(index1).toBeGreaterThanOrEqual(0); + expect(index2).toBeGreaterThan(index1); + expect(index3).toBeGreaterThan(index2); + } + + // A B C + testOrder(, , , ['a', 'b', 'c']); + // A C B + testOrder(, , , ['a', 'c', 'b']); + // B A C + testOrder(, , , ['b', 'a', 'c']); + // B C A + testOrder(, , , ['b', 'c', 'a']); + // C A B + testOrder(, , , ['c', 'a', 'b']); + // C B A + testOrder(, , , ['c', 'b', 'a']); + }); });