Skip to content

May 9th, 2024

Vite Meetup SF

지원하는 기능들

기본적으로 vite는 여타 정적 파일 서버와 크게 다르지 않습니다. 다만, vite는 네이티브 ESM 말고도 기존 번들러에서 제공하던 기능을 대부분 지원한다는 차이점이 있습니다.

NPM을 이용한 디펜던시 import 그리고 사전 번들링

다음 코드는 네이티브 ES에서 정상적으로 실행되지 않습니다:

js
import { someMethod } from 'my-dep'

모듈의 위치를 찾을 수 없기 때문인데, vite는 다음을 기준으로 모듈을 가져오기 때문에 위 코드 역시 정상적으로 실행됩니다.

  1. Vite를 통해 ESM 스타일로 사전에 번들링 된 CommonJS 및 UMD 모듈. 이 과정은 Esbuild를 통해 이루어지며, JavaScript 기반의 다른 번들러보다 빠른 콜드 스타트가 가능합니다.

  2. /node_modules/.vite/deps/my-dep.js?v=f3sf2ebd와 같이 URL을 이용해 ESM을 지원하는 브라우저에서 모듈을 가져올 수 있도록 import 구문을 수정.

참고로, 디펜던시는 반드시 캐시됩니다.

vite는 HTTP 헤더를 이용해 요청한 디펜던시를 브라우저에서 캐싱하도록 합니다. 만약 디펜던시의 수정 또는 디버깅이 필요하다면 여기를 참고해주세요.

Hot Module Replacement

vite는 기본적으로 ESM를 통해 HMR API를 제공합니다. HMR 기능이 있는 프레임워크는 API를 활용하여 페이지를 다시 로드하거나 애플리케이션 상태를 날려버리지 않고 즉각적이고 정확한 업데이트를 제공할 수 있습니다. vite는 Vue Single File Components, React Fast Refresh 또는 @prefresh/vite과 같은 First-party HMR 모듈을 제공하고 있습니다.

물론, create-vite에서 제공하는 템플릿 안에는 HMR 모듈이 포함되어 있기 때문에 굳이 위와 같은 방법을 따르지 않아도 됩니다.

TypeScript

vite는 .ts 파일에 대한 컴파일링 및 Import 역시 지원합니다.

트랜스파일만 수행

Vite는 .ts 파일에 대해서 트랜스파일링만 수행하며, 타입 검사는 IDE와 빌드 프로세스에서 수행된다고 가정합니다.

Vite가 변환 과정의 일부로 타입 검사를 수행하지 않는 이유는 두 작업이 기본적으로 다르기 때문입니다. 트랜스파일링은 파일 단위로 작동할 수 있으며, 이는 Vite의 온디맨드 컴파일 모델과 완벽하게 일치합니다. 이에 반해 타입 검사는 전체 모듈 그래프에 대한 탐색이 필요합니다. Vite의 변환 파이프 라인에 타입 검사를 추가하게 된다면, 결국 Vite의 속도 이점은 사라지게 될 것입니다.

Vite의 역할은 소스 모듈을 가능한 빠르게 브라우저에서 실행할 수 있는 형태로 변환하는 것입니다. 따라서 이를 위해 Vite의 변환 파이프 라인에서 정적 분석 검사를 분리하는 것을 권장합니다. 이러한 원칙은 ESLint와 같은 다른 정적 분석 검사에도 적용됩니다.

  • 프로덕션 빌드를 위해서는 Vite의 빌드 명령어에 tsc --noEmit을 추가로 실행할 수 있습니다.

  • 개발 중 IDE에서 제공하는 힌트 이상의 기능이 필요하다면, 별도의 프로세스에서 tsc --noEmit --watch를 실행해주세요. 만약 브라우저에서 직접 타입 에러를 확인하고 싶다면 vite-plugin-checker를 사용하는 것을 권장합니다.

Vite의 TypeScript 컴파일링은 Esbuild를 이용하며, TypeScript 소스 코드를 JavaScript 소스 코드로 변환하는 작업에 대해 tsc 대비 약 20~30배 정도 빠른 퍼포먼스를 보이고 있습니다. (HMR은 50ms 미만)

