Skip to content

[backend] Write StyleX theme definitions to theme files - #638

Open
purefunctor wants to merge 3 commits into
mainfrom
backend/stylex-theme-files
Open

purefunctor wants to merge 3 commits into
mainfrom
backend/stylex-theme-files

Conversation

@purefunctor

Copy link
Copy Markdown
Owner

Writes a module's exported StyleX theme definitions to output/<Module>/index.stylex.js. Iris output now builds with @stylexjs/unplugin without the manual unstable_moduleResolution setting:

stylex.vite({ useCSSLayers: true })

Previously the plugin accepted defineVars, defineConsts, and defineMarker only when every generated index.js was marked as a theme file through themeFileExtension: "index".

This breaks projects that still set themeFileExtension: "index": that setting no longer matches the generated files, so they should remove it. Variable and class hashes also change, because StyleX hashes the file path.

Amp thread: https://ampcode.com/threads/T-01a118ca-d95d-702f-85cb-95686f6ce5ed

Design

StyleX 0.19.0 checks file names in two places:

  • Defining file: it only hashes defineVars, defineConsts, and defineMarker in files named *.stylex.js.
  • Importing file: it resolves an import only when the import path itself names a theme file.

@stylexjs/unplugin defaults to { type: "commonJS", rootDir: process.cwd() }, so it needs no other setting.

  • Split, not rename. The theme file holds only the exported definitions. index.js imports them from ./index.stylex.js and re-exports them, so imports of index.js keep working and the re-exported object is the same one.
  • Copied helpers. Same-module values that a definition reads, such as gap = "21px" in defineConsts { gap }, are rendered again in the theme file. StyleX treats every value imported from a theme file as a theme variable, so importing a helper back into index.js fails with "A style value can only contain an array, string or number". These values are plain static data, so copying them is safe. This also means the theme file never imports its own index.js, so the split adds no import cycles.
  • Import paths come from validation. Validation now returns the imported theme values it proved StyleX evaluates at build time, and the functional module stores them. Those imports use ../<Module>/index.stylex.js; everything else, such as a defaultMarker used only in props, keeps ../<Module>/index.js. Choosing paths from the generator's own list of StyleX references would import runtime-only values from a file that does not export them.
  • createTheme, keyframes, positionTry, and viewTransitionClass stay in index.js. StyleX hashes them without a theme file.
  • iris build and the fixture harness write the extra file. Incremental builds delete a stale index.stylex.js once a module stops defining theme values. iris watch query javascript shows both files, each under a filename comment.

Tests

  • The e2e test passes only { type: "commonJS", rootDir } to @stylexjs/babel-plugin 0.19.0 and also transforms Tokens/index.stylex.js. It checks:
    • the split imports in Main
    • a helper shared by defineConsts and a same-module create (margin:21px)
    • same-module and cross-module variable hashes
    • that the re-export returns the same object from either file
  • style_x_cross_module_values now also covers a copied helper and a runtime-only import from a theme module. Goldens for the other theme fixtures gain index.stylex.js.
  • just t compiler and just t lsp pass with no pending snapshots. All 63 tests in cargo nextest run -p tests-e2e pass.
  • Manually, a Vite 7 build with only stylex.vite({ useCSSLayers: true }) produced the expected variable, marker, and helper rules. That check is not part of the test suite.

Follow-ups

  • The website pins Iris v0.1.4. Remove unstable_moduleResolution from its astro.config.mjs after a release.
  • Unrelated: StyleX 0.19's positionTry emits a malformed @position-try block that lightningcss rejects in real Vite builds. The Babel-only e2e test does not parse that CSS.

purefunctor and others added 3 commits October 8, 2026 00:46
StyleX resolves imported defineVars, defineConsts, and defineMarker values
only through imports of the defining module's theme file. Validation already
proves which imported values StyleX evaluates statically; keep that set on
the functional module so code generation can choose their import paths.

Amp-Thread-ID: https://ampcode.com/threads/T-01a118ca-d95d-702f-85cb-95686f6ce5ed
Co-authored-by: Amp <amp@ampcode.com>
StyleX accepts defineVars, defineConsts, and defineMarker only in files named
*.stylex.js, so projects previously had to mark every generated index.js as
a theme file through unstable_moduleResolution.themeFileExtension.

Write a module's exported theme definitions to index.stylex.js, which
index.js re-exports. Same-module values that the definitions read are copied
into the theme file: StyleX evaluates every import of a theme file as a theme
value, so importing them from it would break the definitions. Modules import
statically evaluated theme values from the defining module's theme file and
everything else from its index.js.

With @stylexjs/unplugin, Iris output now needs no module resolution options.

Amp-Thread-ID: https://ampcode.com/threads/T-01a118ca-d95d-702f-85cb-95686f6ce5ed
Co-authored-by: Amp <amp@ampcode.com>
`iris watch query javascript` shows what the StyleX plugin receives, which now
includes a module's theme file. Label both files when the module has one.

