Skip to content

fix(extractor): compile factory dynamic utility references - #804

Merged
owjs3901 merged 4 commits into
mainfrom
fix/factory-call-references
Oct 23, 2026
Merged

owjs3901 merged 4 commits into
mainfrom
fix/factory-call-references

Conversation

@owjs3901

@owjs3901 owjs3901 commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Refs #791 (#803 위에 쌓은 PR입니다. #803이 먼저 병합되어야 합니다.)

요약

JSX factory의 동적 스타일 값을 문자열로 직렬화하기 전에 원래 AST의 유틸리티 호출을 빌드 타임에 컴파일합니다. 기존 main에서 발생하던 네 가지 non-class 동적 스타일 ReferenceError를 해결하며, import 제거 정책이나 lexical binding guard를 완화하지 않습니다.

변경한 production은 기존 Rust 세 파일의 추가+삭제 158줄 / 승인 한도 200줄입니다. build-time generic 준비 훅을 기존 재귀 경로에 전달할 뿐, emitted runtime callable/helper/import, dependency, evaluator, style schema/variant, 전체 프로그램 pass는 추가하지 않았습니다.

동작

  • 기존 분류가 CSS variable 경로를 선택한 뒤에만 원래 값 AST를 준비합니다. 따라서 컴파일된 css 클래스 문자열도 여전히 element의 동적 값에 보관되며, 바깥 속성이 새 static 선언으로 바뀌지 않습니다.
  • 원래 네 행의 여덟 인자, 소스, 파일명과 실행식은 그대로입니다. utility global을 주입하지 않고 실제 생성 코드를 실행하며, 보관된 값이 실제 serialized overflow:hidden 규칙을 가리키는지 확인했습니다. 재현의 other는 임의 진단용 속성이므로 바깥 element에 overflow가 적용됐다고 주장하지 않습니다.
  • named/namespace shadow는 로컬 함수로 남고 한 번 실행됩니다. touched selector/member/wrapper/spread/key 복사만 semantic IDs를 보존합니다. mx의 앞선 sibling만 복사하여 분류를 유지하며, 단일 속성 경로에는 새 복사가 없습니다.
  • factory만 기존 visitor를 사용합니다. JSX, css/styled와 이미 방문한 default caller는 no-op 경로를 유지합니다. 별도 sequence serializer도 같은 준비를 거칩니다.
  • bare Identifier는 이 visitor에서 변화와 상태 효과가 없음을 전체 소스로 증명한 뒤 factory callback에서만 건너뜁니다. 복합 식/호출/template/member는 기존 visitor로 처리합니다.
  • 기존 짧은 counter/class/CSS-variable 이름, prefix/reset 결정론, selector cascade 순서는 유지합니다. 누락됐던 내부 CSS가 생성되므로 고장 난 출력과 클래스 라벨이 같다고 약속하지는 않습니다.
원래 행 모드 수정 전 실제 실행 수정 후 실제 실행
12 named / per-file ReferenceError: i is not defined PASS: held 값과 overflow:hidden 규칙 연결
32 namespace / per-file ReferenceError: ui$css is not defined PASS: held 값과 overflow:hidden 규칙 연결
89 named / single CSS ReferenceError: i is not defined PASS: held 값과 overflow:hidden 규칙 연결
109 namespace / single CSS ReferenceError: ui$css is not defined PASS: held 값과 overflow:hidden 규칙 연결
행 12: named / per-file 실제 WASM 출력 전/후

파일: /src/W70h-seam-spread-with-call-alias.js, singleCss=false, 마지막 설정 인자={}. 실행식은 render(false, {}, () => ({}), undefined)입니다.

입력

import { Box as t, css as i } from '@devup-ui/react'; import { jsx as o } from 'react/jsx-runtime'; function render(h, p, getProps, clsx) { return o(t, { ...{ className: 'external', other: i({ overflow: 'hidden' }) } }); }

수정 전 생성 코드

import "@devup-ui/react/devup-ui-0.css";
import { jsx as o } from "react/jsx-runtime";
function render(h, p, getProps, clsx) {
	return o("div", {
		className: "external a-a",
		style: { "--a": i({overflow:`hidden`}) }
	});
}

수정 전 실제 CSS

/*! devup-ui v1.0.83, | Apache License 2.0 | https://devup-ui.com *//*! devup-ui v1.0.83, | Apache License 2.0 | https://devup-ui.com */.a-a{other:var(--a)}

