vite 환경설정

vite.config.ts 파일 작성

client 폴더에 vite.config.ts 파일 열어서 아래와 같이 수정

import path from 'path';
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { VitePWA } from 'vite-plugin-pwa';
import { compression } from 'vite-plugin-compression2';
import { nodePolyfills } from 'vite-plugin-node-polyfills';
import type { Plugin } from 'vite';

// https://vitejs.dev/config/
export default defineConfig(({ command }) => ({
  server: {
    host: 'localhost',
    port: 3090,
    strictPort: false,
    proxy: {
      '/api': {
        target: 'http://localhost:3080',
        changeOrigin: true,
      },
      '/oauth': {
        target: 'http://localhost:3080',
        changeOrigin: true,
      },
    },
  },
  // Set the directory where environment variables are loaded from and restrict prefixes
  envDir: '../',
  envPrefix: ['VITE_', 'SCRIPT_', 'DOMAIN_', 'ALLOW_'],
  plugins: [
    react(),
    nodePolyfills(),
    VitePWA({
      injectRegister: 'auto', // 'auto' | 'manual' | 'disabled'
      registerType: 'autoUpdate', // 'prompt' | 'autoUpdate'
      devOptions: {
        enabled: false, // disable service worker registration in development mode
      },
      useCredentials: true,
      includeManifestIcons: false,
      workbox: {
        globPatterns: [
          '**/*.{js,css,html}',
          'assets/favicon*.png',
          'assets/icon-*.png',
          'assets/apple-touch-icon*.png',
          'assets/maskable-icon.png',
          'manifest.webmanifest',
        ],
        globIgnores: ['images/**/*', '**/*.map'],
        maximumFileSizeToCacheInBytes: 4 * 1024 * 1024,
        navigateFallbackDenylist: [/^\/oauth/, /^\/api/],
      },
      includeAssets: [],
      manifest: {
        name: 'AiChat',
        short_name: 'AiChat',
        start_url: '/',
        display: 'standalone',
        background_color: '#000000',
        theme_color: '#009688',
        icons: [
          {
            src: '/assets/favicon-32x32.png',
            sizes: '32x32',
            type: 'image/png',
          },
          {
            src: '/assets/favicon-16x16.png',
            sizes: '16x16',
            type: 'image/png',
          },
          {
            src: '/assets/apple-touch-icon-180x180.png',
            sizes: '180x180',
            type: 'image/png',
          },
          {
            src: '/assets/icon-192x192.png',
            sizes: '192x192',
            type: 'image/png',
          },
          {
            src: '/assets/maskable-icon.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'maskable',
          },
        ],
      },
    }),
    sourcemapExclude({ excludeNodeModules: true }),
    compression({
      threshold: 10240,
    }),
  ],
  publicDir: command === 'serve' ? './public' : false,
  build: {
    sourcemap: process.env.NODE_ENV === 'development',
    outDir: './dist',
    minify: 'terser',
    rollupOptions: {
      preserveEntrySignatures: 'strict',
      output: {
        manualChunks(id: string) {
          if (id.includes('node_modules')) {
            // High-impact chunking for large libraries
            if (id.includes('@codesandbox/sandpack')) {
              return 'sandpack';
            }
            if (id.includes('react-virtualized')) {
              return 'virtualization';
            }
            if (id.includes('i18next') || id.includes('react-i18next')) {
              return 'i18n';
            }
            if (id.includes('lodash')) {
              return 'utilities';
            }
            if (id.includes('date-fns')) {
              return 'date-utils';
            }
            if (id.includes('@dicebear')) {
              return 'avatars';
            }
            if (id.includes('react-dnd') || id.includes('react-flip-toolkit')) {
              return 'react-interactions';
            }
            if (id.includes('react-hook-form')) {
              return 'forms';
            }
            if (id.includes('react-router-dom')) {
              return 'routing';
            }
            if (id.includes('qrcode.react') || id.includes('@marsidev/react-turnstile')) {
              return 'security-ui';
            }

            if (id.includes('@codemirror/view')) {
              return 'codemirror-view';
            }
            if (id.includes('@codemirror/state')) {
              return 'codemirror-state';
            }
            if (id.includes('@codemirror/language')) {
              return 'codemirror-language';
            }
            if (id.includes('@codemirror')) {
              return 'codemirror-core';
            }

            if (id.includes('react-markdown') || id.includes('remark-') || id.includes('rehype-')) {
              return 'markdown-processing';
            }
            if (id.includes('monaco-editor') || id.includes('@monaco-editor')) {
              return 'code-editor';
            }
            if (id.includes('react-window') || id.includes('react-virtual')) {
              return 'virtualization';
            }
            if (id.includes('zod') || id.includes('yup') || id.includes('joi')) {
              return 'validation';
            }
            if (id.includes('axios') || id.includes('ky') || id.includes('fetch')) {
              return 'http-client';
            }
            if (id.includes('react-spring') || id.includes('react-transition-group')) {
              return 'animations';
            }
            if (id.includes('react-select') || id.includes('downshift')) {
              return 'advanced-inputs';
            }

            // Existing chunks
            if (id.includes('@radix-ui')) {
              return 'radix-ui';
            }
            if (id.includes('framer-motion')) {
              return 'framer-motion';
            }
            if (id.includes('node_modules/highlight.js')) {
              return 'markdown_highlight';
            }
            if (id.includes('katex') || id.includes('node_modules/katex')) {
              return 'math-katex';
            }
            if (id.includes('node_modules/hast-util-raw')) {
              return 'markdown_large';
            }
            if (id.includes('@tanstack')) {
              return 'tanstack-vendor';
            }
            if (id.includes('@headlessui')) {
              return 'headlessui';
            }

            // Everything else falls into a generic vendor chunk.
            return 'vendor';
          }
          // Create a separate chunk for all locale files under src/locales.
          if (id.includes(path.join('src', 'locales'))) {
            return 'locales';
          }
          // Let Rollup decide automatically for any other files.
          return null;
        },
        entryFileNames: 'assets/[name].[hash].js',
        chunkFileNames: 'assets/[name].[hash].js',
        assetFileNames: (assetInfo) => {
          if (assetInfo.names?.[0] && /\.(woff|woff2|eot|ttf|otf)$/.test(assetInfo.names[0])) {
            return 'assets/fonts/[name][extname]';
          }
          return 'assets/[name].[hash][extname]';
        },
      },
      /**
       * Ignore "use client" warning since we are not using SSR
       * @see {@link https://github.com/TanStack/query/pull/5161#issuecomment-1477389761 Preserve 'use client' directives TanStack/query#5161}
       */
      onwarn(warning, warn) {
        if (warning.message.includes('Error when using sourcemap')) {
          return;
        }
        warn(warning);
      },
    },
    chunkSizeWarningLimit: 1500,
  },
  resolve: {
    alias: {
      '~': path.join(__dirname, 'src/'),
      $fonts: path.resolve(__dirname, 'public/fonts'),
    },
  },
}));

