출시·고도화 중
JavaScript 안내서 · 4/6
프로그램이 커지면 코드를 여러 파일로 나누고, 남이 만든 라이브러리를 가져다 써야 합니다. JavaScript에는 두 가지 모듈 방식이 있습니다. 표준인 ES 모듈(ESM)과 Node.js가 오래 써 온 CommonJS입니다. 이 장에서는 두 방식의 차이, npm과 package.json, 스크립트, 그리고 번들링의 기초를 다룹니다.
ES 모듈은 언어 표준에 정의된 모듈 방식으로, 브라우저와 Node.js 모두에서 동작합니다. export로 내보내고 import로 가져옵니다. 이름 붙은 내보내기(named export)는 여러 개 둘 수 있고, 기본 내보내기(default export)는 파일당 하나입니다.
// math.js
export const PI = 3.14159;
export function circleArea(r) {
return PI * r * r;
}
export default function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}// main.js
import sum, { circleArea, PI as pi } from "./math.js";
import * as math from "./math.js";
console.log(sum(1, 2, 3)); // 6
console.log(circleArea(2)); // 12.56636
console.log(pi, math.PI); // 3.14159 3.14159
// 필요할 때 불러오는 동적 import(Promise를 돌려줍니다)
const { readFile } = await import("node:fs/promises");ES 모듈의 특징은 다음과 같습니다.
import 선언은 파일 맨 위에서 정적으로 분석되므로, 도구가 쓰지 않는 코드를 걸러낼 수 있습니다(tree shaking).await를 쓸 수 있습니다.<script type="module" src="main.js">로 불러오고, 상대 경로에 확장자(.js)까지 적어야 합니다.CommonJS는 ES 모듈이 표준이 되기 전부터 Node.js가 써 온 방식입니다. require로 가져오고 module.exports로 내보냅니다. 지금도 많은 패키지와 오래된 프로젝트에서 볼 수 있습니다.
// greet.cjs
function greet(name) {
return `안녕하세요, ${name}님`;
}
module.exports = { greet };
// app.cjs
const { greet } = require("./greet.cjs");
const path = require("node:path");
console.log(greet("유나"));
console.log(path.join("logs", "today.txt"));Node.js는 파일이 어느 방식인지 다음 규칙으로 정합니다.
| 조건 | 해석 방식 |
|---|---|
확장자가 .mjs | ES 모듈 |
확장자가 .cjs | CommonJS |
.js 이고 가장 가까운 package.json에 "type": "module" | ES 모듈 |
.js 이고 "type"이 없거나 "commonjs" | CommonJS |
새 프로젝트라면 package.json에 "type": "module"을 넣고 ES 모듈로 시작하는 것을 권합니다. ES 모듈에서는 CommonJS 패키지를 import로 가져올 수 있습니다. 반대로 CommonJS에서 ES 모듈을 쓸 때는 동적 import()를 쓰는 것이 가장 확실합니다. Node.js 내장 모듈은 node:fs, node:path처럼 node: 접두사를 붙이면 npm 패키지와 헷갈리지 않습니다.
npm은 Node.js와 함께 설치되는 패키지 관리자입니다. 패키지를 설치하면 node_modules에 내려받고, package.json에 이름과 허용 버전 범위를 기록합니다.
npm install zod # 실행에 필요한 패키지(dependencies)
npm install -D vitest # 개발할 때만 필요한 패키지(devDependencies)
npm uninstall zod # 제거
npm outdated # 새 버전이 있는 패키지 확인
npm ci # lock 파일 그대로 깨끗하게 설치(CI 서버용){
"name": "todo-api",
"version": "0.1.0",
"type": "module",
"scripts": {
"start": "node src/server.js",
"dev": "node --watch src/server.js",
"test": "node --test"
},
"dependencies": {
"zod": "^3.23.0"
},
"devDependencies": {
"vitest": "^2.0.0"
}
}버전 앞의 ^는 "주 버전이 같은 범위에서 더 새로운 버전 허용"을, ~는 "부 버전까지 같고 패치 버전만 허용"을 뜻합니다. 실제로 설치된 정확한 버전은 package-lock.json에 기록되므로, 이 파일을 커밋하면 팀원과 서버가 모두 같은 버전을 쓰게 됩니다.
scripts에 적은 명령은 npm run 이름으로 실행합니다. start와 test는 run 없이 npm start, npm test로도 실행할 수 있습니다. 스크립트 안에서는 node_modules/.bin에 설치된 도구를 경로 없이 부를 수 있습니다.
npm run dev
npm test
npm start
# 설치하지 않은 도구를 한 번만 실행
npx prettier --write src브라우저용 앱은 파일이 수십, 수백 개로 나뉘고 npm 패키지도 많이 씁니다. 번들러는 이 파일들을 따라가며 하나 또는 몇 개의 파일로 묶고, 공백 제거(minify), 쓰지 않는 코드 제거(tree shaking), 코드 분할 같은 최적화를 함께 처리합니다.
| 도구 | 특징 |
|---|---|
| Vite | 개발 서버가 빠르고 설정이 간단해 새 프로젝트에 많이 쓰입니다 |
| esbuild | 매우 빠른 번들러이자 변환기입니다 |
| Rollup | 라이브러리 배포용 번들에 강합니다 |
| webpack | 오래 쓰여 온 만큼 플러그인 생태계가 넓습니다 |
# Vite로 새 프로젝트 만들기
npm create vite@latest my-web
# esbuild로 한 파일로 묶고 압축하기
npx esbuild src/main.js --bundle --minify --outfile=dist/app.jsNode.js에서만 실행하는 서버 코드는 보통 번들링 없이 그대로 실행해도 충분합니다. 번들러는 주로 브라우저에 보낼 코드를 다룰 때 필요합니다.
import/export)로 쓰고, package.json에 "type": "module"을 지정합니다.require/module.exports)는 오래된 코드와 패키지에서 여전히 자주 만납니다.dependencies와 devDependencies를 구분하고, package-lock.json은 커밋합니다.scripts에 등록해 npm run으로 실행합니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.