실제 오류: ReferenceError: i is not defined

수정 후 생성 코드

import "@devup-ui/react/devup-ui-0.css";
import { jsx as o } from "react/jsx-runtime";
function render(h, p, getProps, clsx) {
	return o("div", {
		className: "external a-b",
		style: { "--a": `a-a` }
	});
}

수정 후 실제 CSS

/*! devup-ui v1.0.83, | Apache License 2.0 | https://devup-ui.com *//*! devup-ui v1.0.83, | Apache License 2.0 | https://devup-ui.com */.a-b{other:var(--a)}.a-a{overflow:hidden}

수정 후 실제 실행 결과

{
  "target": "div",
  "props": {
    "className": "external a-b",
    "style": {
      "--a": "a-a"
    }
  }
}
행 32: namespace / per-file 실제 WASM 출력 전/후

파일: /src/W70h-seam-spread-with-call-namespace.js, singleCss=false, 마지막 설정 인자={}. 실행식은 render(false, {}, () => ({}), undefined)입니다.

입력

import { Box as t } from '@devup-ui/react'; import * as ui from '@devup-ui/react'; import { jsx as o } from 'react/jsx-runtime'; function render(h, p, getProps, clsx) { return o(t, { ...{ className: 'external', other: ui.css({ overflow: 'hidden' }) } }); }

수정 전 생성 코드

import "@devup-ui/react/devup-ui-0.css";
import * as ui from "@devup-ui/react";
import { jsx as o } from "react/jsx-runtime";
function render(h, p, getProps, clsx) {
	return o("div", {
		className: "external a-a",
		style: { "--a": ui$css({overflow:`hidden`}) }
	});
}

수정 전 실제 CSS

/*! devup-ui v1.0.83, | Apache License 2.0 | https://devup-ui.com *//*! devup-ui v1.0.83, | Apache License 2.0 | https://devup-ui.com */.a-a{other:var(--a)}

실제 오류: ReferenceError: ui$css is not defined

수정 후 생성 코드

import "@devup-ui/react/devup-ui-0.css";
import * as ui from "@devup-ui/react";
import { jsx as o } from "react/jsx-runtime";
function render(h, p, getProps, clsx) {
	return o("div", {
		className: "external a-b",
		style: { "--a": `a-a` }
	});
}

수정 후 실제 CSS

/*! devup-ui v1.0.83, | Apache License 2.0 | https://devup-ui.com *//*! devup-ui v1.0.83, | Apache License 2.0 | https://devup-ui.com */.a-b{other:var(--a)}.a-a{overflow:hidden}

수정 후 실제 실행 결과

{
  "target": "div",
  "props": {
    "className": "external a-b",
    "style": {
      "--a": "a-a"
    }
  }
}
행 89: named / single CSS 실제 WASM 출력 전/후

파일: /src/W70h-seam-spread-with-call-alias.js, singleCss=true, 마지막 설정 인자={}. 실행식은 render(false, {}, () => ({}), undefined)입니다.

입력

import { Box as t, css as i } from '@devup-ui/react'; import { jsx as o } from 'react/jsx-runtime'; function render(h, p, getProps, clsx) { return o(t, { ...{ className: 'external', other: i({ overflow: 'hidden' }) } }); }

수정 전 생성 코드

import "@devup-ui/react/devup-ui.css";
import { jsx as o } from "react/jsx-runtime";
function render(h, p, getProps, clsx) {
	return o("div", {
		className: "external a",
		style: { "--b": i({overflow:`hidden`}) }
	});
}

수정 전 실제 CSS

/*! devup-ui v1.0.83, | Apache License 2.0 | https://devup-ui.com */.a{other:var(--b)}

실제 오류: ReferenceError: i is not defined

수정 후 생성 코드

import "@devup-ui/react/devup-ui.css";
import { jsx as o } from "react/jsx-runtime";
function render(h, p, getProps, clsx) {
	return o("div", {
		className: "external b",
		style: { "--c": `a` }
	});
}

수정 후 실제 CSS

/*! devup-ui v1.0.83, | Apache License 2.0 | https://devup-ui.com */.b{other:var(--c)}.a{overflow:hidden}

수정 후 실제 실행 결과

{
  "target": "div",
  "props": {
    "className": "external b",
    "style": {
      "--c": "a"
    }
  }
}
행 109: namespace / single CSS 실제 WASM 출력 전/후

