[G1-Sync] Manual knowledge update

This commit is contained in:
Antigravity Agent
2026-05-09 21:08:02 +09:00
parent f0befc887a
commit 93ec7e9056
363 changed files with 68333 additions and 64 deletions
@@ -0,0 +1,109 @@
---
id: js-module-system-esm-cjs
title: JS Module System — ESM vs CJS 그리고 dual package
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [javascript, esm, cjs, package, vibe-coding]
tech_stack: { language: "Node.js / TypeScript / bundler", applicable_to: ["Backend", "Library"] }
applied_in: []
aliases: [import/export, require, exports field, package.json]
---
# JS 모듈 시스템 — ESM vs CJS
> 2026 현재도 라이브러리 작성자에게 가장 골치 아픈 영역. **신규 코드는 ESM 디폴트**, but Node 일부 도구 / 레거시는 CJS 만 — package.json `exports` 의 dual export 가 표준 답.
## 📖 핵심 개념
- ESM: `import`/`export`. 정적 분석 가능. tree-shake 친화. 비동기 가능.
- CJS: `require()`/`module.exports`. 동적 (조건부 require). 동기.
- Node.js: `package.json` `"type": "module"` 이면 .js = ESM. 아니면 CJS.
- `.mjs` = 항상 ESM, `.cjs` = 항상 CJS.
## 💻 코드 패턴
### 라이브러리 dual package (ESM + CJS)
```json
{
"name": "my-lib",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"default": "./dist/index.mjs"
},
"./package.json": "./package.json"
},
"files": ["dist"]
}
```
### TypeScript 빌드 — tsup 또는 unbuild
```json
// package.json scripts
{ "build": "tsup src/index.ts --format esm,cjs --dts" }
```
### Subpath exports
```json
{
"exports": {
".": "./dist/index.mjs",
"./parser": "./dist/parser.mjs",
"./internal": null
}
}
```
`null` → 임포트 차단.
### CJS 안에서 ESM 동적 import
```ts
// CJS 환경 (.cjs)
async function loadESM() {
const mod = await import('esm-only-package'); // 동적 import 만 가능
return mod.default;
}
```
### ESM 안에서 CJS import
```ts
// ESM (.mjs / "type":"module")
import lodash from 'lodash'; // CJS default — Node 가 자동 wrap
import { merge } from 'lodash'; // named export 동작 (Node 가 분석)
```
## 🤔 의사결정 기준
| 상황 | 형식 |
|---|---|
| 신규 라이브러리 | ESM-first + dual export |
| 신규 앱 (Next/Vite) | ESM (bundler 가 처리) |
| Node 서버 (TypeScript) | ESM with `"type": "module"` |
| 옛 도구 의존성 다수 (postcss plugin, eslint plugin) | CJS 또는 dual |
| 동적 require 필요 | CJS (또는 ESM 의 `await import`) |
| __dirname / __filename | ESM 에는 없음 — `import.meta.url` + fileURLToPath |
## ❌ 안티패턴
- **ESM 라이브러리에 `main` 만**: CJS 사용자 import 시 default 잘못 판정. exports 필드 명시.
- **CJS + ESM 양쪽에서 같은 인스턴스 가정**: 두 번 evaluate 됨 → 두 인스턴스 (singleton 깨짐).
- **`require("package")` 가 ESM-only 패키지**: 런타임 에러. 동적 import 로.
- **`exports` 필드 빠뜨리고 internal path 노출**: 사용자가 `import 'pkg/dist/foo'` 가능. 명시 차단.
- **TypeScript paths 만 의존**: 빌드 시 alias 안 풀림. tsc-alias 또는 bundler.
- **순환 import**: ESM 도 깨짐. CJS 와 다른 방식으로 깨짐. 의존성 그래프 정리.
- **default export 와 named 혼용**: tree shake / IDE 자동 import 헷갈림. named 권장.
## 🤖 LLM 활용 힌트
- 신규 = ESM. 라이브러리는 dual export.
- exports 필드 + types + import + require 4종 세트.
- Node ESM 환경: `import.meta.url`, top-level await OK.
## 🔗 관련 문서
- [[TypeScript_Const_Assertions]]
- [[DevOps_Build_Performance]]