참고로 타입만을 가져오는 경우 잘못 번들링이 될 수 있으며, 이는 타입 전용 Imports와 Exports를 사용하여 이 문제를 우회할 수 있습니다:

ts
import type { T } from 'only/types'
export type { T }

타입스크립트 컴파일러 옵션

tsconfig.json 파일 내 compilerOptions 설정들의 값을 조작할 때는 특별한 주의가 필요합니다.

isolatedModules

true로 설정해주세요.

esbuild는 타입에 대한 정보 없이 변환(Transpilation)만을 수행하기에, const enum 또는 암시적으로 타입만을 가져오는 것과 같은 특정 기능을 지원하지 않습니다.

이를 감지하기 위해 tsconfig.jsoncompilerOptions 설정을 "isolatedModules": true와 같이 설정해줘야만 하며, 이 설정으로 TS가 위와 같은 상황에서 작동하지 않는 기능들에 대해 경고할 수 있게 됩니다.

일부 라이브러리는 "isolatedModules": true로 설정할 경우 타입 체크가 정상적으로 동작하지 않습니다. 이러한 경우에는 해당 모듈이 이슈를 수정할 때 까지 "skipLibCheck": true를 사용해 오류가 발생되지 않도록 해주세요.

useDefineForClassFields

Vite 2.5.0 부터는 TypeScript의 변환 대상이 ESNext 또는 ES2022 이상인 경우, 기본 값을 true로 설정합니다. 이는 tsc 버전 4.3.2 이상 및 ECMAScript 표준을 따르도록 하는 설정입니다.

그 외의 TypeScript 변환 대상은 false로 설정됩니다.

그러나 다른 프로그래밍 언어나 이전 버전의 TypeScript를 사용하던 사람들에게는 직관적이지 않은 내용일 수 있습니다. 이에 대한 자세한 정보는 TypeScript 3.7 릴리스 노트를 참고할 수 있습니다.

만약 클래스의 필드에 크게 의존하는 라이브러리를 사용하는 경우라면, 이러한 라이브러리를 사용하는 것에 대해 옵션을 수정하는 경우 상당한 주의를 기울어야 합니다.

가령 MobX와 같은 대부분의 라이브러리는 "useDefineForClassFields": true인 것으로 가정하고 동작합니다.

그러나 lit-element를 포함해 일부 라이브러리는 아직 이 새로운 기본값이 적용되지 않았습니다. 이러한 경우에는 useDefineForClassFields의 값을 false로 설정해주세요.

target

Vite는 esbuild와 동일하게 기본적으로 설정된 target 값으로 TypeScript를 트랜스파일하지 않습니다.

이 대신 esbuild.target 옵션을 사용할 수 있으며, 이는 최소한의 트랜스파일링을 위해 esnext로 기본 설정되어 있습니다. 빌드 시, 높은 우선순위를 갖는 build.target 옵션을 사용할 수도 있습니다.

useDefineForClassFields

targetESNext 또는 ES2022 이상이 아니거나, tsconfig.json 파일이 존재하지 않는 경우, useDefineForClassFields는 기본적으로 false로 설정되는데, 이를 esbuild.target의 기본값인 esnext와 함께 사용할 경우 문제가 발생할 수 있습니다. 이는 정적 초기화 블록으로 트랜스파일링되어 브라우저에서 지원되지 않을 수 있기 때문입니다.

따라서, targetESNext 또는 ES2022 이상으로 설정하거나, tsconfig.json을 구성할 때 useDefineForClassFields를 명시적으로 true로 설정하는 것을 권장합니다.

빌드 결과에 영향을 미치는 또 다른 컴파일러 옵션들

skipLibCheck

Vite 스타터 템플릿은 TypeScript의 특정 버전과 설정만을 지원하는 라이브러리가 있을 수 있기 때문에, 의존성에 대한 타입 체크를 수행하지 않기 위해 기본값으로 "skipLibCheck": "true"를 설정하고 있습니다. 자세한 내용은 vuejs/vue-cli#5688을 참고해 주세요.

