Skip to content

Commit d0ec3f5

Browse files
authored
fix: Keep render order in ssr (#225)
* chore: record updateTime * test: add test case
1 parent 9e0b3d7 commit d0ec3f5

File tree

3 files changed

+88
-3
lines changed

3 files changed

+88
-3
lines changed

src/Cache.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ export function pathKey(keys: KeyType[]) {
88
return keys.join(SPLIT);
99
}
1010

11+
/** Record update id for extract static style order. */
12+
let updateId = 0;
13+
1114
class Entity {
1215
instanceId: string;
1316
constructor(instanceId: string) {
@@ -17,6 +20,9 @@ class Entity {
1720
/** @private Internal cache map. Do not access this directly */
1821
cache = new Map<string, ValueType>();
1922

23+
/** @private Record update times for each key */
24+
updateTimes = new Map<string, number>();
25+
2026
get(keys: KeyType[]): ValueType | null {
2127
return this.opGet(pathKey(keys));
2228
}
@@ -43,8 +49,11 @@ class Entity {
4349

4450
if (nextValue === null) {
4551
this.cache.delete(keyPathStr);
52+
this.updateTimes.delete(keyPathStr);
4653
} else {
4754
this.cache.set(keyPathStr, nextValue);
55+
this.updateTimes.set(keyPathStr, updateId);
56+
updateId += 1;
4857
}
4958
}
5059
}

src/extractStyle.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export default function extractStyle(
5959
let styleText = '';
6060

6161
styleKeys
62-
.map<[number, string] | null>((key) => {
62+
.map<[order: number, style: string, updateTime: number] | null>((key) => {
6363
const cachePath = key.replace(matchPrefixRegexp, '').replace(/%/g, '|');
6464
const [prefix] = key.split('%');
6565
const extractFn = ExtractStyleFns[prefix as keyof typeof ExtractStyleFns];
@@ -69,14 +69,20 @@ export default function extractStyle(
6969
if (!extractedStyle) {
7070
return null;
7171
}
72+
const updateTime = cache.updateTimes.get(key) || 0;
7273
const [order, styleId, styleStr] = extractedStyle;
7374
if (key.startsWith('style')) {
7475
cachePathMap[cachePath] = styleId;
7576
}
76-
return [order, styleStr];
77+
return [order, styleStr, updateTime];
7778
})
7879
.filter(isNotNull)
79-
.sort(([o1], [o2]) => o1 - o2)
80+
.sort(([o1, , u1], [o2, , u2]) => {
81+
if (o1 !== o2) {
82+
return o1 - o2;
83+
}
84+
return u1 - u2;
85+
})
8086
.forEach(([, style]) => {
8187
styleText += style;
8288
});

tests/server.spec.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,4 +345,74 @@ describe('SSR', () => {
345345
const pureStyle = extractStyle(cache, true);
346346
expect(pureStyle).toMatchSnapshot();
347347
});
348+
349+
it('extract with order', () => {
350+
// Create 3 components without specified order: A, C, B
351+
const A = () => {
352+
const [token] = useCacheToken<DerivativeToken>(theme, [baseToken], {
353+
cssVar: { key: 'css-var-test' },
354+
});
355+
useStyleRegister({ theme, token, path: ['a'] }, () => ({
356+
'.a': { backgroundColor: token.primaryColor },
357+
}));
358+
return <div className="a" />;
359+
};
360+
const C = () => {
361+
const [token] = useCacheToken<DerivativeToken>(theme, [baseToken], {
362+
cssVar: { key: 'css-var-test' },
363+
});
364+
useStyleRegister({ theme, token, path: ['c'] }, () => ({
365+
'.c': { backgroundColor: token.primaryColor },
366+
}));
367+
return <div className="c" />;
368+
};
369+
const B = () => {
370+
const [token] = useCacheToken<DerivativeToken>(theme, [baseToken], {
371+
cssVar: { key: 'css-var-test' },
372+
});
373+
useStyleRegister({ theme, token, path: ['b'] }, () => ({
374+
'.b': { backgroundColor: token.primaryColor },
375+
}));
376+
return <div className="b" />;
377+
};
378+
379+
function testOrder(
380+
node1: React.ReactElement,
381+
node2: React.ReactElement,
382+
node3: React.ReactElement,
383+
componentMarks: string[],
384+
) {
385+
const cache = createCache();
386+
387+
renderToString(
388+
<StyleProvider cache={cache}>
389+
{node1}
390+
{node2}
391+
{node3}
392+
</StyleProvider>,
393+
);
394+
395+
const plainStyle = extractStyle(cache, true);
396+
const index1 = plainStyle.indexOf(`.${componentMarks[0]}{`);
397+
const index2 = plainStyle.indexOf(`.${componentMarks[1]}{`);
398+
const index3 = plainStyle.indexOf(`.${componentMarks[2]}{`);
399+
400+
expect(index1).toBeGreaterThanOrEqual(0);
401+
expect(index2).toBeGreaterThan(index1);
402+
expect(index3).toBeGreaterThan(index2);
403+
}
404+
405+
// A B C
406+
testOrder(<A />, <B />, <C />, ['a', 'b', 'c']);
407+
// A C B
408+
testOrder(<A />, <C />, <B />, ['a', 'c', 'b']);
409+
// B A C
410+
testOrder(<B />, <A />, <C />, ['b', 'a', 'c']);
411+
// B C A
412+
testOrder(<B />, <C />, <A />, ['b', 'c', 'a']);
413+
// C A B
414+
testOrder(<C />, <A />, <B />, ['c', 'a', 'b']);
415+
// C B A
416+
testOrder(<C />, <B />, <A />, ['c', 'b', 'a']);
417+
});
348418
});

0 commit comments

Comments
 (0)