# Vercel의 Scriptc - 바이너리에 JavaScript 엔진을 포함하지 않는 TypeScript 네이티브 컴파일러

> Clean Markdown view of GeekNews topic #31884. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=31884](https://news.hada.io/topic?id=31884)
- GeekNews Markdown: [https://news.hada.io/topic/31884.md](https://news.hada.io/topic/31884.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-07-28T06:33:27+09:00
- Updated: 2026-07-28T06:33:27+09:00
- Original source: [github.com/vercel-labs](https://github.com/vercel-labs/scriptc)
- Points: 1
- Comments: 1

## Topic Body

- **scriptc**는 일반 TypeScript를 Node·V8·JavaScript 엔진 없이 실행되는 소형 네이티브 바이너리로 컴파일하며, 실제 TypeScript 컴파일러의 타입 검사와 Node 동작 호환성을 유지함
- 코드 구조별로 정적 컴파일 가능 여부를 판정해 기본적으로 네이티브 코드로 만들고, `--dynamic`을 선택한 경우에만 **quickjs-ng**로 npm 패키지의 JavaScript와 `any` 타입 코드를 실행함
- 클래스·제네릭·`async`/`await`·예외·정규식부터 Node 서버 API, `fetch`, npm 의존성까지 지원하며, 미지원 구문은 오류 코드·코드 프레임·수정 힌트와 함께 거부함
- 800개 이상의 프로그램을 Node와 네이티브 바이너리에서 실행해 출력과 종료 코드를 비교하고, **AddressSanitizer**와 참조 횟수 감사로 메모리 오류를 검사함
- Apple M 시리즈 측정에서 시작 시간은 약 **2.4ms**, 정적 바이너리는 170~200KB, 일반적인 RSS는 1~4MB이며, 동적 모드와 내장 의존성을 포함하면 바이너리는 약 3MB가 됨

---

### 정적 컴파일 모델
- 별도 방언이나 어노테이션 없이 기존 TypeScript를 사용하며, `tsconfig.json`의 검사 엄격도와 TypeScript의 실제 `es2025` 라이브러리를 적용함
  - 프로젝트에 `@types/node`가 있으면 함께 타입 검사함
  - lowering이 없는 도달 가능 코드는 정확한 진단을 내고 컴파일을 중단함
- `scriptc coverage`는 분석한 문장 수, 정적으로 컴파일되는 비율, 차단 요소와 오류 코드를 보여줌
  - 예시에서는 4,481개 문장 중 4,451개인 **99%** 가 정적으로 컴파일됨
- 실행 방식은 세 단계로 명시적으로 구분됨
  1. **정적 컴파일**: 기본 모드이며 JavaScript 엔진 없이 네이티브 코드로 변환함
  2. **동적 실행**: `--dynamic`을 지정하면 약 620KB의 [quickjs-ng](https://github.com/quickjs-ng/quickjs)를 포함해 npm 패키지의 JavaScript와 `any` 타입 코드를 실행함
     - 정적 코드로 넘어오는 모든 값을 런타임에 검증함
     - 선언된 타입과 값이 다르면 메모리를 손상시키지 않고 잡을 수 있는 `TypeError`를 던짐
  3. **거부**: 처리할 수 없는 코드는 오류 코드, 코드 프레임, 대체로 수정 힌트를 제공하며 조용히 잘못 컴파일하지 않음

### 지원하는 TypeScript와 표준 라이브러리
- 언어 기능은 단일 상속 클래스와 동적 디스패치, 클로저, 제네릭 단형화, 판별 유니온, 구조 분해, spread, 템플릿 리터럴, getter/setter와 반복자를 지원함
  - 안전성이 증명되면 동적 디스패치를 비가상화함
  - 판별 유니온은 TypeScript의 narrowing을 이용한 태그 값으로 처리함
  - `async`/`await`는 스택풀 파이버와 JavaScript에 맞춘 스케줄링으로 구현함
  - 예외와 `finally`, 선택·기본·나머지 매개변수를 지원함
- 정규식은 QuickJS가 사용하는 것과 동일한 **ECMAScript 호환 바이트코드 인터프리터**를 사용하며, 정규식을 쓰는 바이너리에만 링크함
- 표준 라이브러리는 UTF-16 의미론을 지키는 문자열, JavaScript와 동일한 순서·동일성 규칙을 가진 배열·Map·Set을 포함함
  - `JSON`의 타입 변환은 런타임 검증을 거침
  - `Math`, typed array, `Buffer`, 타입화된 `catch`를 지원하는 `Error` 계층도 제공함

### Node 및 웹 API
- Node API는 `fs`, `path`, `process`, `child_process`, `os`, `crypto`, `url`/`URL`, `zlib`, 타이머와 시그널 처리기를 지원함
  - `fs`는 동기 및 Promise API를 제공함
  - `child_process`는 파이프 스트림을 지원함
  - 이벤트 루프에는 외부 의존성이 없음
- 서버 스택은 `net`, `http`, `https`, `tls`, `dgram`, `dns`, `fs.watch`, `readline`을 포함하며 실제 프록시 서버를 컴파일할 수 있음
  - TLS에는 포함된 **mbedTLS**를 사용함
- `fetch`와 streams, `Headers`, `AbortSignal` 등 WHATWG 웹 API 일부를 같은 네이티브 네트워크·TLS 스택 위에 구현함
  - 리다이렉트, gzip, `AbortSignal.timeout`, Node 형태의 오류 원인을 지원함
  - libcurl이나 시스템 HTTP 의존성을 사용하지 않음

### npm 의존성과 동적 실행
- `--dynamic`에서는 Node의 모듈 해석 방식을 사용하고, 패키지가 제공하는 `.d.ts`를 기준으로 타입 검사함
- npm 패키지의 JavaScript는 빌드 시 바이너리에 포함되므로 실행 중에는 `node_modules`를 읽지 않음
- `scriptc coverage --dynamic`은 각 문장이 정적·동적 영역 중 어디서 실행되는지와 남은 차단 요소를 표시함
- JavaScript 엔진은 명시적으로 동적 모드를 선택할 때만 포함되므로 바이너리 크기가 조용히 늘어나지 않음

### 정확성과 메모리 안전성
- **차등 테스트**는 800개 이상의 프로그램을 Node와 네이티브 바이너리에서 각각 실행해 stdout, stderr, 종료 코드를 바이트 단위로 비교함
  - 숫자 출력은 최단 왕복 표현을 따르며, 100만 개의 `double`을 Node와 비교해 퍼징 검증함
  - 서버는 두 구현 모두에 실제 클라이언트 드라이버를 연결해 테스트함
- 전체 테스트 집합을 AddressSanitizer와 참조 횟수 감사 아래에서 다시 실행하며, 누수와 use-after-free가 있으면 빌드가 실패함
- Node와 의도적으로 다른 동작은 수십 건이며 주로 타이밍 내부 구현과 오류 객체 속성에 관련됨
  - 각 차이는 문서화되고 번호가 부여되며, 숨겨진 차이는 허용하지 않음

### 성능 특성
- Apple M 시리즈에서 Node, Go, Rust, Zig와 동일한 작업 및 바이트 단위로 같은 출력을 기준으로 측정함
- 시작 시간은 약 **2.4ms**로 Node의 약 47ms보다 짧고 Zig와 비슷하며 Go·Rust보다 앞섬
- 정적 바이너리 크기는 170~200KB이고, `--dynamic`과 내장 의존성을 포함하면 약 3MB임
  - 비교 대상으로 제시된 Go 바이너리는 약 2MB, Node SEA는 60~100MB임
- 일반적인 메모리 사용량은 RSS 1~4MB이며 Node는 67~116MB임
- 런타임은 JavaScript에 맞춘 `f64` 의미론을 유지하면서 대부분의 작업에서 시스템 언어와 경쟁함
  - 정수 추론과 소유권 분석은 로드맵에 포함됨

### 명시적 탈출구
- `comptime(() => ...)`은 컴파일러 내부의 격리된 VM에서 TypeScript를 빌드 시점에 실행하고 결과를 리터럴로 바이너리에 넣음
- `--ffi`는 시그니처만 있는 TypeScript 선언을 C ABI 호출에 직접 연결하고, 매니페스트에 선언한 아카이브·객체·시스템 라이브러리를 링크함
  - 경계는 명시적이며 길이 정보가 포함됨
  - 자세한 방식은 [Native FFI guide](https://scriptc.dev/ffi)에서 확인할 수 있음
- `JSON.parse(...) as Config` 같은 **검사된 타입 단언**은 런타임 검증 코드를 삽입함
  - 검증 실패 시 `expected number at $.port, got string`처럼 잘못된 경로와 기대·실제 타입을 담은 예외를 던짐

### 컴파일러 구조
- 처리 과정은 `TypeScript → tsc 파싱·타입 검사 → lowering → typed IR → C → clang → 네이티브 실행 파일` 순서임
- `packages/compiler`는 tsc API 기반 프런트엔드, IR 검증·직렬화, LLVM 및 C 백엔드를 포함함
  - IR만 프런트엔드와 백엔드 사이의 인터페이스로 사용함
  - LLVM이 기본 코드 생성기이며 지원 범위 밖 프로그램에는 투명한 대체 경로를 사용함
  - C는 영구적인 참조 백엔드로 유지되고, `--backend c`로 소스 행 정보가 있는 읽기 가능한 결과를 생성함
- `packages/runtime`은 참조 횟수 기반 값과 순환 수집기, 스택풀 파이버, `kqueue` 이벤트 루프, 서버 스택, JavaScript 호환 숫자 출력을 구현함
  - 기능별 링크 방식을 사용해 실제 사용하는 기능만 바이너리에 포함함
- `packages/cli`는 `scriptc build`, `scriptc run`, `scriptc coverage` 명령을 제공함

### 설치와 개발
- `npm install -g scriptc`로 설치하며 **clang**이 필요함
- 주요 플랫폼은 macOS arm64이고, Linux와 Windows 바이너리는 교차 컴파일함
  - 각 플랫폼은 별도의 차등 테스트 경로로 검증함
- 개발은 `pnpm install && pnpm build`로 시작함
  - `pnpm test`는 차등 테스트 집합과 진단 스냅샷을 실행함
  - `SCRIPTC_SAN=1 pnpm test`는 같은 테스트를 ASan과 참조 횟수 감사 아래에서 실행함
  - `pnpm scriptc build x.ts --emit-ir`은 생성된 C와 `x.ir.json`을 보존함
- 모든 기능은 차등 테스트와 함께 추가되며, 일반 테스트와 메모리 안전성 테스트가 모두 통과해야 병합할 수 있음

## Comments



### Comment 62486

- Author: neo
- Created: 2026-07-28T06:33:31+09:00
- Points: 1

###### [Hacker News 의견들](https://news.ycombinator.com/item?id=49063175) 
- Vercel은 한 달에 한 번꼴로 화제성 프로젝트를 내놓으며 신뢰성과 존재감을 유지하려는 듯함. 진지한 회사나 프로젝트가 **scriptc**를 쓸 것 같지는 않음  
  기여자들은 존중하지만, 코드는 Claude로 생성한 티가 강한데 Claude가 기여자로 표시되지 않아 더 의심스러움
  - 정말 엄청난 규모의 기여임 ;-) [커밋](https://github.com/vercel-labs/scriptc/commit/725b931cf43ba71ec154e378146fe274df0e8f65)
  - 같은 바이브 코딩 주도자가 Vercel이 5월에 대대적으로 공개한 “에이전트를 위한 프로그래밍 언어” **zerolang**도 이끌었음. 1,200개 커밋을 올린 뒤 6월 중순쯤 개발이 멈춤  
    [프로젝트](https://github.com/vercel-labs/zerolang) / [관련 HN 글](https://news.ycombinator.com/item?id=48193539)
  - Simon Willison은 [README만 수정한 듯하며](https://github.com/vercel-labs/scriptc/commits/main/?author=simonw), 프로젝트에 실질적으로 참여하지 않은 것으로 보임
  - 기여 내역을 보면 **한 사람이 99%가량 바이브 코딩**한 듯하며, 컴파일러 관련 배경도 없어 보임
  - 많은 SaaS 제품이 Vercel과 협력하고 있으며, 개발 도구에서는 **Next.js와 React**가 최상위 SDK로 취급됨

- [Porffor](https://porffor.dev)는 한동안 같은 목표를 추구해 왔음. 개발자인 [CanadaHonk](https://honk.foo)는 대단히 뛰어나지만 프로젝트는 여전히 Test262의 약 **68%만 통과**함  
  프로젝트 범위를 잘못 이해한 게 아니라면 Vercel이 이렇게 빠르게 진척을 낸 방식은 상당히 의심스러움
  - 코딩 에이전트 덕분임. 단 일주일 만에 [**91만 8천 줄**을 추가함](https://github.com/vercel-labs/scriptc/graphs/contributors?selectedMetric=additions)

- 전형적인 Vercel식 프로젝트임. 공개한 지 5일, 전부 바이브 코딩, 이유 없이 별 1,500개를 받았지만 누구의 문제도 해결하지 않으며, 길어야 몇 달 뒤에는 유지보수가 중단될 듯함

- 비판만 하기보다 로컬의 여러 프로젝트에 직접 적용해 봤지만, 모두 코드 범위 분석에서 **수백 개 오류**가 발생해 사실상 쓸 수 없었음  
  외부 라이브러리 없이 처음부터 작성하면 바이너리로 컴파일할 수 있겠지만, 그렇다면 Rust, Go, Zig, D, C, V, Ada, C++, Nim, Swift, Kotlin Native, Haskell처럼 애초에 제대로 된 컴파일을 목표로 설계된 언어를 쓰지 않을 이유가 없음

- TypeScript의 장점은 표현력뿐 아니라 거대한 **npm 생태계와의 호환성**임. 대부분의 패키지는 타입 선언으로 인터페이스만 정의하고 실제 코드는 JavaScript로 배포하므로, 패키지를 사용하려면 현실적으로 JavaScript 엔진이 필요함  
  처음부터 새로 시작하고 npm 패키지를 전혀 쓰지 않을 계획이라면 [AssemblyScript](https://www.assemblyscript.org/)를 쓰는 편이 나음. Node는 [TypeScript로 패키지를 배포하지 말라고 명시적으로 권고](https://github.com/nodejs/node/blob/main/doc/api/typescript.md#type-stripping-in-dependencies)하는데, TypeScript는 부 버전 사이에서도 하위 호환되지 않고 컴파일러 설정 역시 패키지 간 이식성이 없기 때문임
  - scriptc는 이런 의존성을 실행해야 할 때 선택적으로 **620KB quickjs-ng 엔진**을 번들에 넣는 방식으로 해결하는 듯함
  - 의존성이 많은 코드보다는, 더 큰 TypeScript 프로젝트와 코드를 공유해야 하는 **용도가 명확한 명령줄 도구**에 사용하고 싶음
  - 타입 없는 라이브러리를 배포해야 할 근거는 되지만, 그 이유가 타입의 가치를 능가한다는 결론은 이해하기 어려움

- 이런 프로젝트 하나면 HN 같은 서비스의 첫 페이지에 계속 노출될 수 있음. 토큰을 투자해 아무도 원하지 않는 그럴듯한 프로젝트를 만들고 공개로 도달 범위를 넓힌 뒤 반복하는 **성장 전략**임  
  12개월 뒤에는 오픈소스 프로젝트의 90%가 실제 사용자 없이 흥미로워 보이기만 하는 바이브 코딩 결과물이 될 수도 있음. 이제 완전한 컴파일러도 쉽게 생성할 수 있지만 핵심은 장기 유지보수와 공동체이며, 눈길을 끄는 제목만으로 사용자가 남지는 않음  
  Vercel이 진지하다면 실제 비용과 위험을 감수해 자사의 **실험적 런타임**으로 채택해야 함

- 아주 훌륭한 문제 영역임. AI로 런타임 코드를 최적화하는 컴파일러를 만드는 비슷한 작업을 Zod에 적용했음: [zod-compiler](https://github.com/gajus/zod-compiler)  
  Zod 스키마를 빌드 시점에 단순 불리언 연산 체인으로 컴파일해 코드 변경 없이 **2~74배 빠르게** 만들며, 플러그인이 Zod 호출을 컴파일된 파싱으로 교체함. 최적화 대부분은 Claude가 100회 넘게 반복하며 작성했음  
  실제 Zod와 결과를 비교할 수 있어 정확성을 주관적으로 판단할 필요가 없다는 점이 scriptc와 같음. 기준 구현과 벤치마크가 있는 컴파일러, 직렬화 도구, 포매터, 질의 계획기 등에 모두 적용 가능함

- Claude로 [scriptc와 Node 벤치마크](https://github.com/EliasWatson/Primes/tree/typescript-scriptc/PrimeTypeScript/solution_3)를 실행했음. 가장 유리한 바이트 배열 결과에서도 scriptc는 전용 최적화 후 **Node 24보다 약 7.5배 느림**  
  대신 실행 파일 시작은 12배 빠르고(1.5ms 대 18.6ms), 메모리는 72배 적게 사용하며(2.5MiB 대 181MiB), 런타임 의존성 없는 단일 370KB 실행 파일로 만들어짐

- 작고 빠른 네이티브 실행 파일이 필요하다는 점을 인정한 것은 좋지만, Java가 수십 년간 겪은 과정을 보면 실용성에는 회의적임. 1990년대의 **GCJ**는 기술적으로 괜찮았어도 생태계 지원이 없었음  
  이후 GraalVM Native가 문제를 더 종합적으로 다루면서 주요 라이브러리와 프레임워크가 호환성 확보에 나섰지만, 지금도 간단한 기존 애플리케이션조차 네이티브로 완벽히 실행하기가 매우 어려움. scriptc 같은 시도는 반갑지만 실용화까지 길고 험난할 듯함
  - Graal 팀도 비슷한 **메타 인터프리터 접근법**을 시도한 것으로 알고 있음. 네이티브 실행이 처리할 수 없는 동적 바이트코드 로딩이나 리플렉션은 Espresso Java 구현으로 해석하려 했음
  - GCJ는 늘 프로토타입에 가까웠음. 진지한 사용자라면 Excelsior JET, BEA JRockit 등 AOT 도구를 갖춘 상용 JDK를 구매했을 것임  
    Excelsior가 사라진 이유 중 하나는 GraalVM과 OpenJ9이 무료로 제공되기 때문일 가능성이 큼. PTC와 Aicas는 관심받지 못하는 임베디드·실시간 고객층 덕분에 여전히 잘 운영되고 있음

- 개발이 지속된다면 **.NET AOT급의 큰 성과**가 될 잠재력이 있음. 공개된 지 며칠뿐이라 지금은 가볍게 시험하는 정도지만, 버려지지 않고 계속 발전한다면 생태계에 상당한 도움이 될 수 있음  
  AI로 만든 코드도 사람이 만든 코드처럼 품질 범위가 넓음. 중요한 소프트웨어라면 직접 작성할 때와 같은 기준으로 생성하고 모든 코드를 검토해야 하며, 그렇게 쓴다면 훌륭한 방식임. 검토가 부족한 프로젝트는 품질과 개발자의 책임감이 낮을 가능성이 있어 채택하기가 더 어려움  
  온라인 논의는 “전부 AI로 생성”과 “절대 AI를 쓰지 않음”이라는 양극단으로 흐르지만, 현실에서는 **사려 깊은 판단을 가속하는 중간 지점**이 합리적임. 그렇지 않은 소프트웨어는 낮은 품질이나 방치될 위험 때문에 사용을 꺼리게 됨