Client Types

vite는 기본적으로 Node.js API 기반의 타입 시스템을 차용하고 있습니다. 따라서 클라이언트 측의 환경을 위해 Shim을 구성하고자 한다면 아래와 같이 d.ts 선언 파일을 추가해주세요.

typescript
/// <reference types="vite/client" />

또는 tsconfig.jsoncompilerOptions.types 옵션에 vite/client를 명시해 줄 수도 있습니다:

json
{
  "compilerOptions": {
    "types": ["vite/client"]
  }
}

이를 통해 다음에 대한 Shim이 제공됩니다:

  • .svg와 같은 에셋
  • Vite를 통해 주입되는 import.meta.env에 명시된 환경 변수 타입들
  • import.meta.hot에 명시된 HMR API 타입들

TIP

기본 타입을 재정의하기 위해서는, 타이핑이 포함된 타입 정의 파일을 만든 뒤 vite/client 위에 해당 타입 파일에 대한 참조를 추가해야 합니다.

가령 *.svg의 기본적인 import 결과를 React 컴포넌트로 만들기 위해서는 다음과 같이 작업해야 합니다:

  • vite-env-override.d.ts (타이핑을 포함하는 파일):
    ts
    declare module '*.svg' {
      const content: React.FC<React.SVGProps<SVGElement>>
      export default content
    }
  • vite/client에 대한 참조를 포함하는 파일:
    ts
    /// <reference types="./vite-env-override.d.ts" />
    /// <reference types="vite/client" />

Vue

vite는 기본적으로 Vue를 지원하고 있습니다.

JSX

.jsx.tsx 역시 사용이 가능합니다. 마찬가지로 esbuild를 이용해 컴파일링합니다.

Vue를 사용자들은 HMR, 글로벌 컴포넌트, 디렉티브 및 슬롯 등 Vue 3에서 제공하고 있는 API를 위해 @vitejs/plugin-vue-jsx를 사용해야 합니다.

React나 Vue를 사용하지 않는다 해도, esbuild 옵션을 이용해 jsxFactoryjsxFragment를 커스터마이징 할 수 있습니다. Preact를 예로 들어보자면 다음과 같습니다:

js
// vite.config.js
import { 
defineConfig
} from 'vite'
export default
defineConfig
({
esbuild
: {
jsxFactory
: 'h',
jsxFragment
: 'Fragment',
}, })

자세한 사항은 esbuild 문서를 참고해주세요.

참고로, Vite에서만 제공되는 옵션인 jsxInject를 이용해 JSX에 대한 헬퍼를 사용할 수도 있습니다.

js
// vite.config.js
import { 
defineConfig
} from 'vite'
export default
defineConfig
({
esbuild
: {
jsxInject
: `import React from 'react'`,
}, })

CSS

.css 파일을 가져오면 HMR을 지원하는 <style> 태그를 통해 페이지에 해당 콘텐츠가 주입됩니다.

CSS @import 그리고 URL 재정의(Rebasing)

vite는 postcss-import를 이용해 CSS의 @import를 처리합니다. 또한, CSS url()로 참조되는 모든 리소스들(다른 디렉터리에 존재한다 해도)에 대해 별다른 설정 없이 자동으로 Base를 맞추어주는 재정의(Rebasing) 작업 역시 진행해주고 말이죠.

별칭을 이용한 @import도 지원하며, URL 재정의나 별칭은 CSS 말고도 Sass와 Less에서도 사용이 가능합니다.

PostCSS

만약 프로젝트에 PostCSS 설정 파일이 존재한다면, vite는 이를 이용해 모든 CSS 파일에 해당 설정을 적용합니다.

참고로 CSS의 축소화는 PostCSS 이후에 진행되며, build.cssTarget 옵션을 이용해 설정할 수 있습니다.

CSS Modules

.module.css 확장자로 끝나는 모든 CSS 파일들은 CSS 모듈 파일로 취급되며, 일반적인 JavaScript 모듈처럼 사용이 가능합니다.

