diff --git a/packages/uniwind/src/components/ScopedVariables/utils.ts b/packages/uniwind/src/components/ScopedVariables/utils.ts index 7705e112..bff7ff59 100644 --- a/packages/uniwind/src/components/ScopedVariables/utils.ts +++ b/packages/uniwind/src/components/ScopedVariables/utils.ts @@ -30,8 +30,9 @@ export const buildScopedVariablesContext = ( ...validateVariables(variables), } as UniwindContextCSSVariables delete mergedVariables.__uniwindVariablesCacheKey + // The key only needs a stable order. Code-unit order avoids localeCompare, which creates a collator per call on Android const variablesCacheKey = JSON.stringify( - Object.entries(mergedVariables).sort(([a], [b]) => a.localeCompare(b)), + Object.entries(mergedVariables).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0), ) mergedVariables.__uniwindVariablesCacheKey = variablesCacheKey diff --git a/packages/uniwind/tests/native/components/scoped-variables.test.tsx b/packages/uniwind/tests/native/components/scoped-variables.test.tsx index 26c3b263..88eb8315 100644 --- a/packages/uniwind/tests/native/components/scoped-variables.test.tsx +++ b/packages/uniwind/tests/native/components/scoped-variables.test.tsx @@ -311,6 +311,38 @@ describe('ScopedVariables', () => { expect(ambiguous.mock.calls[0][0]).not.toEqual(plain.mock.calls[0][0]) }) + test('cache key ignores variable order without locale collation', () => { + const localeCompare = jest.spyOn(String.prototype, 'localeCompare') + const Probe = (props: { test: jest.Mock }) => { + props.test(useUniwindContext().variables?.__uniwindVariablesCacheKey) + + return null + } + + const first = jest.fn() + const second = jest.fn() + + try { + renderUniwind( + + + + + + + + , + ) + + expect(first).toHaveBeenCalledWith('[["--color-primary","#ff0000"],["--gap",8]]') + expect(second).toHaveBeenCalledWith('[["--color-primary","#ff0000"],["--gap",8]]') + // Hermes on Android creates a collator for every localeCompare call + expect(localeCompare).not.toHaveBeenCalled() + } finally { + localeCompare.mockRestore() + } + }) + test('updating the variables prop does not serve stale cached styles', () => { const Wrapper = ({ color }: { color: string }) => (