3-10. React Native 플랫폼과 4/7 시점 성능
기간: 2026년 3월 27일 ~ 4월 7일 커밋: Phase 20, 24 관련 ~30개 핵심:
--platform=react-native프리셋, Metro 호환, Hermes 구문 검증, Rolldown 비교, 4/7 시점 속도 스냅샷
주의 — 이 문서는 4/7 시점의 스냅샷이다. ZTS는 여전히 현재진행형이며, 이후(4/8~)의 작업은 3-11에서 이어진다. 이 시점에서 나타낸 "얼마나 빠른가"도 이후의 HMR 고도화·프로파일 세분화·lazy sourcemap·mtime cache·ModuleGraph 재설계를 거치며 계속 바뀌고 있다.
RN 번들러의 목표: "Metro를 대체할 수 있는가?"
첫날(3/18)부터 방향은 정해져 있었다:
"React Native도 목표라 Flow 지원 하고 싶어" — 3/18 첫 세션 "추후 번들러까지 확장할 생각이긴 한데" — 3/18
그리고 3/21 번들러 설계에서 RN을 구체적으로 고려했다:
"나중에 react-native도 지원 계획이라, strictExecutionOrder를 제공하려면 고민해야할 부분이 뭘까요" — 3/21 "근데 메트로는 CJS 순서 보장 중요한데" — 3/21
이 모든 준비가 마침내 --platform=react-native로 결실을 맺었다.
--platform=react-native 프리셋
하나의 플래그로 RN에 필요한 모든 설정이 자동으로 켜진다:
이 한 줄이 내부적으로 활성화하는 것:
플랫폼 확장자 해석 (--rn-platform)
React Native에서 import Button from './Button'은 플랫폼에 따라 다른 파일을 로드한다:
Metro의 --platform ios와 정확히 동일한 해석 순서. --resolve-extensions 옵션으로 구현되어 있어, RN 외에도 커스텀 플랫폼 확장자가 가능하다.
shimMissingExports — 플랫폼 파일 누락 대응
React Native 코드에는 플랫폼별로만 존재하는 파일이 있다. 예를 들어 ProgressBarAndroid.js는 Android에서만 존재한다. iOS 번들링 시 이 파일의 export를 참조하면 에러가 발생한다.
--shim-missing-exports는 누락된 export를 var xxx = void 0;으로 shim 처리한다. Rolldown도 동일한 옵션을 제공한다 — 이것은 ZTS가 Rolldown의 RN 호환 전략을 참고한 결과다.
configurable_exports — Hermes 호환
Hermes 엔진에서는 Object.defineProperty로 정의된 프로퍼티가 configurable: false이면 동적 모듈 재정의가 불가능하다. RN의 polyfillGlobal() 패턴과 충돌한다.
configurable: true를 추가하는 것은 작은 변경이지만, 이것 없이는 RN 앱이 시작조차 하지 못한다.
Metro 호환 번들 실행 순서
Metro와 ZTS의 번들 구조는 근본적으로 다르다:
Metro는 __d()/__r() 패턴으로 모듈을 격리한다. ZTS는 scope hoisting으로 모듈을 병합한다. 이 차이가 성능 이점(tree-shaking, 번들 크기 절감)을 만들지만, 동시에 모든 호환성 문제의 원인이기도 하다.
IIFE 래핑 제거
RN 번들은 top-level IIFE가 필요 없다. Metro도 전역 스코프에서 실행한다. 불필요한 IIFE는 Hermes 컴파일러에서 추가 오버헤드가 된다.
__esm 래핑과 초기화 순서
엔트리 모듈도 __esm 래퍼로 감싸야 한다. 그렇지 않으면 엔트리의 side-effect가 의존 모듈보다 먼저 실행될 수 있다. React Native에서 이는 InitializeCore 전에 ErrorUtils를 참조하는 크래시로 이어진다.
JSX automatic 기본값
Metro는 JSX automatic mode가 기본값이다. ZTS의 --platform=react-native도 이를 따른다. jsx-runtime import가 자동 주입되므로 사용자가 import React from 'react'를 매번 쓸 필요가 없다.
RN 전역 식별자 충돌 — scope hoisting의 대가
scope hoisting의 가장 큰 부작용은 전역 이름 충돌이다. Metro의 모듈 격리에서는 각 모듈이 독립된 스코프를 가지므로 충돌이 없다. ZTS에서는 모든 모듈이 같은 스코프에 병합된다.
React Native는 polyfillGlobal()로 50개 이상의 전역 이름을 등록한다:
이 이름들과 모듈 내부 변수가 충돌하면 런타임 에러가 발생한다. --global-identifier 옵션으로 linker가 이 이름들을 자동으로 리네이밍한다:
linker가 Performance → Performance$1로 리네이밍하여 충돌을 방지한다. esbuild와 Rolldown에는 이 기능이 없다 — RN 특화 기능이다.
Hermes 구문 검증
RN 앱의 최종 실행 환경은 Hermes다. ZTS 번들이 Hermes에서 올바르게 파싱되는지 검증하는 것이 최종 관문이다.
hermesc(Hermes Compiler)가 ZTS 번들을 바이트코드로 컴파일할 수 있으면, Hermes에서 실행 가능하다는 뜻이다. 에러 0건 — ZTS 번들은 Hermes에서 올바르게 동작한다.
Metro vs ZTS 모듈 수 비교
ZTS가 Metro와 동일하거나 더 많은 모듈을 resolve한다는 것은, 누락 없이 모든 의존성을 처리한다는 증거다. tree-shaking으로 실제 번들 크기는 더 작으면서도.
추가 검증: 번들 내 미변환 패턴 검출
RN 번들의 견고성을 보장하기 위한 4가지 추가 검증:
- raw require() 검출: 번들 내에
require("specifier")가 남아있으면 런타임 에러.require_xxx()로 치환되어야 한다. - __esm 내 CJS exports 부재:
__esm래퍼 안에exports.x = x가 있으면 런타임 에러. - __export getter 변수 정의: getter가 참조하는 변수가 같은 래퍼 안에 정의되어 있어야 한다.
- Node 실행 검증: 실제로 Node.js에서 번들을 실행하여 ReferenceError/SyntaxError 검출.
모든 검증 통과.
Rolldown과의 비교
ZTS의 번들러 설계에서 Rolldown은 가장 중요한 레퍼런스 중 하나다. esbuild가 "실전에서 검증된 알고리즘"이라면, Rolldown은 "최신 설계 사상"을 대표한다.
런타임 헬퍼 호환
핵심 차이는 __esm의 실행 순서 보장이다. esbuild는 lazy initialization으로 "호출될 때" 초기화하지만, Rolldown은 import 순서에 따라 "정해진 순서대로" 초기화한다. RN에서는 후자가 필수다.
tree-shaking 방식
ZTS는 esbuild의 export-level DCE에 Rolldown 스타일의 StmtInfo 기반 문장 수준 분석을 결합했다. 이것은 두 프로젝트의 장점을 취한 하이브리드 접근이다.
기능 비교 (번들러)
ZTS가 esbuild/Rolldown 대비 유일하게 지원하는 영역: React Native 플랫폼, Flow 타입 스트리핑, ES5 다운레벨링, Hermes 호환. 이것이 ZTS의 차별점이다.
4/7 시점 성능 스냅샷: ZTS는 얼마나 빠른가
아래 수치는 4/7일 기준이고, 이후 HMR 고도화와 mtime cache·lazy sourcemap 도입으로 계속 변하고 있다. "도달점"이 아니라 "중간 측정"으로 읽어달라.
번들러 벤치마크 (2,592 모듈 실측, 2026-03-31)
link와 emit에서 ZTS가 esbuild보다 빠르다. tree-shake에서 15배 느린 것은 StmtInfo 기반 정밀 분석의 대가다 — esbuild는 이 수준의 분석을 하지 않는다. 그리고 15ms는 전체 136ms 중 11%에 불과하다.
성능 최적화 히스토리
스모크 테스트 성능
143개 패키지, 평균 0.94x (esbuild 대비). 실패 0개.
0.94x는 esbuild보다 6% 빠르다는 뜻이다. 단일 파일 트랜스파일에서는 ZTS가 esbuild를 앞선다.
왜 빠른가
- Zig의 제로 오버헤드: GC 없음, 런타임 없음. Arena allocator로 한 번에 할당/해제
- 24바이트 고정 AST 노드: 캐시 라인에 최적화. 포인터 체이싱 최소화
- SIMD 공백 스킵 + 식별자 스캔:
@Vector(16, u8)로 16바이트 동시 처리 - emit 병렬화: 모듈별 transform + codegen을 스레드 풀에서 동시 실행
- Producer-Consumer 파이프라인: parse → resolve → spawn을 파이프라인으로 연결
esbuild와 1.24배 차이의 의미
esbuild는 Go로 작성된 7년차 프로젝트다. 2020년 출시 이후 수많은 실전 최적화를 거쳤다. ZTS는 Zig로 작성된 20일차 프로젝트다.
그런데 1.24배 차이밖에 나지 않는다.
- link 단계: ZTS가 3배 빠르다
- emit 단계: ZTS가 2배 빠르다
- scan 단계: 1.3배 느림 — SIMD 추가 최적화 여지가 있다
- tree-shake 단계: 15배 느림 — 하지만 esbuild보다 정밀한 분석을 한다
tree-shake를 esbuild 수준으로 단순화하면 즉시 1.0x 이하가 될 수 있다. 하지만 그것은 "올바른 최적화"가 아니다. 정밀한 tree-shaking은 번들 크기를 줄이고, 최종 사용자의 로딩 시간을 줄인다.
Rolldown/rspack과의 성능 비교
Rolldown과 rspack은 Rust로 작성되었다. 벤치마크 스위트에서 7개 도구(esbuild, SWC, oxc, webpack, rspack, rolldown, Bun)를 비교한다.
ZTS의 포지션: esbuild급 속도 + Rolldown급 기능 + RN 전용 최적화.
숫자로 보는 전체 프로젝트 (4/7 기준)
지금까지 20일, 그리고 계속
4/7까지의 20일 지점에서 Test262 100%, kangax ES5 99%, Hermes 구문 검증 통과, esbuild 1.24배라는 숫자가 나왔다. 하지만 이건 "완성"이 아니라 **"실전에 태우기 전의 기준선"**이다.
이 시점에서 확인된 것:
- Zig로 만든 JS/TS 트랜스파일러는 빠르다
--platform=react-native로 RN 번들을 만들 수 있다- Hermes에서 크래시 없이 구동된다
확인되지 않은 것, 그래서 다음 편이 필요한 이유:
- 실제 RN 앱의 HMR 업데이트 지연이 Metro 수준인가
- Vite/Rollup 플러그인을 얼마나 깊이 수용할 수 있는가
- Reanimated worklet처럼 Babel 의존적인 생태계와 어떻게 접붙일 것인가
- scope hoisting 번들러가 Metro의 extraNodeModules/blockList/resolveRequest를 호환 설정으로 소화할 수 있는가
- ES5 99% 중 남은 1%와, 그 뒤에 숨은 edge case들
4/8 이후의 16일이 이 질문들에 답하는 구간이다 — 3-11. 실전 대응 16일: HMR, Worklet, Metro 심화 로 이어진다.