- 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% 가 정적으로 컴파일됨
- 실행 방식은 세 단계로 명시적으로 구분됨
- 정적 컴파일: 기본 모드이며 JavaScript 엔진 없이 네이티브 코드로 변환함
- 동적 실행:
--dynamic을 지정하면 약 620KB의 quickjs-ng를 포함해 npm 패키지의 JavaScript와any타입 코드를 실행함- 정적 코드로 넘어오는 모든 값을 런타임에 검증함
- 선언된 타입과 값이 다르면 메모리를 손상시키지 않고 잡을 수 있는
TypeError를 던짐
- 거부: 처리할 수 없는 코드는 오류 코드, 코드 프레임, 대체로 수정 힌트를 제공하며 조용히 잘못 컴파일하지 않음
지원하는 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 의존성을 사용하지 않음
- 리다이렉트, gzip,
npm 의존성과 동적 실행
--dynamic에서는 Node의 모듈 해석 방식을 사용하고, 패키지가 제공하는.d.ts를 기준으로 타입 검사함- npm 패키지의 JavaScript는 빌드 시 바이너리에 포함되므로 실행 중에는
node_modules를 읽지 않음 scriptc coverage --dynamic은 각 문장이 정적·동적 영역 중 어디서 실행되는지와 남은 차단 요소를 표시함- JavaScript 엔진은 명시적으로 동적 모드를 선택할 때만 포함되므로 바이너리 크기가 조용히 늘어나지 않음
정확성과 메모리 안전성
- 차등 테스트는 800개 이상의 프로그램을 Node와 네이티브 바이너리에서 각각 실행해 stdout, stderr, 종료 코드를 바이트 단위로 비교함
- 숫자 출력은 최단 왕복 표현을 따르며, 100만 개의
double을 Node와 비교해 퍼징 검증함 - 서버는 두 구현 모두에 실제 클라이언트 드라이버를 연결해 테스트함
- 숫자 출력은 최단 왕복 표현을 따르며, 100만 개의
- 전체 테스트 집합을 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에서 확인할 수 있음
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을 보존함
- 모든 기능은 차등 테스트와 함께 추가되며, 일반 테스트와 메모리 안전성 테스트가 모두 통과해야 병합할 수 있음