Skip to content

전체 예시 — 실제 앱에 포팅하기

빠른 시작로컬에서 ⚙ 버튼을 띄우는 최소 경로(설치 + main.ts 몇 줄)였습니다. 하지만 실제 앱, 특히 배포(검증계) 빌드에서도 디버거를 제대로 쓰려면 main.ts 외에 vite.config·라우터·.env 등 파일 몇 개를 더 건드립니다.

이 페이지는 전형적인 Vue 3 앱(Vite + TypeScript + Pinia + vue-router + axios)에 디버거를 처음부터 끝까지 붙이는 전 과정을, 건드리는 파일 전부와 함께 순서대로 보여줍니다. 각 파일이 필요한지 표로 먼저 정리하고, 그다음 각 파일의 전체 예시를 차례로 제시합니다.

0. 출발점 — 예시 앱 구조

아래는 디버거를 붙이기 의, 흔한 Vue 3 앱입니다. 주문 관리 화면이 있고 Pinia로 인증 상태를, vue-router로 라우팅을, axios로 API를 호출합니다.

shop-web/
├─ package.json
├─ vite.config.ts
├─ index.html
└─ src/
   ├─ main.ts            ← 부트스트랩
   ├─ App.vue
   ├─ router/
   │  ├─ index.ts        ← 라우트 + 전역 가드
   │  └─ guards.ts        ← authGuard
   ├─ stores/
   │  └─ auth.ts          ← Pinia 스토어
   ├─ api/
   │  └─ client.ts        ← axios 인스턴스
   └─ pages/
      ├─ Home.vue · Orders.vue · Admin.vue

1. 무엇을 건드리나 — 파일 체크리스트

핵심부터

로컬(vite dev)에서만 쓸 거라면 main.ts 한 파일이면 끝납니다. 아래 표의 나머지(vite.config·.env.staging·router)는 ① 배포(검증계) 빌드에 디버거를 넣을 때 또는 ② 전역 라우터 가드를 추적할 때만 필요합니다.

파일변경왜 필요한가언제 필요
package.jsondevDependency + 스크립트 추가디버거 설치(-D) + 검증계 빌드 명령(build:staging)항상
src/main.tsasync 부트스트랩 + 조건부 attachDebugger부착 진입점. 디버거를 앱에 붙이는 단 한 곳항상(핵심)
vite.config.tsmode==='staging' 분기로 __VUE_PROD_DEVTOOLS__ + sourcemap:'hidden'배포 빌드에서 컴포넌트 introspection(값·출처칩·식평가)과 스택 원본 복원이 동작검증계 배포 시
.env.stagingVITE_DEBUGGER=on이 모드 빌드에만 디버거를 포함시키는 스위치검증계 배포 시
src/router/index.tstraceGuards(router) 한 줄(정적 import)attach 이전에 등록되는 전역 beforeEach/beforeResolve를 Router 패널에 귀속전역 가드를 추적할 때만

건드리지 않는 것 — 컴포넌트·스토어·axios·서비스

앱 코드는 한 줄도 바꾸지 않습니다. 디버거는 Pinia 스토어·라우터·axios를 import하지 않고 런타임에 발견하고, 네트워크는 XHR/fetch를 바닥에서 잡습니다. 그래서 stores/·pages/·api/는 그대로 둬도 디버거가 알아서 관찰합니다(격리·호환·한계 참고).

2. 파일별 전체 예시 (순서대로)

아래 순서대로 따라가면 됩니다. ①설치 → ②package.json → ③main.ts → ④router → ⑤vite.config → ⑥.env.staging.

2-1. 설치

bash
npm install -D @platform/vue-debugger

vuepeerDependency라 호스트 앱의 vue를 그대로 씁니다(중복 설치 없음). 사내 레지스트리·타르볼·폐쇄망 설치는 설치폐쇄망 반입을 보세요.

2-2. package.json — 의존성 + 빌드 스크립트