interface SourcemapExclude {
  excludeNodeModules?: boolean;
}
export function sourcemapExclude(opts?: SourcemapExclude): Plugin {
  return {
    name: 'sourcemap-exclude',
    transform(code: string, id: string) {
      if (opts?.excludeNodeModules && id.includes('node_modules')) {
        return {
          code,
          // https://github.com/rollup/rollup/blob/master/docs/plugin-development/index.md#source-code-transformations
          map: { mappings: '' },
        };
      }
    },
  };
}


Vite 빌드 도구의 설정 파일vite.config.ts입니다.
Vite는 빠르고 효율적인 프론트엔드 개발 경험을 제공하는 최신 빌드 도구로, 이 설정 파일은 개발 서버, 빌드 프로세스, 플러그인 등을 세부적으로 제어합니다.

파일을 살펴보면 TypeScript로 작성되어 있으며, 여러 Vite 플러그인과 빌드 최적화 설정이 포함되어 있습니다.


vite.config.ts 파일의 구성 요소 설명

1. 전역 설정 (defineConfig)

export default defineConfig(({ command }) => ({ ... }));

  • defineConfig: Vite 설정 객체를 정의하는 헬퍼 함수입니다. IntelliSense를 제공하고 타입 안전성을 높여줍니다.
  • ({ command }): command 인자를 통해 현재 Vite가 개발 서버를 실행하는지('serve'), 아니면 프로덕션 빌드를 하는지('build')에 따라 다른 설정을 적용할 수 있습니다.

