프론트엔드 팀 새 프로젝트 시작할 때 쓰는 셋업 스택 정리. Vue3 + TS + Vite + ESLint + Prettier + Pinia + Vue Router + Tailwind. 실제로 회사에서 이 조합으로 두 개 프로젝트 돌리고 있고 가장 큰 장점은 Vite 개발서버 속도와 Pinia 코드 간결함이다. Vuex 쓰다가 Pinia 가면 못 돌아온다. Vue 3.5 / Vite 6 기준.
왜 이 조합인지
- Vite: HMR 체감이 다르다. 큰 프로젝트에서 webpack dev server 대비 압도적
- Vue3: Composition API + <script setup> 이 주력. 리액트 훅이랑 생각하는 방향이 비슷해서 프론트 리소스 이동도 쉬움
- TypeScript: 솔직히 팀에 2명 이상 되면 필수
- ESLint / Prettier: lint는 논리, 포맷은 Prettier. 영역 분리가 명확해짐
- Pinia: Vuex 대체. 타입 추론 훨씬 강함 (Vuex 5 프로젝트 사실상 Pinia로 수렴했다고 보면 됨)
- Vue Router: SPA 라우팅 표준
- Tailwind: 클래스 이름 싸움에서 해방. 처음엔 HTML이 지저분해 보이는데 2주 쓰면 돌아갈 수가 없음
프로젝트 생성
$ npm create vite@latest 프로젝트명 --template vue-ts
$ cd 프로젝트명 $ npm install $ npm run dev
@types/node 설치
vite.config.ts에서 path 모듈 쓸 때 타입 에러 나는 거 막기 위함.
$ npm install --save-dev @types/node
vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import * as path from "path";
export default defineConfig({
plugins: [vue()],
resolve: {
alias: { "@": path.resolve(__dirname, "./src") },
},
});
tsconfig.json
ViTe의 alias와 TS의 paths를 동일하게 맞춰줘야 에디터에서 경고 안 뜬다. 이거 두 개 동기화 안 맞으면 자동완성은 되는데 빌드 에러나는 짜증 상황이 생김.
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
ESLint + Prettier
$ npm install -D prettier $ npm install -D eslint $ npm install -D eslint-plugin-vue $ npm install -D eslint-config-prettier $ npm install -D @vue/eslint-config-typescript
.eslintrc.js
중요한 건 'prettier'를 extends 맨 뒤에 넣어야 한다는 것. 앞의 규칙들 중 포맷 관련 규칙을 다 꺼버리는 역할이라서 순서가 중요함. 이거 순서 잘못 해서 저장할 때마다 포맷이 왔다갔다 하면 하루 날린다.
module.exports = {
env: {
node: true
},
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'prettier'
],
rules: [
// override or add rules settings here
]
};
Pinia
$ npm install pinia
import { createPinia } from 'pinia';
const pinia = createPinia();
...use(pinia);
Pinia의 진짜 매력은 store를 setup 함수처럼 쓸 수 있다는 것. defineStore('auth', () => { const user = ref(null); ... return { user }; }) 이렇게. Vuex의 state/mutations/actions 세 영역 다닐 필요 없어짐.
Vue Router 4
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
const routes: Array<RouteRecordRaw> = [
{ path: "/", component: () => import("../views/Home.vue") },
{ path: "/about", component: () => import("../views/About.vue") }
];
export default createRouter({
history: createWebHistory(),
routes,
});
컴포넌트를 () => import(...) 동적 import로 선언하면 자동으로 route-level code splitting 된다. 빌드 사이즈 신경쓴다면 처음부터 이렇게.
import router from "./router"; ...use(router);
Tailwind CSS
$ npm install -D tailwindcss@latest postcss@latest autoprefixer@latest $ npx tailwindcss init -p
import 'tailwindcss/tailwind.css';
이 셋업으로 실제 겪은 이슈 몇 가지
초반에 Vite 환경에서 process.env 직접 쓰다가 undefined 만나고 당황함. Vite는 import.meta.env 쓰는 게 맞음. 환경변수명도 VITE_ 접두어 붙여야 노출된다.
그리고 IDE는 VS Code + Volar 확장 (이제 공식 이름은 Vue - Official)을 쓰면 된다. TS 검사 속도를 위해 Take Over Mode 켜두는 걸 추천. 기본 TypeScript 확장을 끄고 Volar가 전담하는 방식.