Lobsters

Friendship ended with Deno, now Node is my best friend

Deno와의 우정은 끝났고, 이제 Node가 가장 친한 친구입니다

Deno를 주로 쓰던 저자가 SvelteKit 프로젝트와 정적 사이트 생성기를 Node.js로 옮긴 경험을 공유합니다. Node.js v26.10.0에서 마이그레이션 작업은 적었고 빌드 시간은 15% 줄었지만, Deno의 권한 관리와 편리한 기본 기능을 선호하는 개발자들의 반론도 나왔습니다.

AI 요약

Deno를 오랫동안 주 런타임으로 사용한 저자는 최근 SvelteKit 프로젝트를 Node.js로 개발하며 달라진 점을 확인했습니다. ECMAScript 기능 지원이 늘었고 예전의 불편한 API도 개선됐으며, CommonJS의 require()를 마주칠 일도 없었다고 합니다. 정적 사이트 생성기도 Node.js v26.10.0으로 옮겼습니다. 이 글은 한 개발자의 사용 경험을 바탕으로 Node.js의 변화와 Deno를 떠난 이유를 설명합니다.

패키지 관리와 공급망 위험

저자는 Node.js 공식 문서가 NVM 설치 과정에서 인터넷의 스크립트를 내려받아 곧바로 셸에 전달하는 방식을 권한다고 지적합니다. 버전 관리는 Fast Node Manager(FNM)를 선택했고, 패키지 관리자로는 PNPM을 사용했습니다. 일부 스크립트가 npm, npx라는 실행 파일 이름을 전제로 해 셸 별칭으로 각각 pnpm, pnpx를 연결했습니다. 다만 글 수정에서 저자는 별칭이 스크립트 안에서 작동한다고 혼동했을 수 있다고 밝혔습니다. 설치 안내를 복사할 때는 여전히 편리하다고 덧붙였습니다.

PNPM의 설치 후 스크립트 차단 기능도 선택 이유로 들었습니다. 악성 패키지가 유포된 직후 바로 설치하지 않도록 pnpm-workspace.yaml에 minimumReleaseAge: 1440, trustPolicy: no-downgrade를 설정했습니다. 처음에는 최소 공개 대기 기간을 한 달로 두려 했지만, 적절한 의존성 버전을 찾기 어려워져 하루로 낮췄습니다. 하루 동안 다른 사용자가 새 버전을 먼저 검증하도록 하겠다는 취지입니다.

TypeScript 패키지와 Node.js의 제한

Node.js는 TypeScript 파일의 타입을 제거해 실행하는 기능을 지원하지만, node_modules 아래에 있는 파일에는 적용하지 않습니다. 글에 나온 오류는 ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING입니다. 저자는 이 제한을 기술적 문제라기보다 TypeScript 패키지 확산을 막으려는 Node.js의 정책으로 해석합니다. TypeScript 패키지를 그대로 배포할 수 없으므로 번들러를 찾아야 했고, tsdown을 사용하면서 설정 파일 두 개를 추가했습니다.

저자는 GitHub 대신 Forgejo를 직접 운영하고 있어, NPM에 배포한 패키지의 provenance가 사라지는 문제도 겪었습니다. 이를 해결하려 PNPM의 신뢰 정책에 자체 패키지를 허용하도록 설정했습니다. 반면 댓글에서는 NPM도 설치 시점 스크립트를 기본 비활성화하는 기능과 최소 공개 기간 설정을 제공한다고 지적했습니다. 다만 댓글 작성자는 기본 동작을 바꾸려면 NPM을 직접 업그레이드해야 하며, minimumReleaseAge는 초가 아니라 일 단위라고 설명했습니다.

정적 사이트 생성기 이전과 성능

실제 이전에서 필요한 코드는 많지 않았습니다. Deno의 파일 시스템 API를 node:fs로 바꾸고, Deno.serve를 Hono의 Node 어댑터로 대체했습니다. 경로 처리도 Deno의 @std/path에서 node:path로 바꿨습니다. 저자는 최소한의 변경만 한 상태에서도 빌드가 15% 빨라졌다고 보고했습니다. 코드에는 여전히 Deno 방식이 남아 있어 Node.js 내장 API를 더 활용하면 성능을 개선할 여지가 있다고 덧붙였습니다.

