Skip to content

fix(extractor): finish static composition and styled capture limits - #754

Merged
owjs3901 merged 21 commits into
mainfrom
fix/composition-rest
Oct 24, 2026
Merged

owjs3901 merged 21 commits into
mainfrom
fix/composition-rest

Conversation

@owjs3901

@owjs3901 owjs3901 commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Refs #688, #690, #695, #685 (정적 합성, styled 캡처와 CSS 레이어).

fix/composition-rest → main입니다. #736의 변경과 #745의 최종 head c3fe4647을 포함하며, #745 및 선행 의존성이 먼저 병합되어야 합니다. 두 번째 실제 병합 d6879f28의 부모는 2d51af46과 c3fe4647입니다. 최신 head는 51295a32542832d4a066d0bdb4cb8bd91717b5d3이며, 자동 병합하지 않습니다.

요약

  • namespace로 가져온 styled 정의를 선택자, 계산된 규칙 키, JSX/컴파일된 JSX의 css prop에서 실제 import 바인딩으로 읽습니다. 같은 이름의 지역 바인딩은 구분합니다.
  • 인라인 태그드 css, 정적으로 읽히는 배열 spread, 중첩 선택자·media·layer의 알려진 mixin, 전체 규칙을 고르는 props 콜백을 빌드 때 합성합니다.
  • 함수 안의 읽기 전용 객체/희소 배열과 const에 바인딩된 styled 콜백을 정적으로 추적합니다. 원래 매개변수·기본값·평가 순서·캡처·export/live binding과 이름 위생을 보존합니다. Emotion/styled-components의 px와 native의 간격 배율은 사용 위치별로 구분합니다.
  • CSS 텍스트의 이름 있는 @layer를 객체 규칙과 같은 레이어 문맥으로 처리합니다. 새 스타일링 런타임·의존성·커버리지 제외·예산 변경은 없습니다. 관리 패키지 changepack은 모두 Patch이며 루트 package.json은 대상이 아닙니다.
  • 이번 마무리 커밋은 루트 test script의 tarpaulin에 --timeout 180만 추가합니다. 제품 동작이나 테스트 단언은 바꾸지 않습니다.

두 부모 대비 제품 소스 규모

아래는 두 번째 병합 직후 d6879f28에 대한 보존된 Rust diff의 물리적 추가/삭제 줄 수입니다. 주석·빈 줄을 포함하고, 테스트 여부를 어휘적으로 분류하여 모호한 줄을 제품 측에 보수적으로 넣었습니다. 최종 head의 새 semantic LOC 측정이나 이번 timeout 커밋의 크기가 아닙니다.

비교 부모 제품/보수적 미분류 추가 / 삭제 테스트·벤치 추가 / 삭제
직전 #754 부모 2d51af46 3,202 / 1,338 5,175 / 29
갱신된 #745 부모 c3fe4647 13,869 / 1,280 7,024 / 185

이 수치를 최초 병합의 역사적 부모 bacd1351 / 58de8430 대비 수치와 혼동하지 않습니다. 이후 중립적 테스트 파일명 정리 f12842a7, Linux 미커버 보강 eab71094, 한 줄 timeout 변경 51295a32가 이어졌습니다. 상속된 전체 파일 수를 이 PR이 새로 작성한 제품 규모로 세지 않습니다.

동작

D1과 원시 방출의 경계

정적으로 아는 합성은 속성 + 선택자 + 브레이크포인트 + 레이어마다 뒤의 authored part가 이기는 D1입니다. css(a,b), 알려진 mixin, styled 상속/다중 파트와 정적 CSS prop 합성에 같은 의미를 적용합니다. props의 className, 외부 클래스처럼 런타임만 아는 합성은 기존 CSS 캐스케이드입니다.

추출의 원시 선언 뷰 R과 authored 의미 뷰 A를 구분합니다. 의미 합성은 A로 수행하고, 일반 클래스 합성과 합성 CSS prop은 최종 결과만 방출합니다. 원시 R 유지가 허용되는 것은 앞 선언 요약과 덮는 known className 선언이 없고, 정확히 하나의 스타일/규칙 파트이며, 문법 껍질을 벗긴 원본이 객체인 standalone CSS prop뿐입니다. 배열·조건·known mixin이 결과적으로 한 원자만 남는다고 standalone이 되지는 않습니다. 방출 권한과 무관하게 미해석 값/오류 검사는 R 전체에 적용하므로 패배한 선언의 오류도 숨기지 않습니다. 단독 콜백의 원래 숫자·shorthand·unitless·0·변수 계약과 count 3 단언을 유지합니다.