css
/* example.module.css */
.red {
  color: red;
}
js
import 
classes
from './example.module.css'
document
.
getElementById
('foo').
className
=
classes
.
red

참고로, CSS 모듈에 대한 동작 방식을 입맛대로 수정할 수 있습니다. css.modules 옵션 옵션을 참고해주세요.

가령 css.modules.localsConvention 옵션을 camelCase로 설정하게 되면(localsConvention: 'camelCaseOnly'), 아래와 같이 가져올 수 있게 됩니다.

js
// .apply-color -> applyColor
import { 
applyColor
} from './example.module.css'
document
.
getElementById
('foo').
className
=
applyColor

CSS Pre-processors

vite는 모던 브라우저만을 타깃으로 하기에, 표준을 준수하도록 postcss-nesting과 같은 CSSWG 초안을 구현한 PostCSS 플러그인과 함께 네이티브 CSS 변수를 사용하도록 권고하고 있습니다.

다시 말해, vite는 기본적으로 .scss, .sass, .less, .styl, .stylus와 CSS 전처리기가 필요한 파일을 지원하지 않습니다. 다만 필요한 경우 어렵지 않게 설치해 바로 사용할 수 있습니다.

bash
# .scss 그리고 .sass
npm add -D sass

# .less
npm add -D less

# .styl 그리고 .stylus
npm add -D stylus

Vue 싱글 파일 컴포넌트를 사용하는 경우, 설치 후 별 다른 설정 없이도 <style lang="sass">와 같은 CSS 전처리기를 바로 사용할 수 있습니다.

Sass나 Less에서의 @import 별칭 또한 Vite에서 사용이 가능합니다. url()로 참조되는 파일들 역시 자동으로 올바른 URL을 갖도록 재정의되고 말이죠.

다만 Stylus의 경우 API 충돌로 인해 Vite의 @import 별칭과 URL 재정의 기능이 제공되지 않습니다.

물론 전처리된 CSS 역시 style.module.scss와 같이 CSS 모듈처럼 사용이 가능합니다. 똑같이 파일 확장자에 .module만 붙여주세요.

페이지 내 CSS 주입 비활성화하기

CSS 콘텐츠의 자동 주입은 ?inline 쿼리 매개변수를 통해 비활성화 할 수 있습니다. 이 경우 처리된 CSS 문자열은 평소와 같이 모듈의 default export로 반환되나, 스타일은 페이지에 주입되지 않습니다.

js
import './foo.css' // 페이지에 스타일이 추가됨
import 
otherStyles
from './bar.css?inline' // 스타일이 추가되지 않음

참고

CSS 파일에서의 기본 및 명명된 Import(import style from './foo.css')는 Vite 5에서 제거되었습니다. 이 대신 ?inline 쿼리를 사용해주세요.

Lightning CSS

Vite 4.4부터 Lightning CSS를 실험적으로 지원합니다. 사용하고자 한다면 css.transformer: 'lightningcss'로 설정하고, lightningcss를 설치해 주세요:

bash
npm add -D lightningcss

이 옵션을 활성화하면 CSS 파일이 PostCSS가 아닌 Lightning CSS로 처리됩니다. css.lightningcss 옵션에 Lightning CSS의 옵션을 전달해 이를 설정할 수 있습니다.

CSS 모듈의 설정은 css.modules(PostCSS가 CSS 모듈을 어떻게 처리하는지에 대한 설정) 대신 css.lightningcss.cssModules를 사용합니다.

기본적으로 Vite는 CSS를 축소하기 위해 esbuild를 사용합니다. Lightning CSS는 build.cssMinify: 'lightningcss'를 통해 CSS 축소기로도 사용할 수도 있습니다.

참고

Lightning CSS를 사용하는 경우 CSS 전처리기는 지원되지 않습니다.

정적 에셋

정적 에셋을 Import 하는 경우, 이에 대한 Public URL이 반환됩니다.

js
import 
imgUrl
from './img.png'
document
.
getElementById
('hero-img').src =
imgUrl

