이번 편의 결과물: 빈 폴더에서 expense-tracker/ 프로젝트가 생성되고, npm run dev로 접속한 브라우저 화면에 “가계부 준비 완료”가 표시됩니다. · 다루는 개념: Node 22/Vite 8 확인, npm create vite로 vanilla-ts 프로젝트 생성, 기본 tsconfig.json 읽기, tsc --noEmit과 vite dev의 차이
이 편에서 만드는 파일
expense-tracker/ (+, 새 프로젝트)
├── index.html (+)
├── package.json (+)
├── tsconfig.json (+)
├── public/
│ └── vite.svg (+)
└── src/
├── main.ts (+)
├── style.css (+)
└── vite-env.d.ts (+)개념 정리
Node 22 / Vite 8 확인
이 과목은 Node.js 22 LTS와 Vite 8을 기준으로 진행합니다. 프로젝트를 만들기 전에 버전을 확인합니다.
| 도구 | 요구 버전 | 확인 명령 |
|---|---|---|
| Node.js | 22 이상(LTS) | node -v |
| npm | Node.js에 포함된 버전 | npm -v |
npm create vite로 vanilla-ts 템플릿 생성
npm create vite@latest는 다양한 템플릿 중 하나를 골라 프로젝트 뼈대를 만들어 주는 공식 스캐폴딩 명령입니다. --template vanilla-ts는 프레임워크 없이 TypeScript만 설정된 템플릿을 의미합니다.
| 템플릿 이름 | 내용 |
|---|---|
vanilla | 프레임워크 없는 순수 JavaScript |
vanilla-ts | 프레임워크 없는 순수 TypeScript(이 과목에서 사용) |
react, react-ts | React(JS/TS) |
기본 tsconfig.json 읽기
npm create vite가 생성하는 tsconfig.json은 아래 옵션들을 기본으로 포함합니다.
| 옵션 | 의미 |
|---|---|
target | 컴파일 결과가 어떤 JS 문법 버전으로 변환될지 |
module | 어떤 모듈 문법(import/export)으로 변환될지 |
strict | 엄격한 타입 검사 규칙 묶음을 모두 켠다(16편에서 하위 옵션을 하나씩 다룸) |
noEmit | 이 설정으로 직접 JS 파일을 만들지 않는다(번들링은 Vite가 담당) |
moduleResolution | 모듈 경로를 해석하는 방식. bundler는 Vite 같은 번들러 환경에 맞춘 방식 |
include | 타입 검사 대상 폴더(src) |
TypeScript 7부터 strict는 명시하지 않아도 기본값이 참(true)이지만, Vite 템플릿은 설정 의도를 코드로 남기기 위해 여전히 명시적으로 적어 둡니다.
tsc —noEmit과 vite dev의 차이
| 명령 | 하는 일 | 타입 오류가 있어도 |
|---|---|---|
npm run dev(vite) | 코드를 변환해 브라우저에 즉시 반영(빠른 개발 서버) | 화면은 계속 뜬다(타입 검사를 하지 않음) |
npx tsc --noEmit | 파일을 생성하지 않고 타입만 검사 | 오류 목록을 출력하고 끝난다(화면과 무관) |
vite dev는 속도를 위해 타입 검사를 건너뛰고 코드 변환만 합니다. 타입 오류가 있어도 화면은 그대로 뜰 수 있으므로, 타입 오류 여부는 tsc --noEmit으로 따로 확인해야 합니다.
실습
1. 버전 확인
node -v
npm -v2. 프로젝트 생성
npm create vite@latest expense-tracker -- --template vanilla-ts
cd expense-tracker
npm install생성된 package.json의 스크립트는 다음과 같습니다.
// expense-tracker/package.json
{
"name": "expense-tracker",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
},
"devDependencies": {
"typescript": "~7.0.0",
"vite": "^8.0.0"
}
}build 스크립트는 지금은 tsc -b(타입 검사 겸 빌드 참조 확인)와 vite build(번들링)를 한 번에 실행합니다. 이 둘을 분리하는 작업은 18편에서 다룹니다.
생성된 tsconfig.json을 확인합니다.
// expense-tracker/tsconfig.json
{
"compilerOptions": {
"target": "ES2023",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}3. 기본 화면 정리
템플릿이 만든 src/counter.ts, src/typescript.svg는 이 과목에서 쓰지 않으므로 지웁니다.
rm src/counter.ts src/typescript.svgindex.html의 제목을 정리합니다.
<!-- expense-tracker/index.html -->
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>expense-tracker</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>src/main.ts를 아래 내용으로 통째로 바꿉니다.
// expense-tracker/src/main.ts
import './style.css'
const app = document.querySelector<HTMLDivElement>('#app')
if (app) {
app.innerHTML = `
<main>
<h1>가계부</h1>
<p>가계부 준비 완료</p>
</main>
`
}document.querySelector<HTMLDivElement>('#app')의 꺾쇠 부분은 반환 타입을 HTMLDivElement로 좁혀 주는 문법입니다. 이 문법 자체(제네릭)는 08편에서 자세히 다루고, 지금은 “#app이 div 요소라는 것을 TypeScript에 알려준다” 정도로만 이해하면 됩니다. app이 null일 수도 있으므로 if (app)으로 확인한 뒤에만 innerHTML을 사용합니다.
src/style.css도 내용을 정리합니다.
/* expense-tracker/src/style.css */
body {
margin: 0;
font-family: system-ui, sans-serif;
background-color: #f8fafc;
color: #0f172a;
}
main {
max-width: 640px;
margin: 0 auto;
padding: 2rem 1rem;
}src/vite-env.d.ts는 템플릿이 만든 그대로 둡니다.
// expense-tracker/src/vite-env.d.ts
/// <reference types="vite/client" />4. 개발 서버 실행
npm run dev5. 타입 체크만 따로 실행
npx tsc --noEmit(출력 없음 — 타입 오류가 없으면 아무것도 출력되지 않고 종료됩니다)확인
- 터미널에 나온 주소(대개
http://localhost:5173)로 접속하면 “가계부”라는 제목과 “가계부 준비 완료” 문구가 보입니다. - 브라우저 개발자 도구 콘솔에 빨간 오류가 없습니다.
npx tsc --noEmit을 실행해도 아무 오류가 출력되지 않습니다(타입 오류 없음을 의미).
직접 해보기
index.html의<title>을 다른 문자열로 바꾸고 새로고침 없이 브라우저 탭 제목이 바로 바뀌는지 확인합니다.main.ts의<p>가계부 준비 완료</p>를 지우고 저장한 뒤, 개발 서버를 다시 켜지 않아도 화면이 즉시 바뀌는지(핫 모듈 교체) 확인하고 원래대로 되돌립니다.
정답 보기
npm run dev로 띄운 Vite 개발 서버는 파일을 저장할 때마다 변경 사항을 자동으로 감지해 브라우저에 반영합니다(핫 모듈 교체). 개발 서버를 껐다 켤 필요 없이 저장만 하면 화면이 즉시 갱신됩니다.
자주 하는 실수
| 증상 | 원인 | 고치는 법 |
|---|---|---|
npm create vite 명령 후 프레임워크 선택 화면이 나옴 | --template vanilla-ts를 빠뜨림 | 명령 뒤에 -- --template vanilla-ts를 정확히 붙인다 |
npm run dev 실행 시 포트 오류 | 다른 프로그램이 같은 포트를 이미 사용 중 | 터미널에 안내된 대체 포트로 접속하거나 기존 프로세스를 종료한다 |
| 화면은 정상인데 타입 오류를 놓침 | vite dev만 보고 tsc --noEmit을 실행하지 않음 | 코드를 크게 바꿀 때마다 npx tsc --noEmit을 따로 실행하는 습관을 들인다 |