2. 개발 서버 설정 (server)

TypeScript
server: {
  host: 'localhost',
  port: 3090,
  strictPort: false,
  proxy: {
    '/api': {
      target: 'http://localhost:3080',
      changeOrigin: true,
    },
    '/oauth': {
      target: 'http://localhost:3080',
      changeOrigin: true,
    },
  },
},
  • host: 'localhost': 개발 서버가 localhost에서 실행되도록 설정합니다.
  • port: 3090: 개발 서버가 3090번 포트에서 실행되도록 설정합니다.
  • strictPort: false: 포트가 이미 사용 중일 경우, Vite가 자동으로 다음 사용 가능한 포트를 찾을지 여부를 제어합니다 (false면 찾고, true면 찾지 않고 에러 발생).
  • proxy: API 요청을 다른 서버로 프록시(중계)하도록 설정합니다. 이는 프론트엔드 개발 서버와 백엔드 서버가 다른 포트에서 실행될 때 CORS(교차 출처 자원 공유) 문제를 피하기 위해 흔히 사용됩니다.
    • /api: /api로 시작하는 모든 요청은 http://localhost:3080으로 전달됩니다.
    • /oauth: /oauth로 시작하는 모든 요청도 http://localhost:3080으로 전달됩니다.
    • changeOrigin: true: 프록시 요청의 Host 헤더를 대상 서버의 URL로 변경합니다. (백엔드 서버가 Host 헤더를 확인하는 경우 필요)

3. 환경 변수 설정 (envDir, envPrefix)

TypeScript
envDir: '../',
envPrefix: ['VITE_', 'SCRIPT_', 'DOMAIN_', 'ALLOW_'],
  • envDir: '../': 환경 변수 파일(예: .env, .env.development)이 위치한 디렉토리를 지정합니다. 여기서는 프로젝트 루트 디렉토리의 한 단계 위 (../)에 .env 파일이 있을 것으로 예상됩니다.
  • envPrefix: [...]: Vite가 로드할 환경 변수의 접두사를 제한합니다. 여기에 명시된 접두사로 시작하는 환경 변수만 import.meta.env를 통해 노출됩니다. 이는 불필요한 환경 변수가 클라이언트 번들에 포함되는 것을 방지하여 보안과 번들 크기 측면에서 유리합니다.

4. 플러그인 설정 (plugins)

