[babel-plugin] Support nested objects in defineConsts#1303
[babel-plugin] Support nested objects in defineConsts#1303j-malt wants to merge 13 commits intofacebook:mainfrom
defineConsts#1303Conversation
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as resolved.
This comment was marked as resolved.
| const outerMost = getOuterMostMemberExpression(path); | ||
|
|
||
| // to handle nested member expressions, we wait until we are at the outer most member expression | ||
| // and then we can extract the full path and evaluate it via the object proxy | ||
| if (outerMost === path) { | ||
| const pathInfo = getFullMemberPath(path); | ||
|
|
||
| if (pathInfo != null && pathInfo.parts.length > 0) { | ||
| const baseObject = evaluateCached(pathInfo.baseObject, state); | ||
| if (!state.confident) { | ||
| return; | ||
| } | ||
|
|
||
| if (baseObject[PROXY_MARKER]) { | ||
| return baseObject[`__nested__${pathInfo.parts.join('.')}`]; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
A previous iteration of this PR made a change directly to the proxy to "build up" nested accesses overtime, and then had this AST traversal extract out the value once we were at the terminal/outermost member access. This worked well for any code which accesses the proxy via this traversal, but broke for code that accessed it in other ways, specifically variables here:
stylex/packages/@stylexjs/babel-plugin/src/visitors/stylex-create-theme.js
Lines 154 to 162 in d8053a2
And themeVars[key] here
A simple way to fix this would've been to special case these accesses or to make them aware of the proxy. Neither of these seem like great solutions as they would leak the implementation details of this evaluate loop throughout the codebase.
My solution here is to have Babel to traverse the AST as normal, but once we are at the terminal/outermost access of a member expression, extract and join the full key path as a dotted string and pass that to the proxy. This keeps the proxy behaving as normal but means that dotted accesses work. I can add some benchmarks in another PR if there are concerns about build performance here.
There was a problem hiding this comment.
I added a quick benchmark in #1312 and updated the benchmark in this PR to use nested keys as well. This change seems to be insignificant on the "complex create" benchmark.
| throw new Error( | ||
| `Conflicting constant paths detected: "${fullPath}". This can happen when you have both nested properties (e.g., {a: {b: 'value'}}) and a literal dotted key (e.g., {'a.b': 'value'}) that resolve to the same path.`, | ||
| ); |
There was a problem hiding this comment.
I couldn't come up with a great way to get around conflicting keys like this and it feels like a pattern to avoid anyways.
5d0beb1 to
cb0520d
Compare
|
Is there anything that would be helpful to get this change moved along? This would be super valuable for us internally. |
|
I'm still going through the code changes, but I want to lay out my thinking here before leaving any code comments: Why I'm generally against thisMy primary opposition to this API change is that So, while, yes, it is possible to extend What I think we should do insteadOne API that we have been discussing for a few weeks is something called In many ways, Trade-offsI would argue that Since Another advantage of So with all of that said, I would like to understand if a If you have a strong argument for why it would be more helpful to extend |
|
I have a few thoughts on this.
If we want to try out this nested |
|
Thanks both for your thoughts! We chatted a bit elsewhere, but just to summarize:
It sounds like the best path forward will be to:
For now, I'll close this PR out. Thanks again! Footnotes
|
57e041a to
5ce36df
Compare
Summary: Adds the foundational shared layer for three new experimental APIs: - stylex.unstable_defineVarsNested - stylex.unstable_defineConstsNested - stylex.unstable_createThemeNested These APIs will allow design tokens to be defined as nested objects instead of flat key-value pairs, addressing community requests (PR facebook#1303) and the documented limitation in defineVars ("variables cannot be nested within another object"). This commit adds: - Flatten/unflatten utilities (stylex-nested-utils.js) that convert between nested and dot-separated key formats - Three shared transform wrappers that delegate to existing flat transforms (defineVars, defineConsts, createTheme) - Import tracking for the three new API names in state-manager and imports visitor - Public API signatures No behavioral changes — the new code is not yet wired to any visitors or public API exports. All existing tests pass unchanged. Next steps: public API stubs visitors + dispatch Fix the flow Test Plan: 1. Unit tests for the utils added: `cd packages/@stylexjs/babel-plugin && && npx jest __tests__/stylex-nested-utils-test.js __tests__/shared-stylex-nested-transforms-test.js --no-coverage` 57 tests pass 2. Confirm there's no regression: `cd packages/@stylexjs/babel-plugin && npx jest --no-coverage` Test Suites: 1 skipped, 37 passed, 37 of 38 total Tests: 64 skipped, 815 passed, 879 total Snapshots: 782 passed, 782 total 3. `npx flow` / `npm run flow` has no errors
Summary:
Completes the babel transform pipeline for the three new experimental APIs:
- stylex.unstable_defineVarsNested
- stylex.unstable_defineConstsNested
- stylex.unstable_createThemeNested
This commit adds:
- Visitor transform for unstable_defineVarsNested (modeled on stylex-define-vars.js). Detects calls via named import or stylex.unstable_defineVarsNested member expression, validates (must be named export, 1 arg), evaluates statically, delegates to shared styleXDefineVarsNested, replaces AST with nested output
- Visitor transform for unstable_defineConstsNested (modeled on stylex-define-consts.js). Same pattern, delegates to shared styleXDefineConstsNested
- Visitor transform for unstable_createThemeNested (modeled on stylex-create-theme.js). Takes 2 args, validates first has __varGroupHash__, delegates to shared styleXCreateThemeNested, supports keyframes/positionTry/types in overrides
- CallExpression dispatch in index.js wiring all three new visitors
- Updated test files to match refactored utils API names
(flattenNestedConfig → flattenNestedVarsConfig)
The full pipeline is now functional:
source code → import detection → visitor → shared transform → AST replacement
Test Plan:
1. New end-to-end tests
npx jest __tests__/transform-stylex-defineVarsNested-test.js __tests__/transform-stylex-defineConstsNested-test.js __tests__/transform-stylex-createThemeNested-test.js --no-coverage 2>&1
PASS __tests__/transform-stylex-defineVarsNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_defineVarsNested()
✓ basic nested tokens (37 ms)
✓ deeply nested tokens (3 levels) (2 ms)
✓ conditional @media values inside nesting (2 ms)
✓ mixed flat and nested values (2 ms)
✓ works with named import (2 ms)
✓ works with renamed named import (1 ms)
✓ produces different hashes for different nested keys (2 ms)
✓ deeply nested conditional with @supports (2 ms)
✓ throws: must be a named export (31 ms)
✓ throws: must have exactly 1 argument (2 ms)
✓ throws: must have an argument (1 ms)
PASS __tests__/transform-stylex-defineConstsNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_defineConstsNested()
✓ basic nested consts with original values preserved (14 ms)
✓ deeply nested constants (3 levels) (2 ms)
✓ j-malt PR facebook#1303 use case: three-tiered tokens with state namespaces (2 ms)
✓ handles number values (1 ms)
✓ works with named import (1 ms)
✓ mixed string and number values (1 ms)
✓ throws: must be a named export (22 ms)
✓ throws: must have exactly 1 argument (1 ms)
PASS __tests__/transform-stylex-createThemeNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_createThemeNested()
✓ creates theme override for nested vars (29 ms)
✓ partial override (only some branches) (3 ms)
✓ conditional override with @media (3 ms)
✓ works with named import (2 ms)
✓ override CSS uses correct var hashes from defineVarsNested (7 ms)
✓ throws: must have exactly 2 arguments (25 ms)
✓ throws: must be assigned to a variable (2 ms)
✓ throws: first arg must have __varGroupHash__ (1 ms)
Test Suites: 3 passed, 3 total
Tests: 27 passed, 27 total
Snapshots: 30 passed, 30 total
2. Regression tests
cd packages/@stylexjs/babel-plugin && npx jest --no-coverage
Full suite: 41 suites pass, 892 tests pass, 810 snapshots pass
Summary:
Completes the babel transform pipeline for the three new experimental APIs:
- stylex.unstable_defineVarsNested
- stylex.unstable_defineConstsNested
- stylex.unstable_createThemeNested
This commit adds:
- Visitor transform for unstable_defineVarsNested (modeled on stylex-define-vars.js). Detects calls via named import or stylex.unstable_defineVarsNested member expression, validates (must be named export, 1 arg), evaluates statically, delegates to shared styleXDefineVarsNested, replaces AST with nested output
- Visitor transform for unstable_defineConstsNested (modeled on stylex-define-consts.js). Same pattern, delegates to shared styleXDefineConstsNested
- Visitor transform for unstable_createThemeNested (modeled on stylex-create-theme.js). Takes 2 args, validates first has __varGroupHash__, delegates to shared styleXCreateThemeNested, supports keyframes/positionTry/types in overrides
- CallExpression dispatch in index.js wiring all three new visitors
- Updated test files to match refactored utils API names
(flattenNestedConfig → flattenNestedVarsConfig)
The full pipeline is now functional:
source code → import detection → visitor → shared transform → AST replacement
Test Plan:
1. New end-to-end tests
npx jest __tests__/transform-stylex-defineVarsNested-test.js __tests__/transform-stylex-defineConstsNested-test.js __tests__/transform-stylex-createThemeNested-test.js --no-coverage 2>&1
PASS __tests__/transform-stylex-defineVarsNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_defineVarsNested()
✓ basic nested tokens (37 ms)
✓ deeply nested tokens (3 levels) (2 ms)
✓ conditional @media values inside nesting (2 ms)
✓ mixed flat and nested values (2 ms)
✓ works with named import (2 ms)
✓ works with renamed named import (1 ms)
✓ produces different hashes for different nested keys (2 ms)
✓ deeply nested conditional with @supports (2 ms)
✓ throws: must be a named export (31 ms)
✓ throws: must have exactly 1 argument (2 ms)
✓ throws: must have an argument (1 ms)
PASS __tests__/transform-stylex-defineConstsNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_defineConstsNested()
✓ basic nested consts with original values preserved (14 ms)
✓ deeply nested constants (3 levels) (2 ms)
✓ j-malt PR facebook#1303 use case: three-tiered tokens with state namespaces (2 ms)
✓ handles number values (1 ms)
✓ works with named import (1 ms)
✓ mixed string and number values (1 ms)
✓ throws: must be a named export (22 ms)
✓ throws: must have exactly 1 argument (1 ms)
PASS __tests__/transform-stylex-createThemeNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_createThemeNested()
✓ creates theme override for nested vars (29 ms)
✓ partial override (only some branches) (3 ms)
✓ conditional override with @media (3 ms)
✓ works with named import (2 ms)
✓ override CSS uses correct var hashes from defineVarsNested (7 ms)
✓ throws: must have exactly 2 arguments (25 ms)
✓ throws: must be assigned to a variable (2 ms)
✓ throws: first arg must have __varGroupHash__ (1 ms)
Test Suites: 3 passed, 3 total
Tests: 27 passed, 27 total
Snapshots: 30 passed, 30 total
2. Regression tests
cd packages/@stylexjs/babel-plugin && npx jest --no-coverage
Full suite: 41 suites pass, 892 tests pass, 810 snapshots pass
Summary: Adds the foundational shared layer for three new experimental APIs: - stylex.unstable_defineVarsNested - stylex.unstable_defineConstsNested - stylex.unstable_createThemeNested These APIs will allow design tokens to be defined as nested objects instead of flat key-value pairs, addressing community requests (PR facebook#1303) and the documented limitation in defineVars ("variables cannot be nested within another object"). This commit adds: - Flatten/unflatten utilities (stylex-nested-utils.js) that convert between nested and dot-separated key formats - Three shared transform wrappers that delegate to existing flat transforms (defineVars, defineConsts, createTheme) - Import tracking for the three new API names in state-manager and imports visitor - Public API signatures No behavioral changes — the new code is not yet wired to any visitors or public API exports. All existing tests pass unchanged. Next steps: public API stubs visitors + dispatch Fix the flow Test Plan: 1. Unit tests for the utils added: `cd packages/@stylexjs/babel-plugin && && npx jest __tests__/stylex-nested-utils-test.js __tests__/shared-stylex-nested-transforms-test.js --no-coverage` 57 tests pass 2. Confirm there's no regression: `cd packages/@stylexjs/babel-plugin && npx jest --no-coverage` Test Suites: 1 skipped, 37 passed, 37 of 38 total Tests: 64 skipped, 815 passed, 879 total Snapshots: 782 passed, 782 total 3. `npx flow` / `npm run flow` has no errors
Summary:
Completes the babel transform pipeline for the three new experimental APIs:
- stylex.unstable_defineVarsNested
- stylex.unstable_defineConstsNested
- stylex.unstable_createThemeNested
This commit adds:
- Visitor transform for unstable_defineVarsNested (modeled on stylex-define-vars.js). Detects calls via named import or stylex.unstable_defineVarsNested member expression, validates (must be named export, 1 arg), evaluates statically, delegates to shared styleXDefineVarsNested, replaces AST with nested output
- Visitor transform for unstable_defineConstsNested (modeled on stylex-define-consts.js). Same pattern, delegates to shared styleXDefineConstsNested
- Visitor transform for unstable_createThemeNested (modeled on stylex-create-theme.js). Takes 2 args, validates first has __varGroupHash__, delegates to shared styleXCreateThemeNested, supports keyframes/positionTry/types in overrides
- CallExpression dispatch in index.js wiring all three new visitors
- Updated test files to match refactored utils API names
(flattenNestedConfig → flattenNestedVarsConfig)
The full pipeline is now functional:
source code → import detection → visitor → shared transform → AST replacement
Test Plan:
1. New end-to-end tests
npx jest __tests__/transform-stylex-defineVarsNested-test.js __tests__/transform-stylex-defineConstsNested-test.js __tests__/transform-stylex-createThemeNested-test.js --no-coverage 2>&1
PASS __tests__/transform-stylex-defineVarsNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_defineVarsNested()
✓ basic nested tokens (37 ms)
✓ deeply nested tokens (3 levels) (2 ms)
✓ conditional @media values inside nesting (2 ms)
✓ mixed flat and nested values (2 ms)
✓ works with named import (2 ms)
✓ works with renamed named import (1 ms)
✓ produces different hashes for different nested keys (2 ms)
✓ deeply nested conditional with @supports (2 ms)
✓ throws: must be a named export (31 ms)
✓ throws: must have exactly 1 argument (2 ms)
✓ throws: must have an argument (1 ms)
PASS __tests__/transform-stylex-defineConstsNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_defineConstsNested()
✓ basic nested consts with original values preserved (14 ms)
✓ deeply nested constants (3 levels) (2 ms)
✓ j-malt PR facebook#1303 use case: three-tiered tokens with state namespaces (2 ms)
✓ handles number values (1 ms)
✓ works with named import (1 ms)
✓ mixed string and number values (1 ms)
✓ throws: must be a named export (22 ms)
✓ throws: must have exactly 1 argument (1 ms)
PASS __tests__/transform-stylex-createThemeNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_createThemeNested()
✓ creates theme override for nested vars (29 ms)
✓ partial override (only some branches) (3 ms)
✓ conditional override with @media (3 ms)
✓ works with named import (2 ms)
✓ override CSS uses correct var hashes from defineVarsNested (7 ms)
✓ throws: must have exactly 2 arguments (25 ms)
✓ throws: must be assigned to a variable (2 ms)
✓ throws: first arg must have __varGroupHash__ (1 ms)
Test Suites: 3 passed, 3 total
Tests: 27 passed, 27 total
Snapshots: 30 passed, 30 total
2. Regression tests
cd packages/@stylexjs/babel-plugin && npx jest --no-coverage
Full suite: 41 suites pass, 892 tests pass, 810 snapshots pass
Summary: Adds the foundational shared layer for three new experimental APIs: - stylex.unstable_defineVarsNested - stylex.unstable_defineConstsNested - stylex.unstable_createThemeNested These APIs will allow design tokens to be defined as nested objects instead of flat key-value pairs, addressing community requests (PR facebook#1303) and the documented limitation in defineVars ("variables cannot be nested within another object"). This commit adds: - Flatten/unflatten utilities (stylex-nested-utils.js) that convert between nested and dot-separated key formats - Three shared transform wrappers that delegate to existing flat transforms (defineVars, defineConsts, createTheme) - Import tracking for the three new API names in state-manager and imports visitor - Public API signatures No behavioral changes — the new code is not yet wired to any visitors or public API exports. All existing tests pass unchanged. Next steps: public API stubs visitors + dispatch Fix the flow Test Plan: 1. Unit tests for the utils added: `cd packages/@stylexjs/babel-plugin && && npx jest __tests__/stylex-nested-utils-test.js __tests__/shared-stylex-nested-transforms-test.js --no-coverage` 57 tests pass 2. Confirm there's no regression: `cd packages/@stylexjs/babel-plugin && npx jest --no-coverage` Test Suites: 1 skipped, 37 passed, 37 of 38 total Tests: 64 skipped, 815 passed, 879 total Snapshots: 782 passed, 782 total 3. `npx flow` / `npm run flow` has no errors
Summary:
Completes the babel transform pipeline for the three new experimental APIs:
- stylex.unstable_defineVarsNested
- stylex.unstable_defineConstsNested
- stylex.unstable_createThemeNested
This commit adds:
- Visitor transform for unstable_defineVarsNested (modeled on stylex-define-vars.js). Detects calls via named import or stylex.unstable_defineVarsNested member expression, validates (must be named export, 1 arg), evaluates statically, delegates to shared styleXDefineVarsNested, replaces AST with nested output
- Visitor transform for unstable_defineConstsNested (modeled on stylex-define-consts.js). Same pattern, delegates to shared styleXDefineConstsNested
- Visitor transform for unstable_createThemeNested (modeled on stylex-create-theme.js). Takes 2 args, validates first has __varGroupHash__, delegates to shared styleXCreateThemeNested, supports keyframes/positionTry/types in overrides
- CallExpression dispatch in index.js wiring all three new visitors
- Updated test files to match refactored utils API names
(flattenNestedConfig → flattenNestedVarsConfig)
The full pipeline is now functional:
source code → import detection → visitor → shared transform → AST replacement
Test Plan:
1. New end-to-end tests
npx jest __tests__/transform-stylex-defineVarsNested-test.js __tests__/transform-stylex-defineConstsNested-test.js __tests__/transform-stylex-createThemeNested-test.js --no-coverage 2>&1
PASS __tests__/transform-stylex-defineVarsNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_defineVarsNested()
✓ basic nested tokens (37 ms)
✓ deeply nested tokens (3 levels) (2 ms)
✓ conditional @media values inside nesting (2 ms)
✓ mixed flat and nested values (2 ms)
✓ works with named import (2 ms)
✓ works with renamed named import (1 ms)
✓ produces different hashes for different nested keys (2 ms)
✓ deeply nested conditional with @supports (2 ms)
✓ throws: must be a named export (31 ms)
✓ throws: must have exactly 1 argument (2 ms)
✓ throws: must have an argument (1 ms)
PASS __tests__/transform-stylex-defineConstsNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_defineConstsNested()
✓ basic nested consts with original values preserved (14 ms)
✓ deeply nested constants (3 levels) (2 ms)
✓ j-malt PR facebook#1303 use case: three-tiered tokens with state namespaces (2 ms)
✓ handles number values (1 ms)
✓ works with named import (1 ms)
✓ mixed string and number values (1 ms)
✓ throws: must be a named export (22 ms)
✓ throws: must have exactly 1 argument (1 ms)
PASS __tests__/transform-stylex-createThemeNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_createThemeNested()
✓ creates theme override for nested vars (29 ms)
✓ partial override (only some branches) (3 ms)
✓ conditional override with @media (3 ms)
✓ works with named import (2 ms)
✓ override CSS uses correct var hashes from defineVarsNested (7 ms)
✓ throws: must have exactly 2 arguments (25 ms)
✓ throws: must be assigned to a variable (2 ms)
✓ throws: first arg must have __varGroupHash__ (1 ms)
Test Suites: 3 passed, 3 total
Tests: 27 passed, 27 total
Snapshots: 30 passed, 30 total
2. Regression tests
cd packages/@stylexjs/babel-plugin && npx jest --no-coverage
Full suite: 41 suites pass, 892 tests pass, 810 snapshots pass
Summary: Adds the foundational shared layer for three new experimental APIs: - stylex.unstable_defineVarsNested - stylex.unstable_defineConstsNested - stylex.unstable_createThemeNested These APIs will allow design tokens to be defined as nested objects instead of flat key-value pairs, addressing community requests (PR facebook#1303) and the documented limitation in defineVars ("variables cannot be nested within another object"). This commit adds: - Flatten/unflatten utilities (stylex-nested-utils.js) that convert between nested and dot-separated key formats - Three shared transform wrappers that delegate to existing flat transforms (defineVars, defineConsts, createTheme) - Import tracking for the three new API names in state-manager and imports visitor - Public API signatures No behavioral changes — the new code is not yet wired to any visitors or public API exports. All existing tests pass unchanged. Next steps: public API stubs visitors + dispatch Fix the flow Test Plan: 1. Unit tests for the utils added: `cd packages/@stylexjs/babel-plugin && && npx jest __tests__/stylex-nested-utils-test.js __tests__/shared-stylex-nested-transforms-test.js --no-coverage` 57 tests pass 2. Confirm there's no regression: `cd packages/@stylexjs/babel-plugin && npx jest --no-coverage` Test Suites: 1 skipped, 37 passed, 37 of 38 total Tests: 64 skipped, 815 passed, 879 total Snapshots: 782 passed, 782 total 3. `npx flow` / `npm run flow` has no errors
Summary:
Completes the babel transform pipeline for the three new experimental APIs:
- stylex.unstable_defineVarsNested
- stylex.unstable_defineConstsNested
- stylex.unstable_createThemeNested
This commit adds:
- Visitor transform for unstable_defineVarsNested (modeled on stylex-define-vars.js). Detects calls via named import or stylex.unstable_defineVarsNested member expression, validates (must be named export, 1 arg), evaluates statically, delegates to shared styleXDefineVarsNested, replaces AST with nested output
- Visitor transform for unstable_defineConstsNested (modeled on stylex-define-consts.js). Same pattern, delegates to shared styleXDefineConstsNested
- Visitor transform for unstable_createThemeNested (modeled on stylex-create-theme.js). Takes 2 args, validates first has __varGroupHash__, delegates to shared styleXCreateThemeNested, supports keyframes/positionTry/types in overrides
- CallExpression dispatch in index.js wiring all three new visitors
- Updated test files to match refactored utils API names
(flattenNestedConfig → flattenNestedVarsConfig)
The full pipeline is now functional:
source code → import detection → visitor → shared transform → AST replacement
Test Plan:
1. New end-to-end tests
npx jest __tests__/transform-stylex-defineVarsNested-test.js __tests__/transform-stylex-defineConstsNested-test.js __tests__/transform-stylex-createThemeNested-test.js --no-coverage 2>&1
PASS __tests__/transform-stylex-defineVarsNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_defineVarsNested()
✓ basic nested tokens (37 ms)
✓ deeply nested tokens (3 levels) (2 ms)
✓ conditional @media values inside nesting (2 ms)
✓ mixed flat and nested values (2 ms)
✓ works with named import (2 ms)
✓ works with renamed named import (1 ms)
✓ produces different hashes for different nested keys (2 ms)
✓ deeply nested conditional with @supports (2 ms)
✓ throws: must be a named export (31 ms)
✓ throws: must have exactly 1 argument (2 ms)
✓ throws: must have an argument (1 ms)
PASS __tests__/transform-stylex-defineConstsNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_defineConstsNested()
✓ basic nested consts with original values preserved (14 ms)
✓ deeply nested constants (3 levels) (2 ms)
✓ j-malt PR facebook#1303 use case: three-tiered tokens with state namespaces (2 ms)
✓ handles number values (1 ms)
✓ works with named import (1 ms)
✓ mixed string and number values (1 ms)
✓ throws: must be a named export (22 ms)
✓ throws: must have exactly 1 argument (1 ms)
PASS __tests__/transform-stylex-createThemeNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_createThemeNested()
✓ creates theme override for nested vars (29 ms)
✓ partial override (only some branches) (3 ms)
✓ conditional override with @media (3 ms)
✓ works with named import (2 ms)
✓ override CSS uses correct var hashes from defineVarsNested (7 ms)
✓ throws: must have exactly 2 arguments (25 ms)
✓ throws: must be assigned to a variable (2 ms)
✓ throws: first arg must have __varGroupHash__ (1 ms)
Test Suites: 3 passed, 3 total
Tests: 27 passed, 27 total
Snapshots: 30 passed, 30 total
2. Regression tests
cd packages/@stylexjs/babel-plugin && npx jest --no-coverage
Full suite: 41 suites pass, 892 tests pass, 810 snapshots pass
Summary: Adds the foundational shared layer for three new experimental APIs: - stylex.unstable_defineVarsNested - stylex.unstable_defineConstsNested - stylex.unstable_createThemeNested These APIs will allow design tokens to be defined as nested objects instead of flat key-value pairs, addressing community requests (PR facebook#1303) and the documented limitation in defineVars ("variables cannot be nested within another object"). This commit adds: - Flatten/unflatten utilities (stylex-nested-utils.js) that convert between nested and dot-separated key formats - Three shared transform wrappers that delegate to existing flat transforms (defineVars, defineConsts, createTheme) - Import tracking for the three new API names in state-manager and imports visitor - Public API signatures No behavioral changes — the new code is not yet wired to any visitors or public API exports. All existing tests pass unchanged. Next steps: public API stubs visitors + dispatch Fix the flow Test Plan: 1. Unit tests for the utils added: `cd packages/@stylexjs/babel-plugin && && npx jest __tests__/stylex-nested-utils-test.js __tests__/shared-stylex-nested-transforms-test.js --no-coverage` 57 tests pass 2. Confirm there's no regression: `cd packages/@stylexjs/babel-plugin && npx jest --no-coverage` Test Suites: 1 skipped, 37 passed, 37 of 38 total Tests: 64 skipped, 815 passed, 879 total Snapshots: 782 passed, 782 total 3. `npx flow` / `npm run flow` has no errors
Summary:
Completes the babel transform pipeline for the three new experimental APIs:
- stylex.unstable_defineVarsNested
- stylex.unstable_defineConstsNested
- stylex.unstable_createThemeNested
This commit adds:
- Visitor transform for unstable_defineVarsNested (modeled on stylex-define-vars.js). Detects calls via named import or stylex.unstable_defineVarsNested member expression, validates (must be named export, 1 arg), evaluates statically, delegates to shared styleXDefineVarsNested, replaces AST with nested output
- Visitor transform for unstable_defineConstsNested (modeled on stylex-define-consts.js). Same pattern, delegates to shared styleXDefineConstsNested
- Visitor transform for unstable_createThemeNested (modeled on stylex-create-theme.js). Takes 2 args, validates first has __varGroupHash__, delegates to shared styleXCreateThemeNested, supports keyframes/positionTry/types in overrides
- CallExpression dispatch in index.js wiring all three new visitors
- Updated test files to match refactored utils API names
(flattenNestedConfig → flattenNestedVarsConfig)
The full pipeline is now functional:
source code → import detection → visitor → shared transform → AST replacement
Test Plan:
1. New end-to-end tests
npx jest __tests__/transform-stylex-defineVarsNested-test.js __tests__/transform-stylex-defineConstsNested-test.js __tests__/transform-stylex-createThemeNested-test.js --no-coverage 2>&1
PASS __tests__/transform-stylex-defineVarsNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_defineVarsNested()
✓ basic nested tokens (37 ms)
✓ deeply nested tokens (3 levels) (2 ms)
✓ conditional @media values inside nesting (2 ms)
✓ mixed flat and nested values (2 ms)
✓ works with named import (2 ms)
✓ works with renamed named import (1 ms)
✓ produces different hashes for different nested keys (2 ms)
✓ deeply nested conditional with @supports (2 ms)
✓ throws: must be a named export (31 ms)
✓ throws: must have exactly 1 argument (2 ms)
✓ throws: must have an argument (1 ms)
PASS __tests__/transform-stylex-defineConstsNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_defineConstsNested()
✓ basic nested consts with original values preserved (14 ms)
✓ deeply nested constants (3 levels) (2 ms)
✓ j-malt PR facebook#1303 use case: three-tiered tokens with state namespaces (2 ms)
✓ handles number values (1 ms)
✓ works with named import (1 ms)
✓ mixed string and number values (1 ms)
✓ throws: must be a named export (22 ms)
✓ throws: must have exactly 1 argument (1 ms)
PASS __tests__/transform-stylex-createThemeNested-test.js
@stylexjs/babel-plugin
[transform] stylex.unstable_createThemeNested()
✓ creates theme override for nested vars (29 ms)
✓ partial override (only some branches) (3 ms)
✓ conditional override with @media (3 ms)
✓ works with named import (2 ms)
✓ override CSS uses correct var hashes from defineVarsNested (7 ms)
✓ throws: must have exactly 2 arguments (25 ms)
✓ throws: must be assigned to a variable (2 ms)
✓ throws: first arg must have __varGroupHash__ (1 ms)
Test Suites: 3 passed, 3 total
Tests: 27 passed, 27 total
Snapshots: 30 passed, 30 total
2. Regression tests
cd packages/@stylexjs/babel-plugin && npx jest --no-coverage
Full suite: 41 suites pass, 892 tests pass, 810 snapshots pass
* [1/n] Add shared utils for nested APIs Summary: Adds the foundational shared layer for three new experimental APIs: - stylex.unstable_defineVarsNested - stylex.unstable_defineConstsNested - stylex.unstable_createThemeNested These APIs will allow design tokens to be defined as nested objects instead of flat key-value pairs, addressing community requests (PR #1303) and the documented limitation in defineVars ("variables cannot be nested within another object"). This commit adds: - Flatten/unflatten utilities (stylex-nested-utils.js) that convert between nested and dot-separated key formats - Three shared transform wrappers that delegate to existing flat transforms (defineVars, defineConsts, createTheme) - Import tracking for the three new API names in state-manager and imports visitor - Public API signatures No behavioral changes — the new code is not yet wired to any visitors or public API exports. All existing tests pass unchanged. Next steps: public API stubs visitors + dispatch Fix the flow Test Plan: 1. Unit tests for the utils added: `cd packages/@stylexjs/babel-plugin && && npx jest __tests__/stylex-nested-utils-test.js __tests__/shared-stylex-nested-transforms-test.js --no-coverage` 57 tests pass 2. Confirm there's no regression: `cd packages/@stylexjs/babel-plugin && npx jest --no-coverage` Test Suites: 1 skipped, 37 passed, 37 of 38 total Tests: 64 skipped, 815 passed, 879 total Snapshots: 782 passed, 782 total 3. `npx flow` / `npm run flow` has no errors * [2/n] Add public nested APIs signature Summary: Runtime-throwing exports (after defineVars): unstable_defineVarsNested — throws "Unexpected 'stylex.unstable_defineVarsNested' call at runtime" unstable_defineConstsNested — throws "Unexpected 'stylex.unstable_defineConstsNested' call at runtime" unstable_createThemeNested — throws "Unexpected 'stylex.unstable_createThemeNested' call at runtime" IStyleX type definition — added all three as (...args: $FlowFixMe) => $FlowFixMe _legacyMerge — attached all three for backwards compatibility Test Plan: Regression: 39 suites, 872 tests, 782 snapshots — all passing. * [3/n] Add visitor transforms and dispatch for nested APIs Summary: Completes the babel transform pipeline for the three new experimental APIs: - stylex.unstable_defineVarsNested - stylex.unstable_defineConstsNested - stylex.unstable_createThemeNested This commit adds: - Visitor transform for unstable_defineVarsNested (modeled on stylex-define-vars.js). Detects calls via named import or stylex.unstable_defineVarsNested member expression, validates (must be named export, 1 arg), evaluates statically, delegates to shared styleXDefineVarsNested, replaces AST with nested output - Visitor transform for unstable_defineConstsNested (modeled on stylex-define-consts.js). Same pattern, delegates to shared styleXDefineConstsNested - Visitor transform for unstable_createThemeNested (modeled on stylex-create-theme.js). Takes 2 args, validates first has __varGroupHash__, delegates to shared styleXCreateThemeNested, supports keyframes/positionTry/types in overrides - CallExpression dispatch in index.js wiring all three new visitors - Updated test files to match refactored utils API names (flattenNestedConfig → flattenNestedVarsConfig) The full pipeline is now functional: source code → import detection → visitor → shared transform → AST replacement Test Plan: 1. New end-to-end tests npx jest __tests__/transform-stylex-defineVarsNested-test.js __tests__/transform-stylex-defineConstsNested-test.js __tests__/transform-stylex-createThemeNested-test.js --no-coverage 2>&1 PASS __tests__/transform-stylex-defineVarsNested-test.js @stylexjs/babel-plugin [transform] stylex.unstable_defineVarsNested() ✓ basic nested tokens (37 ms) ✓ deeply nested tokens (3 levels) (2 ms) ✓ conditional @media values inside nesting (2 ms) ✓ mixed flat and nested values (2 ms) ✓ works with named import (2 ms) ✓ works with renamed named import (1 ms) ✓ produces different hashes for different nested keys (2 ms) ✓ deeply nested conditional with @supports (2 ms) ✓ throws: must be a named export (31 ms) ✓ throws: must have exactly 1 argument (2 ms) ✓ throws: must have an argument (1 ms) PASS __tests__/transform-stylex-defineConstsNested-test.js @stylexjs/babel-plugin [transform] stylex.unstable_defineConstsNested() ✓ basic nested consts with original values preserved (14 ms) ✓ deeply nested constants (3 levels) (2 ms) ✓ j-malt PR #1303 use case: three-tiered tokens with state namespaces (2 ms) ✓ handles number values (1 ms) ✓ works with named import (1 ms) ✓ mixed string and number values (1 ms) ✓ throws: must be a named export (22 ms) ✓ throws: must have exactly 1 argument (1 ms) PASS __tests__/transform-stylex-createThemeNested-test.js @stylexjs/babel-plugin [transform] stylex.unstable_createThemeNested() ✓ creates theme override for nested vars (29 ms) ✓ partial override (only some branches) (3 ms) ✓ conditional override with @media (3 ms) ✓ works with named import (2 ms) ✓ override CSS uses correct var hashes from defineVarsNested (7 ms) ✓ throws: must have exactly 2 arguments (25 ms) ✓ throws: must be assigned to a variable (2 ms) ✓ throws: first arg must have __varGroupHash__ (1 ms) Test Suites: 3 passed, 3 total Tests: 27 passed, 27 total Snapshots: 30 passed, 30 total 2. Regression tests cd packages/@stylexjs/babel-plugin && npx jest --no-coverage Full suite: 41 suites pass, 892 tests pass, 810 snapshots pass * [4/n] Code refactoring in utils Summary: No functionality changes. Just refactor the code to be cleaner with reusable utils. * [5/n] Support cross-file resolution Summary: The Problem When importing nested tokens cross-file (import { tokens } from './tokens.stylex.js'), accessing tokens.button.primary.background fails because the theme ref proxy is flat — proxy['button'] returns "var(--xHash)" (a string), then "var(--xHash)"['primary'] returns undefined. The Fix (2 changes to evaluate-path.js) 1. getFullMemberPath() helper — walks a chain of MemberExpressions to collect all property names: tokens.button.primary.background → { basePath: <tokens>, parts: ['button', 'primary', 'background'] } 2. Modified MemberExpression handler — before falling through to recursive evaluation, checks if the base is a proxy. If so, resolves the full dotted key in one shot: // Instead of: proxy['button'] → "var(--hash)" → "var(--hash)"['primary'] → undefined ❌ // Now does: proxy['button.primary.background'] → resolveKey('button.primary.background') → "var(--xHash)" ✅ The proxy's resolveKey already hashes whatever string it receives. The dotted key 'button.primary.background' produces the same hash that defineVarsNested generated during compilation (because flattening produces the same dotted key). So the var references match — zero changes to the proxy itself. Test Plan: Added tests in transform-stylex-defineVarsNested-test.js to cover the cross-file use case * [6/n] Add comprehensive docstrings with examples * [7/n] Add visual demos / integration tests for nested APIs and design system theming Summary: Added two interactive demo apps showcasing the three nested experimental APIs in the example-nextjs project. Navigate: cd examples/example-nextjs && npm run example:dev / → Main app (link to nested demo) /nested-demo → Nested APIs demo (link to DS theming demo) /ds-demo → Design System Theming demo ────────────────────────────────────────────────────── /nested-demo — Nested APIs Showcase Interactive demo with theme switching (Ocean / Sunset / Forest): • Same-file consumption — badges styled with tokens.badge.info.bg, sizing from consts.badge.borderRadius, surface cards from tokens.surface.card • Cross-file consumption — CrossFileSection.tsx imports tokens from tokens.stylex.ts and uses them in a separate stylex.create() call, proving cross-file resolution works for nested APIs • Theme overrides — sunsetTheme and forestTheme created with unstable_createThemeNested, including page background (surface.bg) that changes per theme • API reference cards — documents each API with description Files: app/nested-demo/tokens.stylex.ts — token definitions (all 3 APIs) app/nested-demo/page.tsx — interactive demo page app/nested-demo/CrossFileSection.tsx — cross-file demo component ────────────────────────────────────────────────────── /ds-demo — Design System Theming Demonstrates the three-tier token architecture pattern used by design systems like XDS: Tier 1: Primitives → defineConstsNested (compile-time, zero cost) Tier 2: Semantic Tokens → defineVarsNested (CSS vars, themeable) Tier 3: Themes → createThemeNested (partial overrides) • Tier 1 primitives — full color palette (blue/green/orange/purple/ red/gray), spacing scale, border radii, font stacks, font weights, all defined with defineConstsNested (inlined at compile time) • Tier 2 semantics — intent-based names (color.accent, color.surface, color.textPrimary, elevation.sm/md/lg) with dark mode variants, referencing Tier 1 primitives • Tier 3 themes — Purple, Green, Orange brand themes that partially override only accent colors + background via createThemeNested • Component showcase — buttons, badges, cards, inputs all referencing only tokens.* (no hard-coded colors), re-skinned instantly • Live token swatches — real-time visualization of semantic token values Files: app/ds-demo/tokens.stylex.ts — three-tier token architecture app/ds-demo/page.tsx — interactive demo page ────────────────────────────────────────────────────── Other changes: • app/page.tsx — navigation link to /nested-demo from main app • Fixed Flow errors: updated type cast syntax (value as any) in stylex-nested-utils.js, tightened return types in shared stylex-define-vars-nested.js and stylex-define-consts-nested.js with recursive NestedVarOutput/NestedConstOutput types Test Plan: 1. Build verification & Navigate to the app routes to verify working end-to-end: cd examples/example-nextjs && npx next build Route (app) ├ ○ / ├ ○ /ds-demo └ ○ /nested-demo 2. Flow: npx flow → No errors! 3. Full test suite: cd packages/@stylexjs/babel-plugin && npx jest --no-coverage Test Suites: 41 passed, 1 skipped, 1 pre-existing failure (43 total) Tests: 891 passed, 64 skipped, 1 pre-existing failure (956 total) Snapshots: 805 passed * [9/n] Ensure type dsiambiguity with unstable_conditional() Summary: The added unstable_conditional() wrapper is purely a type-level disambiguation that tells Flow/TypeScript "this object is a conditional value, not a namespace." Test Plan: Added tests in test suite * [10/n] Update demo app to use the unstable_conditional() wrapper Summary: Added usage and docs for the newunstable_conditional() wrapper in demo app * [11/n] Add strong typing for nested APIs Summary: * Add nested API types (DefineVarsNested, DefineConstsNested, CreateThemeNested, Conditional) to StyleXTypes * Remove @ts-nocheck from nested demo files * Fix eslint stylex-valid-styles for nested declarations * Add strong return types for unstable_defineVarsNested and unstable_createThemeNested * [CI] `npm run prettier` Test Plan: npm run prettier * Updates to demos * Add tests for cross-package imports & fix types * Guard against keys containing internal flattening seperators Test Plan: cd stylex/packages/@stylexjs/babel-plugin && npx jest __tests__/stylex-nested-utils-test.js --no-coverage 2>&1 --------- Co-authored-by: Chun Wang <chunwang.me@gmail.com>
What changed / motivation ?
Our design system uses three-tiered tokens with component specific tokens like
$designSystem-component-button-primary-background-defaultand$designSystem-component-button-primary-background-hovered. This structure lends itself naturally to a nested theme object:We use
defineConststo implement this theme object (defineVars doesn't work for us, certain product requirements mean that we need the values of our theme object to be CSS variable references that are defined in an external file).Currently there is no good way to implement a structure like this with
defineConsts. Long camel case keys is an option, but has awkward Intellisense/auto-complete support. Splitting up the definitions into many top-leveldefineConstscalls (buttonColours,buttonBorderRadius, etc.) is the closest we can get, but is annoying in a few ways:(# of components) * (# of properties))This PR adds support for arbitrarily nested objects in
defineConsts. That is, the following is now valid StyleX:Additional Context
Fixture tests were added. I made a small change to the
defineConstsdocumentation to suggest that the objects can be nested. See comments for some notes on the code.Pre-flight checklist
Contribution Guidelines