Skip to content

백엔드 프레임워크와 함께 사용하기 ​

참고

기존의 백엔드(Rails, Laravel 등)를 사용해 HTML을 제공하기를 원하지만, 에셋은 Vite를 이용하고자 한다면, Awesome Vite의 방법을 확인해 보세요.

커스텀 통합이 필요하다면, 이 가이드의 단계를 따라 수동으로 구성할 수 있습니다.

  1. Vite 설정 파일에서, 진입점을 설정하고 매니페스트를 활성화합니다:

    vite.config.js
    js
    export default 
    defineConfig
    ({
    // 기본 .html 진입점을 덮어씁니다
    input
    : '/path/to/main.js',
    server
    : {
    cors
    : {
    // 브라우저에서 접속할 오리진
    origin
    : 'http://my-backend.example.com',
    }, },
    build
    : {
    // outDir에 .vite/manifest.json을 생성합니다
    manifest
    : true,
    }, })

    만약 모듈 프리로드 폴리필을 비활성화하지 않았다면, 진입점에 폴리필을 가져와야 합니다.

    js
    // 앱 진입점의 시작 부분에 추가합니다
    import 'vite/modulepreload-polyfill'
  2. 개발 단계에서는 서버의 HTML 템플릿에 다음을 추가합니다(http://localhost:5173을 Vite가 실행 중인 로컬 URL로 대체):

    html
    <!-- 개발 환경인 경우 -->
    <script type="module" src="http://localhost:5173/@vite/client"></script>
    <script type="module" src="http://localhost:5173/main.js"></script>

    이후 Vite의 에셋에 접근할 수 있도록 아래 두 가지 옵션 중 하나를 적용해 주세요:

    • 백엔드 서버가 Vite 서버에 대한 에셋 요청을 프록시 하도록 설정
    • 에셋의 URL이 상대 경로 대신 백엔드 서버 URL을 사용해 가져와질 수 있도록 server.origin 옵션값을 설정

    위 옵션은 이미지와 같은 에셋이 정상적으로 로드되기 위해 필요합니다.

    만약 React를 @vitejs/plugin-react와 함께 사용한다면, 플러그인이 HTML을 수정할 수 없기 때문에, 위의 스크립트 이전에 다음을 추가해야 합니다(http://localhost:5173을 Vite가 실행 중인 로컬 URL로 대체):

    html
    <script type="module">
      import RefreshRuntime from 'http://localhost:5173/@react-refresh'
      RefreshRuntime.injectIntoGlobalHook(window)
      window.$RefreshReg$ = () => {}
      window.$RefreshSig$ = () => (type) => type
      window.__vite_plugin_react_preamble_installed__ = true
    </script>
  3. 프로덕션에서는 vite build 실행 후 다른 에셋 파일과 함께 .vite/manifest.json 파일이 생성됩니다. 매니페스트 파일의 예시는 다음과 같습니다:

    .vite/manifest.json
    json
    {
      "_shared-B7PI925R.js": {
        "file": "assets/shared-B7PI925R.js",
        "name": "shared",
        "css": ["assets/shared-ChJ_j-JJ.css"]
      },
      "_shared-ChJ_j-JJ.css": {
        "file": "assets/shared-ChJ_j-JJ.css",
        "src": "_shared-ChJ_j-JJ.css"
      },
      "logo.svg": {
        "file": "assets/logo-BuPIv-2h.svg",
        "src": "logo.svg"
      },
      "baz.js": {
        "file": "assets/baz-B2H3sXNv.js",
        "name": "baz",
        "src": "baz.js",
        "isDynamicEntry": true
      },
      "views/bar.js": {
        "file": "assets/bar-gkvgaI9m.js",
        "name": "bar",
        "src": "views/bar.js",
        "isEntry": true,
        "imports": ["_shared-B7PI925R.js"],
        "dynamicImports": ["baz.js"]
      },
      "views/foo.js": {
        "file": "assets/foo-BRBmoGS9.js",
        "name": "foo",
        "src": "views/foo.js",
        "isEntry": true,
        "imports": ["_shared-B7PI925R.js"],
        "css": ["assets/foo-5UjPuW-k.css"]
      }
    }

    매니페스트는 소스 파일을 빌드 출력 및 디펜던시에 매핑합니다:

    manifestfooviews/foo.js(entry)shared_shared-B7PI925R.js(common chunk)foo->sharedimportsfoocssfoo.cssfoo->foocsscssbarviews/bar.js(entry)bar->sharedimportsbazbaz.js(dynamic import)bar->bazdynamicImportssharedcssshared.cssshared->sharedcsscsslogologo.svg(asset)
    manifestfooviews/foo.js(entry)shared_shared-B7PI925R.js(common chunk)foo->sharedimportsfoocssfoo.cssfoo->foocsscssbarviews/bar.js(entry)bar->sharedimportsbazbaz.js(dynamic import)bar->bazdynamicImportssharedcssshared.cssshared->sharedcsscsslogologo.svg(asset)

    매니페스트는 Record<name, chunk> 구조를 가지며, 각 청크는 ManifestChunk 인터페이스를 따릅니다:

    ts
    interface ManifestChunk {
      /**
       * 알고 있는 경우 이 청크 / 에셋의 입력 파일 이름
       */
      src?: string
      /**
       * 이 청크 / 에셋의 출력 파일 이름
       */
      file: string
      /**
       * 이 청크가 임포트하는 CSS 파일 목록
       */
      css?: string[]
      /**
       * 이 청크가 임포트하는 에셋 파일 목록(CSS 파일 제외)
       */
      assets?: string[]
      /**
       * 이 청크 또는 에셋이 진입점인지 여부
       */
      isEntry?: boolean
      /**
       * 알고 있는 경우 이 청크 / 에셋의 이름
       */
      name?: string
      /**
       * 이 청크가 동적 진입점인지 여부
       *
       * 이 필드는 JS 청크에만 있습니다.
       */
      isDynamicEntry?: boolean
      /**
       * 이 청크가 정적으로 임포트하는 청크 목록
       *
       * 값은 매니페스트의 키입니다. 이 필드는 JS 청크에만 있습니다.
       */
      imports?: string[]
      /**
       * 이 청크가 동적으로 임포트하는 청크 목록
       *
       * 값은 매니페스트의 키입니다. 이 필드는 JS 청크에만 있습니다.
       */
      dynamicImports?: string[]
    }

    매니페스트의 각 항목은 다음 중 하나를 나타냅니다:

    • 진입 청크: build.rolldownOptions.input에 지정된 파일에서 생성됩니다. 이러한 청크는 isEntry: true를 가지며, 키는 프로젝트 루트 기준 상대적인 src 경로입니다.
    • 동적 진입 청크: 동적 가져오기에서 생성됩니다. 이러한 청크는 isDynamicEntry: true를 가지며, 키는 프로젝트 루트 기준 상대적인 src 경로입니다.
    • 비진입(Non-entry) 청크: 생성된 파일 이름에 _ 접두사를 붙여 키로 사용합니다.
    • 에셋 청크: 이미지나 폰트와 같은 에셋에서 생성됩니다. 키는 프로젝트 루트 기준 상대적인 src 경로입니다.
    • CSS 파일: build.cssCodeSplit이 false인 경우, style.css 키로 단일 CSS 파일이 생성됩니다. build.cssCodeSplit이 false가 아닌 경우, 키는 JS 청크와 유사하게 생성됩니다(즉, 진입 청크는 _ 접두사가 없고 비진입 청크는 _ 접두사가 있음).

    JS 청크(에셋이나 CSS가 아닌 청크)는 정적 및 동적 임포트 정보를 포함합니다. 두 값은 모두 매니페스트에서 해당 청크에 매핑되는 키입니다. 청크는 관련 CSS와 에셋 파일이 있는 경우 그 목록도 포함합니다.

  4. 해시된 파일 이름으로 링크를 렌더링하거나 지시문을 미리 로드하기 위해 이 파일을 사용할 수 있습니다.

    아래는 올바른 링크를 렌더링하는 HTML 템플릿 예시입니다. 여기서 사용된 구문은 설명을 위한 것이므로, 실제 사용 시에는 서버 템플릿 언어로 대체해 주세요. importedChunks 함수 역시 예시를 위한 것이며, Vite에서 제공하지 않습니다.

    html
    <!-- 프로덕션 환경인 경우 -->
    
    <!-- manifest[name].css의 각 cssFile에 대해 -->
    <link rel="stylesheet" href="/{{ cssFile }}" />
    
    <!-- importedChunks(manifest, name)의 각 chunk에 대해 -->
    <!-- chunk.css의 각 cssFile에 대해 -->
    <link rel="stylesheet" href="/{{ cssFile }}" />
    
    <script type="module" src="/{{ manifest[name].file }}"></script>
    
    <!-- importedChunks(manifest, name)의 각 chunk에 대해 -->
    <link rel="modulepreload" href="/{{ chunk.file }}" />

    구체적으로, HTML을 생성하는 백엔드는 매니페스트 파일과 진입점이 주어졌을 때 다음 순서를 따라야 합니다. 최적 성능을 위해 이 순서를 따르는 것을 권장합니다:

    1. 진입점 청크의 css 목록(있는 경우)에 있는 각 파일에 대한 <link rel="stylesheet"> 태그
    2. 진입점의 imports 목록에 있는 모든 청크를 재귀적으로 따라가며, 가져온 각 청크의 css 목록(있는 경우)의 각 CSS 파일에 대한 <link rel="stylesheet"> 태그를 포함
    3. 진입점 청크의 file 키에 대한 태그. JavaScript의 경우 <script type="module">, CSS의 경우 <link rel="stylesheet">가 될 수 있습니다.
    4. 선택적으로, 임포트된 각 JavaScript 청크의 file에 대한 <link rel="modulepreload"> 태그를 포함합니다. 이 경우에도 진입점 청크에서 시작해 임포트를 재귀적으로 따라갑니다.

    위 예시 매니페스트를 예로 들자면, 진입점 views/foo.js에 대해 다음 태그가 포함되어야 합니다.

    html
    <link rel="stylesheet" href="assets/foo-5UjPuW-k.css" />
    <link rel="stylesheet" href="assets/shared-ChJ_j-JJ.css" />
    <script type="module" src="assets/foo-BRBmoGS9.js"></script>
    <!-- 선택 사항 -->
    <link rel="modulepreload" href="assets/shared-B7PI925R.js" />

    진입점 views/bar.js에 대해서는 아래 코드가 포함되어야 합니다.

    html
    <link rel="stylesheet" href="assets/shared-ChJ_j-JJ.css" />
    <script type="module" src="assets/bar-gkvgaI9m.js"></script>
    <!-- 선택 사항 -->
    <link rel="modulepreload" href="assets/shared-B7PI925R.js" />
    importedChunks 의사 구현체

    아래 예시 코드는 importedChunks 의사 구현체입니다. TypeScript로 작성되어 있으며, 실제 적용 시에는 사용하는 프로그래밍 언어와 템플릿 언어에 맞게 수정이 필요합니다:

    ts
    import type { Manifest, ManifestChunk } from 'vite'
    
    export default function importedChunks(
      manifest: Manifest,
      name: string,
    ): ManifestChunk[] {
      const seen = new Set<string>()
    
      function getImportedChunks(chunk: ManifestChunk): ManifestChunk[] {
        const chunks: ManifestChunk[] = []
        for (const file of chunk.imports ?? []) {
          const importee = manifest[file]
          if (seen.has(file)) {
            continue
          }
          seen.add(file)
    
          chunks.push(...getImportedChunks(importee))
          chunks.push(importee)
        }
    
        return chunks
      }
    
      return getImportedChunks(manifest[name])
    }

    청크 임포트 맵 지원(실험적 기능)

    실험적인 build.chunkImportMap 옵션을 사용한다면 HTML에 임포트 맵도 삽입해야 합니다.

    임포트 맵은 출력 디렉터리의 importmap.json으로 생성됩니다. <script type="module"> 태그나 <link rel="modulepreload"> 태그보다 먼저 <script type="importmap"> 태그를 삽입해야 합니다.