TypeScript
plugins: [
  react(),
  nodePolyfills(),
  VitePWA({ /* ... */ }),
  sourcemapExclude({ excludeNodeModules: true }),
  compression({ threshold: 10240 }),
],
  • react(): @vitejs/plugin-react 플러그인을 활성화합니다. 이 플러그인은 React 애플리케이션 개발에 필요한 Fast Refresh (HMR), JSX 변환 등을 지원합니다.
  • nodePolyfills(): vite-plugin-node-polyfills 플러그인을 활성화합니다. 브라우저 환경에서 Node.js 내장 모듈(예: path, buffer, crypto 등)을 사용할 수 있도록 Polyfill을 제공합니다. 이는 일부 라이브러리가 Node.js 환경에 의존하는 경우 유용합니다.
  • VitePWA({ ... }): vite-plugin-pwa 플러그인을 활성화합니다. 이 플러그인은 Progressive Web App (PWA) 기능을 프로젝트에 쉽게 추가할 수 있도록 해줍니다.
    • injectRegister: 'auto': 서비스 워커(Service Worker) 등록 코드를 자동으로 주입합니다.
    • registerType: 'autoUpdate': 서비스 워커가 자동으로 업데이트되도록 설정합니다.
    • devOptions: { enabled: false }: 개발 모드에서는 서비스 워커 등록을 비활성화합니다. (개발 시 캐싱 문제 방지)
    • useCredentials: true: PWA가 인증 정보를 사용하여 요청할 수 있도록 합니다.
    • includeManifestIcons: false: manifest.json에 아이콘을 포함할지 여부. (여기서는 manifest 섹션에서 직접 정의하므로 false)
    • workbox: Workbox 라이브러리를 통해 서비스 워커의 캐싱 전략을 구성합니다.
      • globPatterns: 서비스 워커가 캐시할 파일 패턴을 지정합니다.
      • globIgnores: 캐시에서 제외할 파일 패턴을 지정합니다.
      • maximumFileSizeToCacheInBytes: 캐시할 수 있는 최대 파일 크기 (4MB).
      • MapsFallbackDenylist: index.html로 대체 라우팅되지 않을 URL 패턴을 지정합니다. (예: API 요청은 대체되지 않음)
    • manifest: 웹 앱 매니페스트(manifest.webmanifest 또는 manifest.json)의 내용을 정의합니다. 이는 PWA가 모바일 홈 화면에 추가될 때 사용되는 이름, 아이콘, 테마 색상 등을 설정합니다.
  • sourcemapExclude({ excludeNodeModules: true }): 사용자 정의 플러그인입니다. 이 플러그인은 node_modules 폴더의 파일에 대한 소스맵 생성을 비활성화합니다. 이는 빌드 시간을 단축하고 최종 번들 크기를 줄이는 데 도움이 됩니다.
  • compression({ threshold: 10240 }): vite-plugin-compression2 플러그인을 활성화합니다. 빌드 시 생성된 파일을 압축하여 전송 크기를 줄입니다. threshold: 10240은 10KB보다 큰 파일만 압축하도록 설정합니다.

5. publicDir

TypeScript
publicDir: command === 'serve' ? './public' : false,
  • publicDir: 정적 자산(예: 이미지, 폰트)이 위치하는 디렉토리를 지정합니다.
  • command === 'serve' ? './public' : false: 개발 서버(npm run dev)를 실행할 때는 ./public 폴더를 정적 파일 서빙 디렉토리로 사용하고, 빌드(npm run build)할 때는 이 기능을 비활성화합니다 (빌드 시에는 자산이 assets 폴더로 처리됨).

6. 빌드 설정 (build)

TypeScript
build: {
  sourcemap: process.env.NODE_ENV === 'development',
  outDir: './dist',
  minify: 'terser',
  rollupOptions: { /* ... */ },
  chunkSizeWarningLimit: 1500,
},
  • sourcemap: process.env.NODE_ENV === 'development': 개발 환경(development)에서만 소스맵을 생성하도록 설정합니다. 소스맵은 디버깅에 유용하지만, 프로덕션 빌드에서는 파일 크기를 늘릴 수 있습니다.
  • outDir: './dist': 빌드 결과물이 저장될 출력 디렉토리를 ./dist로 설정합니다.
  • minify: 'terser': 번들된 JavaScript 코드를 terser를 사용하여 압축(minify)합니다.
  • rollupOptions: 내부적으로 Vite가 사용하는 Rollup 번들러에 대한 추가 설정을 정의합니다.
    • preserveEntrySignatures: 'strict': 엔트리 파일의 내보내기(export) 시그니처를 엄격하게 유지합니다.
    • output: 빌드 출력 파일의 이름을 제어합니다.
      • manualChunks(id: string): **코드 분할(Code Splitting)**을 수동으로 설정하는 핵심 부분입니다. node_modules 내의 특정 대형 라이브러리들을 별도의 청크(chunk) 파일로 분리합니다. 예를 들어, @codesandbox/sandpacksandpack.js 청크로, react-virtualizedvirtualization.js 청크로 분리되어 초기 로딩 시 불필요한 코드를 다운로드하지 않도록 최적화합니다.
      • entryFileNames, chunkFileNames, assetFileNames: 생성될 JavaScript, CSS, 이미지 등 자산 파일의 이름 형식을 정의합니다. 해시([hash])를 포함하여 브라우저 캐싱 문제를 방지합니다. 폰트 파일(woff, ttf 등)은 assets/fonts 폴더로 별도 저장되도록 설정되어 있습니다.
    • onwarn: Rollup이 경고 메시지를 출력할 때 특정 경고를 무시하거나 커스텀 처리를 할 수 있도록 합니다. 여기서는 "Error when using sourcemap" 경고를 무시하도록 설정되어 있습니다.
  • chunkSizeWarningLimit: 1500: 생성된 청크 파일의 크기가 1500KB(1.5MB)를 초과하면 경고를 표시합니다.