Deno를 떠난 이유와 다른 사용자의 반론

저자는 Deno가 런타임 개발보다 스타트업 제품에 집중하면서 혁신을 이어가지 못했다고 평가합니다. 직원 절반이 해고됐다는 주장도 덧붙였습니다. 직접 겪은 문제로는 몇 주간 고쳐지지 않은 ZSH 연동 오류, JSR의 잦은 429 (Too Many Requests) 응답, 여러 HTTP 요청이 동시에 들어올 때 Deno가 처리하지 못하는 버그를 들었습니다. JSR 지원팀은 계정 삭제 요청에는 빠르게 대응했지만, 패키지의 옛 버전은 계속 설치할 수 있다고 했습니다.

댓글에는 다른 런타임에서 Node.js로 돌아간 경험이 이어졌습니다. Bun 프로젝트를 Node.js로 옮긴 한 개발자는 Playwright 호환성 문제와 테스트 커버리지 데이터 오류를 겪었다고 했습니다. 특히 사용자에게 설명 없이 진행된 Rust 포팅이 신뢰를 잃은 계기였다고 밝혔습니다. 비상시 Node.js로 옮길 수 있게 설계해둔 덕에 전환은 순조로웠다고 합니다. 다른 참여자는 Bun의 Playwright 문제가 최근에야 해결됐다고 덧붙였습니다.

Deno를 계속 쓰겠다는 반론도 나왔습니다. 한 댓글 작성자는 Deno의 JSX 번들러가 사전 컴파일 최적화를 제공하고, npm:foo 형식으로 패키지를 불러오면 package.json이나 node_modules 없이 짧은 스크립트를 작성할 수 있다고 설명했습니다. 저자가 Node.js에서 확인한 15% 성능 향상은 해당 작업의 결과일 뿐이며, 다른 벤치마크에서는 Deno가 더 빠르다고도 지적했습니다. Deno의 권한 관리가 장점이라는 의견도 나왔습니다. Node.js의 권한 기능을 언급한 댓글에 다른 참여자는 특정 호스트나 IP를 허용하는 기능이 있는지 질문했습니다.

