본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout
TechProjectsBooksAbout
© 2026 Kreath. All rights reserved.
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 4장: npm 호환과 패키지 관리
2026년 7월 18일·프로그래밍·

4장: npm 호환과 패키지 관리

Deno 2의 npm 호환성, JSR(JavaScript Registry), import map, deno add를 통한 패키지 관리, 그리고 Node.js에서 Deno로의 마이그레이션 전략을 다룹니다.

16분598자7개 섹션
typescriptperformancetoolingsecuritydeveloper-experience
공유
deno-runtime4 / 10
12345678910
이전3장: Deno의 보안 모델 - 권한 기반 실행다음5장: 웹 표준 API와 Deno 네이티브 기능

Deno 1의 가장 큰 약점

Deno 1.x가 아무리 뛰어난 설계와 보안을 갖추고 있었더라도, npm 생태계와의 단절은 치명적인 약점이었습니다. 수백만 개의 npm 패키지를 사용할 수 없다는 것은 실무에서 Deno를 채택하기 어렵게 만드는 가장 큰 장벽이었습니다.

Express, React, Prisma, zod, lodash 같은 핵심 라이브러리를 사용할 수 없다면, 아무리 런타임이 좋아도 프로덕션에 적용하기 어렵습니다. Deno 팀도 이 현실을 직시했고, Deno 2에서 이 문제를 정면으로 해결했습니다.

npm: 접두사를 통한 네이티브 호환

기본 사용법

Deno 2에서는 npm: 접두사를 통해 npm 패키지를 직접 import할 수 있습니다. 별도의 설치 과정이 필요 없습니다.

npm: 접두사로 패키지 사용
typescript
// npm 패키지를 직접 import
import express from "npm:express@4";
import { z } from "npm:zod@3";
import chalk from "npm:chalk@5";
 
// 바로 사용 가능
const app = express();
 
const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});
 
console.log(chalk.green("서버 시작"));
app.listen(3000);

npm: 접두사 뒤에 패키지명과 버전을 지정하면, Deno가 자동으로 해당 패키지를 다운로드하고 캐시합니다. 전역 캐시를 사용하므로 한 번 다운로드한 패키지는 다른 프로젝트에서도 재사용됩니다.

Info

npm: 접두사를 사용하면 Deno가 내부적으로 Node.js 호환 레이어를 활성화합니다. require(), __dirname, process 등 Node.js 전용 API가 해당 패키지 내에서 동작하도록 호환성 심(shim)을 제공합니다.

버전 지정 방식

npm 패키지 버전 지정
typescript
// 정확한 버전
import lodash from "npm:lodash@4.17.21";
 
// 메이저 버전 범위
import express from "npm:express@4";
 
// 마이너 버전 범위
import zod from "npm:zod@3.22";
 
// 최신 버전 (프로덕션에서는 권장하지 않음)
import dayjs from "npm:dayjs";

node_modules 디렉토리 지원

Deno 2는 선택적으로 node_modules 디렉토리를 생성할 수 있습니다. 일부 도구나 라이브러리가 node_modules의 존재를 전제로 동작하는 경우에 유용합니다.

deno.json - node_modules 설정
json
{
  "nodeModulesDir": "auto"
}

nodeModulesDir 옵션에는 세 가지 값을 사용할 수 있습니다.

nodeModulesDir 옵션
text
"none"  - node_modules를 생성하지 않음 (기본값)
"auto"  - 필요할 때 자동으로 node_modules 생성
"manual" - npm install로 직접 관리하는 node_modules 사용
Tip

기존 Node.js 프로젝트를 마이그레이션할 때는 "auto"로 시작하여 호환성을 확보한 후, 점진적으로 "none"으로 전환하는 것을 권장합니다.

JSR: 차세대 패키지 레지스트리

npm의 한계와 JSR의 등장

npm 레지스트리는 JavaScript 생태계의 핵심 인프라이지만, 몇 가지 구조적 한계를 안고 있습니다.