파일: /src/W70h-seam-spread-with-call-namespace.js, singleCss=true, 마지막 설정 인자={}. 실행식은 render(false, {}, () => ({}), undefined)입니다.

입력

import { Box as t } from '@devup-ui/react'; import * as ui from '@devup-ui/react'; import { jsx as o } from 'react/jsx-runtime'; function render(h, p, getProps, clsx) { return o(t, { ...{ className: 'external', other: ui.css({ overflow: 'hidden' }) } }); }

수정 전 생성 코드

import "@devup-ui/react/devup-ui.css";
import * as ui from "@devup-ui/react";
import { jsx as o } from "react/jsx-runtime";
function render(h, p, getProps, clsx) {
	return o("div", {
		className: "external a",
		style: { "--b": ui$css({overflow:`hidden`}) }
	});
}

수정 전 실제 CSS

/*! devup-ui v1.0.83, | Apache License 2.0 | https://devup-ui.com */.a{other:var(--b)}

실제 오류: ReferenceError: ui$css is not defined

수정 후 생성 코드

import "@devup-ui/react/devup-ui.css";
import * as ui from "@devup-ui/react";
import { jsx as o } from "react/jsx-runtime";
function render(h, p, getProps, clsx) {
	return o("div", {
		className: "external b",
		style: { "--c": `a` }
	});
}

수정 후 실제 CSS

/*! devup-ui v1.0.83, | Apache License 2.0 | https://devup-ui.com */.b{other:var(--c)}.a{overflow:hidden}

수정 후 실제 실행 결과

{
  "target": "div",
  "props": {
    "className": "external b",
    "style": {
      "--c": "a"
    }
  }
}

새로 생기는 오류

새 오류 종류나 evaluator 정책은 추가하지 않았습니다. 다만 이전에 factory 내부에서 방문되지 않아 사라진 utility를 런타임에 호출하던 읽을 수 없는 css/keyframes 값도 기존 located build error로 거절됩니다. 예를 들어 factory의 other: css({color:tone}), other: keyframes({from:{opacity:tone}})는 다음 기존 요구사항을 냅니다. direct utility control의 동일 요구사항도 보존했습니다.

Error: /src/W70l-refused.js:1:131: `css()` cannot use `tone` at build time: its values must be literals, theme tokens or constants, or be computed from them
Error: /src/W70l-refused.js:1:145: `keyframes()` cannot use `tone` at build time: its values must be literals, theme tokens or constants, or be computed from them
Error: /src/W70l-refused.js:1:160: `css()` cannot use `tone` at build time: its values must be literals, theme tokens or constants, or be computed from them
Error: /src/W70l-refused.js:1:179: `keyframes()` cannot use `tone` at build time: its values must be literals, theme tokens or constants, or be computed from them

남는 한계

빌드가 알 수 없는 유틸리티 스타일 값은 정적인 값/theme token/상수로 작성해야 합니다. 일반 element의 기존 동적 값과 inline scale/absence 예외는 변경하지 않았습니다. 일반 helper의 기존 spread/eager 순서 문제는 이 수정에 섞지 않았으며 별도 보류 항목입니다.

2026-10-23 unknown spread는 순수 pass-through라는 새 결정은 이 PR에서 구현하지 않습니다. 기존 className/style 병합을 포함한 별도 #756 재작업과 후속 queue가 소유합니다. 이 PR의 literal-spread 네 행과 pre-serialization 결함은 해당 정책과 독립적입니다.