build(프로덕션, 디버거 0)와 build:staging(검증계, 디버거 포함)을 나눕니다.

json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "build:staging": "vite build --mode staging",
    "preview": "vite preview"
  },
  "devDependencies": {
    "@platform/vue-debugger": "^0.17.4"
  }
}

2-3. src/main.ts — 부착 진입점 (전체)

app.use(pinia)·app.use(router) 뒤, app.mount()에 붙입니다. 부트스트랩 전체를 async 함수로 감싸 디버거를 동적 import()로 지연 로드합니다.

ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import { router } from './router'

async function bootstrap() {
  const app = createApp(App)

  // ① 상태·라우팅을 먼저 등록 — 그래야 디버거가 스토어·라우터를 발견한다
  app.use(createPinia())
  app.use(router)

  // ② 디버거: 로컬(vite dev) 또는 VITE_DEBUGGER=on 검증계 빌드에서만.
  //    프로덕션 빌드에선 두 조건이 '정적 false' → 이 블록과 동적 import가 통째 제거된다(디버거 0).
  if (import.meta.env.DEV || (import.meta.env.MODE === 'staging' && import.meta.env.VITE_DEBUGGER === 'on')) {
    const { attachDebugger } = await import('@platform/vue-debugger')
    attachDebugger(app, {
      // 옵션은 모두 선택. 아래는 자주 쓰는 예시(전체는 옵션 레퍼런스).
      ignoreUrls: ['/heartbeat', /datadog|sentry/], // APM 비콘 노이즈 제외
      report: {
        endpoint: '/internal/reports',
        allowHosts: ['reports.corp.example'], // 전송 허용 호스트(필수)
      },
    })
  }

  // ③ 마운트는 디버거 부착 '뒤'에
  app.mount('#app')
}
bootstrap()

top-level await는 금지

await import()를 모듈 최상단에 직접 쓰지 마세요. vite dev(esbuild)에선 되지만 vite build 기본 타깃(es2020)이 top-level await를 거부해 검증계 빌드가 깨집니다. 반드시 위처럼 async function으로 감싸세요.

옵션 전체(bridges·maxEntries·hotkey·ignoreStorageKeys·report.redact…)는 설정 옵션에 있습니다. attachDebugger{ version, open(), close(), toggle(), destroy() } 핸들을 반환합니다(비브라우저 환경에선 null).

2-4. src/router/index.ts — 전역 가드 추적 (선택)

/admin처럼 **라우트별 beforeEnter**가 막는 경우는 설정이 필요 없습니다(디버거가 attach 시 자동으로 '어느 가드가 막았나'를 잡습니다). **전역 beforeEach/beforeResolve**만, 그것을 등록하기 직전에 traceGuards(router) 한 줄을 넣습니다.

ts
import { createRouter, createWebHistory } from 'vue-router'
// ⚠ traceGuards는 '정적 import'다 — 가드 등록 직전에 '동기'로 불려야 하므로 동적 import로는 순서를 못 맞춘다.
import { traceGuards } from '@platform/vue-debugger'
import { useAuthStore } from '@/stores/auth'
import { authGuard } from './guards'

export const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: () => import('@/pages/Home.vue') },
    { path: '/orders', component: () => import('@/pages/Orders.vue') },
    // 라우트별 가드 — 디버거가 자동 귀속(설정 불필요)
    { path: '/admin', component: () => import('@/pages/Admin.vue'), beforeEnter: () => (useAuthStore().isAdmin ? true : '/') },
  ],
})

if (import.meta.env.DEV || (import.meta.env.MODE === 'staging' && import.meta.env.VITE_DEBUGGER === 'on')) {
  traceGuards(router) // ← 전역 가드 등록 '전에' (동기 — 이 한 줄이 전부)
}

router.beforeEach(authGuard) // 이제 authGuard가 막거나 리다이렉트하면 Router 패널에 귀속된다

정적 import인데 왜 프로덕션에 안 새나