npm 레지스트리의 한계
text
1. TypeScript를 네이티브로 지원하지 않음
   - .d.ts 파일을 별도로 관리해야 함
   - @types/* 패키지에 의존
 
2. ESM/CJS 이중 발행의 복잡성
   - 두 모듈 시스템을 모두 지원하려면 빌드 설정이 복잡
 
3. 패키지 품질 검증 부재
   - 누구나 발행 가능, 품질 기준 없음
 
4. 보안 검증 한계
   - 패키지 소유권 탈취 사례 빈발

JSR(JavaScript Registry)는 이러한 한계를 해결하기 위해 Deno 팀이 만든 차세대 패키지 레지스트리입니다.

JSR 사용법

JSR 패키지 사용
typescript
// jsr: 접두사로 JSR 패키지 import
import { assertEquals } from "jsr:@std/assert@1";
import { join } from "jsr:@std/path@1";
import { parse } from "jsr:@std/csv@1";
 
// Deno 표준 라이브러리는 모두 JSR에서 관리됩니다

JSR 패키지를 프로젝트에 추가하는 방법은 다음과 같습니다.

deno add로 패키지 추가
bash
# JSR 패키지 추가
deno add jsr:@std/assert
deno add jsr:@std/path
 
# npm 패키지도 추가 가능
deno add npm:express@4
deno add npm:zod
 
# 추가된 패키지는 deno.json의 imports에 자동 등록됩니다
deno.json - deno add 결과
json
{
  "imports": {
    "@std/assert": "jsr:@std/assert@^1.0.0",
    "@std/path": "jsr:@std/path@^1.0.0",
    "express": "npm:express@^4.18.0",
    "zod": "npm:zod@^3.22.0"
  }
}

JSR에 패키지 발행하기

JSR에 패키지를 발행하는 과정은 npm보다 단순합니다. TypeScript 소스를 그대로 발행할 수 있어 빌드 과정이 필요 없습니다.

deno.json - 패키지 발행 설정
json
{
  "name": "@myorg/my-library",
  "version": "1.0.0",
  "exports": "./mod.ts"
}
JSR 발행
bash
# 패키지 발행
deno publish
 
# 발행 전 검증 (드라이런)
deno publish --dry-run
Info

JSR에 발행된 패키지는 npm에서도 사용할 수 있습니다. JSR이 자동으로 TypeScript를 JavaScript로 변환하고, .d.ts 파일과 package.json을 생성하여 npm 호환 형식으로 제공합니다. 따라서 JSR 패키지 하나만 발행하면 Deno, Node.js, Bun 모두에서 사용 가능합니다.

Import Map: 모듈 경로 관리

Import Map의 역할

Import Map은 모듈 경로를 별칭(alias)으로 매핑하는 기능입니다. deno.json의 imports 필드가 이 역할을 합니다.

deno.json - Import Map 활용
json
{
  "imports": {
    "@/": "./src/",
    "@db/": "./src/database/",
    "@utils/": "./src/utils/",
    "@std/assert": "jsr:@std/assert@^1.0.0",
    "express": "npm:express@^4.18.0",
    "zod": "npm:zod@^3.22.0",
    "hono": "jsr:@hono/hono@^4.0.0"
  }
}
Import Map을 활용한 깔끔한 import
typescript
// Import Map 덕분에 깔끔한 경로 사용 가능
import { UserService } from "@/services/user.ts";
import { connectDB } from "@db/connection.ts";
import { formatDate } from "@utils/date.ts";
import { assertEquals } from "@std/assert";
import { Hono } from "hono";

패키지 버전 중앙 관리

Import Map의 큰 장점은 패키지 버전을 한 곳에서 관리할 수 있다는 점입니다. Node.js에서는 package.json이 이 역할을 하지만, Deno에서는 deno.json의 imports가 그 역할을 합니다.

버전 중앙 관리 예시
json
{
  "imports": {
    "zod": "npm:zod@^3.22.4"
  }
}
프로젝트 어디서든 동일한 버전 사용
typescript
// src/routes/user.ts
import { z } from "zod"; // 3.22.4 사용
 
// src/routes/product.ts
import { z } from "zod"; // 동일한 3.22.4 사용
 
// 버전 변경은 deno.json 한 곳에서만 수행

Lock 파일과 의존성 무결성

deno.lock의 역할

deno.lock 파일은 프로젝트의 모든 의존성에 대한 해시값을 기록합니다. 이를 통해 의존성이 변조되지 않았음을 검증할 수 있습니다.

Lock 파일 관리
bash
# Lock 파일 자동 생성/업데이트 (기본 동작)
deno run main.ts
 
# Lock 파일 무결성 검증 (CI에서 사용)
deno run --frozen main.ts
 
# Lock 파일 없이 실행 (권장하지 않음)
deno run --no-lock main.ts
Warning

프로덕션 배포와 CI/CD 환경에서는 반드시 --frozen 플래그를 사용하세요. 이 플래그는 deno.lock에 기록된 해시와 실제 다운로드된 모듈의 해시가 일치하는지 검증합니다. 불일치 시 즉시 에러가 발생하므로, 공급망 공격이나 의존성 변조를 감지할 수 있습니다.

Node.js에서 Deno로 마이그레이션

단계별 마이그레이션 전략

기존 Node.js 프로젝트를 Deno로 마이그레이션하는 것은 한 번에 이루어질 필요가 없습니다. Deno 2의 npm 호환성 덕분에 점진적 마이그레이션이 가능합니다.

1단계: 호환 모드로 실행

가장 먼저, 기존 Node.js 프로젝트를 Deno에서 그대로 실행하는 것부터 시작합니다.

deno.json - 1단계 설정
json
{
  "nodeModulesDir": "auto",
  "tasks": {
    "dev": "deno run --allow-all src/index.ts",
    "test": "deno test --allow-all"
  }
}

이 단계에서는 --allow-all을 사용하여 먼저 동작을 확인하고, 점진적으로 권한을 줄여나갑니다.

2단계: import 경로 전환

package.json 의존성을 deno.json의 Import Map으로 이전합니다.

package.json (기존)
json
{
  "dependencies": {
    "express": "^4.18.0",
    "zod": "^3.22.0",
    "pg": "^8.11.0"
  }
}
deno.json (전환 후)
json
{
  "imports": {
    "express": "npm:express@^4.18.0",
    "zod": "npm:zod@^3.22.0",
    "pg": "npm:pg@^8.11.0"
  }
}

3단계: Deno 네이티브로 전환

npm 패키지를 Deno 네이티브 또는 JSR 패키지로 교체하고, Node.js API를 Deno API로 전환합니다.

Node.js API에서 Deno API로 전환
typescript
// Before: Node.js 방식
import { readFileSync } from "node:fs";
const content = readFileSync("./data.json", "utf-8");
 
// After: Deno 방식
const content = await Deno.readTextFile("./data.json");
 
// Before: Node.js HTTP 서버
import http from "node:http";
const server = http.createServer((req, res) => {
  res.end("Hello");
});
server.listen(3000);
 
// After: Deno HTTP 서버
Deno.serve({ port: 3000 }, (_req) => {
  return new Response("Hello");
});

주의사항

마이그레이션 시 주의해야 할 점들이 있습니다.

마이그레이션 주의사항
text
1. 네이티브 바이너리 모듈
   - node-gyp 기반 C++ 모듈은 호환되지 않을 수 있음
   - bcrypt, sharp 등 네이티브 모듈 대안 확인 필요
 
2. require() 사용 코드
   - Deno는 기본적으로 ESM만 지원
   - npm 패키지 내부의 require()는 호환 레이어가 처리
   - 프로젝트 코드의 require()는 import로 전환 필요
 
3. 글로벌 변수 차이
   - __dirname, __filename은 import.meta로 대체
   - process 객체 접근 방식 변경
 
4. 테스트 프레임워크
   - Jest/Mocha에서 Deno.test 또는 호환 러너로 전환
글로벌 변수 전환 예시
typescript
// Node.js
const dir = __dirname;
const file = __filename;
 
// Deno
const dir = new URL(".", import.meta.url).pathname;
const file = new URL(import.meta.url).pathname;
 
// 또는 @std/path 활용
import { dirname, fromFileUrl } from "jsr:@std/path";
const dir = dirname(fromFileUrl(import.meta.url));
Tip

마이그레이션 과정에서 문제가 발생하면 deno info 명령으로 의존성 트리를 확인할 수 있습니다. 어떤 모듈이 어디서 로드되는지, 해결 순서는 어떻게 되는지를 시각적으로 보여줍니다.

패키지 관리 전략 요약

Deno 2의 패키지 관리는 유연합니다. 프로젝트 상황에 맞는 전략을 선택할 수 있습니다.

전략적합한 상황설정
JSR 중심새 프로젝트, Deno 네이티브jsr: 접두사 사용
npm 호환기존 Node.js 마이그레이션npm: 접두사 + nodeModulesDir
혼합 사용점진적 전환 프로젝트Import Map에서 두 레지스트리 혼용

어떤 전략을 선택하든, Import Map을 통한 중앙 관리와 Lock 파일을 통한 무결성 검증은 항상 유지하는 것이 좋습니다.

다음 장에서는 Deno가 제공하는 웹 표준 API와 Deno 전용 API를 살펴봅니다. fetch, WebSocket, Streams부터 Deno.serve, Deno.KV까지 Deno의 API 설계 철학을 이해합니다.

이 글이 도움이 되셨나요?

관련 글

프로그래밍

3장: Deno의 보안 모델 - 권한 기반 실행

Deno의 권한 기반 보안 모델을 심층 분석합니다. 각 권한 플래그의 동작 원리, Node.js와의 보안 비교, 공급망 공격 방어, 그리고 실무 보안 모범 사례를 다룹니다.

2026년 7월 16일·20분
프로그래밍

5장: 웹 표준 API와 Deno 네이티브 기능

Deno가 채택한 웹 표준 API(fetch, WebSocket, Web Crypto, Streams)와 Deno 전용 API(Deno.serve, Deno.KV, Deno.open)를 심층적으로 분석합니다.

2026년 7월 21일·15분
프로그래밍

6장: Bun과의 비교 - 성능, 호환성, 생태계

Deno 2와 Bun을 심층 비교합니다. 아키텍처 차이, 성능 벤치마크, API 호환성, 생태계 성숙도, 그리고 프로젝트 특성에 따른 선택 기준을 제시합니다.

2026년 7월 24일·18분
이전 글3장: Deno의 보안 모델 - 권한 기반 실행
다음 글5장: 웹 표준 API와 Deno 네이티브 기능

댓글

목차

약 16분 남음
  • Deno 1의 가장 큰 약점
  • npm: 접두사를 통한 네이티브 호환
    • 기본 사용법
    • 버전 지정 방식
    • node_modules 디렉토리 지원
  • JSR: 차세대 패키지 레지스트리
    • npm의 한계와 JSR의 등장
    • JSR 사용법
    • JSR에 패키지 발행하기
  • Import Map: 모듈 경로 관리
    • Import Map의 역할
    • 패키지 버전 중앙 관리
  • Lock 파일과 의존성 무결성
    • deno.lock의 역할
  • Node.js에서 Deno로 마이그레이션
    • 단계별 마이그레이션 전략
    • 1단계: 호환 모드로 실행
    • 2단계: import 경로 전환
    • 3단계: Deno 네이티브로 전환
    • 주의사항
  • 패키지 관리 전략 요약