검증

  • head: 36da206; 테스트한 staged tree: 4519f8300bd7025ca4340e64bc36342ab7ad8c35.

  • native RED 24 PASS / 110 FAIL 이후 최종 focused 142 PASS / 0 FAIL, 기존 factory suite 222 PASS / 0 FAIL. 원래 main observer를 수정하거나 다른 지원 코드를 복사하지 않았습니다.

  • default-stack cargo test --workspace 6659 PASS / 0 FAIL / 0 ignored, cargo fmt, stable strict workspace/all-targets/all-features clippy 통과했습니다.

  • fresh bun install, binding WASM build, root build 및 bun lint 통과했습니다. lint는 0 errors와 기존 React 경고 20건입니다.

  • official latest private Bun 1.4.4: 6857 PASS / 0 FAIL / 89 snapshots, 실제 All files 함수/라인 100.00 / 100.00. 공유 Bun 1.4.3도 동일 6857 / 0 / 100.00 / 100.00입니다. 공유 설치를 변경하지 않았습니다.

  • public WASM 8 + 60 + 32 = 100 positive/control 행과 16 located refusal/control 행 통과했습니다. named/namespace, per-file/single CSS, responsive/selector/template/sequence/binary/helper/member, keyframes, mx, parameter/block-local shadow, absence/lazy/once-only 및 reset/prefix를 검증했습니다.

  • 최종 WASM SHA256: bdb305ae4d122152f7055a00e7366f0e41a4d1e32bee8fbb2cdfc41a3f0eecfa. 새 runtime callable/helper/import는 없으며 기존 namespace import 제거 정책도 그대로입니다.

  • 정확한 test: restore latest Bun coverage and preserve sparse values #803 base를 새 private Cargo target에서 빌드하고 원래 base WASM hash와 일치함을 확인한 뒤, 같은 프로세스에서 8개 입력을 각각 5000회 x 10 AB/BA 교대 측정했습니다. 모든 출력 hash는 동일하고 median delta 범위는 **-2.90% ~ +1.31%**입니다. 속도 향상이나 완전한 성능 동등성 주장은 하지 않습니다.

  • 동일 도구 local WASM size 비교는 raw +0.706%, gzip +0.168%, JS glue raw/gzip **0%**로 네 metric 모두 1% 정책 이내입니다. Linux CI의 exact-source 비교가 최종 gate입니다.

  • jobs=2, stable/default Rust stack, CI unset, build/test 6 GiB 및 hook 12 GiB watchdog, 7 GiB free-memory preflight를 사용합니다. LSP daemon은 timeout, JS/JSON 서버는 설치 거절 상태이므로 clean-LSP라고 주장하지 않습니다.

  • managed changepack은 bindings/devup-ui-wasm/package.json Patch 한 개이며 Cargo/root package key는 없습니다.

  • exact-tree full hook: PASS, 650s, peak 4.72 GiB.

최종 CI

39945316026 SUCCESS, head 36da206, 재실행 0회입니다. 필수 validation 8개 job과 Codecov patch가 모두 SUCCESS이며 main-only release/deploy skip만 정상적으로 남습니다.

Job ID 결과
publish 120386441952 SUCCESS
benchmark 120386442067 SUCCESS
react-18 120386442060 SUCCESS
wasm-size 120386441737 SUCCESS
webpack-extends-theme 120386442033 SUCCESS
vinext-rsc-css-e2e 120386441992 SUCCESS
release-compat 120386442058 SUCCESS
landing-next-e2e 120386442117 SUCCESS

실제 publish log의 Linux Rust 100.00% (16638/16638), Bun All files 100.00 / 100.00를 확인했습니다. WASM size의 네 metric도 모두 PASS입니다.

| wasm/raw | 6012478 | 6054962 | 6073344 | 42484 | 0.707 | PASS |
| wasm/gzip | 2186060 | 2189697 | 2208768 | 3637 | 0.166 | PASS |
| glue/raw | 43165 | 43165 | 44032 | 0 | 0.000 | PASS |
| glue/gzip | 7327 | 7327 | 8192 | 0 | 0.000 | PASS |

vinext prerender 및 per-file/singleCss E2E와 release-compat의 실제 실행 step도 성공했습니다. workflow의 Node20/ubuntu-latest migration annotation은 기존 안내이며 실패가 아닙니다.

owjs3901 and others added 4 commits October 22, 2026 23:59
Refs #683

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

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

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

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

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

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

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

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

codecov Bot commented Oct 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
...tor/src/extractor/extract_style_from_expression.rs 100.00% <100.00%> (ø)
.../extractor/extract_style_from_member_expression.rs 100.00% <100.00%> (ø)
libs/extractor/src/factory_dynamic_value_tests.rs 100.00% <100.00%> (ø)
libs/extractor/src/lib.rs 100.00% <ø> (ø)
libs/extractor/src/visit.rs 100.00% <100.00%> (ø)
...plugin/src/rules/no-useless-tailing-nulls/index.ts 100.00% <100.00%> (ø)

... and 84 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.

@owjs3901
owjs3901 merged commit d121976 into main Oct 23, 2026
11 checks passed
@owjs3901
owjs3901 deleted the fix/factory-call-references branch October 23, 2026 11:57
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