Lobsters 반응

  • @gcollazo — 최근 Bun 프로젝트를 Node.js로 옮겼습니다. Bun을 선택할 때는 내장 TypeScript 지원, 현대적인 표준 라이브러리와 테스트 프레임워크, 빠른 성능, NPM 생태계와의 호환성에 끌렸습니다. 실제로는 주요 의존성인 Playwright에서 여러 문제를 겪었고, 테스트 커버리지 데이터도 틀렸습니다. 결정적으로, 하룻밤 사이에 Rust로 포팅한 일을 보고 프로젝트에 대한 신뢰를 잃었습니다. Rust를 싫어해서가 아니라, 실제 소프트웨어를 운영하는 사용자에게 책임을 다하지 않았기 때문입니다. 비상시에 Node.js로 옮길 수 있도록 만들어둔 덕에 전환했고, 후회하지 않습니다.
    • @abrambleninja — JavaScript 생태계 바깥에서 봐도 문제는 브라우저 밖의 JavaScript 환경마다 표준이 다르다는 점 같습니다. 이 글에서 Deno의 파일 시스템 API와 HTTP 서버 기능을 언급한 것처럼, Node.js와 Deno는 모두 V8을 사용하지만 백엔드용 API는 각자 다릅니다. Bun은 Safari에서 쓰는 JavaScriptCore를 사용하지만, 주요 차이는 시스템 API 쪽일 것 같습니다.
    • @kv — Bun은 Playwright 문제를 최근에야 고친 것으로 압니다. 저도 그 문제 때문에 고생했습니다.
  • @zetashift — 몇 년 전 JavaScript 빌드 도구에 빠져 ESM과 CJS, TypeScript 지원, 느린 개발 시간을 파고들며 Vite, ESBuild, Deno, Turbopack, CSS 도구를 써봤습니다. 재미는 있었지만 실제로 나아진 점은 별로 없었습니다. Bun을 써보고 Node.js의 안정성을 당연하게 여겼다는 걸 깨달아 돌아왔습니다. Node.js v20부터 v22 즈음에 좋은 기능이 들어오면서 빠르게 바뀌는 런타임을 따로 써야 할 필요도 줄었습니다. Deno가 좋은 기본 설정을 많이 제공한다는 점은 여전히 좋고, 오픈소스 Deno가 Biome이나 Servo처럼 이어질 수도 있다고 봅니다.
  • @matthiasportzel — 최근 Deno를 즐겁게 쓰고 있습니다. JSX 번들러가 사전 컴파일이라는 최적화로 다른 도구보다 나은 JavaScript를 생성합니다. 짧은 스크립트에서 npm:foo를 가져오면 package.json이나 node_modules 없이 쓸 수 있는 점도 편리합니다. deno.json의 import 블록이 브라우저 import map과 닮은 점도 좋아합니다. Node.js가 낫다는 점을 더 자세히 다루지 않고 한 작업의 빌드가 15% 빨라졌다고만 한 부분은 아쉽습니다. 다른 작업에서는 Deno가 더 빠른 결과도 있습니다. ZSH 연동 문제나 동시 요청 버그도 구체적인 설명이 없어 궁금합니다.
  • @lilac — 인터넷에서 스크립트를 받아 Bash에 바로 전달하는 게 왜 문제인가요? 어차피 인터넷에서 임의의 코드를 내려받아 실행할 텐데, 처음에 셸 스크립트가 있느냐가 무슨 차이인가요?
    • @dbushell — 내용을 확인하지 않고 바로 Bash에 전달하는 방식은 좋지 않습니다. 웹 호스팅이 약한 지점이 될 수 있습니다.
    • @notgull — 인터넷의 임의 명령을 셸에 바로 전달하는 방식에 프로그래머를 익숙하게 만든 건 오래된 실수입니다. 개발자를 노린 악성코드가 이 습관을 악용하는 경우가 꽤 있습니다.
  • @leela — NPM 설치 시점의 라이프사이클 스크립트는 allow-scripts 설정으로 비활성화할 수 있습니다. 최소 패키지 공개 기간도 min-release-age로 설정할 수 있습니다.
  • @bakkot — 그렇지 않습니다. NPM도 이제 설치 후 스크립트를 비활성화하고 최소 공개 기간을 설정할 수 있습니다. 다만 경고만 보이는 상태가 아니라 기본 비활성화 동작을 쓰려면 NPM을 직접 업그레이드해야 합니다. 이 기능은 Node.js 26에 포함되기에는 NPM 12 출시가 늦었습니다. NPM의 최소 공개 기간 설정도 생겼지만, 단위가 초가 아니라 일인 점은 답답합니다. 그 밖에도 util.parseArgs, util.styleText, --env-file, node:test, fetch, glob, SQLite, 실험 단계인 ZIP 지원이 있습니다. 대부분의 애플리케이션에서는 의존성이 필요 없거나 Express 정도면 충분합니다.
  • @KevinMGranger — Node.js에 Deno의 권한 시스템이 추가되지 않는다면 옮길 이유가 없습니다.
    • @chenghiz — Deno의 권한 시스템은 실제로 도움이 되기에는 세밀하지 않은 것 같습니다. 권한 기능을 낮은 수준으로 구현한 정도로 보입니다. 실제로 유용하게 쓰시나요?
    • @KevinMGranger — 특정 호스트나 IP만 네트워크에 접근하도록 제한할 수 있습니다. 제가 원하는 기능은 그 정도면 충분합니다. 파일 시스템 제한도 쓸 수 있겠지만, 실제 작업은 어차피 컨테이너에서 실행하겠습니다.
  • @zem — 2026년에 TypeScript를 지원하지 않으려고 일부러 나선 도구를 왜 쓰고 싶겠습니까?

원문: dbushell.com / 번역·요약: Trawling