프로덕션 빌드에선 분기가 false가 되어 traceGuards(...) 호출이 제거되고, 디버거 패키지의 sideEffects: false 덕에 import 자체가 트리셰이킹됩니다(누출 0). 가드 동작은 바뀌지 않습니다(원래 반환값·length 보존). 자세한 이유는 라우터 가드 추적을 보세요.

2-5. vite.config.ts — 배포 빌드용 두 스위치 (전체)

mode === 'staging'일 때만 두 가지를 켭니다. 둘 다 배포(vite build) 빌드에서 디버거가 '제대로' 보이게 하는 데 필요합니다(로컬 vite dev는 둘 다 불필요).

ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig(({ mode }) => {
  const debug = mode === 'staging'
  return {
    plugins: [vue()],
    resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } },

    // ① 컴포넌트 introspection: 켜야 setup 바인딩이 노출돼 '값·출처칩·식 평가'가 보인다.
    //    끄면(=기본 프로덕션) 트리는 보여도 State가 전부 비거나 undefined.
    define: debug ? { __VUE_PROD_DEVTOOLS__: 'true' } : {},

    // ② 소스맵: .map은 만들되 JS엔 sourceMappingURL 주석을 안 남긴다('hidden').
    //    디버거가 .map을 직접 받아 스택을 'src/…:라인 (함수)' 원본 위치로 복원한다.
    build: { sourcemap: debug ? 'hidden' : false },
  }
})

.map은 외부 공개 금지

소스맵은 원본 코드를 복원합니다. 검증계(내부망)에만 두고 외부 공개망엔 배포하지 마세요. 매트릭스·이유 전체는 환경별 설정에 있습니다.

2-6. .env.staging — 검증계 빌드 스위치

--mode staging 빌드에서만 로드되는 환경 파일입니다. 이 한 줄이 main.ts·routerVITE_DEBUGGER === 'on' 분기를 켭니다.

bash
# .env.staging — 이 모드(staging) 빌드에만 디버거를 포함시킨다
VITE_DEBUGGER=on

2-7. (변경 없음) 스토어 · axios — 그대로 둡니다

참고로, 디버거가 관찰하는 앱 코드는 전혀 바꾸지 않습니다. 아래는 예시 앱의 스토어와 axios 인스턴스인데, 한 줄도 손대지 않아도 Pinia 탭·네트워크 탭에 그대로 잡힙니다.

ts
// src/stores/auth.ts — 변경 없음. 디버거가 런타임에 발견해 Pinia 탭에 띄운다.
import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', {
  state: () => ({ user: null as null | { name: string }, isAdmin: false }),
  actions: { login(name: string) { this.user = { name } } },
})
ts
// src/api/client.ts — 변경 없음. axios 인스턴스가 몇 개든 인터셉터 등록 없이 네트워크 탭에 잡힌다.
import axios from 'axios'
export const api = axios.create({ baseURL: '/api' })

(선택) 깔끔하게 — debuggerBootstrap(app) 한 줄로

app.use(...)가 여기저기 흩어져 main.ts가 길어진다면, §2-3의 조건 + 동적 import + attachDebugger 블록을 함수 하나로 분리app.mount() 바로 위에서 한 줄로 부르면 됩니다.

ts
import type { App } from 'vue'

export async function debuggerBootstrap(app: App): Promise<void> {
  // ⚠ 조건은 반드시 '리터럴'로 — import.meta.env.* 가 정적 치환돼야 프로덕션에서 통째 제거된다(아래 표)
  if (!(import.meta.env.DEV || (import.meta.env.MODE === 'staging' && import.meta.env.VITE_DEBUGGER === 'on'))) return

  const { attachDebugger } = await import('@platform/vue-debugger')
  attachDebugger(app, {
    ignoreUrls: ['/heartbeat', /datadog|sentry/],
    report: { endpoint: '/internal/reports', allowHosts: ['reports.corp.example'] },
  })
}
ts
import { debuggerBootstrap } from './debugger-bootstrap'

