diff --git a/src/__tests__/native/root-variable-registry.test.ts b/src/__tests__/native/root-variable-registry.test.ts new file mode 100644 index 00000000..700448f9 --- /dev/null +++ b/src/__tests__/native/root-variable-registry.test.ts @@ -0,0 +1,30 @@ +import { rootVariables, universalVariables } from "../../native-internal/root"; + +test("the module's exports are the registries published on globalThis", () => { + const registries = globalThis.__react_native_css_root_variable_registries; + + expect(registries).toBeDefined(); + expect(rootVariables).toBe(registries?.root); + expect(universalVariables).toBe(registries?.universal); +}); + +test("a second copy of the module shares the registries and does not re-seed", () => { + // resetModules evaluates the module again against the same globalThis, as a second build in one bundle does + const firstCopy = jest.requireActual< + typeof import("../../native-internal/root") + >("../../native-internal/root"); + + expect(firstCopy.rootVariables("__rn-css-rem").get()).toBe(14); + firstCopy.rootVariables("__rn-css-rem").set([[16]]); + + jest.resetModules(); + const secondCopy = jest.requireActual< + typeof import("../../native-internal/root") + >("../../native-internal/root"); + + // The module body really re-ran, so the assertions below are about two copies + expect(secondCopy).not.toBe(firstCopy); + + expect(secondCopy.rootVariables).toBe(firstCopy.rootVariables); + expect(secondCopy.rootVariables("__rn-css-rem").get()).toBe(16); +}); diff --git a/src/native-internal/root.ts b/src/native-internal/root.ts index e45a7d11..520c0833 100644 --- a/src/native-internal/root.ts +++ b/src/native-internal/root.ts @@ -29,16 +29,43 @@ const rootVariableFamily = () => { }); }; -export const rootVariables = rootVariableFamily(); -export const universalVariables = rootVariableFamily(); - -rootVariables("__rn-css-rem").set([[14]]); -// eslint-disable-next-line @typescript-eslint/no-unsafe-argument -rootVariables("__rn-css-color").set([ - [ - Platform.OS === "ios" - ? PlatformColor("label", "labelColor") - : PlatformColor("?attr/textColorPrimary", "SystemBaseHighColor"), - ], - // eslint-disable-next-line @typescript-eslint/no-explicit-any -] as any); +interface RootVariableRegistries { + root: ReturnType; + universal: ReturnType; +} + +declare global { + var __react_native_css_root_variable_registries: + | RootVariableRegistries + | undefined; +} + +// Seeded with the registries, so a copy that loads after the stylesheet inject keeps the project's rem +function createRootVariableRegistries(): RootVariableRegistries { + const registries: RootVariableRegistries = { + root: rootVariableFamily(), + universal: rootVariableFamily(), + }; + + registries.root("__rn-css-rem").set([[14]]); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + registries.root("__rn-css-color").set([ + [ + Platform.OS === "ios" + ? PlatformColor("label", "labelColor") + : PlatformColor("?attr/textColorPrimary", "SystemBaseHighColor"), + ], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ] as any); + + return registries; +} + +// The exports map gives import and require different builds, and Metro resolves each per requesting module +globalThis.__react_native_css_root_variable_registries ??= + createRootVariableRegistries(); + +export const rootVariables = + globalThis.__react_native_css_root_variable_registries.root; +export const universalVariables = + globalThis.__react_native_css_root_variable_registries.universal;