URL 쿼리를 이용해 에셋을 가져올 때 어떻게 이를 가져올 것인지 명시할 수도 있습니다.

js
// URL로 에셋 가져오기
import 
assetAsURL
from './asset.js?url'
js
// String 타입으로 에셋 가져오기
import 
assetAsString
from './shader.glsl?raw'
js
// 웹 워커 가져오기
import 
Worker
from './worker.js?worker'
js
// Base64 포맷의 문자열 형태로 웹 워커 가져오기
import 
InlineWorker
from './worker.js?worker&inline'

좀 더 자세히 알고 싶다면, 정적 에셋 핸들링하기 섹션을 참고해주세요.

JSON

JSON 파일은 바로 Import가 가능합니다. 물론, 가져올 필드를 지정할 수도 있습니다.

js
// 객체 형태로 가져오기
import 
json
from './example.json'
// 필드를 지정해 가져오기 (트리 셰이킹 됩니다.) import {
field
} from './example.json'

Glob Import

vite는 import.meta.glob 함수를 이용해 여러 모듈을 한 번에 가져올 수 있도록 지원하고 있습니다. 이 때, Glob 패턴을 이용합니다.

js
const 
modules
= import.meta.
glob
('./dir/*.js')

위 코드는 아래와 같이 변환됩니다.

js
// Vite를 통해 변환된 코드
const modules = {
  './dir/foo.js': () => import('./dir/foo.js'),
  './dir/bar.js': () => import('./dir/bar.js')
}

이렇게 가져온 modules를 순회하여 각 모듈에 접근할 수 있게 됩니다.

js
for (const path in modules) {
  modules[path]().then((mod) => {
    console.log(path, mod)
  })
}

기본적으로 import.meta.glob 함수를 이용하면, 동적(Dynamic) Import를 이용해 파일의 청크를 가져옵니다. 만약 동적으로 Import하는 것이 아니라 직접 모듈을 가져오고자 한다면, 두 번 째 인자로 { eager: true } 객체를 전달해주세요.

js
const 
modules
= import.meta.
glob
('./dir/*.js', {
eager
: true })

위 코드는 아래와 같이 변환됩니다.

js
// Vite를 통해 변환된 코드
import * as __glob__0_0 from './dir/foo.js'
import * as __glob__0_1 from './dir/bar.js'
const modules = {
  './dir/foo.js': __glob__0_0,
  './dir/bar.js': __glob__0_1
}

Glob 패턴 배열

첫 번째 인자는 Glob 패턴의 배열로 전달할 수 있습니다.

js
const 
modules
= import.meta.
glob
(['./dir/*.js', './another/*.js'])

네거티브 Glob 패턴

! 접두사를 이용해 네거티브 Glob 패턴도 나타낼 수 있습니다. Glob 패턴 매칭 결과에서 일부 파일을 무시하고자 하는 경우, 첫 번째 인수에 제외할 네거티브 Glob 패턴을 추가해주세요:

js
const 
modules
= import.meta.
glob
(['./dir/*.js', '!**/bar.js'])
js
// 아래는 Vite에 의해 생성되는 코드입니다.
const modules = {
  './dir/foo.js': () => import('./dir/foo.js')
}

Named Imports

import 옵션을 이용해 모듈의 일부만 가져올 수도 있습니다.

ts
const 
modules
= import.meta.
glob
('./dir/*.js', {
import
: 'setup' })
ts
// 아래는 Vite에 의해 생성되는 코드입니다.
const modules = {
  './dir/foo.js': () => import('./dir/foo.js').then((m) => m.setup),
  './dir/bar.js': () => import('./dir/bar.js').then((m) => m.setup)
}

eager와 같이 사용하면 모듈에 대한 트리 셰이킹도 가능합니다.

ts
const 
modules
= import.meta.
glob
('./dir/*.js', {
import
: 'setup',
eager
: true,
})
ts
// 아래는 Vite에 의해 생성되는 코드입니다:
import { setup as __glob__0_0 } from './dir/foo.js'
import { setup as __glob__0_1 } from './dir/bar.js'
const modules = {
  './dir/foo.js': __glob__0_0,
  './dir/bar.js': __glob__0_1
}

