14.2 환경 설정 — create-vue, Vite, 프로젝트 구조, Vue DevTools
Vue 프로젝트 시작하기
Vue 3 프로젝트를 시작하는 공식 방법은 create-vue 스캐폴딩 도구를 사용하는 것입니다. create-vue는 내부적으로 Vite를 번들러로 사용하며, TypeScript, Vue Router, Pinia, 테스트 도구 등 다양한 옵션을 대화형으로 선택할 수 있습니다.
Node.js 18 이상이 필요합니다.
node --version으로 버전을 확인하세요.
create-vue로 프로젝트 생성
npm create vue@latest
명령어를 실행하면 대화형 프롬프트가 나타납니다:
✔ Project name: … my-vue-app
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? … No / Cypress / Nightwatch / Playwright
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes
✔ Add Vue DevTools 7 extension for debugging? (experimental) … No / Yes
Scaffolding project in ./my-vue-app...
Done.
이후 다음 명령어로 개발 서버를 시작합니다:
cd my-vue-app
npm install
npm run dev
브라우저에서 http://localhost:5173을 열면 Vue 앱이 실행됩니다.
Vite란 무엇인가?
Vite(비트, 프랑스어로 "빠르다")는 Evan You가 만든 차세대 프론트엔드 빌드 도구입니다. Vue CLI(webpack 기반)를 대체하는 공식 권장 도구입니다.
Vite의 핵심 특징
| 특징 | 설명 |
|---|---|
| 즉시 서버 시작 | 번들링 없이 네이티브 ES Module로 제공 → 수백 ms 내 시작 |
| 빠른 HMR | 변경된 모듈만 교체 → 앱 크기와 무관하게 즉각 반영 |
| 최적화된 빌드 | 프로덕션 빌드 시 Rollup 사용, tree-shaking 내장 |
| 플러그인 생태계 | Rollup 플러그인 호환, Vue/React/Svelte 등 공식 지원 |
Vite vs Vue CLI (webpack) 성능 비교
Vue CLI (webpack):
콜드 스타트 → [모든 파일 번들링] → 서버 준비 (수십 초)
HMR → [관련 청크 재빌드] → 브라우저 업데이트 (수 초)
Vite:
콜드 스타트 → [ES Module 직접 제공] → 서버 준비 (< 1초)
HMR → [변경 모듈만 업데이트] → 브라우저 업데이트 (< 50ms)
프로젝트 구조 이해
create-vue로 생성된 프로젝트의 기본 구조입니다:
my-vue-app/
├── public/ # 정적 파일 (그대로 복사됨)
│ └── favicon.ico
├── src/ # 소스 코드
│ ├── assets/ # 이미지, 폰트 등 (Vite가 처리)
│ ├── components/ # 재사용 가능한 Vue 컴포넌트
│ │ └── HelloWorld.vue
│ ├── composables/ # Composition API 로직 재사용 (컨벤션)
│ ├── router/ # Vue Router 설정 (선택)
│ │ └── index.js
│ ├── stores/ # Pinia 상태 관리 (선택)
│ │ └── counter.js
│ ├── views/ # 페이지 레벨 컴포넌트 (라우팅 대상)
│ │ ├── HomeView.vue
│ │ └── AboutView.vue
│ ├── App.vue # 루트 컴포넌트
│ └── main.js # 앱 진입점
├── index.html # HTML 진입점 (Vite 방식)
├── vite.config.js # Vite 설정
├── package.json
└── README.md
index.html — Vite의 HTML 진입점
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Vue App</title>
</head>
<body>
<div id="app"></div>
<!-- type="module"이 Vite의 ES Module 방식 핵심 -->
<script type="module" src="/src/main.js"></script>
</body>
</html>
src/main.js — 앱 진입점
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import './assets/main.css'
const app = createApp(App)
app.use(createPinia()) // Pinia 상태 관리 플러그인
app.use(router) // Vue Router 플러그인
app.mount('#app') // index.html의 #app div에 마운트
src/App.vue — 루트 컴포넌트
<script setup>
import { RouterLink, RouterView } from 'vue-router'
</script>
<template>
<header>
<nav>
<RouterLink to="/">홈</RouterLink>
<RouterLink to="/about">소개</RouterLink>
</nav>
</header>
<!-- 현재 라우트에 해당하는 뷰가 여기에 렌더링됨 -->
<RouterView />
</template>
Vite 설정 파일
vite.config.js는 Vite의 동작을 세밀하게 제어합니다.
기본 설정
// vite.config.js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue(), // Vue SFC 지원
],
resolve: {
alias: {
// '@'를 src/ 폴더로 단축 (import '@/components/...' 가능)
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})
고급 설정 — 개발 서버, 프록시, 환경 변수
// vite.config.js (고급)
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
// 개발 서버 설정
server: {
port: 3000, // 기본 5173 대신 3000 사용
open: true, // 서버 시작 시 브라우저 자동 열기
proxy: {
// '/api'로 시작하는 요청을 백엔드 서버로 프록시
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
// 빌드 설정
build: {
outDir: 'dist',
sourcemap: true, // 프로덕션 소스맵 생성
rollupOptions: {
output: {
// 청크 분할 전략
manualChunks: {
vendor: ['vue', 'vue-router', 'pinia'],
},
},
},
},
})
환경 변수 설정
Vite는 .env 파일로 환경 변수를 관리합니다. 클라이언트 코드에서 접근 가능한 변수는 VITE_ 접두사가 필요합니다.
# .env (공통 — 모든 환경에 적용)
VITE_APP_TITLE=My Vue App
# .env.development (개발 환경)
VITE_API_BASE_URL=http://localhost:8080/api
# .env.production (프로덕션 환경)
VITE_API_BASE_URL=https://api.myapp.com
<script setup>
// 컴포넌트에서 환경 변수 사용
const apiBase = import.meta.env.VITE_API_BASE_URL
const appTitle = import.meta.env.VITE_APP_TITLE
console.log(import.meta.env.MODE) // "development" 또는 "production"
</script>
Single File Component(SFC) 구조 심화
.vue 파일은 세 블록으로 구성됩니다:
<!-- MyComponent.vue -->
<!-- 1. Template: 컴포넌트의 HTML 구조 -->
<template>
<div class="my-component">
<h1>{{ title }}</h1>
<slot /> <!-- 슬롯: 부모가 내용을 주입할 수 있는 자리 -->
</div>
</template>
<!-- 2. Script: 컴포넌트의 JavaScript 로직 -->
<script setup>
// setup 컴파일러 매크로 사용
defineProps({
title: {
type: String,
required: true,
},
})
</script>
<!-- 3. Style: 컴포넌트 전용 스타일 -->
<!-- scoped: 이 컴포넌트에만 적용되는 스타일 -->
<style scoped>
.my-component {
padding: 1rem;
}
h1 {
color: #42b883; /* Vue 초록색 */
}
</style>
스타일 블록 옵션
<!-- scoped: 컴포넌트 범위 내에서만 적용 -->
<style scoped>
.btn { color: red; } /* 이 컴포넌트의 .btn만 빨간색 */
</style>
<!-- module: CSS Modules — 클래스 이름을 JS 객체로 사용 -->
<style module>
.btn { color: blue; }
</style>
<!-- 사용: :class="$style.btn" -->
<!-- lang="scss": SCSS/Sass 사용 (npm install -D sass 필요) -->
<style lang="scss" scoped>
$primary: #42b883;
.container {
.title { color: $primary; }
}
</style>
<!-- lang="less": Less 사용 -->
<style lang="less" scoped>
@primary: #42b883;
</style>
실전 예제 — 완전한 프로젝트 초기 셋업
새 Vue 3 프로젝트를 처음부터 설정하는 전체 과정입니다.
1단계: 프로젝트 생성
npm create vue@latest my-blog-app
# TypeScript: Yes
# Vue Router: Yes
# Pinia: Yes
# ESLint: Yes
# Prettier: Yes
cd my-blog-app
npm install
2단계: 경로 별칭 확인
// vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})
3단계: TypeScript 설정
// tsconfig.json (핵심 부분)
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"paths": {
"@/*": ["./src/*"]
}
}
}
4단계: 전역 CSS 및 디자인 토큰 설정
/* src/assets/base.css */
:root {
--color-primary: #42b883;
--color-secondary: #35495e;
--color-background: #ffffff;
--color-text: #2c3e50;
--font-size-base: 16px;
--border-radius: 8px;
}
*, *::before, *::after {
box-sizing: border-box;
}
body {
font-family: system-ui, -apple-system, sans-serif;
font-size: var(--font-size-base);
color: var(--color-text);
background: var(--color-background);
margin: 0;
}
/* src/assets/main.css */
@import './base.css';
5단계: 첫 번째 페이지 컴포넌트
<!-- src/views/HomeView.vue -->
<script setup lang="ts">
import { ref, onMounted } from 'vue'
interface Post {
id: number
title: string
excerpt: string
date: string
}
const posts = ref<Post[]>([])
const loading = ref(true)
onMounted(async () => {
// 실제로는 API 호출
await new Promise(resolve => setTimeout(resolve, 500))
posts.value = [
{ id: 1, title: 'Vue 3 시작하기', excerpt: 'Composition API의 매력', date: '2025-01-01' },
{ id: 2, title: 'Pinia로 상태 관리', excerpt: '직관적인 상태 관리', date: '2025-01-15' },
]
loading.value = false
})
</script>
<template>
<main>
<h1>블로그 홈</h1>
<div v-if="loading" class="loading">불러오는 중...</div>
<ul v-else class="post-list">
<li v-for="post in posts" :key="post.id" class="post-item">
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt }}</p>
<time>{{ post.date }}</time>
</li>
</ul>
</main>
</template>
<style scoped>
.post-list {
list-style: none;
padding: 0;
}
.post-item {
border: 1px solid #ddd;
border-radius: var(--border-radius);
padding: 1rem;
margin-bottom: 1rem;
}
</style>
Vue DevTools
Vue DevTools는 Vue 컴포넌트 트리, 반응형 상태, 이벤트, 라우터 상태 등을 실시간으로 검사할 수 있는 브라우저 확장 프로그램입니다.
설치 방법
- 브라우저 확장: Chrome Web Store 또는 Firefox Add-ons에서 "Vue.js devtools" 설치
- Vite 플러그인(선택):
create-vue스캐폴딩 시 DevTools 옵션 활성화
# 플러그인 방식으로 설치
npm install -D vite-plugin-vue-devtools
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VueDevTools from 'vite-plugin-vue-devtools'
export default defineConfig({
plugins: [
vue(),
VueDevTools(), // 개발 모드에서만 활성화됨
],
})
DevTools 주요 탭
| 탭 | 기능 |
|---|---|
| Components | 컴포넌트 트리 탐색, props/state/emit 실시간 확인 |
| Pinia | 스토어 상태 조회 및 직접 수정 |
| Router | 현재 라우트, 히스토리, 라우트 매칭 정보 |
| Timeline | 컴포넌트 이벤트, 사용자 이벤트, 퍼포먼스 타임라인 |
| Assets | 이미지, 폰트 등 에셋 확인 |
고수 팁
npm 스크립트 커스터마이징
// package.json
{
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"test:unit": "vitest",
"test:e2e": "playwright test",
"lint": "eslint . --ext .vue,.js,.ts --fix",
"format": "prettier --write src/"
}
}
절대 경로 임포트 활용
// ❌ 상대 경로 (깊어질수록 지저분해짐)
import MyComponent from '../../../components/MyComponent.vue'
// ✅ 절대 경로 (@ = src/)
import MyComponent from '@/components/MyComponent.vue'
import { useAuth } from '@/composables/useAuth'
import { useUserStore } from '@/stores/user'
Vite 플러그인 생태계
# 자동 임포트 (import 문 자동 생성)
npm install -D unplugin-auto-import unplugin-vue-components
// vite.config.js
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
dts: 'src/auto-imports.d.ts',
}),
Components({
dts: 'src/components.d.ts',
}),
],
})
자동 임포트를 설정하면 ref, computed, onMounted 등을 매 파일마다 임포트하지 않아도 됩니다.
빌드 결과물 분석
# 번들 크기 시각화
npm install -D rollup-plugin-visualizer
// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
vue(),
visualizer({ open: true }), // 빌드 후 stats.html 자동 오픈
],
})
npm run build
# → dist/ 생성 + stats.html로 번들 구성 시각화