Skip to content

fix(extractor): preserve JavaScript semantics and eliminate redundant arrow wrappers - #756

Open
owjs3901 wants to merge 59 commits into
mainfrom
fix/js-semantics
Open

owjs3901 wants to merge 59 commits into
mainfrom
fix/js-semantics

Conversation

@owjs3901

@owjs3901 owjs3901 commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

JSX·styled 변환에서 불필요한 arrow와 정적 스타일 선택의 중간 맵·배열을 제거하고, JavaScript의 평가 순서와 단일 평가를 보존합니다. #791·#806을 포함한 main을 반영했습니다.

정적 스타일 선택은 아래처럼 직접 클래스 맵을 조회합니다. 선택 키가 고정되어 있으면 최종 클래스 문자열 하나로 접습니다.

<span className={{
  lg: "typo-buttonLg",
  md: "typo-button",
  sm: "typo-buttonSm",
  tag: "typo-tag"
}[size] || ""}>{children}</span>
// 고정된 선택: <div className={"a b"} />
  • 정적 클래스만 선택하는 경우 태그용 배열과 이중 조회를 제거합니다. apply_var_typography-5/6, 정적·반응형 객체 선택 등 11개 스냅샷을 단순화했으며 CSS·클래스·반응형 단계 메타데이터는 동일합니다. 태그용 배열 26개를 줄였습니다.
  • spread가 포함된 배열에서도 알려진 숫자·문자열은 정적 클래스로 생성합니다. 배열·iterator·키 조회는 원본 식으로 한 번만 실행하며, 선택값을 강제 변환 없이 비교하고 나머지는 CSS 변수로 처리합니다. 관련 5개 스냅샷에서 정적 클래스를 복구합니다. 매개변수 기본값·클래스 필드·직접 eval에 노출된 스코프에는 최적화를 위한 arrow를 만들지 않습니다.
  • 한 번 쓰는 캡처는 원래 식으로 출력하고, 재사용하거나 뒤쪽 prop보다 먼저 평가해야 하는 값은 기존 실행 스코프의 지역 변수로 보존합니다. 동적 분기 값 전체의 평가, getter·키 변환의 호출 횟수와 예외 시점을 유지합니다. 배열의 가변 키·상속 속성처럼 단순화의 안전성을 증명할 수 없는 경우는 기존 처리를 유지합니다.
  • JSX와 jsx·jsxs·jsxDEV·createElement의 평가 순서, unknown spread, className·style 우선순위를 보존합니다. 객체·배열에서 반응형 값을 선택할 때 없는 키를 읽어도 예외가 나지 않도록 optional chaining의 AST를 유지합니다.
  • styled 렌더·attrs·DOM 속성 처리의 중간 함수를 렌더 함수의 지역 변수와 블록으로 합칩니다. getter·iterator, 메서드 this, 직접·간접 eval, 함수 이름, 매개변수 기본값, strict 모드, 지연 실행 참조를 실행 테스트로 검증합니다.
  • Oxc 0.156.0·wasm-bindgen 0.2.130·TypeScript 7.0.2와 TypeScript 6·React 18 호환성 검사를 포함합니다. Binaryen의 제어 흐름 정규화·전역 이름 분석·레지스터 최적화를 적용합니다. 크기와 커버리지 기준은 유지합니다.

790개 스냅샷의 생성 코드 AST를 모두 검사했습니다. 파싱 오류와 누락은 0개이며, 실제 렌더·콜백 arrow 723개와 동적 styled 대상의 인스턴스별 보존에 필요한 즉시 호출 4개가 남습니다. 새 arrow는 추가하지 않았습니다. 이번 정적 선택 단순화에는 정상·없는 키, null·undefined·Symbol, 중복 키·배열 구멍, 키 변환·예외·상속 조회와 평가 순서를 확인하는 회귀 테스트 71개와 spread 선택 실행 테스트 30개를 추가했습니다.

검증 커밋: 41f90ea7a7ddcb788f6aa09f77566fba7d7db11a
검증 소스 트리: 48275cb127ac45ffedc33b8cbec9ba7f5b20d246

정상 커밋 훅에서 포맷, 모든 target·feature의 Clippy(-D warnings), ESLint와 전체 테스트를 실행했습니다.

로컬 검증 결과
Rust 전체 13,387개 통과 / 실패·무시 0
extractor 유닛 12,033개 통과 / 실패·무시·필터링 0
Bun 6,779개 통과 / 함수·라인 커버리지 100%
Vite 872개 통과 / 함수·라인 커버리지 100%
정식 WASM 빌드·실제 WASM 선택값 실행 20건 통과

크기는 CI와 동일하게 gzip 레벨 9로 측정했습니다.

로컬 크기 검증 실제 / 기준 (bytes)
wasm 6,654,909 / 6,738,944
wasm gzip 2,431,393 / 2,432,000
glue 40,482 / 45,056
glue gzip 5,616 / 8,192

최신 커밋의 CI에서 8개 검증 모두 통과했습니다: benchmark, wasm-size, landing-next-e2e, react-18, vinext-rsc-css-e2e, webpack-extends-theme, publish, release-compat.