default export를 가져오고자 하는 경우에는 import 옵션 값을 default로 설정해주세요.

ts
const 
modules
= import.meta.
glob
('./dir/*.js', {
import
: 'default',
eager
: true
})
ts
// 아래는 Vite에 의해 생성되는 코드입니다:
import __glob__0_0 from './dir/foo.js'
import __glob__0_1 from './dir/bar.js'
const modules = {
  './dir/foo.js': __glob__0_0,
  './dir/bar.js': __glob__0_1
}

커스텀 쿼리

query 옵션을 이용해 Import에 대한 쿼리를 작성할 수 있습니다. 예를 들어, 문자열 형태 또는 URL 형태로 에셋을 가져올 수 있습니다:

ts
const 
moduleStrings
= import.meta.
glob
('./dir/*.svg', {
query
: '?raw',
import
: 'default',
}) const
moduleUrls
= import.meta.
glob
('./dir/*.svg', {
query
: '?url',
import
: 'default',
})
ts
// 아래는 Vite에 의해 생성되는 코드입니다:
const moduleStrings = {
  './dir/foo.svg': () => import('./dir/foo.js?raw').then((m) => m['default']),
  './dir/bar.svg': () => import('./dir/bar.js?raw').then((m) => m['default']),
}
const moduleUrls = {
  './dir/foo.svg': () => import('./dir/foo.js?url').then((m) => m['default']),
  './dir/bar.svg': () => import('./dir/bar.js?url').then((m) => m['default']),
}

다른 플러그인에서 사용할 목적으로 커스텀 쿼리를 작성할 수도 있습니다:

ts
const 
modules
= import.meta.
glob
('./dir/*.js', {
query
: {
foo
: 'bar',
bar
: true },
})

Glob Import 유의 사항

Glob 패턴과 관련하여 다음의 사항을 유의해주세요:

  • 이 기능들은 Vite에서 제공하는 기능입니다. (ES 표준이나 웹 브라우저에서 제공하는 기능이 아니에요.)
  • Glob 패턴 사용 시, 상대 경로(./) 또는 절대 경로(/) 또는 resolve.alias 옵션을 통해 별칭으로 지정된 경로 만을 이용해야 합니다.
  • Glob 패턴 매칭은 fast-glob을 이용합니다. 자세한 것은 지원하는 Glob 패턴 목록을 참고해주세요.
  • import.meta.glob으로 전달되는 모든 인자는 리터럴 값을 전달해야 합니다. 변수나 표현식을 사용할 수 없습니다.

동적 Import

Glob Import와 마찬가지로 Vite는 변수를 사용한 동적인 Import도 지원합니다.

ts
const module = await import(`./dir/${file}.js`)

변수 file은 깊이가 1인 파일에 대해서만 나타낼 수 있습니다. 가령 filefoo/bar인 경우에는 Import가 실패하게 됩니다. 좀 더 다양한 기능이 필요한 경우에는 Glob Import 기능을 사용해주세요.

WebAssembly

사전에 컴파일 된 .wasm 파일 역시 ?init 쿼리를 이용해 가져올 수 있습니다. 불러와진 모듈의 export defaultWebAssembly.InstancePromise를 반환하는 초기화 함수가 들어가 있습니다:

js
import 
init
from './example.wasm?init'
init
().
then
((
instance
) => {
instance
.
exports
.
test
()
})

초기화 함수를 호출할 때 imports 옵션을 사용할 수도 있는데, 이 값은 WebAssembly.instantiate 함수의 두 번째 인자인 importObject로 전달됩니다:

js
init
({
imports
: {
someFunc
: () => {
/* ... */ } } }).
then
(() => {
/* ... */ })

프로덕션 빌드 시 assetsInlineLimit 옵션의 값보다 작은 크기를 갖는 .wasm 파일은 Base64 문자열 포맷으로 변환됩니다. 파일의 크기가 그보다 크다면 정적 에셋으로 처리되어 요청(Fetch) 시 이를 가져오는 방식으로 동작합니다.

