TECH NOTE · AUTOMATION OPS
Node 22는 –experimental-strip-types로 TypeScript를 빌드 없이 실행합니다. 테스트도 node –test가 내장이라 프레임워크가 필요 없지만, 타입을 지우기만 하므로 enum은 못 씁니다.
예제에 빌드 단계를 넣으면 예제가 아니다
블로그 글에 딸린 코드 예제를 만들고 있었다. 원칙이 하나 있었다 — 받아서 한 줄로 돌아가야 한다.
TypeScript로 쓰고 싶었는데 여기서 막혔다. tsc를 넣으면 npm install이 앞에 붙고, package.json과 tsconfig.json이 따라오고, node_modules가 생긴다. 판정 로직 100줄을 보여주려고 그걸 다 받게 할 수는 없다.
Node 22가 이걸 해결한다.
node --experimental-strip-types sheet.ts
빌드 단계가 없다. 설정 파일도 없다.
타입을 지우고 실행한다
이름 그대로다. --experimental-strip-types는 타입 표기를 지우고 남은 JavaScript를 실행한다.
export interface FrameRect {
readonly x: number;
readonly y: number;
}
export function resolveFrames(
manifest: SpriteManifest,
state: string,
): readonly FrameRect[] | null {
return manifest.layout[state] ?? null;
}
interface는 통째로 사라지고, 함수의 타입 표기(: SpriteManifest, : readonly FrameRect[] | null)도 지워진다. 남는 건 함수 본문뿐이다.
타입 검사를 하지 않는다. 지우기만 한다. 타입이 틀려도 그냥 돌아간다. 검사가 필요하면 tsc --noEmit을 따로 돌려야 한다.
Node 22.6부터 들어갔고, 22.18부터는 플래그 없이도 동작한다. 확인은 이렇게 한다.
node --version
테스트도 내장이다
여기가 실제로 편한 부분이다. Node에 테스트 러너가 들어 있어서 Jest나 Vitest를 안 받아도 된다.
import { test } from "node:test";
import assert from "node:assert/strict";
import { resolveFrames } from "./sheet.ts";
test("행을 넘는 상태에서 계산이 깨진다", () => {
const actual = resolveFrames(SAMPLE, "walk")!;
assert.equal(actual[0].y, 512);
assert.equal(actual[4].y, 512 * 5);
});
돌리는 명령이다.
node --experimental-strip-types --test sheet.test.ts
출력은 TAP 형식이라 그대로 읽힌다.
# pass 10
# fail 0
의존성 0개로 타입이 있는 코드에 테스트까지 붙는다.
걸리는 곳 두 군데
편하지만 제약이 있다. 모르고 쓰면 왜 안 되는지 한참 헤맨다.
`enum`을 못 쓴다
타입을 지우기만 하므로, 런타임에 코드를 만들어 내는 문법은 통하지 않는다.
enum Owner { UI, CHARACTER } // 안 된다
enum은 지운다고 사라지는 게 아니라 실제 객체로 컴파일되어야 한다. 지우면 Owner가 존재하지 않게 되므로 Node가 거부한다.
같은 이유로 파라미터 프로퍼티(constructor(private x: number))와 네임스페이스도 안 된다.
대신 이렇게 쓴다.
export const Owner = {
UI: "ui",
CHARACTER: "character",
} as const;
객체 리터럴은 그냥 JavaScript라 지울 것이 없다.
`import` 경로에 확장자를 적어야 한다
import { resolveFrames } from "./sheet"; // 안 된다
import { resolveFrames } from "./sheet.ts"; // 이렇게
번들러를 쓸 때는 확장자를 생략하는 게 관행이라 손이 먼저 나간다. Node는 ESM 규칙을 따르므로 경로를 그대로 해석하고, 확장자가 없으면 파일을 못 찾는다.
.js로 적는 것도 안 된다. 실제 파일이 .ts이기 때문이다. 이 점이 tsc의 관행과 반대라 헷갈린다.
어디에 쓸 만한가
예제와 작은 도구에 맞는다. 받는 사람이 아무것도 설치하지 않아도 되는 것이 핵심 가치다.
실무 프로젝트에는 권하기 어렵다. 타입 검사를 안 하므로 CI에서 tsc --noEmit이 따로 필요하고, enum을 쓰는 기존 코드가 있으면 걸린다.
정리하면 이렇다.
| 상황 | 맞나 |
|---|---|
| 블로그 예제, 저장소 샘플 | 맞다 |
| 스크립트 한두 개짜리 도구 | 맞다 |
| 타입 검사가 필요한 프로덕션 코드 | tsc를 따로 돌려야 한다 |
enum을 쓰는 기존 코드베이스 |
고쳐야 한다 |
정리
--experimental-strip-types는 타입을 지우고 실행한다. 검사하지 않는다node --test가 내장이라 테스트 프레임워크가 필요 없다enum·파라미터 프로퍼티·네임스페이스는 못 쓴다. 런타임 코드를 만들어 내기 때문이다import경로에.ts확장자를 적어야 한다- 의존성 없이 배포할 예제에 특히 맞는다
“받아서 한 줄로 돌아간다”를 지키려면 빌드 단계를 없애야 하는데, TypeScript를 포기하지 않고도 그게 된다.