Skip to content

Metro bundles ESM and CommonJS runtimes; CSS variables inject into the wrong rootVariables family #409

Description

@pauloedurezende

Bug summary

With react-native-css@3.0.7, Expo 57 / React Native 0.86 / Metro 0.84 can bundle both dist/module and dist/commonjs copies of the native runtime. StyleCollection and VariableContext are globalized, but rootVariables / universalVariables are module-local.

The transformed CSS module is CommonJS and StyleCollection.inject() therefore closes over the CommonJS rootVariables family. Components rewritten from react-native use the ESM runtime, whose varResolver() reads the ESM family. The stylesheet rules are registered (because StyleCollection is shared), but any runtime var(...) resolves to undefined.

This makes direct/inlined utility values work while mode-aware CSS-variable-backed colors render transparent.

Version / environment

  • react-native-css: 3.0.7
  • nativewind: 5.0.0-preview.4
  • Expo: 57.0.11
  • React Native: 0.86.2
  • Metro: 0.84.4
  • pnpm: 11.17.0
  • iOS simulator (same architecture should affect Android)

Runtime evidence

Using the React Native CDP inspector against the live app:

__r(337).StyleCollection === __r(1247).StyleCollection
// true

__r(337).rootVariables === __r(1247).rootVariables
// false

__r(337).rootVariables("color-surface-primary").get()
// undefined

__r(1247).rootVariables("color-surface-primary").get()
// "#fff"

Here module 337 is dist/module/native-internal/index.js, while 1247 is dist/commonjs/native-internal/index.js.

Both runtimes are present in the same bundle:

dist/module/native-internal/root.js
dist/commonjs/native-internal/root.js
dist/module/native/styles/variables.js
dist/commonjs/native/styles/variables.js

The CSS class itself is correctly registered:

globalThis.__react_native_css_style_collection
  .styles("bg-utility-neutral-900")
  .get()
// [{ d: [[[{ }, "var", "color-utility-neutral-900", 1], "backgroundColor", 1]], dv: 1, ... }]

Resolving the same rule through each runtime proves the split:

const get = observable => observable.get();
const rules = globalThis.__react_native_css_style_collection
  .styles("bg-utility-neutral-900")
  .get();

__r(730).calculateProps(get, rules).normal
// ESM calculate-props: { style: {} }

__r(1263).calculateProps(get, rules).normal
// CJS calculate-props: { style: { backgroundColor: "#171717" } }

The generated bundle shows the CSS injection module depending on the CommonJS native-internal module, while React Native wrappers and useNativeCss use ESM modules.

Why this happens

src/native-internal/style-collection.ts stores StyleCollection on globalThis, and variables.tsx similarly globalizes VariableContext. But src/native-internal/root.ts creates these per module instance:

export const rootVariables = rootVariableFamily();
export const universalVariables = rootVariableFamily();

So ESM/CJS duplication is survivable for registered styles but not variables.

Metro 0.84 intentionally resolves package conditional exports by source import vs require, so a package containing both forms can legitimately include both targets in one bundle.

Suggested fix

Make the root/universal variable families canonical across module formats, similar to StyleCollection and VariableContext (for example, store both on globalThis), and add a regression test/build fixture where CSS injection comes from the CommonJS export while rendering uses the ESM export.

A package-level alternative would be to expose one canonical React Native target above import/require in the exports map, but globalizing the variable registries would also defend against duplicate package instances.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    auto-triagedIssue has been automatically triaged by the auto-triage workflowbugSomething isn't workingconfirmedBug reproduced and confirmed by triageneeds-deep-triageNeeds Tier 2 triage (runtime/simulator, self-hosted runner)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions