3-16. binding-lite·메모리 오염·패키지 분리, 그리고 이름이 바뀐 날
기간: 2026년 5월 1일 ~ 5월 7일 (7일) 커밋: zts/zntc 약 402개 (5/1 58 · 5/2 73 · 5/3 71 · 5/4 43 · 5/5 57 · 5/6 64 · 5/7 36) 핵심: esbuild 스타일
tsconfigRaw통합 (A1 시리즈), binding-lite 트랜스파일 fast path (#2352), 메모리 오염 근본 수정 —import_recordspecifier use-after-free(#raw-require)·var hoisting의0xAAslice UAF가12개 형제 사이트로 fan-out, stack-overflow hardening (스택 버퍼 → ArrayList, #2475/#2484), AST intern-map perf, numeric cross-module const folding, RN codegen rn-0.780.84 골든 매트릭스 (#2462), Flow 파서를 "ts처럼" — type-level 정보 보존, core-js 자동 폴리필 에픽 (runtimePolyfills: auto, RuntimeTarget ios12/hermes0.7),@zntc/*모노레포 패키지 분리 (core/web/server/react-native/vite-plugin/wasm), BoringSSL dev TLS, 번개(bungae) RN 데브서버 흡수 + 번개 폐기, dev 에러 오버레이(Shadow DOM), 그리고 2026-05-07 15:40 KST — 프로젝트 이름이zts에서zntc로 바뀜 진행 중 — 패키지 분리·core-js·RN codegen 매트릭스·번개 흡수 모두 이 7일에 머지됐지만 후속 phase가 줄줄이 열려 있다.
이 편의 위치
3-15는 4/24~4/30 — config 시스템, CJS DCE, styled/emotion 트랜스포머, 문서 사이트 개편, 그리고 "프로덕션 레벨 배포에 부족한 게 뭐예요?"라는 첫 질문 — 으로 끝났다. 이 편은 그 질문에 대한 답을 코드 구조 차원에서 밀어붙인 7일이다.
세 가지가 동시에 일어난다. (1) 성능을 한 번 더 — 트랜스파일은 binding-lite fast path, 번들러는 numeric const folding과 graph discovery 프로파일. (2) 안정성을 한 번 더 — Zig의 슬라이스/스택 다루는 코드에서 use-after-free와 stack overflow를 근본 수정. (3) 배포 가능한 모양으로 — 단일 저장소를 @zntc/* 모노레포로 쪼개고, RN 번들러 래퍼였던 "번개"를 흡수하고, 그리고 — npm org @zts가 이미 선점돼 있어서 — 프로젝트 이름을 바꿨다.
5/1 — esbuild 스타일 tsconfigRaw, 그리고 두 워크스페이스
tsconfigRaw 통합 A1 시리즈
tsconfig.json을 JS API에서 어떻게 받을 것인가. esbuild는 tsconfigRaw 옵션으로 raw 문자열/객체를 받는다. zts도 그 방식으로:
tsconfigRaw를 JS API에, JS 쪽 파싱은 버리고, jsx 머지. 이때 napi! 가 잘못된 jsx 옵션에 대해 조용히 classic으로 떨어지던 걸 strict throw로 바꿨다(breaking change). 시리즈 PR을 자동 wakeup으로 CI 폴링 → 머지까지 무인화하기 시작한 것도 이날:
ancestor walk cache는 "자주 일어나는 이슈 아니예요?? 모노레포나 이런경우" → "일단 이슈만 등록하고 스킵" — over-engineering 판정. writer-passing 리팩토링 + BoundedArray 도입도 "호출 빈도 대비 이득 미미"로 스킵. "언젠간 하는 게 낫나?" → "지금은 아니다"가 이날 여러 번 나왔다.
"여기서 B 작업해주실래요?"
zts(Claude)와 zts-codex(Codex)를 의식적으로 분리해서 쓰기 시작했다는 가장 명시적인 문장. 같은 origin remote, 같은 이슈/PR 풀, 다른 작업 디렉터리.
Codex: "느린 구간 다 1등 하고 싶어"
같은 날 Codex 쪽에서는 벤치마크 CI를 보다가:
여기서 binding-lite — semantic 분석을 전부 돌리지 않고 transpile에 필요한 최소(함수/var/param shadow)만 보는 fast path — POC가 시작된다(#2352). 마지막 질문("컨텍스트 안 잃으려면?")에 대한 답은 "매 작업마다 이슈 #2352에 진행상황 기록"으로 정착됐다.
5/2 — binding-lite, 그리고 메모리 오염
binding-lite Phase 2/3
binding-lite는 5/2에 Phase 2 hardening(함수/var/param shadow 처리, TransformPlan 히트율 리포팅), Phase 3 scope-aware shadow, 그리고 대형 25K/50K/100K 라인 프로파일 fixture까지. module_specifier_map(#2393)도. "결과가 바뀐 건 아니고 내부 동작만 최적화된 거지?" — fast path는 동작을 바꾸면 안 된다.
#raw-require — import_record specifier use-after-free
이날 진짜 사건. 번들에 raw require()가 새어 나가는 버그를 추적했더니 — import_record의 specifier가 use-after-free였다. 슬라이스가 가리키던 메모리가 재할당되면서 다른 데이터를 가리키게 된 것. 근본 수정은 그 중복본을 graph.zig로 옮기는 것.
이어서 더 무서운 게 나왔다 — var hoisting에서 rename-slice의 0xAA use-after-free. Zig의 GeneralPurposeAllocator는 free된 메모리를 0xAA로 칠한다. 그 패턴이 보였다는 건 free된 슬라이스를 읽고 있다는 뜻. 원인은 동일 — 슬라이스를 쥐고 있는데 그 백킹 배열이 realloc됨. 같은 패턴이 es2015_class / transformer / es2015_generator에 걸쳐 ~12개 형제 사이트에 있었고(#2422/#2426), 한 번에 같은 수정을 적용했다. 파서/트랜스포머의 uninitialized-memory 버그 2개도 추가로 근본 추적(Flow 패턴 작업을 막고 있던 것). NAPI free-on-deinit 누수(#2396), TsconfigCache per-process(#2367)도.
cjs_output 추가, 트리쉐이킹 전략을 GitHub 문서 + Astro 문서 양쪽에 정리:
5/3 — stack-overflow hardening, numeric const folding
#2475 — 스택 버퍼를 ArrayList로
4/30의 Buffer overflow 차단 (256B 스택 버퍼) 같은 임시 방어들이 이날 정면으로 다뤄진다. ast-walk / semantic / transformer / worklet의 스택 한정 재귀 walker를 명시적 스택 / ArrayList로 전환(#2475 감사, #2484). containsReturn / containsYield는 5/9에 iterative DFS로.
AST intern-map perf(addString dedupe, Span-key HashMap, FunctionExtra/ArrowExtra typed offset structs), tree-shake numeric-const chain folding도 이날.
Codex: graph discovery 병목, numeric const folding
Codex 쪽은 그래프 discovery 병목을 프로파일로 쪼갰다 — graph.discover.scan.worker.parse, pm.setup.read가 큰 비중. 그리고 numeric cross-module const folding으로 모노레포 번들에서 5051개 모듈이 출력되던 걸 (rolldown은 ~50개) 대폭 축소:
5/4 — RN codegen 매트릭스, Flow를 "ts처럼", core-js 자동 폴리필
RN codegen 골든 매트릭스 #2462
@react-native/codegen을 Zig로 포팅한 것 — Fabric 컴포넌트의 ComponentShape를 TS/Flow 타입에서 뽑아 JS view config를 emit. 5/15/3에 schema_builder, type-alias/interface indexer, validator + ZTS 14001499 에러 코드, view_config_emitter를 쌓았고 5/4엔 버전별 골든 매트릭스 — rn-0.78 → 0.84, 각 버전 3개 core spec, @react-native/codegen 레퍼런스와 byte-diff 스냅샷:
(codegen → rn_codegen 리네임도 이날.)
"Flow도 ts처럼 구현해주시죠"
zts Flow 파서는 type-level 함수 정보를 strip하고 있었다 — Babel은 보존한다. RN codegen이 Flow 타입에서 인자 이름을 뽑아야 하는데 AST에 정보가 없어서 안 됐다. 결론은 "Flow도 TS처럼 타입 노드를 보존". 이후 5/1~5/2에 걸쳐 Flow object body / TS property signature 보존, Flow enum 문법(#2401, flow-enums-runtime), Pick/Omit/WithDefault/Array<T> 같은 유틸리티 타입 처리가 채워진다.
core-js 자동 폴리필 에픽
runtimePolyfills: off|auto|usage|entry, coreJs, RuntimeTarget(ios12/hermes0.7/react-native 0.70/node18), core-js-compat 연동. 처음엔 Babel parser pre-scan으로 감지하다가 → "우리 Zig 파서/그래프로 그래프 기반 감지"로 전환(#2518). rspack과 타깃 정렬. react-query v5 + es5 다운레벨에서 replaceAll 폴리필이 자동 주입되는지 스모크로 확인.
dev 에러 오버레이 + 서버 분리 논의
dev 에러 오버레이를 div → Shadow DOM으로(소스맵 적용, X로만 닫기, ESM 서빙). 그리고 "데브서버를 별도 패키지(@zts/server)로 빼야 하나"가 처음 떠올랐다 — 5/5의 패키지 분리 대설계로 이어진다.
5/5 — 패키지 분리 대설계, BoringSSL, 번개 폐기
@zntc/core / @zntc/web / @zntc/server / @zntc/react-native
단일 저장소를 패키지로 쪼개는 큰 설계. 며칠에 걸친 토론이 이날 결론에 도달한다:
최종: @zntc/core(트랜스파일·번들·NAPI) + @zntc/web(CSS 파이프라인 — css-modules/css-parser/postcss/sass/loader, zts.mjs에서 추출) + @zntc/server(private — HMR + WS + Watcher, BoringSSL dev TLS) + @zntc/react-native(asset/babel/codegen/require-context/metro-resolve-request 플러그인 팩토리, RN preset, zts-hmr-client.js, Metro HMR adapter) + vite-plugin-zts(나중에 @zntc/vite-plugin) + @zntc/wasm + @zntc/shared. zts 자체를 self-build로 도그푸드.
BoringSSL — bun은 어떻게?
dev 서버 HTTPS를 위한 TLS — BoringSSL로(bun이 쓰는 방식 참고). 프리빌트를 GitHub Release에 올려서 옵셔널 의존성으로.
번개 폐기 → 예제·데브서버 이관
"번개"(bungae) — 3-11부터 RN 번들러 래퍼였던 별도 저장소. 이제 @zntc/react-native로 흡수하고 번개 자체는 deprecated. 예제(examples/react-native-bare RN 0.85, examples/react-native-expo Expo 55/RN 0.83)와 데브서버를 전부 이쪽으로 옮긴다 — 5/6의 작업.
5/6 — 번개 흡수 전수조사, 그리고 이름을 바꾸기로
"번개에서 그대로 제대로 옮겨온거 맞죠?"
이날은 번개의 RN 데브서버 — babel 옵션, 단축키, ignoreList, prelude, polyfill, 임의 상수 선언 — 가 @zntc/react-native로 빠짐없이 옮겨졌는지 전수조사하는 날이었다:
zts dev --platform=react-native, Metro 호환 서버(cli-server-api, dev-middleware, asset registry handler /assets/*, symbolicate + customizeFrame hook, terminal UI — Metro 스타일 배너/로그 + r/d/?/c/i/a/j 단축키), zts.config.ts의 RN 영역 매핑. Reanimated 회귀(__extends 가 non-enumerable static 메서드를 상속 안 함, for-of temp var span이 엉뚱한 위치)도 이날 잡혔다.
@zts가 이미 있다 — 그래서 zntc
저녁, 배포를 준비하다가 막힌다:
@zts org는 선점됨. zwc(SWC 따라했다고 욕먹을라), zinc(있음), znc(있음) — 결국 zntc ("Zig Native Transpiler Compiler"). Codex 쪽에선 zts → znts로 시작했다가 zntc임 쏘리 이름 잘못 말함으로 정정.
5/7 — 이름을 바꾼 날
chore: rename zts to zntc (15:40 KST, 972b207c)
이날 오후 3시 40분, 687개 파일이 한 커밋에서 바뀐다 — 식별자, CI 액션(setup-zts→setup-zntc), 에러 코드 파일(zts1204.md→zntc1204.md), zts.config.ts→zntc.config.ts, zts.mjs→zntc.mjs, 테스트 fixture, HMR client. origin remote는 ohah/zntc.git으로. 패키지 스코프는 vite-plugin-zts→@zntc/vite-plugin, 새 @zntc/rspack-loader 등으로 이어진다(5/9~5/10).
"홍보를 해야할까?"
이름 바꾸는 김에, 새벽에 프로젝트 전체를 한 발 떨어져서 본다:
장점은 "RN도 되고 Flow도 되는 빠른 번들러"라는 자기 인식이 분명해졌다. 홍보 시점은 미정 — 하지만 0.1.0 배포 준비(다음 편)는 이날 이후 본격화된다.
헤르메스/롤리팝, 그리고 "왜 메트로보다 큰가"
RN preset 정책을 "Metro-equivalent downleveling"으로 정리(Hermes ES2015+async 매트릭스, legal_comments 기본 none). inline-requires(#2679 — Metro 스타일, "oxc/rolldown도 이 패턴 쓰는 거고?"), mangle-props 검토.
parse_arena heap ptr, HMR 113→89ms
parse_arena를 heap 포인터로 옮겨 store round-trip에서 dangling BufNode panic 근본 수정(#2694). feat(init) React Native CLI 오버레이 initializer. perf(emit) — dev 모드 incremental rebuild가 전체 번들 출력을 스킵 → HMR wall 113ms → 89ms (-21%). chunk sourcemap emit을 hash substitution 이후로 미뤄 file 필드 정확도(#2661). pnpm/bun farm scoped-package + transitive 테스트(#1924).
도그푸드 중 실제 앱 버그도 발견 — 환불 버튼만 누르고 뜬 모달에서 환불 다시 안 눌러서 제대로 환불이 진행 안된것 같아 (번개 앱을 zts로 빌드해 쓰다가 잡은 UX 버그).
7일간 관통한 것들
5/7 시점에 진행 중인 에픽
다음 편
3-17은 5/8~5/12 — plugin 시스템 통일(comptime-generic 어댑터, sync JS plugin), 대규모 파일 분리 리팩터(~200 커밋, 코어 파일 라인 수 줄이기), TS Project References, npm 배포 파이프라인(changesets·OIDC·9-platform prebuilt 매트릭스), react-refresh, MetafileAnalyzer, 문서 사이트 대규모 보강, 0.1.0 publish prep — 그리고 이 17일을 관통한 Codex 병행 효과에 대한 회고를 담는다.