Rust 커버리지는 **100.00% (25148/25148 lines)**입니다. CI 크기 보고서의 소스 트리도 검증 커밋과 일치합니다.

CI 크기 검증 실제 / 기준 (bytes) 결과
wasm raw 6,652,193 / 6,738,944 통과
wasm gzip 2,431,426 / 2,432,000 통과
glue raw 40,482 / 45,056 통과
glue gzip 5,616 / 8,192 통과

owjs3901 and others added 11 commits October 5, 2026 00:13
Refs #695

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #695

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #695

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #695

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #695

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #695

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #695

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #695

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #695

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #695

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #695

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@github-actions

github-actions Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Changepacks

@devup-ui/wasm@1.0.83 → 1.0.84 - bindings/devup-ui-wasm/package.json

Patch

  • A project module re-exporting Devup UI (export { Box, css } from '@devup-ui/react', export * from '@devup-ui/react', export { styled as s }, export * as Devup, a default export of an import, chains of barrels and the compat entry) now works: a file importing Box or css from it compiles exactly as if it imported from the package, following the barrel through the resolver (relative paths, tsconfig paths, package exports), and hasDevupUI sees such a file. Members read from a namespace or default import of the package (Devup['css'], const { css } = Devup, Devup.css as a value) compile where the build can follow them, Devup.x for a name the package compiles being read like the named import. New build errors, located at the use: what a barrel re-exports but the build cannot read (an unresolvable export * or module, a namespace of modules re-exporting the package) is an error where its name is used instead of a runtime crash, and a namespace member read by a key the build cannot know (Devup[key]), written, or destructured in a form it cannot follow (a rest, a computed key, a default value, a nested pattern, an exported declaration, or members mixed with ones that stay at runtime) is an error that names the members to read. A namespace passed on whole (Object.keys(Devup), spyOn(Devup, 'css'), export default Devup) is left as it is
  • css(a, b) composing classes whose styles the build knows (a const bound to css() in the file, or a css() result another module exports with a rule object every value of which is known) merges their styles: a later part's declaration replaces an earlier one's for the same property, selector, breakpoint and layer, also under conditions (css(base, cond && danger)) and for ||/?? parts, where the classes used to be joined and the stylesheet order picked the winner. vanilla-extract style([a, b]) passes each composed style as its own argument, so the later one wins, and a style composed again later (style([a, b, a])) is kept. Classes the build does not know (strings, props, CSS Modules) are kept as they are
  • styled(Base) extending a styled component the file binds to a const renders Base's tag directly with Base's styles composed under its own, so the extension's declarations replace Base's for the same property, selector, breakpoint and layer instead of losing to them by stylesheet order; Base's attrs apply before the extension's. attrs merge as styled-components merges them: className is joined with the caller's, style is merged and other props replace earlier ones. On an element, a className or style spread after the explicit prop now wins, as React merges props in the order they are written
  • Emotion's now compiles at build time with no runtime: the element becomes what its child function ({ css, cx, theme }) => ... renders, each css and cx call becomes the classes it composes (later styles replace earlier ones; css reads strings as CSS text and its tagged templates split around mixins; cx keeps unknown classes, reads { name: condition } objects as conditional classes and composes nested css and cx calls), and theme.a.b in their values becomes var(--a-b). Numbers in css rules are px lengths, as Emotion reads them, and top-level constants are inlined. New build errors, each at file:line:col: a without exactly one child function taking { css, cx, theme } that gives what it renders at once (attributes, other children, rest or default parameters, async or generator functions, several statements); reading the theme other than as theme.a.b in a css value; using css or cx other than by calling them; a cx object with a spread, getter or method; parts css and cx cannot compose (runtime calls, spreads, a condition choosing between styles and classes). @devup-ui/react/compat exports ClassNames with the ClassNamesContent and ClassNamesArg types, and the @emotion/react declarations include them
  • Emotion and styled-components component selectors now compile at build time: a styled component the file defines that another style selects (${Child} { ... } in CSS text, [Child] or [&:hover ${Child}] as a rule key) gets a short marker class, which only such components get, and the selector reads it as .marker; a component extending it, or an element inlining it for its css prop, keeps the marker. Selectors written without & now nest under the component as stylis nests them (ul { ... } is & ul, a selector list nests each selector, :hover applies to the component itself) instead of becoming global rules, and rule-object keys starting with ., #, [, *, >, + or ~ are read as nested selectors instead of properties
  • Emotion's css prop compiles at build time while @emotion/react is aliased: on tags and Devup UI components always, and on every element once the file imports @emotion/react or @emotion/styled or names @emotion/react in a @jsxImportSource pragma; jsx, jsxs and jsxDEV from @emotion/react/jsx-runtime (or jsx-dev-runtime) and jsx from @emotion/react compile it too, so libraries built with Emotion's JSX runtime do as well. The prop becomes the element's className, with CSS variables in its style for values only the runtime gives: arrays and conditions compose with a later part replacing what an earlier one sets, css() classes the file knows compose by their styles, strings and templates are CSS text, css`` mixins split the text around them, a function of the theme reads theme.a.b as var(--a-b), unitless numbers are px as in Emotion (constants included), and a className holding known css() classes overrides the prop as Emotion's registered classes do. A styled component the file defines renders its tag in the element's place when the prop overrides its styles and it renders a tag with no attrs or props read and the element has no spread, as or forwardedAs. Emotion's JSX runtime imports become react/jsx-runtime, the pragma names react, and jsx comes from @devup-ui/react/compat (React's createElement); @devup-ui/react/compat/css-prop types the prop on React.Attributes. New build errors, each with file:line:column and the code: a css prop part the build cannot read (a call, an element), a style object declared inside a function or with let, a binding only running the module gives or code changes, a theme function that does more than return rules or reads the theme other than as theme.a.b in a value, an interpolation CSS text cannot place or a mixin inside a nested rule, and a css prop overriding a styled component's styles where its tag cannot be rendered in place. Composing css() with a part that reads a known binding (such as a keyframes name) now reads its value
  • A project whose tsconfig.json (or jsconfig.json) builds JSX with Emotion (compilerOptions.jsxImportSource of @emotion/react, through extends and project references too) now compiles the css prop on every element of every file, and its .tsx and .jsx files are built with React's JSX runtime through a /** @jsxImportSource react */ pragma, which a file's own JSX pragma overrides, so the bundler no longer imports Emotion's runtime. The plugins read the setting into a new @emotion/react/jsx-runtime alias (importAliases can turn it off with false, and it is left out when @emotion/react is not aliased); @devup-ui/react/compat/emotion-jsx-runtime declares Emotion's JSX runtimes as React's so the project type-checks without Emotion installed. plugin-utils exports readJsxImportSource, and mergeImportAliases takes the JSX import source
  • style-order-range only checks styleOrder where the build reads it: a prop of a Devup UI component and a top-level key of a style object passed to css/globalCss/keyframes. A styleOrder key in nested data, in a prop value such as an element passed as a prop, or in a call of a function sharing a name with an Object.prototype member is no longer reported. The array, typography and media rules already skip non-style data, and css-utils-literal-only accepts a css()/keyframes() result held in a const inside a function. No new build errors.
  • hasDevupUI takes the import aliases and is true for a file extraction changes: one importing a redirected package (@emotion/styled, ...), naming @emotion/react in a @jsxImportSource pragma, or in a project whose tsconfig jsxImportSource is @emotion/react, so the Bun plugin no longer skips files whose only Devup UI is Emotion's css prop or an aliased import. Aliases of Devup UI now compile like the original: export const B = Box, const c = css; export { c as cc }, export const D = Devup and export const C = Devup.css in a barrel (the barrel module exports them as re-exports of the package, and what imports them follows them), export { Box } after an import, export default Box, const D = Devup namespace aliases, and const inner = css declared inside a function. A css() or keyframes() const that a function reads before its declaration compiles. New build error: a css()/keyframes() const read before its declaration runs (outside a function) is located and says to move the declaration above its first read
  • Preserve JavaScript semantics for exact imported enum and Math values including negative zero, lexical constants, binding-aware CommonJS require, React createElement calls, type-only imports, and source-ordered props evaluation with native spread snapshots and CSS-variable fallbacks without a styling runtime. Migrate UI-kit style defaults by pure reordering and test reset CSS through compiled output. Report located build errors for runtime styleOrder values, unknowable whole-object style overrides, opaque selector literals, genuinely unmovable lazy logical suspension, Devup UI namespaces passed whole to runtime code, and immediate style reads in the temporal dead zone of lexical bindings; deferred initialized reads keep folding and pre-assignment var reads remain dynamic.
  • Resolve compiled styling APIs and components by their lexical binding so shadowed local names, including minified library identifiers, remain ordinary JavaScript and JSX. No new build errors; existing diagnostics now apply only to the compiled binding they describe.
  • Styled components the build generates are wrapped in React's forwardRef, so a ref passed to them reaches the element they render on React 18 as well as React 19; the file imports forwardRef from react when it defines one
  • Styled components no longer pass every prop to the tag they render: $ props, theme and the props their style functions or attrs read are kept away from a tag unless it takes them as attributes, and shouldForwardProp (Emotion options or styled-components withConfig) is evaluated at build time. A shouldForwardProp the build cannot evaluate is a build error naming the file, line and code, with the forms it accepts. An element using a styled component the file defines drops props the component neither reads nor passes on; spread props are passed as written
  • Styled components render what their as prop names, defaulting to the tag or component they were defined with, and pass forwardedAs on as as, as styled-components and Emotion do; as used to reach the rendered tag as an attribute. Component.withComponent(target) on a styled component the file binds to a const builds a component rendering the same styles and attrs as target (a tag name or a component JSX can name), where it used to call a method the generated function does not have and throw
  • Tailwind classes in a className: each class compiles on its own and every class Tailwind does not define (custom, plugin and analytics classes) stays as written, where any recognized class used to rebuild the whole className without them and could rewrite part of another class; a class touching a template interpolation (icon-${name}) is left as written, and template classNames no longer get spaces inserted around interpolations. A class whose variant is not understood stays as written instead of applying unconditionally; variants follow Tailwind v4: stacked variants compose (hover:focus: matches the element itself), hover is guarded by @media (hover: hover), group-/peer- use the group/peer marker classes (and group-hover/name), and data-, aria-, has-, nth-, , **, supports-[…] and [&…]/[@media …] variants compile. Utilities emit all their declarations (size-, rounded-t-, truncate, sr-only, not-sr-only, text-sm with its line height, text-sm/6, leading-), translate/rotate/scale/skew compose through Tailwind's individual properties and registered variables (@Property) with negative values kept negative, and arbitrary values follow Tailwind's grammar: spaces for underscores except in url() and var() names, _ for an underscore, spaces around math operators, type hints, [property:value] and (--variable) shorthands. Words that only share a utility's prefix (select-wrapper, order-summary, cursor-foo) are no longer compiled

@devup-ui/bun-plugin@1.0.22 → 1.0.23 - packages/bun-plugin/package.json

Patch

  • Bun plugin works with Bun.build: DevupUI() (from @devup-ui/bun-plugin/register) serves the generated stylesheet to the bundler's CSS loader once every other module is loaded, so the build emits a CSS output with the styles of the whole bundle instead of an empty module; class names are short there and readable under the runtime (debug option). Files importing the packages Devup UI takes the place of (@emotion/react, @emotion/styled, styled-components, @vanilla-extract/css), @stylexjs/stylex, or a subpath or re-export of these or @devup-ui/react, are compiled instead of skipped
  • A project whose tsconfig.json (or jsconfig.json) builds JSX with Emotion (compilerOptions.jsxImportSource of @emotion/react, through extends and project references too) now compiles the css prop on every element of every file, and its .tsx and .jsx files are built with React's JSX runtime through a /** @jsxImportSource react */ pragma, which a file's own JSX pragma overrides, so the bundler no longer imports Emotion's runtime. The plugins read the setting into a new @emotion/react/jsx-runtime alias (importAliases can turn it off with false, and it is left out when @emotion/react is not aliased); @devup-ui/react/compat/emotion-jsx-runtime declares Emotion's JSX runtimes as React's so the project type-checks without Emotion installed. plugin-utils exports readJsxImportSource, and mergeImportAliases takes the JSX import source
  • hasDevupUI takes the import aliases and is true for a file extraction changes: one importing a redirected package (@emotion/styled, ...), naming @emotion/react in a @jsxImportSource pragma, or in a project whose tsconfig jsxImportSource is @emotion/react, so the Bun plugin no longer skips files whose only Devup UI is Emotion's css prop or an aliased import. Aliases of Devup UI now compile like the original: export const B = Box, const c = css; export { c as cc }, export const D = Devup and export const C = Devup.css in a barrel (the barrel module exports them as re-exports of the package, and what imports them follows them), export { Box } after an import, export default Box, const D = Devup namespace aliases, and const inner = css declared inside a function. A css() or keyframes() const that a function reads before its declaration compiles. New build error: a css()/keyframes() const read before its declaration runs (outside a function) is located and says to move the declaration above its first read

@devup-ui/components@0.1.60 → 0.1.61 - packages/components/package.json

Patch

  • Preserve JavaScript semantics for exact imported enum and Math values including negative zero, lexical constants, binding-aware CommonJS require, React createElement calls, type-only imports, and source-ordered props evaluation with native spread snapshots and CSS-variable fallbacks without a styling runtime. Migrate UI-kit style defaults by pure reordering and test reset CSS through compiled output. Report located build errors for runtime styleOrder values, unknowable whole-object style overrides, opaque selector literals, genuinely unmovable lazy logical suspension, Devup UI namespaces passed whole to runtime code, and immediate style reads in the temporal dead zone of lexical bindings; deferred initialized reads keep folding and pre-assignment var reads remain dynamic.

@devup-ui/eslint-plugin@1.0.22 → 1.0.23 - packages/eslint-plugin/package.json

Patch

  • style-order-range only checks styleOrder where the build reads it: a prop of a Devup UI component and a top-level key of a style object passed to css/globalCss/keyframes. A styleOrder key in nested data, in a prop value such as an element passed as a prop, or in a call of a function sharing a name with an Object.prototype member is no longer reported. The array, typography and media rules already skip non-style data, and css-utils-literal-only accepts a css()/keyframes() result held in a const inside a function. No new build errors.

@devup-ui/next-plugin@1.0.90 → 1.0.91 - packages/next-plugin/package.json

Patch

  • A project whose tsconfig.json (or jsconfig.json) builds JSX with Emotion (compilerOptions.jsxImportSource of @emotion/react, through extends and project references too) now compiles the css prop on every element of every file, and its .tsx and .jsx files are built with React's JSX runtime through a /** @jsxImportSource react */ pragma, which a file's own JSX pragma overrides, so the bundler no longer imports Emotion's runtime. The plugins read the setting into a new @emotion/react/jsx-runtime alias (importAliases can turn it off with false, and it is left out when @emotion/react is not aliased); @devup-ui/react/compat/emotion-jsx-runtime declares Emotion's JSX runtimes as React's so the project type-checks without Emotion installed. plugin-utils exports readJsxImportSource, and mergeImportAliases takes the JSX import source

@devup-ui/plugin-utils@1.0.17 → 1.0.18 - packages/plugin-utils/package.json

Patch

  • Webpack and Rsbuild builds hold every style in their stylesheets: a stylesheet module is built on its first import, so styles extracted later (the global styles of files webpack or Rsbuild reached late, often outside src) were missing from the output. The files the entries reach under src are now extracted before the build, the shared base on disk is kept current, and when a stylesheet still changed during the build it is compiled once more, writing only the complete result. Rsbuild serves each stylesheet from the current sheet instead of a module-wide string kept only when it grew, which left edits that shortened the CSS and later builds in the same process stale. plugin-utils adds computeReachableFiles
  • A project whose tsconfig.json (or jsconfig.json) builds JSX with Emotion (compilerOptions.jsxImportSource of @emotion/react, through extends and project references too) now compiles the css prop on every element of every file, and its .tsx and .jsx files are built with React's JSX runtime through a /** @jsxImportSource react */ pragma, which a file's own JSX pragma overrides, so the bundler no longer imports Emotion's runtime. The plugins read the setting into a new @emotion/react/jsx-runtime alias (importAliases can turn it off with false, and it is left out when @emotion/react is not aliased); @devup-ui/react/compat/emotion-jsx-runtime declares Emotion's JSX runtimes as React's so the project type-checks without Emotion installed. plugin-utils exports readJsxImportSource, and mergeImportAliases takes the JSX import source

@devup-ui/react@1.0.44 → 1.0.45 - packages/react/package.json

Patch

  • Emotion's now compiles at build time with no runtime: the element becomes what its child function ({ css, cx, theme }) => ... renders, each css and cx call becomes the classes it composes (later styles replace earlier ones; css reads strings as CSS text and its tagged templates split around mixins; cx keeps unknown classes, reads { name: condition } objects as conditional classes and composes nested css and cx calls), and theme.a.b in their values becomes var(--a-b). Numbers in css rules are px lengths, as Emotion reads them, and top-level constants are inlined. New build errors, each at file:line:col: a without exactly one child function taking { css, cx, theme } that gives what it renders at once (attributes, other children, rest or default parameters, async or generator functions, several statements); reading the theme other than as theme.a.b in a css value; using css or cx other than by calling them; a cx object with a spread, getter or method; parts css and cx cannot compose (runtime calls, spreads, a condition choosing between styles and classes). @devup-ui/react/compat exports ClassNames with the ClassNamesContent and ClassNamesArg types, and the @emotion/react declarations include them
  • Emotion's css prop compiles at build time while @emotion/react is aliased: on tags and Devup UI components always, and on every element once the file imports @emotion/react or @emotion/styled or names @emotion/react in a @jsxImportSource pragma; jsx, jsxs and jsxDEV from @emotion/react/jsx-runtime (or jsx-dev-runtime) and jsx from @emotion/react compile it too, so libraries built with Emotion's JSX runtime do as well. The prop becomes the element's className, with CSS variables in its style for values only the runtime gives: arrays and conditions compose with a later part replacing what an earlier one sets, css() classes the file knows compose by their styles, strings and templates are CSS text, css`` mixins split the text around them, a function of the theme reads theme.a.b as var(--a-b), unitless numbers are px as in Emotion (constants included), and a className holding known css() classes overrides the prop as Emotion's registered classes do. A styled component the file defines renders its tag in the element's place when the prop overrides its styles and it renders a tag with no attrs or props read and the element has no spread, as or forwardedAs. Emotion's JSX runtime imports become react/jsx-runtime, the pragma names react, and jsx comes from @devup-ui/react/compat (React's createElement); @devup-ui/react/compat/css-prop types the prop on React.Attributes. New build errors, each with file:line:column and the code: a css prop part the build cannot read (a call, an element), a style object declared inside a function or with let, a binding only running the module gives or code changes, a theme function that does more than return rules or reads the theme other than as theme.a.b in a value, an interpolation CSS text cannot place or a mixin inside a nested rule, and a css prop overriding a styled component's styles where its tag cannot be rendered in place. Composing css() with a part that reads a known binding (such as a keyframes name) now reads its value
  • A project whose tsconfig.json (or jsconfig.json) builds JSX with Emotion (compilerOptions.jsxImportSource of @emotion/react, through extends and project references too) now compiles the css prop on every element of every file, and its .tsx and .jsx files are built with React's JSX runtime through a /** @jsxImportSource react */ pragma, which a file's own JSX pragma overrides, so the bundler no longer imports Emotion's runtime. The plugins read the setting into a new @emotion/react/jsx-runtime alias (importAliases can turn it off with false, and it is left out when @emotion/react is not aliased); @devup-ui/react/compat/emotion-jsx-runtime declares Emotion's JSX runtimes as React's so the project type-checks without Emotion installed. plugin-utils exports readJsxImportSource, and mergeImportAliases takes the JSX import source

@devup-ui/reset-css@1.0.31 → 1.0.32 - packages/reset-css/package.json

Patch

  • Preserve JavaScript semantics for exact imported enum and Math values including negative zero, lexical constants, binding-aware CommonJS require, React createElement calls, type-only imports, and source-ordered props evaluation with native spread snapshots and CSS-variable fallbacks without a styling runtime. Migrate UI-kit style defaults by pure reordering and test reset CSS through compiled output. Report located build errors for runtime styleOrder values, unknowable whole-object style overrides, opaque selector literals, genuinely unmovable lazy logical suspension, Devup UI namespaces passed whole to runtime code, and immediate style reads in the temporal dead zone of lexical bindings; deferred initialized reads keep folding and pre-assignment var reads remain dynamic.

@devup-ui/rsbuild-plugin@1.0.67 → 1.0.68 - packages/rsbuild-plugin/package.json

Patch

  • Webpack and Rsbuild builds hold every style in their stylesheets: a stylesheet module is built on its first import, so styles extracted later (the global styles of files webpack or Rsbuild reached late, often outside src) were missing from the output. The files the entries reach under src are now extracted before the build, the shared base on disk is kept current, and when a stylesheet still changed during the build it is compiled once more, writing only the complete result. Rsbuild serves each stylesheet from the current sheet instead of a module-wide string kept only when it grew, which left edits that shortened the CSS and later builds in the same process stale. plugin-utils adds computeReachableFiles
  • A project whose tsconfig.json (or jsconfig.json) builds JSX with Emotion (compilerOptions.jsxImportSource of @emotion/react, through extends and project references too) now compiles the css prop on every element of every file, and its .tsx and .jsx files are built with React's JSX runtime through a /** @jsxImportSource react */ pragma, which a file's own JSX pragma overrides, so the bundler no longer imports Emotion's runtime. The plugins read the setting into a new @emotion/react/jsx-runtime alias (importAliases can turn it off with false, and it is left out when @emotion/react is not aliased); @devup-ui/react/compat/emotion-jsx-runtime declares Emotion's JSX runtimes as React's so the project type-checks without Emotion installed. plugin-utils exports readJsxImportSource, and mergeImportAliases takes the JSX import source

@devup-ui/vite-plugin@1.0.73 → 1.0.74 - packages/vite-plugin/package.json

Patch

  • A project whose tsconfig.json (or jsconfig.json) builds JSX with Emotion (compilerOptions.jsxImportSource of @emotion/react, through extends and project references too) now compiles the css prop on every element of every file, and its .tsx and .jsx files are built with React's JSX runtime through a /** @jsxImportSource react */ pragma, which a file's own JSX pragma overrides, so the bundler no longer imports Emotion's runtime. The plugins read the setting into a new @emotion/react/jsx-runtime alias (importAliases can turn it off with false, and it is left out when @emotion/react is not aliased); @devup-ui/react/compat/emotion-jsx-runtime declares Emotion's JSX runtimes as React's so the project type-checks without Emotion installed. plugin-utils exports readJsxImportSource, and mergeImportAliases takes the JSX import source

@devup-ui/webpack-plugin@1.0.71 → 1.0.72 - packages/webpack-plugin/package.json

Patch

  • Webpack and Rsbuild builds hold every style in their stylesheets: a stylesheet module is built on its first import, so styles extracted later (the global styles of files webpack or Rsbuild reached late, often outside src) were missing from the output. The files the entries reach under src are now extracted before the build, the shared base on disk is kept current, and when a stylesheet still changed during the build it is compiled once more, writing only the complete result. Rsbuild serves each stylesheet from the current sheet instead of a module-wide string kept only when it grew, which left edits that shortened the CSS and later builds in the same process stale. plugin-utils adds computeReachableFiles
  • A project whose tsconfig.json (or jsconfig.json) builds JSX with Emotion (compilerOptions.jsxImportSource of @emotion/react, through extends and project references too) now compiles the css prop on every element of every file, and its .tsx and .jsx files are built with React's JSX runtime through a /** @jsxImportSource react */ pragma, which a file's own JSX pragma overrides, so the bundler no longer imports Emotion's runtime. The plugins read the setting into a new @emotion/react/jsx-runtime alias (importAliases can turn it off with false, and it is left out when @emotion/react is not aliased); @devup-ui/react/compat/emotion-jsx-runtime declares Emotion's JSX runtimes as React's so the project type-checks without Emotion installed. plugin-utils exports readJsxImportSource, and mergeImportAliases takes the JSX import source

@codecov

codecov Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
libs/extractor/src/barrel.rs 100.00% <ø> (ø)
libs/extractor/src/barrel/aliases.rs 100.00% <100.00%> (ø)
libs/extractor/src/barrel/namespace_aliases.rs 100.00% <100.00%> (ø)
libs/extractor/src/bound_style_callbacks.rs 100.00% <ø> (ø)
libs/extractor/src/build_time_values.rs 100.00% <ø> (ø)
libs/extractor/src/build_time_values/exact_math.rs 100.00% <100.00%> (ø)
libs/extractor/src/composition.rs 100.00% <ø> (ø)
libs/extractor/src/css_boundary.rs 100.00% <100.00%> (ø)
libs/extractor/src/css_boundary_contract_tests.rs 100.00% <100.00%> (ø)
libs/extractor/src/css_prop.rs 100.00% <100.00%> (ø)
... and 41 more

... and 38 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Merge updated parent PR 750 (74afb76), containing main 11790be. Conflicts: libs/extractor/src/prop_modify_utils.rs and conditional Tailwind snapshot. Share main's per-class Tailwind compiler between normal generation and PR 756 class capture, retaining semantic IDs and single evaluation. Regenerate the conditional snapshot to combine main's extra line-height declaration with the captured controller; retain all assertions and update five exact template-code expectations for PR 756 single-coercion IIFEs. Full workspace tests, fresh WASM/root builds and real WASM probe pass; no snapshots were hand-merged.

Refs #686

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
owjs3901 added a commit that referenced this pull request Oct 8, 2026
No conflicted files; retain PR changes and merge updated parent #756, which contains main 11790be.
owjs3901 added a commit that referenced this pull request Oct 8, 2026
Merge #756 at f2fc451, retaining the
style-order metadata policy and forwarding/callback provenance from faff4a0.
Keep the current scope-aware style-order-range rule and clarify that nested
style metadata is checked while ordinary props and data are ignored.

Coordinator-approved integration reconciliation: the incoming
test_tailwind_keeps_other_classes cases 07, 08, 09, 14 and 15 retain the
current once-only __devupValue0 controller captures; case 10 folds the
truthy literal a without reading the right side of OR. See the source-fed
failure evidence D/W38g-756-test-conflict.md and successful executed controls
W38g-756-tailwind-qa3.log. Existing HEAD assertions and goldens are unchanged.

Reclassify exactly the two incoming ESLint fixtures containing nested zero
and a computed styleOrder zero from valid to invalid, retaining their source
and ordinary props/data controls. #766 reads metadata at those positions;
#756 alone predated it. Pin the 2+1 original diagnostic locations, as recorded
in W38g-756-eslint-focused.log. Rule production behavior is unchanged.

Fresh gates: Rust1.99 clippy, default workspace4119/0, rebuilt WASM/root,
Bun6472/0 aggregate100%, lint0errors/2baselinewarnings, original48probes with
only the incoming1.0.82-to1.0.83 banner delta, and named literal/wrapper QA.
The incoming guarded-hover/active cascade finding is reported separately,
not fixed by this merge, per coordinator attribution instructions.

Refs #684 #685

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
owjs3901 added a commit that referenced this pull request Oct 8, 2026
Merge #757 at 78ef660 after the genuine
#756 integration975f3371. Recursive merge bases74afb760 and6d0c9c83 produce
an identical tree3c14e7e427686d8802d1e0722afc33515d3eed28 with no conflicts
or source changes. Preserve both exact base histories and normal hook gates.

Keep the approved six incoming Tailwind expectation and two incoming ESLint
fixture reconciliations from the prior merge, current JSX capture/constant-fold
semantics, located metadata errors, and existing HEAD assertions/goldens.
F195 and the remaining coverage units are separate subsequent changes.

Refs #684 #685

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
owjs3901 added a commit that referenced this pull request Oct 8, 2026
Refs #690, #693

Merge exact f2fc451 retaining the current native/readback/provenance and incoming namespace/JS-semantics changes, without resetting merge state. Resolve five combined source/snapshot seams, use direct private tracking fields and current evaluate_located test API, and bind the incoming reachable-file helper to existing resolveSourceFile. Coordinator-approved merge-test alignments: lexical local require is not a module request; genuine unbound require keeps its original helper.ts:1:25 semantic site and unchanged cause/Fix/no-publication, while reference-free exploration falls back to entry.tsx:2:23 ingress; eight native consumer cases evaluate independent runtime sentinels, exact once-only getters/calls and wrong-slot/restoration instead of generated identifier spellings; only positive g uses valid styleOrder={undefined}, with the complete original invalid JSX retained as the located a.tsx:9:34 negative. All other fixtures and oracles remain unchanged; incoming capture/spread snapshot preserves the existing CJS_DYNAMIC alias. Full workspace4719, Rust1.99clippy, fresh WASM/root, Bun5535, preservation/API/carrier/graph and50both-mode browser positive/wrong-class/restoration gates passed. The prior advisory B Oracle result was lost to tool failure; coordinator authorized full-diff self-review and objective gates, not a claimed clean Oracle review. No contextual carrier/plugin delivery or out-of-scope Devup-only composition fix is included.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #682 #695 #688 #689

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@owjs3901 owjs3901 changed the title fix(extractor): preserve JavaScript semantics across values and element calls fix(extractor): preserve JavaScript semantics and eliminate redundant arrow wrappers Nov 7, 2026
owjs3901 and others added 19 commits November 7, 2026 19:45
The extraction engine is moving to napi, so the WASM size budget no longer
guards what ships. Its comment-stripping post-build step only hid
wasm-bindgen 0.2.130's own glue growth and pulled a JS install into the
WASM build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dcu across all manifests (apps/react-18 stays on React 18), cargo update,
oxc 0.158. oxc_sourcemap stays on 9 since oxc_codegen 0.158 still uses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`<Box {...{ animation: `${slide} 3s` }} />` compiled to a CSS variable
while the same attribute compiled to a static class, as it did on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
They only served the removed size budget and lengthen every WASM build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A statement whose value opens by assigning generated captures runs those
assignments first wherever it runs, so they become a `const` right before
it instead of a hoisted `let` plus a comma assignment. Captures in places
that may not run keep the shared `let`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Once its stages are inlined, a styled renderer now keeps one scope where no
name collides, declares constants where nothing reassigns, drops single-use
temporaries, and destructures the DOM props out of its fresh `rest` ahead of
the return instead of inside the JSX.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lations

A dynamic style whose capture the output no longer declares now records the
expression written in its place rather than the dropped name, and a value
interpolated in an untagged CSS template reaches its style variable as is,
as React already writes it as text, instead of through `${...}`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ues in

Captures no longer travel through `let` slots assigned in comma
sequences. StyleX condition results, unit conversions and element
captures are passed as arguments; a test that suspends is always an
argument so `yield`/`await` stay in the authored function.

A final pass turns a generated call in a statement's first-evaluated
position into `const` declarations before the statement, when no name
it declares is read elsewhere there, is a parameter, or could be seen by
direct eval. Renderer simplification now splits sequences in head
positions, folds single-write destructuring into `const`, runs to a
fixed point, and simplifies frames only after inlining. Omitted styled
props join the `$` filter instead of a destructuring record, and
mixins read from set constants need no capture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… before it

When `as` is the last prop that must be captured and names the element or
chooses between names, it stays where the element evaluates it first:
the props written before it are passed in, then the condition picks a
static element. `items.map` with `as={i.href ? 'a' : Link}` renders
`<a>`/`<Link>` again instead of a runtime `DevupAs`, reading `i.id`
before `i.href` as written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ting it

An element call whose tag is only known at runtime was rebuilt from a
copy taken before its props were visited, and the replacement waited
for the next call or element to finish. A call left in its props, such
as `id: tick('id')`, finished first and took it, leaving the outer call
reading `__devupFactory0` and the captures where none were bound.

The call is now rebuilt after it and the values it captured are
visited, so the copy carries what they compile to. With that fixed,
reads of `const` bindings already set where they are read count as
constant for evaluation order, as the order model always described.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ue branches

A generated declaration nothing reads, holding a value made only to be
named, goes. One that only names a parameter or an import nothing
writes or rebinds goes too, its reads reading that name instead, so
`<__devupRecord10 />` after `const __devupRecord10 = Dyn` is `<Dyn />`.
A generated arrow left only returning a value is written as it.

A branch packet `test ? [true, value] : [false]` whose value is never
`undefined` holds that value itself: which branch ran is whether it is
defined, or whether it is truthy where it always is when given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed values

A branch capture whose one branch gives a single value never
`undefined` and whose other gives none is now built as that value,
`test ? value : void 0`, where it is made, instead of being repacked
after the fact from the shape of its packet.

The build ran code for a style value even where the module handed the
binding it reads to unknown code, as long as the build could not read
that binding itself: `const made = make(); watch(made);` let
`css({ p: f() })` bake `made.p` in although `watch` may change it. What
such code reads is now checked where it runs: a value it hands on that
holds an object there computes nothing, leaving the error the build
gives for any value known only at runtime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…onJS exports in order

- A constant string or array has its length, and a string its UTF-16
  units, so `PRIMARY.length` is the number it is.
- A style prop of a component the module computes from constants with
  its own code, such as `color={darken(0.4, PRIMARY)}`, is computed like
  the arguments of `css()`. An element prop reads a hole or `undefined`
  as it is, so a value JSON would turn into `null` is left to runtime.
- A conditional whose branches each give the styles one whole value is
  captured as that one value, so `a === b ? `${a}` : `${a}`` no longer
  repacks a flag no class reads.
- A string a template literal holds as a value is written as its text,
  except in a tagged template, whose tag reads the parts as written.
- `CommonJS` exports are read as the top-level statements leave them in
  order: `module.exports = { A: 'a' }; module.exports = { A: 'b' }`
  exports `b`. Once `module.exports` holds another object, `exports.x`
  writes to the one no importer reads, which the build baked in before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ches only for its effects

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The build wrote checks whose answer it had just written, fallbacks never
reached and values wrapped to be unwrapped again. Each is now written as
the code it means, running the same steps in the same order:

- The styled renderer no longer checks the record it wrote itself:
  `[2] !== void 0` and `[1] === void 0` fold where the build knows the
  slot, and a composing component reads the same classes as its own.
- A pass after the IIFEs unwrap simplifies what remains: `(0, f)()` to
  `f()`, string-only `[...].filter(Boolean).join(" ")` to its text,
  `"a" || x` and `x ?? void 0` to their left side, conditions read only
  for whether they hold (`c ? true : false`, `c ? d : false`, a value
  guarded by its own guard), conditionals of a known or repeated test,
  templates held in templates, objects spread in place in objects and
  JSX, `?.` on a generated name holding an object, and leading values a
  sequence reads to drop.
- A generated constant declared as another is read as that one, found
  by scope, so components repeating the same names lose them too.
- A conditional whose branches are the same code keeps its condition
  only for its effects; the restore `branch_capture` did for it is gone.

Each rule keeps what reading changes: a member or name called through a
sequence keeps its `this` and indirect `eval`, a function keeps the name
it would infer, `typeof` is only dropped for declared names, and `key`
stays spread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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