참고

WebAssembly를 위한 ES 모듈 제안 사항은 현재 지원되지 않습니다. 이 대신 vite-plugin-wasm 또는 기타 커뮤니티 플러그인을 사용해 이를 처리하세요.

WebAssembly 모듈에 접근하기

여러 번 인스턴스화 하는 등의 이유로 Module 객체에 대한 접근이 필요하다면, URL 접미사를 이용해 에셋을 가져오고, 이를 이용해 인스턴스화를 수행해 주세요:

js
import 
wasmUrl
from 'foo.wasm?url'
const
main
= async () => {
const
responsePromise
=
fetch
(
wasmUrl
)
const {
module
,
instance
} =
await WebAssembly.
instantiateStreaming
(
responsePromise
)
/* ... */ }
main
()

Node.js에서 모듈 가져오기

SSR에서 ?init을 이용해 fetch()를 수행하는 경우, TypeError: Invalid URL 에러가 발생할 수 있습니다. Support wasm in SSR 이슈를 참고해주세요.

아래는 이에 대한 대안이며, 프로젝트의 기본 디렉터리는 현재 위치한 디렉터리입니다:

js
import 
wasmUrl
from 'foo.wasm?url'
import {
readFile
} from 'node:fs/promises'
const
main
= async () => {
const
resolvedUrl
= (await import('./test/boot.test.wasm?url')).
default
const
buffer
= await
readFile
('.' +
resolvedUrl
)
const {
instance
} = await WebAssembly.
instantiate
(
buffer
, {
/* ... */ }) /* ... */ }
main
()

Web Workers

생성자를 통해 가져오기

웹 워커 스크립트는 new Worker()new SharedWorker()를 통해서도 가져올 수 있습니다. 접미사를 사용하는 방식에 비해 이는 표준에 좀 더 가까우며, 일반적으로 워커를 사용할 때 권장되는 방식입니다.

ts
const worker = new Worker(new URL('./worker.js', import.meta.url))

"module" 타입의 워커를 생성할 수 있도록 생성자에 옵션을 전달할 수도 있습니다:

ts
const worker = new Worker(new URL('./worker.js', import.meta.url), {
  type: 'module',
})

워커 탐지는 new Worker() 선언 내부에서 직접 new URL() 생성자를 사용할 때만 작동합니다. 또한 모든 옵션 매개변수는 정적 값(예: 문자열 리터럴)이어야 합니다.

쿼리 접미사를 통해 가져오기

웹 워커 스크립트는 ?worker 또는 ?sharedworker 접미사를 붙여 가져올 수 있습니다. 모듈의 export default 로는 워커의 생성자가 들어가게 됩니다.

js
import 
MyWorker
from './worker?worker'
const
worker
= new
MyWorker
()

워커 스크립트에서 importScripts() 대신 ESM import 구문을 사용할 수도 있습니다. 참고: 개발 서버에서는 브라우저 네이티브 API에 의존하지만, 프로덕션 빌드에서는 다양한 브라우저를 지원하도록 컴파일됩니다.

마지막으로, 기본적으로 워커의 경우 프로덕션 빌드 분리된 청크로 컴파일됩니다. 만약 분리된 청크가 아니라 Base64 포맷의 문자열로 이를 사용하고자 한다면, inline 쿼리를 이용해주세요:

js
import 
MyWorker
from './worker?worker&inline'

만약 워커를 URL로 검색하고자 한다면, url 쿼리를 추가해주세요:

js
import 
MyWorker
from './worker?worker&url'

모든 워커에 대한 번들링 설정은 워커 옵션을 참고해주세요.

콘텐츠 보안 정책 (Content Security Policy, CSP)

CSP를 배포하려면 Vite 내부적으로 특정 지시문 또는 구성을 설정해야 합니다.

'nonce-{RANDOM}'