일반 styled 다중 파트도 같은 정규화 경계를 사용합니다. 승인된 진단 기대값 변경은 정확히 두 건입니다.

입력 순서 이전 선택 집합 → 승인된 집합 유효 승자
앞 padding 2 + 뒤 콜백 {padding:3,p:4} [12px,16px] → [16px] 16px 그대로
콜백 {padding:3,p:4} + 뒤 padding 2 [16px,8px] → [8px] 8px 그대로

이는 sheet 정렬의 우연이 아니라 D1 선택입니다. 단독 직접 콜백의 [12px,16px]와 원본 count 3 검증은 변경하지 않았습니다. 정확한 과거 main 78d7c2b2673bdeaa0870bac6a2ec424706986de7의 immutable archive 네이티브 프로브에서도 styled.div({padding:'8px'},{p:'4px'})가 두 클래스를 선택하고 CSS의 4px-before-8px 순서 때문에 8px가 이기는 같은 버그가 확인되었습니다. 수정 후 전체 생성 렌더러와 실제 StyleSheet QA는 양쪽 CSS 모드에서 4px만 선택하여 유효 4px를 확인했습니다. 이 기록은 당시의 네이티브 QA이지 새 브라우저 computed-style 측정이 아닙니다.

캡처·평가·레이어

  • 읽기 전용 const에 바인딩된 전체 규칙/일반 속성 콜백은 증명된 원본 함수 AST를 사용합니다. 데이터로 쓰이는 함수나 기본값을 전역 변환하지 않고, 원래 props 인자·this/arguments 의미·평가 횟수와 순서를 지킵니다. 알 수 없는 별칭은 값으로 남습니다.
  • 생성 renderer의 이름은 사용자 rest, style, className 및 가린 바인딩과 충돌하지 않습니다. 원래 semantic binding ID와 공개 export/live binding을 보존합니다. 나중에 호출되는 캡처와 즉시 실행의 TDZ를 구분합니다.
  • 알려진 mixin은 중첩 selector/media/layer 문맥을 유지합니다. 레이어 등록 순서는 최종 선택 전에 보존하고, escaped dot과 중첩 dot을 구분합니다. 레이어 순서 선언은 globalCss에 둡니다.
  • ||는 falsy, ??는 nullish, &&는 원래 JS 조건을 따릅니다. 전역 undefined/void 0/null과 가려진 undefined를 구별합니다. 숫자와 callback preparation은 호출 위치의 라이브러리 문맥을 사용합니다.
  • #745의 위치 진단·동적 준비와 #754의 authored-order projection을 함께 보존합니다. 전체 공개 SSR/RSC/slot/결정론 증거를 이번 한 줄 변경만으로 새로 측정했다고 주장하지 않습니다.

기존 공개 WASM/SSR 기록의 예로 namespace CSS prop은 <span title="child" class="padding-0-4px--255 color-0-blue--255 c-1-Child"></span>을 반환했고, Emotion 바인딩 콜백은 <div class="c-1-Choice padding-0-3px--255 line-height-0-2--255"></div>을 반환했습니다. 이는 당시 debug 이름의 실제 기록이며 현재 head의 클래스 문자열을 고정하는 계약이 아닙니다.

새로 생기는 오류

