## HTML 엔트리포인트
### main - index.html (프로젝트 루트)
```html
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta content="IE=edge" http-equiv="X-UA-Compatible" />
<meta content="width=device-width,initial-scale=1.0" name="viewport" />
<meta content="no-cache, no-store, must-revalidate" http-equiv="Cache-Control" />
<meta content="no-cache" http-equiv="Pragma" />
<meta content="0" http-equiv="Expires" />
<link href="/favicon.ico" rel="icon" />
<link href="/cdn/Pretendard-1.3.9/web/static/pretendard.css" rel="stylesheet" />
<script src="/env.js"></script>
<script>
// API 서버 preconnect 동적 추가
;(function () {
var apiUrl = (window.__ENV__ && window.__ENV__.VITE_API_BASE_URL) || ''
if (!apiUrl) return
try {
var origin = new URL(apiUrl).origin
var link = document.createElement('link')
link.rel = 'preconnect'
link.href = origin
link.crossOrigin = 'anonymous'
document.head.appendChild(link)
} catch (e) {}
})()
</script>
<title>데이터메이커 시냅스</title>
</head>
<body style="background-color: #1a1f2c; margin: 0;">
<noscript><strong>JavaScript를 활성화해주세요.</strong></noscript>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
```
### staging - public/index.html (Webpack 템플릿)
```html
<!doctype html>
<html lang="">
<head>
<meta charset="utf-8" />
<meta content="IE=edge" http-equiv="X-UA-Compatible" />
<meta content="width=device-width,initial-scale=1.0" name="viewport" />
<meta content="no-cache, no-store, must-revalidate" http-equiv="Cache-Control" />
<link href="<%= BASE_URL %>favicon.ico" rel="icon" />
<link href="<%= BASE_URL %>cdn/Pretendard-1.3.9/web/static/pretendard.css" rel="stylesheet" />
<title><%= process.env.BRAND_TITLE || '데이터메이커 시냅스' %></title>
</head>
<body style="background-color: #1e2533; margin: 0;">
<noscript><strong>JavaScript를 활성화해주세요.</strong></noscript>
<div id="app"></div>
</body>
</html>
```
### HTML 차이점
| 항목 | main | staging |
|------|------|---------|
| 위치 | 프로젝트 루트 `index.html` | `public/index.html` |
| 템플릿 문법 | 없음 (정적 HTML) | `<%= %>` (HtmlWebpackPlugin) |
| 스크립트 로드 | `<script type="module" src="/src/main.js">` | Webpack이 자동 주입 |
| 환경변수 | `env.js` 런타임 로드 + preconnect | `process.env` 빌드타임 주입 |
| 배경색 | `#1a1f2c` | `#1e2533` |
| 타이틀 | 하드코딩 | 환경변수 기반 (`BRAND_TITLE`) |
## 앱 엔트리포인트 (main.js)
### main (Vue 3.x)
```javascript
import { createApp } from 'vue'
import App from '@/app/app.vue'
import store from '@/app/app-store'
import router from '@/app/app-routes'
import i18n from '@/i18n'
import { initSentry } from '@/plugins/sentry'
import { registerPlugins } from '@/plugins'
import { registerComponents } from '@/plugins/components'
const app = createApp(App)
app.use(store)
app.use(router)
app.use(i18n)
initSentry(app, router)
registerPlugins(app)
registerComponents(app)
app.mount('#app')
// Service Worker 등록
if ('serviceWorker' in navigator) {
/* ... */
}
```
### staging (Vue 2.x)
```javascript
import Vue from 'vue'
import App from '@/app/app.vue'
import store from '@/app/app-store'
import router from '@/app/app-routes'
import i18n from '@/i18n'
// Vue 2 플러그인 등록
import VueCompositionAPI from '@vue/composition-api'
import { BootstrapVue } from 'bootstrap-vue'
import VueToast from 'vue-toast-notification'
import VueWorker from 'vue-worker'
import VJsoneditor from 'v-jsoneditor'
import CKEditor from '@ckeditor/ckeditor5-vue2'
import VueFormulate from '@braid/vue-formulate'
Vue.use(VueCompositionAPI)
Vue.use(BootstrapVue)
Vue.use(VueToast)
Vue.use(VueWorker)
Vue.use(VJsoneditor)
Vue.use(CKEditor)
Vue.use(VueFormulate)
// 글로벌 컴포넌트 등록
Vue.component('v-select', vSelect)
Vue.component('page-header', PageHeader)
// ... 기타 글로벌 컴포넌트
// Sentry 초기화
Sentry.init({ Vue, dsn: '...' })
new Vue({
router,
store,
i18n,
render: (h) => h(App)
}).$mount('#app')
```
:::note[핵심 차이점]
- Vue 3: `createApp()` + `app.use()` 체인
- Vue 2: `new Vue({}).$mount()` + `Vue.use()` 전역 등록
- Vue 2에서는 `@vue/composition-api` 백포트 필요
- Vue 2에서는 BootstrapVue, CKEditor 등 추가 플러그인 사용
:::
## 라우터 설정
### main (Vue Router 4)
```javascript
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
...dashboardRoutes,
...annotatorRoutes,
...authRoutes,
{ path: '/:pathMatch(.*)*', redirect: '/auth/login' }
]
})
// Navigation Guard
router.beforeEach(async (to, from, next) => {
// 인증 및 테넌트 확인
})
```
### staging (Vue Router 3)
```javascript
import VueRouter from 'vue-router'
const router = new VueRouter({
mode: 'history',
routes: [
...dashboardRoutes,
...annotatorRoutes,
{ path: '*', redirect: '/auth/login' }
]
})
// Navigation Guard
router.beforeEach(async (to, from, next) => {
// 인증 및 테넌트 확인
})
```
| 항목 | main | staging |
|------|------|---------|
| 생성 방식 | `createRouter()` | `new VueRouter()` |
| History | `createWebHistory()` | `mode: 'history'` |
| Catch-all | `/:pathMatch(.*)*` | `*` |
## 상태 관리 (Vuex)
### main (Vuex 4)
```javascript
import { createStore } from 'vuex'
import createPersistedState from 'vuex-persist'
const store = createStore({
modules: {
annotator,
imageAnnotator,
pcdAnnotator,
textAnnotator,
audioAnnotator,
videoAnnotator,
promptAnnotator,
dashboard
},
plugins: [new VuexPersistence({ /* ... */ }).plugin]
})
```
### staging (Vuex 3)
```javascript
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
const store = new Vuex.Store({
modules: {
annotator,
imageAnnotator,
pcdAnnotator,
textAnnotator,
audioAnnotator,
videoAnnotator,
promptAnnotator,
dashboard
},
plugins: [createPersistedState({ /* ... */ })]
})
```
| 항목 | main | staging |
|------|------|---------|
| 생성 방식 | `createStore()` | `new Vuex.Store()` |
| Persistence | `vuex-persist` | `vuex-persistedstate` |
## 국제화 (i18n)
### main (vue-i18n 11)
```javascript
import { createI18n } from 'vue-i18n'
const i18n = createI18n({
legacy: false, // Composition API 모드
globalInjection: true, // $t 템플릿에서 사용 가능
locale: 'ko',
fallbackLocale: 'ko',
messages: {
/* import.meta.glob('./locales/*.json') */
}
})
```
### staging (vue-i18n 8)
```javascript
import VueI18n from 'vue-i18n'
const i18n = new VueI18n({
locale: 'ko',
fallbackLocale: 'ko',
messages: {
/* require.context('./locales', ...) */
}
})
```
| 항목 | main | staging |
|------|------|---------|
| 생성 방식 | `createI18n()` | `new VueI18n()` |
| API 모드 | Composition (`legacy: false`) | Options (legacy) |
| 파일 로드 | `import.meta.glob` | `require.context` |