html.cspNonce 옵션이 설정되면, Vite는 스타일시트 및 모듈 프리로드를 위한 <script>, <style> 그리고 <link> 태그에 지정된 nonce 속성을 추가합니다. 이 옵션이 설정되면 Vite는 메타 태그(<meta property="csp-nonce" nonce="PLACEHOLDER" />)를 주입합니다.

property="csp-nonce" 속성을 가진 메타 태그의 nonce 값은 개발 중 또는 빌드 후 필요에 따라 Vite에서 사용합니다.

WARNING

각 요청마다 자리 표시자를 고유한 값으로 대체해야 합니다. 이를 따르지 않으면 리소스 정책은 쉽게 우회될 수 있습니다. (자세한 내용은 Vite CSP 플레이그라운드의 vite.config.js 파일을 참고해 주세요. - 옮긴이)

data:

빌드 시, Vite는 일정 크기 이하의 에셋을 데이터 URI로 인라인합니다. 관련 지시문(예: img-src, font-src)에 data:를 허용하거나, build.assetsInlineLimit: 0으로 설정하여 인라인을 비활성화해야 합니다.

WARNING

script-srcdata:를 허용하지 마세요. 임의 스크립트 삽입을 허용하게 됩니다.

빌드 최적화

아래는 추가적인 설정 없이 기본적으로 빌드 프로세스에 적용되는 기능들에 대한 목록입니다. 물론, 필요 시 각각의 기능들에 대한 비활성화가 가능합니다.

CSS 코드 분리

vite는 비동기적으로 불러와지는 청크 내에 CSS 코드가 포함된 경우, 이를 자동으로 추출해 파일로 분리합니다. 이후 해당 청크를 불러올 때 <link> 태그를 이용해 분리된 CSS 코드를 불러오게끔 하며, CSS가 모두 계산된 후에 청크를 렌더하도록 합니다. 굳이 왜 이렇게 복잡한 과정을 거칠까요? 바로 이 과정을 통해 CSS가 렌더링될 때 화면이 잠깐 반짝이는 FOUC 현상을 회피할 수 있게 되기 때문입니다.

물론, 모든 CSS가 그냥 일반적인 각각의 파일로 저장된 경우라면 굳이 이러한 기능을 사용할 필요가 없습니다. 이러한 경우 build.cssCodeSplit 옵션의 값을 false로 설정해 비활성화가 가능합니다.

Preload Directives Generation

vite는 빌드 시 Direct Import 구문에 대해 <link ref="modulepreload"> 디렉티브를 이용해 미리 모듈을 캐싱하도록 자동으로 변환합니다. 덕분에 해당 모듈을 필요로 하는 경우 이를 바로 사용할 수 있게 됩니다. (modulepreload에 대한 더 자세한 내용은 MDN doc 또는 Google developers 문서를 참고해주세요. - 옮긴이)

Async Chunk Loading Optimization

빌드 시, 때때로 Rollup은 "공통(Common)" 청크 파일을 생성합니다. 보통 두 개 이상의 모듈에서 공유되는 청크가 이러한데, 이를 Dynamic Import를 이용해 불러오는 경우 다음과 같은 상황이 발생됩니다. (브라우저는 AB 모듈을 필요로 하며(Dynamic Import), AB 모듈은 공통적으로 모듈 C를 필요로 하는 경우(Direct Import)입니다. - 옮긴이)

Entry async chunk A common chunk C async chunk B dynamic import direct import

최적화되지 않은 경우, 먼저 비동기적으로 A 청크가 불러와지게 되고, A 청크가 모두 파싱된 후에서야 C 청크가 필요하다는 사실을 알게 되기에 다음과 같은 네트워크 왕복이 필요합니다.

Entry ---> A ---> C

vite는 Preload 스텝을 이용해 A를 가져올 때 C 청크를 병렬적(Parallel)으로 가져올 수 있도록 Dynamic Import 구문을 자동으로 재작성합니다.

Entry ---> (A + C)

vite는 모든 Direct Import 구문에 대해 Preload 하도록 함으로써, 쓸 데 없이 낭비되는 네트워크 왕복을 줄이도록 구성합니다.

Released under the MIT License.