7. 경로 별칭 설정 (resolve)

TypeScript
resolve: {
  alias: {
    '~': path.join(__dirname, 'src/'),
    '$fonts': path.resolve(__dirname, 'public/fonts'),
  },
},
  • alias: 모듈 임포트 시 사용할 경로 별칭(alias)을 설정합니다.
    • '~': path.join(__dirname, 'src/'): ~ 접두사를 사용하여 ./src 폴더를 참조할 수 있도록 합니다. 예를 들어, import MyComponent from '~/components/MyComponent'; 와 같이 사용할 수 있습니다. (Babel 설정의 babel-plugin-root-import와 유사)
    • '$fonts': path.resolve(__dirname, 'public/fonts'): $fonts 접두사를 사용하여 public/fonts 폴더를 참조할 수 있도록 합니다.

8. sourcemapExclude 함수 정의

TypeScript
interface SourcemapExclude { /* ... */ }
export function sourcemapExclude(opts?: SourcemapExclude): Plugin { /* ... */ }
  • 이 부분은 node_modules에서 가져온 파일에 대한 소스맵 생성을 제외하는 커스텀 Vite 플러그인 정의입니다. plugins 배열에서 sourcemapExclude({ excludeNodeModules: true })로 사용되고 있습니다.

결론

vite.config.ts 파일은 매우 포괄적인 Vite 설정으로, 다음과 같은 주요 목적을 가지고 있습니다:

  • 개발 서버 구성: 프록시 설정을 포함하여 효율적인 개발 환경을 제공합니다.
  • 환경 변수 관리: 필요한 환경 변수만 노출하고 관리합니다.
  • PWA 지원: 웹 애플리케이션을 프로그레시브 웹 앱으로 만들 수 있도록 서비스 워커 및 매니페스트 설정을 포함합니다.
  • 성능 최적화:
    • 코드 분할(Code Splitting): manualChunks를 통해 대형 라이브러리와 특정 모듈을 별도의 청크로 분리하여 초기 로딩 성능을 최적화합니다.
    • 압축: 빌드된 파일을 압축하여 전송 크기를 줄입니다.
    • 소스맵 최적화: node_modules에 대한 소스맵 생성을 비활성화하여 빌드 속도를 높이고 파일 크기를 줄입니다.
  • 개발 편의성: 경로 별칭 설정을 통해 모듈 임포트를 간소화하고, Node.js Polyfill을 제공하여 브라우저 환경 호환성을 높입니다.

이러한 설정들은 현대적인 웹 애플리케이션의 개발 생산성과 최종 사용자 경험(성능, 오프라인 지원 등)을 동시에 최적화하는 데 기여합니다.

댓글

이 블로그의 인기 게시물

아파치 보안관련 기본설정

티베로 이관 작업 절차

윈도우 네트워크 리소스 사용권한 오류