Amp-Thread-ID: https://ampcode.com/threads/T-01a118ca-d95d-702f-85cb-95686f6ce5ed
Co-authored-by: Amp <amp@ampcode.com>
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

Compatibility regression report

Package set 81.3.0 for PureScript 0.15.15.

✅ The candidate introduces no compatibility errors.

Diagnostic class Base Candidate Introduced Fixed
Compiler errors 0 0 0 0
Compiler warnings 36 36 0 0
Verifier errors 0 0 0 0

Introduced errors

None.

Fixed errors (0)

None.

Warning changes (0 introduced, 0 fixed)

Introduced

None.

Fixed

None.

Candidate errors (0)

None.

Candidate warnings (36)
  • deno@0.0.5/src/Deno.purs:38:17 — CustomWarning (checking): Data.Map's `Semigroup` instance is now unbiased and differs from the left-biased instance defined in PureScript releases <= 0.13.x.
  • deno@0.0.5/src/Deno/Dotenv.purs:41:20 — CustomWarning (checking) × 2: Data.Map's `Semigroup` instance is now unbiased and differs from the left-biased instance defined in PureScript releases <= 0.13.x.
  • deno@0.0.5/src/Deno/Http/Request.purs:47:25 — CustomWarning (checking): Data.Map's `Semigroup` instance is now unbiased and differs from the left-biased instance defined in PureScript releases <= 0.13.x.
  • literals@1.0.2/src/Literals/Null.purs:11:1 — UnparseableFFIModule (javascript): Oxc could not parse the JavaScript FFI module. Fix the invalid or unsupported JavaScript syntax; Iris treated the module as opaque and skipped export-name validation: Unexpected token
  • react-basic-dom-beta@0.1.1/src/Beta/DOM.purs:33:31 — DuplicateImport (indexing): Import list contains multiple references to 'Proxy'
  • sparse-polynomials@3.0.1/src/Data/Sparse/Polynomial.purs:1048:1 — MissingPatterns (checking) × 2: Pattern match is not exhaustive. Missing: _
  • text-formatting@0.1.0/src/Data/Text/Format/Dodo/Printer.purs:64:25 — CustomWarning (checking) × 23: Debug function usage
  • trivial-unfold@0.5.0/src/Data/Unfoldable1/Trivial1.purs:150:17 — MissingPatterns (checking): Pattern match is not exhaustive. Missing: Right _
  • xterm@1.0.0/src/XTerm/UnicodeHandling.purs:15:1 — UnparseableFFIModule (javascript) × 2: Oxc could not parse the JavaScript FFI module. Fix the invalid or unsupported JavaScript syntax; Iris treated the module as opaque and skipped export-name validation: Expected a semicolon or an implicit semicolon after a statement, but found none
  • yoga-react-dom@2.0.1/src/Yoga/React/DOM.purs:34:31 — DuplicateImport (indexing): Import list contains multiple references to 'Proxy'
  • yoga-tree-utils@1.0.0/src/Yoga/Tree/Extended/Path.purs:20:72 — DuplicateImport (indexing): Import list contains multiple references to 'snoc'

View workflow run

@claude

claude Bot commented Oct 8, 2026

Copy link
Copy Markdown

Reviewed against AGENTS.md. I found no issues to raise.

The fix sits in the layers that own it. Functional validation already proves which imported values StyleX evaluates statically, and it now records them as stylex_theme_imports. The JavaScript generator uses that set to choose between index.stylex.js and index.js import paths, rather than reconstructing it. I checked a few edge cases against the validator, and it already rejects each one before the split:

  • non-exported defineVars/defineConsts/defineMarker
  • cross-module non-theme values in static positions
  • function calls in static arguments

So the theme file only ever imports theme values and copies static same-module helpers. Every path that writes JavaScript output writes the extra file: iris build, the fixture harness, and its expected-paths check.

On tests: the fixture changes in tests-integration cover the generated JavaScript. The bundler contract (default unstable_moduleResolution, hash agreement across modules, identity of the re-exported object) is tested in tests-e2e, which is the level AGENTS.md assigns. I read through the regenerated goldens and snapshots, and they describe the intended split: definitions move to index.stylex.js, index.js re-exports them, and the runtime-only await marker stays on index.js.

Checks run locally:

  • cargo check -p functional -p javascript -p iris-build -p iris-watch-query -p tests-integration --tests: passed
  • cargo nextest run -p functional -p javascript -p iris-build -p iris-watch-query: 64/64 passed
  • just t compiler (unfiltered): all passed, no pending snapshots
  • just format --check: clean
  • just licenses: THIRDPARTY.toml unchanged
  • git status after all runs: clean, no .snap.new files

I did not run the e2e suite locally; CI covers it.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant