Deno 2.0에서 달라진 것: npm 호환 범위, JSR, 워크스페이스, Node에서 옮겨 갈 때

이 글의 핵심

Deno 2.0은 Node·npm과의 상호 운용을 전제로 한 메이저 전환입니다. package.json·node_modules·워크스페이스를 이해하고, JSR과 npm 명세자(specifier)로 의존성을 설계한 뒤, 공식 벤치마크와 그 한계를 함께 보며 마이그레이션 순서를 잡을 수 있습니다.

이 글의 핵심

Deno 2.0(2024년 10월 공개)은 기존 JavaScript 인프라와의 호환과, 단일 실행 파일로 제공되는 올인원 도구체인을 함께 노린 메이저 릴리스입니다. 이 글은 package.json·node_modules를 Deno가 어떻게 받아들이는지, npm:과 jsr: 의존성을 어떻게 나눠 쓰는지, 워크스페이스를 어떻게 구성하는지 살펴보고, 공식 벤치마크를 읽을 때의 주의점과 Node 프로젝트를 옮기는 순서를 정리합니다.

ECMAScript 모듈(ESM), package.json의 의존성 필드, 모노레포의 워크스페이스 개념을 알고 있다고 가정합니다.


Deno 2.0이 바꾼 전제

Deno 1.x가 URL 기반 원격 import와 설정 최소화를 강하게 내세웠다면, Deno 2.0은 팀 단위 채택과 기존 코드베이스를 염두에 두고 Node·npm 호환을 정식 기능으로 끌어올렸습니다. 그래서 기존 npm 자산을 그대로 소비하면서, 권한 모델이나 내장 도구, deno compile 같은 Deno 고유 기능은 필요한 곳에만 점진적으로 들이는 방식이 현실적인 선택지가 되었습니다.

주요 변경사항 요약

영역내용
Node·npm 호환package.json, node_modules, npm 워크스페이스 인식
패키지 관리deno install, deno add, deno remove로 의존성 설치·갱신
레지스트리JSR을 공식 생태계 축으로 제시, 표준 라이브러리는 JSR의 @std 스코프로 제공
비공개 npmNode와 같이 .npmrc로 프라이빗 레지스트리·스코프 설정
워크스페이스deno.json의 workspace로 멀티 패키지 관리, npm 워크스페이스와 혼합 가능
도구deno fmt의 HTML·CSS·YAML 지원, deno lint의 Node 관련 규칙과 자동 수정, deno test의 node:test 지원, deno task의 package.json 스크립트 실행
릴리스 모델2.1부터 LTS 채널 도입(지원 기간은 공식 문서의 정책을 확인)

이 중 npm 호환과 JSR은 의존성 설계를 바꾸는 축이므로 아래에서 따로 설명합니다.


npm 패키지 호환성: 무엇이 그대로 되고 무엇이 조건부인가

Deno 2.0은 npm 패키지를 Deno 런타임에서 해석할 수 있도록 모듈 해석과 Node API 호환 층을 강화했습니다. 다만 모든 패키지가 수정 없이 동작한다는 뜻은 아니며, Node 내장 모듈의 세부 동작에 기대는 코드와 네이티브 애드온(Node-API)은 사전 검증이 필요합니다.

npm: 명세자(specifier): 매니페스트 없이 npm 쓰기

작은 스크립트에서는 package.json 없이도 npm 패키지를 직접 가져올 수 있습니다. 매니페스트가 없으면 Deno는 이런 의존성을 전역 캐시에 두므로, 디렉터리에 node_modules를 만들지 않고 실행됩니다.

// 버전을 명세자에 고정하면 재현성이 좋아집니다.
import chalk from "npm:[email protected]";

console.log(chalk.blue("Hello, world!"));

일회성 스크립트나 CI 보조 스크립트처럼 프로젝트 뼈대 없이 외부 라이브러리를 쓰고 싶을 때 편리합니다. 다만 버전 문자열이 소스 여러 곳에 흩어지면 팀 전체가 같은 버전을 쓰도록 관리하기 어려우므로, 규모가 커지면 deno.json의 imports나 package.json으로 옮기는 것이 좋습니다.

deno.json의 import map으로 bare import 정리

deno.json에 별칭을 두면 npm 패키지를 일반 패키지 이름으로 import할 수 있습니다.

{
  "imports": {
    "chalk": "npm:[email protected]"
  }
}
import chalk from "chalk";
console.log(chalk.green("bare import"));

Deno는 모듈을 해석할 때 chalk를 npm:[email protected]으로 치환합니다. 버전 범위(npm:chalk@^5)를 쓰면 실제로 해석된 버전은 deno.lock에 기록되므로, 재현 가능한 설치가 필요하면 락파일을 커밋하고 CI에서 --frozen 옵션으로 락파일과 다른 해석을 막을 수 있습니다.

package.json과 node_modules를 Node처럼

Deno 2.0은 기존 Node 프로젝트 레이아웃을 이해합니다. package.json과 node_modules가 있는 저장소에서 deno install로 의존성을 설치하고, deno task로 scripts를 실행하고, deno test로 테스트를 돌릴 수 있습니다.

실무에서 의미 있는 점은 점진적 도입이 가능하다는 것입니다. 빌드 체인과 서버는 Node에 둔 채 포맷·린트·테스트만 Deno 도구로 바꿔 볼 수 있습니다. 반대로 순수 Deno 프로젝트로 유지하고 싶다면 deno.json 중심으로 두고 npm은 꼭 필요한 패키지에만 쓰도록 경계를 정할 수 있습니다.

프라이빗 npm 레지스트리 (.npmrc)

사내 스코프 패키지를 쓰는 경우, Node·npm과 같이 .npmrc에 레지스트리 URL과 인증 정보를 두면 Deno가 이를 읽습니다. 토큰은 ${NPM_TOKEN}처럼 환경 변수로 참조하고, 저장소에 평문으로 커밋하지 않아야 합니다.

npm 호환의 내부 동작

Deno 2의 npm 호환은 Node를 그대로 복제한 것이 아니라, npm 패키지 메타데이터를 읽어 Deno 런타임이 이해하는 모듈 그래프로 재구성하는 층에 가깝습니다.

해석기(resolver)는 package.json의 exports·imports와 조건부 export를 따라 실제 파일 경로를 고릅니다. Node와 다른 결과가 나온다면 엔트리 포인트와 서브패스 export 설정부터 의심하는 것이 순서입니다. npm 패키지 안의 CommonJS 모듈은 Deno가 ESM에서 import할 수 있도록 감싸서 제공하는데, ESM과 CJS 빌드를 함께 배포하는 dual package에서는 타입 정의와 실제 런타임 export가 어긋나는 문제가 드러나기 쉽습니다.

Node-API 기반 네이티브 애드온은 플랫폼별 바이너리에 의존하므로 Windows·Linux·macOS CI 매트릭스에서 로딩 실패를 미리 확인하는 것이 안전합니다. 또 npm:으로 가져온 패키지는 전역 캐시에 저장되므로, 같은 버전인데 동작이 이상하다면 캐시가 손상됐을 가능성을 의심하고 deno clean으로 캐시를 비워 볼 수 있습니다.


JSR (JavaScript Registry)

JSR은 Deno 팀이 만든 JavaScript 패키지 레지스트리로, ESM만 허용하고, TypeScript 소스를 그대로 게시하며, 여러 런타임 호환 정보와 문서를 자동으로 생성하는 것을 목표로 합니다. Deno 표준 라이브러리는 @std 스코프로 JSR에서 제공됩니다.

npm과의 관계

Deno에서는 jsr: 명세자나 deno add jsr:@std/path로 JSR 패키지를 가져오며, npm 의존성과 섞어 쓸 수 있습니다. Node 등 다른 런타임에서 JSR 패키지를 쓸 때는 JSR이 제공하는 npm 호환 레이어(npx jsr add ...)를 거치는데, 이때 JSR이 TypeScript 소스를 .js와 .d.ts로 변환해 제공합니다.

언제 JSR을 우선할까

라이브러리가 Deno·Node·브라우저 등 여러 런타임에서 깨지지 않아야 하고, TypeScript 소스를 그대로 배포하면서 문서를 자동으로 얻고 싶다면 JSR을 검토할 만합니다. 반면 이미 npm 중심으로 게시·버저닝 프로세스가 굳어진 팀은 당장은 npm을 유지하고 Deno는 소비자 런타임으로만 쓰는 편이 비용이 적습니다.


Workspaces와 모노레포

Deno 2.0의 워크스페이스는 여러 하위 패키지를 한 저장소에서 다루기 위한 기능입니다. 루트 deno.json에 멤버 디렉터리를 나열하는 것이 기본 형태입니다.

{
  "workspace": ["./packages/add", "./packages/subtract"]
}

각 멤버는 자체 deno.json에 이름, 버전, imports, 일부 린트·포맷 설정을 가질 수 있고, 멤버끼리는 패키지 이름으로 서로를 import할 수 있습니다.

npm 워크스페이스와의 혼합

Deno 2.0은 npm workspaces도 이해하므로, 일부 패키지는 package.json, 일부는 deno.json으로 관리하는 혼합 모노레포를 구성할 수 있습니다. 조직 전체를 한 번에 Deno로 옮기기보다 팀이나 제품 단위로 경계를 나누는 전략과 잘 맞습니다.

deno publish와 멀티 패키지

워크스페이스 루트에서 deno publish를 실행하면 멤버들 사이의 의존 관계를 고려해 게시합니다. 그래도 CI에서는 버전을 올리는 규칙과 변경 감지 방식을 팀 차원에서 정해 두어야 합니다.


성능 벤치마크: 숫자를 어떻게 읽을 것인가

Deno 팀은 Deno 2.0 발표와 함께 패키지 매니저 성능과 HTTP 서버 벤치마크를 제시했습니다. 이런 수치를 읽을 때 중요한 것은 측정 조건(콜드/핫 캐시, OS, 버전)과 내 워크로드와의 거리입니다.

deno install (공식 수치)

발표 글에 따르면 deno install은 npm보다 콜드 캐시에서 약 15%, 핫 캐시에서 약 90% 빨랐습니다. 이는 Deno 팀의 측정 환경에서 나온 수치이며, 의존성 수, 락파일 유무, 네트워크에 따라 결과는 크게 달라집니다. CI 시간을 줄이는 것이 목적이라면 실제 프로젝트로 npm/pnpm 파이프라인과 직접 비교해 보는 것이 가장 정확합니다.

HTTP 벤치마크와 정정 공지

Deno 2.0 발표 글에는 이후 HTTP 벤치마크에 대한 정정이 추가되었습니다. 4GB를 넘는 힙이 필요한 사용자를 위해 V8 포인터 압축을 끈 빌드가 배포되었고, 그 영향으로 실제 처리량이 그래프보다 낮다는 내용입니다. 벤치마크가 특정 버전과 빌드 옵션에 묶여 있다는 점을 보여 주는 사례이므로, 런타임 선택은 지연 시간과 메모리 프로파일을 내 서비스로 직접 측정하고 운영 팀의 숙련도까지 함께 고려해 판단해야 합니다.

그 밖의 차이

짧은 CLI나 서버리스 스타일 작업에서는 시작 시간이 중요합니다. 포맷·린트·테스트가 런타임 바이너리 하나에 들어 있다는 점은 신규 팀원의 환경 구성 부담을 줄여 줍니다. deno serve --parallel은 CPU 코어 수만큼 인스턴스를 띄워 요청을 분산하므로 멀티 코어를 활용할 수 있지만, 인스턴스끼리 메모리를 공유하지 않는다는 점을 애플리케이션 설계에 반영해야 합니다.


