3-12. 호환성 겹층 — 워클렛·Stage 3 decorator·ESTree 어댑터
기간: 2026년 4월 11일 ~ 4월 14일 (4일) 커밋: zts 약 122개 핵심: Reanimated 워클렛 Phase 6, TC39 Stage 3 decorator(TS 스타일), UMD/AMD/HTTPS dev server,
#x in objErgonomic Brand Check, RSC directive 보존, per-module sourcemap, ESTree 어댑터 채택, function params Phase 0~2, TS declaration emit, mangleProps 구현 시작
이 편의 위치
3-11에서 플러그인 스택이 NAPI로 전환되고 HMR 기본 구조가 잡히자, 즉시 호환성의 겹층이 드러났다 — 단순 JS가 아니라 Reanimated 워클렛, Stage 3 decorator, UMD/AMD, RSC directive. 겹겹이 쌓인 생태계 규약을 번들러가 하나씩 수용해야 한다.
4/11 — Stage 3 decorator·UMD/AMD·HTTPS
하루에 28커밋 — 전부 실전 호환성 메꾸기.
빌드 환경 정비와 서브모듈 재편
번개 모노레포가 가지고 있던 롤다운 서브모듈을 zts 서브모듈로 교체. 외부 개발자의 postinstall 실패는 mise.toml에 zig 설치를 명시해서 해결. 원격 CI도 이 형태로 일원화.
NAPI 전환 회귀
이전엔 CLI 경로로 우회되고 있었다. NAPI로 통일하자 실경로가 드러남. 이후 디버깅 원칙:
Reanimated 파싱 실패
오전 내내 RN에서 react-native-reanimated 포함 번들이 ";' expected로 실패:
- 공식 비교군은 롤다운으로 통일
- 이슈에 Metro·롤다운·zts 결과물 라인 번호까지 비교 첨부
Stage 3 decorator — TS 스타일로
저녁 핵심 결정:
esbuild는 레거시 TS decorator를 기준으로 변환한다. 하지만 최신 TS 컴파일러는 이미 TC39 Stage 3로 넘어갔다. zts는 TS 공식 런타임과 일치시킨다:
- field decorator per-field initializer
access: { get, set, has }프로퍼티 생성- TC39 decorator 변환 +
@decorator export [default] class보존 --target=es5에서는 decorator + private backing WeakMap lowering
decorator 테스트 중 흔들린 원칙:
테스트를 구현에 맞추는 게 아니라 구조를 맞춘다. 며칠 뒤 CLAUDE.md 글로벌 규칙으로 박히게 된다.
CSS 번들링 방향 정리
밤:
Lightning CSS vs 자체 구현. Rust 바이너리 의존을 감수하는 쪽 A 선택. 구현은 백로그.
Vite 호환 범위 확정
Vite 호환을 @vitejs/plugin-swc 수준 — "zts를 Vite 플러그인으로 꽂아 쓸 수 있다" 수준으로 국한. 번들러 전체 교체가 아니라 transpile 교체.
UMD/AMD / HTTPS dev server / 그 외
같은 날 축약 작업:
- namespace 변수 충돌 회귀 테스트 (scope hoisting 시 모듈 스코프 변수와 전역 충돌)
- TS enum hoisting — ESM factory 안의 enum이 안 올라가던 문제
- UMD/AMD external 의존성 처리 + 실제 React E2E 테스트
- HTTPS dev server (
--certfile,--keyfile) + 통합 테스트 3개 import.meta.globeager/import 옵션 + glob E2E 4개
4/12 — 짧은 하루, 스모크 CI 확장, 워클렛 해시 추적
CLAUDE.md·메모리 슬림화.
emotion / styled-components / tailwind / MobX 스모크 CI 확장.
워클렛 해시·initData 차이 추적
밤:
reanimated의 Babel plugin은 워클렛으로 마킹된 함수에 __workletHash/__initData 메타데이터를 삽입한다. zts 생성값이 롤다운 기반 레퍼런스의 값과 달랐다. 원인은 여러 층이었다.
"공유 헬퍼는 구조적이라 스코프 밖" 기각
"PR 범위"라는 이유 자체를 기각하고 근본으로 끌고 갔다.
babel-plugin-reanimated의 테스트 169개 전수 포팅 지시. 다음 날 이것들이 워클렛 Phase 6 완성의 근거가 된다.
4/13 — 워클렛 Phase 2~6, 드래그 사건, AST 안정화의 싹
하루 32커밋 + PR 16건 머지. 워클렛 버그픽스를 Phase로 나눠 연달아 정리.
훅 외의 확장점
오전:
plugin hook 이외의 확장점(preset / phase stack) 조사. 구현은 보류, 설계 문서로.
core-js 옵션
--core-js=3 옵션 도입. swc·babel과 동일한 UX.
"드래그가 안 된다" 사건
오후 2시:
Metro 번들 vs zts 번들 라인 대조.
한 건 고쳐서 비슷한 누락을 전수 조사 — 이 구간의 일관된 루틴. linker의 export default getter body가 비어있던 케이스 외에:
- shorthand destructuring + sourceMap 필드를
__initData.code에 추가 - ES5 class lowering + Babel-style anonymous names
- implicit context + globals + CJS dehoist
워클렛 핵심 버그들
4/12~13에 누적된 워클렛 원인들:
- traverse 시 directive stripping 유실
- closure analysis shadowing 버그
assignment_expression이collectBindingNames에서 누락if_statement/try_statement/for_in이 ternary data를 사용 안 함- JS_GLOBALS를 pre-visit body에서 확장 안 해줌
- TS/Flow node stripping이 worklet 변환 전에 돌아야 함
- known function callee 자동 워클릿화 (
useAnimatedStyle등 호출 인자) - original
binding_identifierspan을 scope hoisting rename에 사용 - object method/getter/setter body를 closure analysis에 순회
__stackDetails를global.Error()와 함께 emit — Babel plugin과 호환
Babel plugin 브리지
Babel plugin bridge 구현 착수. Reanimated처럼 Babel 생태계에 깊이 의존한 플러그인을 zts에서 얇게 수용.
PluginState 구조체가 이날 worklet과 refresh로 분리됐다. object-hook property values의 auto-workletization도 같이.
MCP·RSC — 장기 백로그
MCP/SSE는 장기 백로그.
RSC는 즉시 작업:
"use client"/"use server" directive 보존 (#1223). multi-entry CLI, WatchRebuildEvent, per-module standalone sourcemap, Metro x_facebook_sources per-source function map도 같이 들어갔다.
AST import attributes / import defer-source / TS type param modifiers 보존은 이 과정에서 "AST가 생각보다 많은 걸 잃어버리고 있다"는 인식으로 이어졌다.
"AST는 안정화 어떻게 하는데요?"
이 편에서 가장 결정적인 저녁 발화:
이 발화가 다음 주 전체를 결정한다.
4/14 — ESTree 어댑터 채택, #x in obj, mangleProps
아침 첫 발화:
zts의 내부 AST(24바이트 고정)를 그대로 공개하는 대신, 외부에 ESTree 호환 어댑터를 노출한다. wasm API·AST API 안정화와 한 묶음.
function params 리팩터 Phase 0~2
같은 날 function params를 formal_parameters NodeList로 통일. arrow / function / class method / constructor 모두 동일한 표현.
이 정규화의 결과:
- ESTree 어댑터에서 FormalParameter 배열로 바로 매핑 가능
- transformer가 공통 유틸 (
visitExtraList)로 한 번만 visit - per-param rename/destructure lowering의 호출부 단일화
Hermes와 #x in obj
ES2022 Ergonomic Brand Check(#x in obj)는 Hermes가 지원하지 않는다. 임시 우회 대신 파서 레벨 다운레벨링. 더 나아가 RN 플랫폼에서는 사용자 ES target을 무시하고 강제 오버라이드. 설정 미스로 Hermes에서 크래시가 나는 것을 원천 차단.
워클렛 안에서 해결 못 하는 문제는 파서 레벨 lowering(C안) 선택.
arrow 정규화
arrow ↔ function 변환 규칙을 일반화. RN 프리셋에서 block_scoping(let/const → var)와 arrow → function 다운레벨도 같이.
mangleProps
mangleProps — 프라이빗으로 간주되는 프로퍼티(보통 _/$ 프리픽스)를 압축. rolldown/rspack 전략 기준.
TypeScript declaration emit + browserslist
같은 날:
.d.ts자동 생성- browserslist를 optional 의존성으로 전환 (없어도 zts가 돈다)
build API인터페이스 구현 (zts.build({ entryPoints, outfile, ... }))
Hermes 버전 매트릭스
Hermes 버전별 미지원 기능 매트릭스 정리. RN preset이 이 매트릭스를 기반으로 다운레벨 target을 강제 오버라이드한다.
AST 정규화 미비 전수조사
arrow 처럼 표면적으로 구분되지만 내부적으로는 동일해야 하는 노드들을 전수조사. 이후 3일간 AST epic의 재료.
세션 승계 방식
긴 에픽은 메모리 파일(project_function_params_phase2.md) + 부트스트랩 문구로 세션 사이에 승계. 이 방식이 이후 RFC #1672, #1634에도 그대로 쓰인다.
4일간 관통한 것들
이어서 — 3-13에서
4/14 저녁 "AST 정규화 덜된 거 있는지 전수조사"가 끝나자 다음 질문이 바로 나왔다:
- 심볼 테이블 — AoS(현재) vs SoA(oxc 방식). 어느 쪽이 장기 유지보수에 유리한가
- SIGBUS로 크래시 나는 상황을 어떻게 친절한 에러로 바꿀 수 있는가
- Metro 호환 API 표면적(watchFolders, extraNodeModules, resolveRequest, ...) 감사
그리고 이 모든 질문의 배경에 있는 더 큰 질문:
- "zts와 번개는 어디까지 해야 하는가?"
다음 편 3-13. Metro 경계 확정과 근본 수정 원칙로 이어진다.