이번 timeout/커버리지 마무리로 새 오류 종류나 허용 정책을 추가하지 않습니다. 합성 기능이 새로 읽는 경로에서도 기존의 파일:줄:열, 문제 코드, 이유와 필요한 조건을 담는 located refusal 및 오류 우선순위를 유지합니다.

  • 컴포넌트의 익명 @layer, CSS-wide 예약어/잘못된 레이어 이름, 레이어 순서 문장은 거부합니다. 이름 있는 블록을 사용하고 순서는 globalCss에 선언해야 합니다. 예: @layer inherit는 유효한 dot-separated CSS identifier가 필요하다는 위치 있는 오류입니다.
  • 중첩 selector/media/layer에서 빌드가 읽을 수 없는 mixin, 알 수 없는 전체 규칙 구조/인자 spread, 변경되거나 스타일 밖으로 새는 지역 구조, 증명할 수 없는 바인딩 콜백과 즉시 실행 TDZ는 위치 있는 오류입니다. 원래 매개변수/기본값을 지워 억지로 허용하지 않습니다.
  • 가려진 undefined를 알 수 없는 전체 규칙으로 쓰거나 namespace styled의 CSS prop이 기존 합성 overlap 계약을 위반하면 원래 위치의 오류를 유지합니다. standalone CSS API의 런타임 값도 정적인 값인 척 실행하지 않습니다.
  • CSS 합성 API 안의 알 수 없는 spread와 JSX 요소의 알 수 없는 DOM pass-through spread를 혼동하지 않습니다. 이 PR이 unknown JSX spread의 키를 추측하여 스타일로 바꾸는 새 정책을 추가하지 않습니다.

남는 한계

  • 스타일링 패키지를 직접 import하지 않고 프로젝트 alias의 styled 컴포넌트만 가져오는 package-less imported-retarget 감지 누락은 별도 main 기반 PR #806이 수정하며 사용자 리뷰 대기입니다. #754로 포팅하지 않았습니다. 원래 두 역사적 부모 bacd1351 / 58de8430에서 import { Dyn } from '@/Base'; export const Retargeted = Dyn.withComponent('a')가 양쪽 CSS 모드 모두 원문 그대로, styles 0, dependencies [], css_file/map None임을 확인했습니다. resolver 호출은 각각 0회와 package-barrel 선행 조회 1회이며, 후자는 retarget 성공이 아닙니다. 기존 공개 전체 모듈 실행은 남은 호출의 TypeError를 기록했습니다.
  • fix(extractor): preserve falsy zero, scaled dynamic values and absence #772 소유인 native JSX의 동적 bare number/null 및 static false 한계는 그대로입니다. 보호된 원본 numeric 단언을 바꾸거나 이 PR에서 native JSX 정책을 확장하지 않았습니다.
  • 외부/불투명 클래스, 읽을 수 없는 모듈 정의, 임의 효과·변경 가능한 값, 런타임만 아는 규칙 키는 정적 D1을 보장할 수 없습니다. 값은 기존 CSS 변수 경로, 알 수 없는 규칙 구조는 located error, 외부 클래스는 CSS 캐스케이드를 따릅니다. 모듈 전체를 임의 실행하지 않습니다.
  • 커리하지 않은 styled(tag, styles)의 공개 객체 계약을 확장하지 않습니다. 내보낸 styled 마커의 기존 정책을 유지합니다.
  • 로컬 LSP는 미설치/기존 설치 거절로 사용할 수 없어 clean으로 주장하지 않습니다. 로컬 총할당량·최종 peak-performance와 별도의 최종 public-proof/formal-size 단위를 새로 실행했다는 주장도 하지 않습니다.

검증