Node.js vs Deno 2.0: 경쟁이 아니라 역할 분담

둘 다 V8 위에서 JavaScript·TypeScript를 실행하지만, 제품 철학과 운영 모델이 다릅니다.

비교 축Node.jsDeno 2.0
생태계npm·CJS의 역사가 깊고, 많은 도구·프레임워크가 Node를 기본 가정으로 함npm·JSR을 소비할 수 있으며, 권한 모델·내장 도구·웹 표준 API가 차별점
보안 기본값기본은 전체 권한. --permission 플래그로 파일·자식 프로세스 등을 제한하는 권한 모델 제공권한이 기본적으로 꺼져 있고 --allow-*로 명시적으로 허용
TypeScript최근 버전은 타입 제거 방식으로 .ts 실행을 지원하지만 타입 검사는 별도 도구 필요기본 지원, deno check로 타입 검사
배포 형태서버·CLI·Electron 등 광범위deno compile로 단일 실행 파일 배포

레거시 CJS 코드나 특정 네이티브 모듈에 깊게 묶인 서비스는 당분간 Node에 두는 것이 안전합니다. 새 마이크로서비스나 내부 CLI처럼 권한 모델과 도구 일원화의 이점이 큰 곳에서 Deno 2.0을 후보로 두는 방식이 무난합니다.


실전 마이그레이션 순서

아래는 Deno 1.x 또는 기존 Node(ESM) 프로젝트에서 옮길 때의 권장 순서입니다.

사전 점검

먼저 모듈 형식을 확인합니다. CJS 비중이 높다면 ESM으로 옮기는 리팩터링 범위를 미리 가늠해 둡니다. 다음으로 node-gyp나 Node-API 기반 네이티브 의존성이 있는지 확인하고, Deno에서 실제로 로드되는지 검증합니다. 마지막으로 package.json의 scripts를 deno task로 얼마나 대체할지 팀에서 합의합니다.

도구부터 도입하기

포맷·린트·테스트처럼 런타임과 분리하기 쉬운 영역부터 Deno 명령을 도입하면 위험이 작습니다. deno fmt, deno lint --fix, 그리고 node:test로 작성된 테스트를 deno test로 실행해 보는 것부터 시작할 수 있습니다.

의존성 정리

npm 중심 프로젝트라면 deno install로 node_modules를 구성하고, 실행 중 문제가 되는 패키지를 목록으로 정리합니다. Deno 중심 프로젝트라면 deno add와 deno remove로 deno.json(또는 package.json)을 갱신하는 흐름을 표준으로 정합니다.

권한 플래그 운영

로컬에서는 -A로 모든 권한을 열어 빠르게 확인할 수 있지만, CI와 프로덕션에서는 --allow-net=api.example.com처럼 필요한 범위만 허용하는 것을 목표로 합니다. 로컬에서는 동작하는데 배포 환경에서만 실패한다면 권한, 파일 경로, 환경 변수 차이를 먼저 확인합니다.

공식 마이그레이션 문서

세부 API와 플래그 변경은 Deno 1.x → 2.x 마이그레이션 가이드에 정리되어 있습니다.


자주 겪는 문제와 점검 순서

증상1차 가설확인 방법
로컬에선 되고 CI만 실패권한·환경 변수·캐시 차이CI와 같은 --allow-* 조합으로 로컬에서 재현
특정 서브패스 import만 실패exports 해석 차이Node와 Deno에서 같은 엔트리로 최소 재현
네이티브 모듈 로딩 오류플랫폼 바이너리·glibc 버전해당 .node 의존성을 제거하거나 순수 JS 대체 패키지 사용
워크스페이스 멤버 간 의존성 꼬임루트와 멤버의 imports·버전 범위 충돌루트·멤버 설정을 정리하고 락파일을 하나로 유지
같은 버전인데 동작이 다름캐시 손상, 전역 캐시와 node_modules 혼용deno clean 후 재설치, nodeModulesDir 설정 확인

참고 자료


같이 보면 좋은 글