async function bootstrap() {
  const app = createApp(App)
  app.use(createPinia())
  app.use(router)
  // ... 흩어진 app.use(...) 전부 위에 ...

  await debuggerBootstrap(app) // ← app.mount 바로 위 한 줄
  app.mount('#app')
}
bootstrap()

함수로 빼도 프로덕션 누출 0은 그대로 유지됩니다 — import.meta.env.*는 전역 정적 치환이라 함수 안이어도 if (false)로 죽고, 그 안의 동적 import()도 함께 제거됩니다. 단 아래를 지키세요.

규칙이유
조건을 함수 안에 리터럴로 (import.meta.env.DEV || …)정적 치환 대상이라야 프로덕션에서 본문·동적 import가 제거됨
조건을 boolean 인자로 빼지 말 것 (debuggerBootstrap(app, enabled) ❌)정적 치환 대상이 사라져 dead-code 제거가 깨지고 디버거가 프로덕션에 새어 나감
async + awaitmountcollector가 첫 렌더·요청 전에 설치되도록
traceGuards는 합치지 말 것라우터 모듈에서 정적 import + 가드 등록 직전 동기 호출이라 비동기 함수에 못 넣음(§2-4 그대로)

검증: npm run build(프로덕션) 후 grep -rl "__VUE_DEBUGGER__" dist/assets0.

3. 실행 — 세 가지 환경

같은 코드가 환경에 따라 다르게 빌드됩니다. 핵심은 디버거는 로컬·검증계에만 들어가고, 프로덕션엔 0이라는 점입니다.

bash
npm run dev
# import.meta.env.DEV === true → 디버거 자동 ON. vite.config·.env 설정 불필요.
bash
npm run build:staging   # = vite build --mode staging
# .env.staging의 VITE_DEBUGGER=on → 디버거 + __VUE_PROD_DEVTOOLS__ + .map
npm run preview         # 빌드본 미리보기 → ⚙ FAB 확인
bash
npm run build
# 분기가 정적 false → 동적 import·traceGuards 호출이 dead-code로 제거. 디버거 흔적 0.

4. 확인

  1. npm run dev → 우측 하단 ⚙ FAB. Alt+Shift+D(또는 Esc로 닫기).
  2. 화면 조작 → 네트워크 탭에 axios 요청 기록, 컴포넌트 탭 인스펙션으로 값·출처칩 확인.
  3. 프로덕션 안전: npm run build 후 누출 0 확인.
    bash
    grep -rl "__VUE_DEBUGGER__" dist/assets 2>/dev/null && echo "⚠ 누출!" || echo "✓ 프로덕션 깨끗"

전체 검증 체크리스트와 CI 누출 게이트는 설치 검증을, 안 보이거나 값이 비면 트러블슈팅을 보세요.

부록 — webpack / Vue CLI 변형

import.meta.env가 없는 webpack/Vue CLI는 두 군데만 바꿉니다(나머지 파일 구조는 동일):

Vitewebpack / Vue CLI
import.meta.env.DEV || (import.meta.env.MODE === 'staging' && import.meta.env.VITE_DEBUGGER === 'on')process.env.NODE_ENV !== 'production' || (__VDBG_STAGING__ && process.env.VUE_APP_DEBUGGER === 'on')__VDBG_STAGING__는 빌드 스크립트에 묶인 DefinePlugin 상수(연동의 webpack 예시 참고)
vite.config.tsdefine/build.sourcemapvue.config.jsconfigureWebpack.devtool('hidden-source-map') + DefinePlugin으로 __VUE_PROD_DEVTOOLS__

DefinePluginprocess.env.*를 정적 치환해야 프로덕션 dead-code 제거가 똑같이 동작합니다. 분기 매트릭스 전체는 환경별 설정에 있습니다.

개발/검증 환경 전용 인페이지 Vue 디버거 · 진짜 프로덕션엔 코드가 들어가지 않습니다