최신 Linux CI: 40083409496, head 51295a32, 첫 시도 완료. publish, benchmark, landing-next-e2e, vinext-rsc-css-e2e, release-compat, react-18, webpack-extends-theme 모두 success입니다. PR 이벤트의 release/deploy-pages는 의도적으로 skipped이며, 실패는 아래 승인된 wasm-size뿐입니다. job/workflow 재실행은 없었습니다.

  • Linux LLVM tarpaulin --fail-under 100: 100.00%, 19,872 / 19,872 줄. extractor 8,098 passed / 0 failed, 74.40초에 완료했습니다. 추가 커버리지 수정 커밋은 필요하지 않았습니다.
  • Linux Bun 1.4.4: root 6,249 pass / 0 fail, Vite 867 pass / 0 fail, 각각 실제 집계 행 All files | 100.00 | 100.00 |(함수/줄). 별도 bun-plugin preload-race 10 pass / 0 fail. 로컬 Vite 868과 CI 867은 관측된 실행 개수이며 같은 수치라고 바꿔 쓰지 않습니다.
  • 사용자의 **2026-10-24 timeout 결정("늘려야지 뭐")**에 따라 기본 60초를 바이너리당 180초로 늘렸습니다(fix(extractor): locate diagnostics and enforce deterministic evaluation #757 241d7cfb와 동일). 이전 8,073개 extractor 테스트는 58.71초, 8,098개는 기존 run 40071121185의 두 시도에서 각각 약 60.02초에 timeout되어 커버리지를 측정하지 못했으며, 이번 Linux 실측 74.40초로 완료했습니다. 이는 빌드 성능 면제나 테스트 삭제가 아닙니다.
  • Windows tarpaulin 0.37.2 / llvm_profparser의 LLVM 23 +7 CounterPtr 결함 때문에 로컬 false-zero 카운터 증거를 Linux Rust100으로 대체하지 않으며 XML을 보정하지 않았습니다(이번 정상 훅 원본은 28,294/28,950 = 97.73%).
  • 앞선 eab71094의 두 proven-unreachable arm은 admission guard와 caller chain으로 도달 불가를 증명한 뒤 출력·오류·우선순위를 유지하는 최소 재구조화로 접었고, 양쪽 guard의 허용/거부 입력은 실제 테스트로 보강했습니다. 제외·panic·unreachable·expect·새 런타임 분기는 추가하지 않았습니다.
  • 이번 fresh private clone에서 bun install, WASM 포함 루트 build, verify:dist와 정상 전체 커밋 훅이 통과했습니다. Bun 1.4.4, Rust 1.99.1, jobs 2/default stack, 실행 전 메모리 7 GiB·C: 25 GiB 최소 가드를 적용했습니다. 훅 Rust 9,328 pass / 0 fail, root 6,249/0, Vite 868/0, 양쪽 Bun All files 함수/줄 100.00/100.00입니다. 최초 훅은 Windows CRLF lint 1건으로 테스트 전에 멈췄고 해당 JSON 줄 포맷만 고친 새 정상 시도가 통과했습니다. hook bypass는 없었습니다.
  • Linux 크기: wasm/raw 6,373,036 / base 6,096,656 / limit 6,158,336 (+4.533%), wasm/gzip 2,283,580 / base 2,202,045 / limit 2,224,128 (+3.703%) — 사용자의 2026-10-24 fix(extractor): finish static composition and styled capture limits #754 일회성 wasm-size 예외입니다. glue/raw 43,165 / 43,165, glue/gzip 7,327 / 7,327은 통과했습니다. 예산은 그대로이며 napi가 기본 엔진이 될 예정이라는 결정과 별개로 성능 면제는 없습니다.
  • 사용자의 마무리 요청에 따라 남은 로컬 public-proof·performance·formal-size 단위는 이번 CI의 e2e·benchmark·wasm-size job으로 대체했으며, CI 결과 이상의 로컬 증거를 주장하지 않습니다.

커밋 51295a32는 package.json 한 파일의 timeout 한 줄만 바꾸고 정상 푸시 한 번으로 전달했습니다. 기존 merge·coverage·중립적 이름 정리 이력은 그대로이며 force-push, amend, rebase, merge/승인/종료/draft 변경은 하지 않았습니다.

owjs3901 and others added 7 commits October 3, 2026 22:22
…ClassNames cx calls, read style functions of props

Refs #690, #695

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

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

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

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

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

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

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…ines

Refs #688, #689, #690, #695, #685

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

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

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

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

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

  • 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
  • Compose namespace-imported styled components, inline tagged CSS and exact CSS spreads with later styles winning, and compile conditional props rule objects and read-only function-local style shapes without a styling runtime. Local function captures keep lexical hygiene, so user names like rest, style or className never collide with generated code. Styled callbacks bound to read-only const arrow or function declarations compile like inline callbacks while keeping the original exports and live bindings. Emotion numbers on the left operand of OR and nullish whole-rule choices become px like the right operand. Preserve Emotion numeric units and captured values, read exact imported computations, and retain named CSS layer metadata through composition. Unknowable shapes and spreads keep located build errors, including an unknown whole-rule shape held by a local or shadowing binding named undefined; namespace css props report the same non-inlineable overlap error as named imports, and anonymous layers, invalid layer names, and component layer-order statements report located errors directing users to named layers and globalCss.
  • 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
  • Numbers in rules written against Emotion, styled-components or vanilla-extract now read as px lengths when a constant stands in for the literal, as they do for a literal written in place: css({ padding: gap }) with const gap = 8, styled.div({ margin: SIZE }), <Global styles={{ body: { margin: GAP } }} /> and the same with imported constants and constant style objects give 8px, while unitless properties keep bare numbers, 0 stays 0 and Devup shorthands such as p keep the spacing scale. css${mixin}; color: red; outside the css prop composes the mixin by styles, so a later declaration replaces the mixin's, and a css... template is a known style a later css() composes. Inline css() calls inside css() arguments (css(css({ ... }), { ... }), css(cond && css({ ... }), base)) are composed with the other parts, the later winning, instead of being compiled to a class of their own. No new build errors
  • 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.
  • A styled component another module defines now composes at build time exactly as one the file defines itself: styled(ImportedBase)(...), styled(ImportedBase).attrs(...), ImportedBase.withComponent(...) and the Emotion css prop on ImportedBase render the base's tag with the base's styles under their own (the later declaration wins per property, selector, breakpoint and layer), merge attrs in order and filter props by what the styles read and shouldForwardProp. The build reads the other module through the same module resolution imported constants use (relative paths, tsconfig paths, package exports, include libraries) and reports it as a dependency, so a change to it rebuilds the importing file. A definition the build cannot read exactly (a base only the runtime gives, an attrs or style expression reading a binding of the defining module, a module with a build error, an unresolvable module, an import cycle) stays a runtime component that wraps, as before. Every exported styled component now carries a marker class computed from its file and its export name alone, so ${ImportedChild} { ... } in CSS text and [ImportedChild] as a rule key select it from another file; the marker replaces the registry-ordered one, which two files could not compute alike. No new build errors: the css prop on an imported styled component now reports the same override error a local one does when its styles overlap the component's
  • A style object declared with const inside a function and passed to css(), styled(), globalCss(), keyframes(), a style prop or a spread (also through the Emotion, styled-components and vanilla-extract aliases) now compiles when every entry is written out and the object is only read as styles, instead of leaving the object where the class belongs; the build errors, at the binding, when the object is also read elsewhere (where code may change it), and at the call when it holds values only the runtime gives. Emotion's ClassNames cx keeps a function call's result as a class that stays as written, as @emotion/css cx does. css() and styled() compose an array of rule objects under a condition. A style function of the props (styled.div((props) => ({ ... })), and as a later argument) compiles: each value reading the props becomes a CSS variable the component sets (a number is a px length unless the property is unitless), theme reads (including theme.a[2]) become var(--a-2), and a condition chooses per value. New build errors: styled(...).attrs(...) chained after the styles (chain .attrs before them), a style function of the props that does not give one rule object at once with every entry written out, and a style object declared in a function that is also read elsewhere
  • 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

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

Patch

  • Auto-update: depends on '@devup-ui/react' via a local workspace dependency

@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

  • Auto-update: depends on '@devup-ui/react' via a local workspace dependency

@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/bound_callback_captures.rs 100.00% <100.00%> (ø)
libs/extractor/src/bound_callback_export_tests.rs 100.00% <100.00%> (ø)
libs/extractor/src/bound_callback_numeric_tests.rs 100.00% <100.00%> (ø)
libs/extractor/src/bound_callback_properties.rs 100.00% <100.00%> (ø)
libs/extractor/src/bound_style_callbacks.rs 100.00% <100.00%> (ø)
libs/extractor/src/bound_style_callbacks_tests.rs 100.00% <100.00%> (ø)
libs/extractor/src/capture_public_exports.rs 100.00% <100.00%> (ø)
libs/extractor/src/composition.rs 100.00% <100.00%> (ø)
libs/extractor/src/composition_keys.rs 100.00% <100.00%> (ø)
libs/extractor/src/composition_residual.rs 100.00% <100.00%> (ø)
... and 80 more
🚀 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.

owjs3901 and others added 14 commits October 8, 2026 18:05
No conflicted files; retain PR changes and merge updated parent #740, which contains main 11790be.
No conflicted files; retain PR changes and merge updated parent #745, which contains main 11790be.
No conflicted files; retain PR changes and merge updated parent #736, which contains main 11790be.
Preserve the updated parent guards and local-style features while capturing
conditional rule controllers once and retaining static theme path checks.

Refs #707

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

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

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…e objects

Refs #688

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@owjs3901
owjs3901 merged commit 697aea7 into main Oct 24, 2026
10 of 11 checks passed
@owjs3901
owjs3901 deleted the fix/composition-rest branch October 24, 2026 11:47
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.

2 participants