From 7c5d554324d95e6804313d017f0b208b5315d718 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Fri, 17 Jul 2026 08:36:16 -0400 Subject: [PATCH 1/3] feat(ui): Stylex POC --- packages/ui/.gitignore | 2 + packages/ui/package.json | 3 + packages/ui/src/mosaic-x/button.tsx | 116 ++++++ packages/ui/src/mosaic-x/demo-live.html | 106 ++++++ packages/ui/src/mosaic-x/demo.html | 64 ++++ packages/ui/src/mosaic-x/index.ts | 3 + packages/ui/src/mosaic-x/tokens.stylex.ts | 31 ++ packages/ui/tsconfig.mosaic-x.json | 6 + packages/ui/tsdown.mosaic-x.config.mts | 29 ++ pnpm-lock.yaml | 111 +++++- references/mosaic-stylex-migration.md | 441 ++++++++++++++++++++++ 11 files changed, 904 insertions(+), 8 deletions(-) create mode 100644 packages/ui/.gitignore create mode 100644 packages/ui/src/mosaic-x/button.tsx create mode 100644 packages/ui/src/mosaic-x/demo-live.html create mode 100644 packages/ui/src/mosaic-x/demo.html create mode 100644 packages/ui/src/mosaic-x/index.ts create mode 100644 packages/ui/src/mosaic-x/tokens.stylex.ts create mode 100644 packages/ui/tsconfig.mosaic-x.json create mode 100644 packages/ui/tsdown.mosaic-x.config.mts create mode 100644 references/mosaic-stylex-migration.md diff --git a/packages/ui/.gitignore b/packages/ui/.gitignore new file mode 100644 index 00000000000..17ca222a57a --- /dev/null +++ b/packages/ui/.gitignore @@ -0,0 +1,2 @@ +# StyleX migration POC build output (regenerate with `pnpm build:mosaic-x`) +dist-mosaic-x diff --git a/packages/ui/package.json b/packages/ui/package.json index 348c1a0f3de..6c66d912bc2 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -72,6 +72,7 @@ "build": "pnpm build:umd && pnpm build:esm && pnpm check:no-rhc && pnpm type-check", "build:analyze": "rspack build --config rspack.config.js --env production --env analyze", "build:esm": "tsdown", + "build:mosaic-x": "tsdown --config tsdown.mosaic-x.config.mts", "build:rsdoctor": "RSDOCTOR=true rspack build --config rspack.config.js --env production", "build:umd": "rspack build --config rspack.config.js --env production", "bundlewatch": "FORCE_COLOR=1 bundlewatch --config bundlewatch.config.json", @@ -103,6 +104,7 @@ "@solana/wallet-adapter-base": "catalog:module-manager", "@solana/wallet-adapter-react": "catalog:module-manager", "@solana/wallet-standard": "catalog:module-manager", + "@stylexjs/stylex": "0.19.0", "@swc/helpers": "catalog:repo", "copy-to-clipboard": "3.3.3", "core-js": "catalog:repo", @@ -119,6 +121,7 @@ "@rspack/core": "catalog:rspack", "@rspack/dev-server": "catalog:rspack", "@rspack/plugin-react-refresh": "catalog:rspack", + "@stylexjs/rollup-plugin": "0.19.0", "@svgr/rollup": "^8.1.0", "@svgr/webpack": "^6.5.1", "@types/webpack-env": "^1.18.8", diff --git a/packages/ui/src/mosaic-x/button.tsx b/packages/ui/src/mosaic-x/button.tsx new file mode 100644 index 00000000000..d5679c50a41 --- /dev/null +++ b/packages/ui/src/mosaic-x/button.tsx @@ -0,0 +1,116 @@ +import * as stylex from '@stylexjs/stylex'; +import React from 'react'; + +import { colors, radius, space } from './tokens.stylex'; + +// Helper to keep spacing math readable. `space['--cl-spacing']` compiles to +// `var(--cl-spacing)`, so this yields e.g. `calc(var(--cl-spacing) * 2)`. +const s = (n: number) => `calc(${space['--cl-spacing']} * ${n})`; + +const styles = stylex.create({ + base: { + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + gap: s(2), + borderRadius: radius['--cl-radius-md'], + borderWidth: '1px', + borderStyle: 'solid', + borderColor: 'transparent', + fontWeight: 500, + fontFamily: 'inherit', + cursor: 'pointer', + transitionProperty: 'background-color, border-color, color, opacity', + transitionDuration: '150ms', + outline: { + default: 'none', + ':focus-visible': `2px solid color-mix(in oklab, ${colors['--cl-primary']} 50%, transparent)`, + }, + outlineOffset: '2px', + }, + + // intent × variant + filledPrimary: { + backgroundColor: { + default: colors['--cl-primary'], + ':hover': `color-mix(in oklab, ${colors['--cl-primary']}, ${colors['--cl-primary-foreground']} 12%)`, + ':active': `color-mix(in oklab, ${colors['--cl-primary']}, ${colors['--cl-primary-foreground']} 24%)`, + }, + color: colors['--cl-primary-foreground'], + }, + filledDestructive: { + backgroundColor: { + default: colors['--cl-destructive'], + ':hover': `color-mix(in oklab, ${colors['--cl-destructive']}, ${colors['--cl-destructive-foreground']} 12%)`, + ':active': `color-mix(in oklab, ${colors['--cl-destructive']}, ${colors['--cl-destructive-foreground']} 24%)`, + }, + color: colors['--cl-destructive-foreground'], + }, + outlinePrimary: { + backgroundColor: 'transparent', + borderColor: colors['--cl-border'], + color: colors['--cl-foreground'], + }, + + // size + sizeSm: { paddingBlock: s(1), paddingInline: s(2), fontSize: '0.75rem', lineHeight: 'calc(1 / 0.75)' }, + sizeMd: { paddingBlock: s(2), paddingInline: s(4), fontSize: '0.875rem', lineHeight: 'calc(1.25 / 0.875)' }, + + // state / modifiers + fullWidth: { width: '100%' }, + disabled: { opacity: 0.5, cursor: 'not-allowed', pointerEvents: 'none' }, +}); + +export interface ButtonProps extends React.ComponentPropsWithRef<'button'> { + intent?: 'primary' | 'destructive'; + variant?: 'filled' | 'outline'; + size?: 'sm' | 'md'; + fullWidth?: boolean; +} + +// Stable, hand-authored class every consumer can target: `.cl-button { … }`. +// StyleX never attaches rules to this class; it exists purely as a targeting +// hook. Consumer rules on it are unlayered and therefore beat StyleX's +// `@layer`-wrapped atomic classes (see mosaic-stylex-migration.md). +const STABLE_CLASS = 'cl-button'; + +export const Button = React.forwardRef(function Button( + { + intent = 'primary', + variant = 'filled', + size = 'md', + fullWidth = false, + disabled = false, + className, + children, + ...rest + }, + ref, +) { + const atomic = stylex.props( + styles.base, + variant === 'filled' && intent === 'primary' && styles.filledPrimary, + variant === 'filled' && intent === 'destructive' && styles.filledDestructive, + variant === 'outline' && styles.outlinePrimary, + size === 'sm' ? styles.sizeSm : styles.sizeMd, + fullWidth && styles.fullWidth, + disabled && styles.disabled, + ); + + return ( + + ); +}); diff --git a/packages/ui/src/mosaic-x/demo-live.html b/packages/ui/src/mosaic-x/demo-live.html new file mode 100644 index 00000000000..6ddb103163b --- /dev/null +++ b/packages/ui/src/mosaic-x/demo-live.html @@ -0,0 +1,106 @@ + + + + + + + mosaic-x — live component demo + + + + + + + + + +
+ + + + + diff --git a/packages/ui/src/mosaic-x/demo.html b/packages/ui/src/mosaic-x/demo.html new file mode 100644 index 00000000000..b46e3c8e62c --- /dev/null +++ b/packages/ui/src/mosaic-x/demo.html @@ -0,0 +1,64 @@ + + + + + + + mosaic-x StyleX POC + + + + + + + + + diff --git a/packages/ui/src/mosaic-x/index.ts b/packages/ui/src/mosaic-x/index.ts new file mode 100644 index 00000000000..15343ba8a0e --- /dev/null +++ b/packages/ui/src/mosaic-x/index.ts @@ -0,0 +1,3 @@ +export { Button } from './button'; +export type { ButtonProps } from './button'; +export { colors, radius, space } from './tokens.stylex'; diff --git a/packages/ui/src/mosaic-x/tokens.stylex.ts b/packages/ui/src/mosaic-x/tokens.stylex.ts new file mode 100644 index 00000000000..ade1f79495e --- /dev/null +++ b/packages/ui/src/mosaic-x/tokens.stylex.ts @@ -0,0 +1,31 @@ +import * as stylex from '@stylexjs/stylex'; + +// Mosaic-X design tokens. +// +// defineVars normally hashes variable names (e.g. `--x1a2b3c`), which consumers +// cannot target. Keys that start with `--` are emitted verbatim, so these become +// real, stable custom properties (`--cl-foreground`, …). A consumer can override +// any of them in plain CSS without touching StyleX: +// +// :root { --cl-foreground: red; } +// +// StyleX still emits `:root { --cl-foreground: black; }` as the default. +export const colors = stylex.defineVars({ + '--cl-foreground': 'black', + '--cl-background': 'white', + '--cl-primary': 'oklch(0.205 0 0)', + '--cl-primary-foreground': 'oklch(0.985 0 0)', + '--cl-destructive': 'oklch(0.577 0.245 27.325)', + '--cl-destructive-foreground': 'oklch(0.985 0 0)', + '--cl-border': 'oklch(0.922 0 0)', + '--cl-muted-foreground': 'oklch(0.556 0 0)', +}); + +export const radius = stylex.defineVars({ + '--cl-radius-md': '0.375rem', + '--cl-radius-full': 'calc(infinity * 1px)', +}); + +export const space = stylex.defineVars({ + '--cl-spacing': '0.25rem', +}); diff --git a/packages/ui/tsconfig.mosaic-x.json b/packages/ui/tsconfig.mosaic-x.json new file mode 100644 index 00000000000..d4b1a2302da --- /dev/null +++ b/packages/ui/tsconfig.mosaic-x.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsxImportSource": "react" + } +} diff --git a/packages/ui/tsdown.mosaic-x.config.mts b/packages/ui/tsdown.mosaic-x.config.mts new file mode 100644 index 00000000000..004756dc287 --- /dev/null +++ b/packages/ui/tsdown.mosaic-x.config.mts @@ -0,0 +1,29 @@ +import stylexPlugin from '@stylexjs/rollup-plugin'; +import { defineConfig } from 'tsdown'; + +// Isolated POC build: compiles ONLY src/mosaic-x with the StyleX rollup plugin, +// emitting transformed ESM + a single static mosaic.css stylesheet that consumers +// import. Kept separate from the main tsdown build so the Emotion-based code is +// untouched. `useCSSLayers` wraps StyleX's atomic rules in `@layer priorityN` for +// correct intra-StyleX precedence; consumers import the sheet into a layer they +// control (`@import '@clerk/ui/mosaic.css' layer(components)`) under which those +// nest cleanly, and override from a later layer. +export default defineConfig({ + entry: ['./src/mosaic-x/index.ts'], + outDir: './dist-mosaic-x', + format: ['esm'], + dts: true, + clean: true, + target: 'es2022', + platform: 'browser', + minify: false, + // Use the standard React JSX runtime, not Emotion's — mosaic-x must be Emotion-free. + tsconfig: './tsconfig.mosaic-x.json', + external: ['react', 'react-dom', '@stylexjs/stylex'], + plugins: [ + stylexPlugin({ + fileName: 'mosaic.css', + useCSSLayers: true, + }), + ], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4d6560b3da..4af7ae1e596 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -798,10 +798,10 @@ importers: version: 4.5.4(@types/node@25.6.0)(rollup@4.61.0)(typescript@6.0.3)(vite@6.4.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.20.6)(yaml@2.9.0)) vitest: specifier: 4.1.6 - version: 4.1.6(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(happy-dom@20.10.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.20.6)(yaml@2.9.0)) + version: 4.1.6(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(vitest@4.1.5))(happy-dom@20.10.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.20.6)(yaml@2.9.0)) vitest-axe: specifier: ^0.1.0 - version: 0.1.0(vitest@4.1.6(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(happy-dom@20.10.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.20.6)(yaml@2.9.0))) + version: 0.1.0(vitest@4.1.6(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(vitest@4.1.5))(happy-dom@20.10.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.20.6)(yaml@2.9.0))) packages/hono: dependencies: @@ -1189,6 +1189,9 @@ importers: '@solana/wallet-standard': specifier: catalog:module-manager version: 1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@6.0.0)(react@18.3.1) + '@stylexjs/stylex': + specifier: 0.19.0 + version: 0.19.0 '@swc/helpers': specifier: catalog:repo version: 0.5.21 @@ -1238,6 +1241,9 @@ importers: '@rspack/plugin-react-refresh': specifier: catalog:rspack version: 2.0.1(@rspack/core@2.0.6(@swc/helpers@0.5.21))(react-refresh@0.17.0) + '@stylexjs/rollup-plugin': + specifier: 0.19.0 + version: 0.19.0 '@svgr/rollup': specifier: ^8.1.0 version: 8.1.0(rollup@4.61.0)(typescript@6.0.3) @@ -1519,6 +1525,10 @@ packages: resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@8.0.0': + resolution: {integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-string-parser@8.0.0-rc.6': resolution: {integrity: sha512-BCkFy+zN6kXQed3YOT7aJl93NfDSzQc3pBfsvTVPs9gU9X3V0aefEF5kwBT0E+mDWH9QgKaZstYUQN9VdQZT4g==} engines: {node: ^22.18.0 || >=24.11.0} @@ -1531,6 +1541,10 @@ packages: resolution: {integrity: sha512-nVJ+1JcCgntv8d78rRo++o2wuODT0Irknx2BF8Np4Ft2CRgjLqIs4qzSZ8b66yGbBdMWGmZBO9WEZv1hhNiSpg==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-validator-identifier@8.0.4': + resolution: {integrity: sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-validator-option@7.29.7': resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} engines: {node: '>=6.9.0'} @@ -1557,6 +1571,11 @@ packages: engines: {node: ^22.18.0 || >=24.11.0} hasBin: true + '@babel/parser@8.0.4': + resolution: {integrity: sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==} + engines: {node: ^22.18.0 || >=24.11.0} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} engines: {node: '>=6.9.0'} @@ -2110,6 +2129,10 @@ packages: resolution: {integrity: sha512-p7/ABylAYlexb31wtRdIfH9L9A0Z2T/9H6zAqzqndkY2PLkvNNc580wGhp/gGKN4Sp9sQvSkhc6Oga8/O+wTyw==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/types@8.0.4': + resolution: {integrity: sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==} + engines: {node: ^22.18.0 || >=24.11.0} + '@base-org/account@2.0.1': resolution: {integrity: sha512-tySVNx+vd6XEynZL0uvB10uKiwnAfThr8AbKTwILVG86mPbLAhEOInQIk+uDnvpTvfdUhC1Bi5T/46JvFoLZQQ==} @@ -2405,6 +2428,9 @@ packages: resolution: {integrity: sha512-KEUw/mGu+EDRhYWRTNGHIimVCs9NvMFaIXOGrHSXoCteKLE5EsJnmPjOPpYorjXVg/0xG0fbdVw720azw1z4ag==} hasBin: true + '@dual-bundle/import-meta-resolve@4.2.1': + resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==} + '@dxup/nuxt@0.4.1': resolution: {integrity: sha512-gtYffW6OfWNvoLW+XD3Mx/K8uUq08PMGLYJoDxc92EzZAWqR0FhcR5iaLm5r/OxyGTKz+P5f5Y7Aoir9+SjYaw==} peerDependencies: @@ -3014,7 +3040,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {node: '>=0.10.0'} + engines: {'0': node >=0.10.0} '@expo/cli@0.22.28': resolution: {integrity: sha512-lvt72KNitGuixYD2l3SZmRKVu2G4zJpmg5V7WfUBNpmUU5oODBw/6qmiJ6kSLAlfDozscUk+BBGknBBzxUrwrA==} @@ -5861,6 +5887,18 @@ packages: resolution: {integrity: sha512-w8CEY73X/7tw2KKlL3iOk679V9bWseE4GzNz3zlaYxcTjmcmWOathRb0emgo/QQ3eoNzmq68+2Y2gxluAv3xGw==} engines: {node: '>=12.16'} + '@stylexjs/babel-plugin@0.19.0': + resolution: {integrity: sha512-2SnVZIl047TtqKW0nRTn7Irz0kmoot1bxdGDE/HwfwfF7dJYHqwkIFg92s87v3hj7zxJobrHWwDx1p3qvQXW4g==} + + '@stylexjs/rollup-plugin@0.19.0': + resolution: {integrity: sha512-HkPbod7x+gmQmdiPbZc3AdSBjKIknYlngAigtL3+KFNF/Yr+kd8Xke0qSaCB4P/FGbobJMofIDJrmhPZ/dWvOA==} + + '@stylexjs/shared@0.19.0': + resolution: {integrity: sha512-7Yym70cQH5sPzJHgzJX3FkL9ZCptbC3IbiRijN9YLYU/5OywB/3c519Xv1DOzvxqjv8urSxh98/9HxLaPG7sDg==} + + '@stylexjs/stylex@0.19.0': + resolution: {integrity: sha512-CnUFp7YMaDLDeemsWOfJgoC/gKM5P/yBNMcpJaE6ChJmXr7s0DJwSeGTTlHJcqqwN9OW1qGtmARWLFhGZN1pTA==} + '@svgr/babel-plugin-add-jsx-attribute@6.5.1': resolution: {integrity: sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==} engines: {node: '>=10'} @@ -8453,6 +8491,9 @@ packages: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} + css-mediaquery@0.1.2: + resolution: {integrity: sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==} + css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} @@ -14495,6 +14536,9 @@ packages: peerDependencies: postcss: ^8.5.14 + styleq@0.2.1: + resolution: {integrity: sha512-L0TR0NQb+X4/ktDEKmjWyp27gla+LUYi/by5k5SjKXf6/pvZP7wbwEB5J+tqxdFVPgzbsuz+d4RTScO/QZquBw==} + stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} @@ -16338,7 +16382,7 @@ snapshots: '@babel/generator@8.0.0-rc.6': dependencies: - '@babel/parser': 8.0.0-rc.6 + '@babel/parser': 8.0.4 '@babel/types': 8.0.0-rc.6 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 @@ -16446,12 +16490,16 @@ snapshots: '@babel/helper-string-parser@7.29.7': {} + '@babel/helper-string-parser@8.0.0': {} + '@babel/helper-string-parser@8.0.0-rc.6': {} '@babel/helper-validator-identifier@7.29.7': {} '@babel/helper-validator-identifier@8.0.0-rc.6': {} + '@babel/helper-validator-identifier@8.0.4': {} + '@babel/helper-validator-option@7.29.7': {} '@babel/helper-wrap-function@7.28.3': @@ -16482,6 +16530,10 @@ snapshots: dependencies: '@babel/types': 8.0.0-rc.6 + '@babel/parser@8.0.4': + dependencies: + '@babel/types': 8.0.4 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -17172,6 +17224,11 @@ snapshots: '@babel/helper-string-parser': 8.0.0-rc.6 '@babel/helper-validator-identifier': 8.0.0-rc.6 + '@babel/types@8.0.4': + dependencies: + '@babel/helper-string-parser': 8.0.0 + '@babel/helper-validator-identifier': 8.0.4 + '@base-org/account@2.0.1(@types/react@18.3.28)(bufferutil@4.1.0)(react@18.3.1)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@18.3.1))(utf-8-validate@5.0.10)(zod@4.4.3)': dependencies: '@noble/hashes': 1.4.0 @@ -17625,6 +17682,8 @@ snapshots: which: 4.0.0 yocto-spinner: 1.2.0 + '@dual-bundle/import-meta-resolve@4.2.1': {} + '@dxup/nuxt@0.4.1(magicast@0.5.3)(typescript@6.0.3)': dependencies: '@dxup/unimport': 0.1.2 @@ -21661,6 +21720,38 @@ snapshots: '@stripe/stripe-js@5.6.0': {} + '@stylexjs/babel-plugin@0.19.0': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@dual-bundle/import-meta-resolve': 4.2.1 + '@stylexjs/shared': 0.19.0 + '@stylexjs/stylex': 0.19.0 + postcss-value-parser: 4.2.0 + transitivePeerDependencies: + - supports-color + + '@stylexjs/rollup-plugin@0.19.0': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + '@stylexjs/babel-plugin': 0.19.0 + lightningcss: 1.32.0 + transitivePeerDependencies: + - supports-color + + '@stylexjs/shared@0.19.0': {} + + '@stylexjs/stylex@0.19.0': + dependencies: + css-mediaquery: 0.1.2 + invariant: 2.2.4 + styleq: 0.2.1 + '@svgr/babel-plugin-add-jsx-attribute@6.5.1(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -23539,7 +23630,7 @@ snapshots: ast-kit@3.0.0-beta.1: dependencies: - '@babel/parser': 8.0.0-rc.6 + '@babel/parser': 8.0.4 estree-walker: 3.0.3 pathe: 2.0.3 @@ -24753,6 +24844,8 @@ snapshots: crypto-random-string@2.0.0: {} + css-mediaquery@0.1.2: {} + css-select@4.3.0: dependencies: boolbase: 1.0.0 @@ -32406,6 +32499,8 @@ snapshots: postcss: 8.5.15 postcss-selector-parser: 7.1.1 + styleq@0.2.1: {} + stylis@4.2.0: {} sucrase@3.34.0: @@ -33507,7 +33602,7 @@ snapshots: optionalDependencies: vite: 7.3.5(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.20.6)(yaml@2.9.0) - vitest-axe@0.1.0(vitest@4.1.6(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(happy-dom@20.10.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.20.6)(yaml@2.9.0))): + vitest-axe@0.1.0(vitest@4.1.6(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(vitest@4.1.5))(happy-dom@20.10.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.20.6)(yaml@2.9.0))): dependencies: aria-query: 5.3.2 axe-core: 4.11.4 @@ -33515,7 +33610,7 @@ snapshots: dom-accessibility-api: 0.5.16 lodash-es: 4.18.1 redent: 3.0.0 - vitest: 4.1.6(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(happy-dom@20.10.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.20.6)(yaml@2.9.0)) + vitest: 4.1.6(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(vitest@4.1.5))(happy-dom@20.10.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.20.6)(yaml@2.9.0)) vitest-chrome@0.1.0: dependencies: @@ -33598,7 +33693,7 @@ snapshots: transitivePeerDependencies: - msw - vitest@4.1.6(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(happy-dom@20.10.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.20.6)(yaml@2.9.0)): + vitest@4.1.6(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(vitest@4.1.5))(happy-dom@20.10.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.20.6)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.6 '@vitest/mocker': 4.1.6(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.20.6)(yaml@2.9.0)) diff --git a/references/mosaic-stylex-migration.md b/references/mosaic-stylex-migration.md new file mode 100644 index 00000000000..eedb5250435 --- /dev/null +++ b/references/mosaic-stylex-migration.md @@ -0,0 +1,441 @@ +# Mosaic → StyleX migration: findings + POC + +Status: **spike / proof-of-concept complete.** This documents an evaluation of +migrating Mosaic off Emotion (runtime CSS-in-JS) onto +[StyleX](https://stylexjs.com) (compile-time atomic CSS), including a real, +build-verified POC in `packages/ui/src/mosaic-x/`. Nothing here is shipped; +Mosaic is still Emotion-based and greenfield/unused, so we can change the model +freely. + +## Context — why consider this + +Today Mosaic authors styles with Emotion. `useRecipe`/`useSlot` merge plain +style objects and Emotion serializes them to `