3-21. 이슈 큐를 따라가는 여름 — preserve-modules와 0.1.3·0.1.4
기간: 2026년 7월 1일 ~ 8월 10일 커밋: zntc 143개 (7월 115 · 8월 28) 릴리즈: v0.1.3 (7/12) · v0.1.4 (7/19~20) 핵심:
preserve-modules× minify × splitting 조합에서 터지던 네이밍 붕괴를 cross-file 네이밍 인프라를 켜서 정리(#4532 시리즈), CJS interop 잔여(#4510), JSX namespace 결함(#4596), external 지정자 매핑·re-export(#4616·#4621), 다운레벨 ESM의 top-level await(#4598) — 그리고 "증상이 아니라 원인을 막는다"는 규칙
이 편의 위치
3-20까지가 만드는 구간이었다면, 이 편은 쌓인 이슈를 하나씩 닫는 구간이다. 커밋 143개 중 대부분이 fix(bundler)고, 제목에 이슈 번호가 붙는다. 그 사이 무게중심은 완전히 maru로 넘어가 있어서, zntc 작업은 며칠 단위로 몰아서 들어온다 — 7월 4일 이후 열흘 공백, 7월 21일 이후 여드레 공백, 8월에는 세 번.
7/12~7/13 — v0.1.3, 그리고 청크 export의 진실
7월 12일에 v0.1.3이 나갔다. 릴리즈 자동화도 함께 손봤다 — root/platform/optionalDeps 버전 lockstep, publish 직후 bun.lock 자동 동기화.
기술 쪽은 code splitting의 export 정합성이었다.
#4492— 같은 청크 소비자에게 크로스-청크 공개명을 적용하던 문제#4495— const-inline이나 미사용 import로 DCE된 심볼이 청크 export에 남던 문제
7/14~7/19 — preserve-modules라는 조합 폭탄
이 구간의 주인공은 preserve-modules다. 모듈 구조를 그대로 유지해 출력하는 모드인데, minify·splitting·CJS·RN 다운레벨과 겹치면 이름이 붕괴한다. 이슈가 줄줄이 열렸다.
해법은 6월 2일에 비활성 상태로 넣어 뒀던 cross-file 네이밍 인프라를 켜는 것이었다(#4532). 그다음 minify의 preserve-modules mangle을 splitting과 통합하고(증상1), 소비자-로컬 유일성을 집합 기반으로 바꾸고(#4576), export-line을 단일화했다(증상4).
이 구간에 규칙 두 개가 반복해서 나온다.
두 번째 문장이 이 편 전체의 기준이다. 같은 이슈에서 증상 1·2·4처럼 번호를 나눠 붙이는 것도, 각 증상이 서로 다른 원인에서 나온다는 것을 전제하기 때문이다.
7월 19~20일에 v0.1.4가 나갔다. 릴리즈 과정에서 bun.lock 동기화와 sync-lockfile push 훅 우회가 또 걸렸다.
7/21~8/1 — incremental과 JSX
#4557— const-baked 소비자를 provider가 바뀔 때만 evict(B-precise). incremental 빌드에서 불필요한 무효화를 줄이는 작업이다.#4596— JSX namespace 멤버가 미선언 참조로 나오던 결함. 원인은 합성 import span 오염과 JSX 멤버 색인 누락 두 가지였다.- 8/1 —
jsx: preserve에서 실체화된 namespace 변수가 intrinsic 태그로 해석되던 문제.
작업을 이어 붙이는 방식도 이 시기부터 달라진다. 세션이 끊기면 이전 세션 기록을 읽어 이어받는다.
8/8~8/10 — external, 그리고 top-level await
8월의 세 날에 네 건이 들어갔다.
#4611— CSS@import의 bare 지정자가 tsconfig paths에 가로채이던 문제#4616— external 지정자를 다른 이름으로 방출하는 매핑#4621— external re-export가 파싱 불가능한 출력을 내거나 재export를 잃던 문제#4598— 다운레벨 ESM의 top-level await
마지막이 가장 까다로웠다. TLA는 모듈 평가를 비동기로 만드는데, 이를 낮은 타겟으로 내리면 "타겟 준수"와 "값 정확성"이 충돌한다. 1차 수정 뒤 code splitting 청크에서 SyntaxError 회귀가 나왔고, multi-format 가드는 철회했으며, 4차 리뷰에서 나온 회귀 4건을 더 잡고서야 닫혔다. 최종 결론은 범위를 좁히는 것이었다 — "기다려 줄 기계가 있는 위상으로 한정".
ES5(RN/Hermes) 소비자를 promise 체이닝으로 바꾸는 후속(#4631)은 9월 14일 기준으로도 열린 PR이다.
이 구간에서도 방법은 같다 — 남의 구현을 먼저 보고, 자기 결론을 의심한다.
후속: 8/31 · 9/10 — 옆 프로젝트가 낸 버그로 세 번 배포
8월 10일 이후 zntc 에 직접 붙은 날은 이틀뿐인데, 그 이틀에 릴리즈가 셋 나갔다. 계기는 @ohah/pdf다 — TS 를 zntc 로 굽다가 소스맵 결함 둘을 찾았다.
- 한 줄 블록 안 주석 뒤 소스맵이 한 줄씩 밀리던 것 —
} catch { /* 주석 */ }를 세 줄로 펴면서 생성 줄 셈을 놓쳤다 - 소스맵 열을 UTF-16 단위로 세지 않던 것
transpile이 소스맵을 출력 자리에 맞춰 마무리하지 않던 것
이걸 고쳐 v0.1.5 를 내고, 릴리스 파이프라인이 만든 CHANGELOG 를 oxfmt 형식으로 맞추고 bun.lock 동기화를 PR 로 내어 오토머지를 걸도록 바꾼 뒤 v0.1.6 을 같은 날 냈다(8/31). 9월 10일 v0.1.7 은 resolver 가 형제 파일보다 동명 디렉토리 index 를 먼저 잡던 해석 순서와, wasm VFS 번들러가 entry 의 import 를 해석하지 못하던 것(listDir 부재)을 고쳤다.
세 릴리즈 모두 "만들던 사람이 다른 물건을 만들다 밟은 것" 이 계기다. 새 기능은 없고, 있는 것이 정확해졌다.
9월 셋째 주에 셋이 더 나갔다. v0.1.8(9/17)은 해석 못 한 import 를 포맷에 맞게 방출하고(ESM 에 require() 금지) return/throw/yield 앞 줄 주석의 ASI, ESM-wrap 의 export default 이중 선언, onResolve external 미반영, --outfile/--outdir 이 산출물 보류 게이트를 건너뛰던 것을 고쳤다. v0.1.9(9/19)는 package.json "type":"module" 을 사용자 프로젝트 코드에도 적용하고 "module" 필드 해석분에 Node CJS interop 을 걸지 않는다(#4659·#4666). v0.1.10(9/21)은 dev 서버의 CSS 결함을 몰아서 닫았다 — watch 이벤트가 CSS 등 asset 산출물을 보고하고, css-update href 를 주입된 <link> 와 맞춰 전체 리로드를 없애고, dev 산출 디렉토리를 CSS 탐색에서 제외하고, macOS 원자적 저장 후 watch 가 서던 것과 사라진 CSS import 의 <link> 를 지우는 것까지(#4660·#4671~#4683). CSS 파이프라인 사전 패스의 비용 측정과 D2a 선행 조건 재판정은 RFC §11 에 남겼다.
3월 18일부터 여기까지
2026년 9월 21일 기준 최신 배포는 v0.1.10, npm 패키지는 @zntc/core다. 커밋 총량은 약 5,280개(3월 1,073 · 4월 1,801 · 5월 1,932 · 6월 280 · 7월 115 · 8월 41 · 9월 41)로, 다섯 달 중 두 달 반에 88%가 몰려 있다. 프로젝트가 식었다기보다, 같은 방식으로 만들 다음 물건(maru)이 생긴 결과에 가깝다.