이미지 로딩 중...
AI Generated
2025. 11. 24. · 4 Views
Vite에서 에셋 처리 및 정적 리소스 관리 완벽 가이드
이미지, 폰트, CSS부터 웹 워커까지 Vite에서 다양한 정적 리소스를 효율적으로 관리하는 방법을 배워보세요. 실무에서 바로 적용할 수 있는 최적화 팁과 설정 방법을 친절하게 안내합니다.
목차
- 이미지_폰트_SVG_임포트_방법
- Public_디렉토리_활용
- CSS_SCSS_Less_전처리기_설정
- CSS_Modules_사용법
- PostCSS_통합_및_Tailwind_설정
- 웹_워커와_WASM_파일_처리
1. 이미지_폰트_SVG_임포트_방법
시작하며
여러분이 프로젝트에 로고 이미지나 아이콘을 추가하려고 할 때, 어디에 파일을 두어야 할지, 어떻게 불러와야 할지 막막했던 적 있나요? 특히 빌드 후에 이미지 경로가 깨지거나, 폰트가 로드되지 않는 문제를 겪어본 분들이 많을 겁니다.
이런 문제는 정적 리소스를 모듈처럼 임포트하지 않고 상대 경로로 직접 참조할 때 자주 발생합니다. 빌드 도구가 파일을 추적하지 못해서 최적화나 해싱이 적용되지 않고, 결과적으로 배포 환경에서 경로 오류가 생기는 것이죠.
바로 이럴 때 필요한 것이 Vite의 에셋 임포트 기능입니다. 이미지나 폰트를 JavaScript 모듈처럼 import하면, Vite가 자동으로 파일을 추적하고 최적화된 경로를 제공합니다.
개요
간단히 말해서, Vite에서는 이미지, 폰트, SVG 같은 정적 파일을 JavaScript 파일처럼 import 구문으로 불러올 수 있습니다. 왜 이 방법이 필요할까요?
첫째, Vite가 파일의 변경을 감지해서 자동으로 브라우저를 새로고침해줍니다. 둘째, 빌드 시에 파일 이름에 해시를 추가해서 브라우저 캐싱 문제를 해결합니다.
셋째, 파일이 실제로 사용되는지 추적해서 사용하지 않는 리소스는 번들에서 제외합니다. 예를 들어, 대시보드에서 사용자 프로필 이미지 100개를 관리하는 경우, import로 불러온 이미지만 번들에 포함되고 나머지는 자동으로 제외됩니다.
기존에는 이미지를 <img src="./logo.png">처럼 상대 경로로 직접 참조했다면, 이제는 import logo from './logo.png'로 임포트해서 <img src={logo}>처럼 변수로 사용할 수 있습니다. 핵심 특징은 다음과 같습니다: 1) 모든 이미지 포맷(PNG, JPG, GIF, WebP, AVIF) 지원, 2) SVG를 컴포넌트나 URL로 불러올 수 있음, 3) 폰트 파일(WOFF2, TTF 등)도 동일하게 임포트 가능.
이러한 특징들이 개발 경험을 크게 향상시키고 빌드 최적화를 자동화해줍니다.
코드 예제
// 이미지를 모듈처럼 임포트 - Vite가 최적화된 경로를 반환합니다
import logoUrl from './assets/logo.png'
import avatarUrl from './assets/avatar.jpg'
// SVG를 URL로 임포트
import iconUrl from './assets/icon.svg'
// React 컴포넌트에서 사용
function Header() {
return (
<header>
<img src={logoUrl} alt="Logo" />
<img src={avatarUrl} className="avatar" />
<img src={iconUrl} width="24" height="24" />
</header>
)
}
// CSS에서 폰트 임포트 (url()로 참조 시 자동 처리)
// @font-face { font-family: 'Custom'; src: url('./fonts/custom.woff2'); }
설명
이것이 하는 일: Vite는 정적 리소스를 JavaScript 모듈처럼 취급해서, 개발 중에는 원본 파일 경로를 제공하고, 빌드 시에는 최적화된 파일명(해시 포함)과 경로를 자동으로 생성합니다. 첫 번째로, import logoUrl from './assets/logo.png' 부분은 이미지 파일을 모듈로 불러옵니다.
Vite는 이 구문을 만나면 파일의 존재를 확인하고, 개발 서버에서는 /src/assets/logo.png 같은 경로를 반환하고, 빌드 시에는 /assets/logo-a3f2b1c9.png 같은 해시가 포함된 경로를 반환합니다. 이렇게 하는 이유는 파일 내용이 바뀔 때마다 해시가 달라져서 브라우저가 오래된 캐시를 사용하지 않고 새 파일을 다운로드하기 때문입니다.
두 번째로, SVG 파일도 동일하게 임포트할 수 있습니다. import iconUrl from './assets/icon.svg'는 SVG를 URL로 불러오는 방식이고, 만약 React 컴포넌트로 사용하고 싶다면 import { ReactComponent as Icon } from './assets/icon.svg'처럼 플러그인을 추가할 수 있습니다.
이렇게 하면 SVG를 <Icon />처럼 컴포넌트로 사용해서 색상이나 크기를 props로 동적으로 제어할 수 있습니다. 세 번째로, 폰트 파일은 CSS에서 url()로 참조하면 Vite가 자동으로 추적합니다.
예를 들어 @font-face { src: url('./fonts/custom.woff2'); } 같은 CSS 코드가 있으면, Vite가 빌드 시에 폰트 파일을 dist 폴더로 복사하고 경로를 자동으로 업데이트해줍니다. 직접 import로 불러올 수도 있지만, 보통은 CSS에서 처리하는 것이 더 간편합니다.
여러분이 이 방법을 사용하면 다음과 같은 이점을 얻을 수 있습니다: 1) 파일 경로 오류를 개발 단계에서 바로 발견할 수 있습니다(파일이 없으면 빌드가 실패), 2) 브라우저 캐싱을 신경 쓰지 않아도 됩니다(해시가 자동으로 추가됨), 3) 사용하지 않는 이미지는 자동으로 번들에서 제외되어 최종 번들 크기가 줄어듭니다.
실전 팁
💡 작은 이미지(4KB 미만)는 자동으로 base64 인라인으로 변환되어 HTTP 요청 수를 줄입니다. 이 기준은 vite.config.js에서 build.assetsInlineLimit로 조정할 수 있습니다.
💡 SVG를 React 컴포넌트로 사용하려면 vite-plugin-svgr 플러그인을 설치하세요. 그러면 import { ReactComponent as Icon } from './icon.svg' 문법을 사용해서 동적으로 스타일을 제어할 수 있습니다.
💡 이미지에 쿼리 파라미터를 추가해서 특별한 처리를 할 수 있습니다. 예: import imgUrl from './img.png?url'은 항상 URL을 반환하고, ?raw는 파일 내용을 문자열로 가져옵니다.
💡 폰트는 preload를 사용해서 미리 로드하면 렌더링 성능이 향상됩니다. HTML에 <link rel="preload" href={fontUrl} as="font" type="font/woff2" crossorigin>을 추가하세요.
💡 TypeScript를 사용한다면 vite/client 타입을 import해야 이미지 임포트 시 타입 오류가 발생하지 않습니다. vite-env.d.ts에 /// <reference types="vite/client" />를 추가하세요.
2. Public_디렉토리_활용
시작하며
여러분이 robots.txt, favicon.ico, 또는 외부 라이브러리가 참조하는 정적 파일을 프로젝트에 추가할 때, 이런 파일들을 src 폴더에 넣고 import로 불러오는 게 맞을까요, 아니면 다른 방법이 있을까요? 이런 파일들은 빌드 과정에서 변환하거나 해시를 추가할 필요가 없고, 항상 고정된 경로로 접근해야 하는 경우가 많습니다.
예를 들어, robots.txt는 반드시 /robots.txt 경로에 있어야 검색 엔진이 찾을 수 있고, 외부 결제 라이브러리가 /payment-callback.html을 참조한다면 이 경로는 절대 바뀌면 안 됩니다. 바로 이럴 때 필요한 것이 Vite의 public 디렉토리입니다.
public 폴더에 있는 파일은 빌드 시 그대로 복사되어 루트 경로로 제공됩니다.
개요
간단히 말해서, public 디렉토리는 빌드 과정을 거치지 않고 그대로 배포되는 정적 파일을 저장하는 특별한 폴더입니다. 왜 이 기능이 필요할까요?
첫째, SEO 관련 파일(robots.txt, sitemap.xml)은 정확한 경로에 있어야 합니다. 둘째, 브라우저 파비콘이나 manifest 파일은 HTML에서 절대 경로로 참조합니다.
셋째, 외부 서비스(결제 게이트웨이, 소셜 로그인 등)가 특정 경로의 파일을 요구하는 경우가 있습니다. 예를 들어, 페이스북 도메인 인증을 위한 HTML 파일이나 Google Search Console 인증 파일 같은 것들이죠.
기존에는 이런 파일들을 빌드 스크립트로 수동으로 복사하거나, 웹 서버 설정으로 별도로 관리했다면, 이제는 단순히 public 폴더에 넣기만 하면 Vite가 자동으로 처리합니다. 핵심 특징은 다음과 같습니다: 1) public 폴더의 파일은 루트 경로(/)로 제공됩니다, 2) 파일 이름이나 내용이 바뀌어도 해시가 추가되지 않습니다, 3) import로 불러올 수 없고 절대 경로로만 참조합니다.
이러한 특징들이 특수한 정적 파일 관리를 단순화해줍니다.
코드 예제
// public 폴더 구조:
// public/
// favicon.ico
// robots.txt
// images/
// logo-large.png
// HTML에서 public 파일 참조 (절대 경로 사용)
<!DOCTYPE html>
<html>
<head>
<link rel="icon" type="image/x-icon" href="/favicon.ico">
</head>
<body>
<!-- public/images/logo-large.png 참조 -->
<img src="/images/logo-large.png" alt="Large Logo">
</body>
</html>
// JavaScript에서 public 파일 참조
const logoUrl = '/images/logo-large.png'
fetch('/robots.txt').then(res => res.text())
설명
이것이 하는 일: public 디렉토리에 있는 파일은 Vite의 빌드 프로세스를 완전히 우회해서, 개발 중에는 /로 바로 접근할 수 있고, 빌드 후에는 dist 폴더의 루트에 그대로 복사됩니다. 첫 번째로, public 폴더의 위치는 프로젝트 루트입니다.
즉, your-project/public/에 파일을 넣으면 됩니다. 개발 서버를 실행하면 http://localhost:5173/favicon.ico처럼 루트 경로로 바로 접근할 수 있습니다.
이것은 마치 전통적인 웹 서버의 정적 파일 서빙과 동일한 방식입니다. 두 번째로, public 폴더의 파일은 import로 불러올 수 없습니다.
import logo from '/images/logo.png'처럼 작성하면 오류가 발생합니다. 대신 HTML이나 JavaScript에서 문자열 경로로만 참조합니다.
이렇게 하는 이유는 Vite가 이 파일들을 추적하지 않기 때문입니다. 파일이 없어도 빌드는 성공하지만 런타임에 404 오류가 발생할 수 있으니 주의해야 합니다.
세 번째로, public 폴더의 하위 디렉토리 구조는 그대로 유지됩니다. public/images/logo.png는 /images/logo.png로 접근하고, public/docs/guide.pdf는 /docs/guide.pdf로 접근합니다.
빌드 후에는 dist/images/logo.png, dist/docs/guide.pdf 경로에 복사됩니다. 언제 public을 사용하고 언제 import를 사용해야 할까요?
기본 원칙은 이렇습니다: 1) JavaScript/CSS에서 참조하는 이미지/폰트 → import 사용, 2) 고정된 경로가 필요한 파일(robots.txt, favicon.ico) → public 사용, 3) 빌드 최적화가 필요 없는 대용량 미디어 파일 → public 사용. 예를 들어, 사용자 가이드 PDF나 샘플 데이터 파일은 public에 넣는 것이 적합합니다.
여러분이 public 폴더를 올바르게 사용하면 다음과 같은 이점이 있습니다: 1) SEO와 메타데이터 파일 관리가 명확해집니다, 2) 외부 서비스 연동이 간편해집니다, 3) 빌드 시간이 단축됩니다(변환 과정이 없으므로).
실전 팁
💡 public 폴더의 경로를 변경하고 싶다면 vite.config.js에서 publicDir: 'static'처럼 설정할 수 있습니다. 기본값은 'public'입니다.
💡 환경변수 import.meta.env.BASE_URL을 사용하면 배포 서브디렉토리를 고려한 경로를 만들 수 있습니다. 예: ${import.meta.env.BASE_URL}images/logo.png는 /my-app/images/logo.png처럼 변환됩니다.
💡 대용량 파일(100MB 이상)은 public 폴더 대신 CDN을 사용하는 것이 좋습니다. Git 저장소 크기가 커지고 배포 시간이 길어지기 때문입니다.
💡 public 폴더의 파일은 TypeScript 타입 체크를 받지 않으므로, 존재 여부를 런타임에 확인하는 로직을 추가하는 것이 안전합니다. 예: fetch('/data.json').catch(() => console.error('File not found'))
💡 개발 중에 public 폴더의 파일을 추가하거나 수정하면 브라우저를 수동으로 새로고침해야 합니다. HMR(Hot Module Replacement)이 적용되지 않기 때문입니다.
3. CSS_SCSS_Less_전처리기_설정
시작하며
여러분이 스타일링 작업을 할 때 변수, 중첩, 믹스인 같은 고급 기능을 사용하고 싶었던 적 있나요? 순수 CSS로는 복잡한 스타일을 관리하기 어렵고, 특히 대규모 프로젝트에서는 코드 중복이 심해지고 유지보수가 힘들어집니다.
이런 문제는 CSS 전처리기를 사용하면 해결됩니다. SCSS나 Less 같은 전처리기는 프로그래밍 언어처럼 변수, 함수, 조건문을 제공해서 스타일 코드를 체계적으로 작성할 수 있게 해줍니다.
하지만 전통적인 빌드 도구에서는 전처리기 설정이 복잡하고, 로더와 플러그인을 수동으로 구성해야 했습니다. 바로 이럴 때 필요한 것이 Vite의 내장 전처리기 지원입니다.
추가 설정 없이 패키지만 설치하면 SCSS, Less, Stylus를 바로 사용할 수 있습니다.
개요
간단히 말해서, Vite는 CSS 전처리기를 기본적으로 지원해서 별도의 로더 설정 없이 바로 사용할 수 있습니다. 왜 전처리기가 필요할까요?
첫째, 변수를 사용해서 색상 팔레트나 간격 같은 디자인 시스템을 중앙에서 관리할 수 있습니다. 둘째, 중첩 문법으로 HTML 구조와 유사하게 스타일을 작성해서 가독성이 높아집니다.
셋째, 믹스인과 함수로 반복되는 스타일 패턴을 재사용할 수 있습니다. 예를 들어, 버튼 스타일을 믹스인으로 만들어두면 primary, secondary, danger 버튼을 쉽게 생성할 수 있습니다.
기존에는 webpack에서 sass-loader, less-loader를 설치하고 복잡한 설정 파일을 작성해야 했다면, Vite에서는 그냥 npm install -D sass만 실행하면 끝입니다. 핵심 특징은 다음과 같습니다: 1) SCSS, Sass, Less, Stylus 모두 지원, 2) 소스맵이 자동으로 생성되어 디버깅이 쉬움, 3) HMR이 완벽하게 작동해서 스타일 변경이 즉시 반영됩니다.
이러한 특징들이 개발 속도를 크게 향상시킵니다.
코드 예제
/* styles.scss - SCSS 문법 사용 */
$primary-color: #3498db;
$spacing-unit: 8px;
// 믹스인 정의 - 재사용 가능한 스타일 블록
@mixin button-style($bg-color) {
padding: $spacing-unit * 2 $spacing-unit * 3;
background: $bg-color;
border: none;
border-radius: 4px;
&:hover {
background: darken($bg-color, 10%);
}
}
.btn-primary {
@include button-style($primary-color);
}
// main.js에서 SCSS 파일 임포트
import './styles.scss'
설명
이것이 하는 일: Vite는 .scss, .sass, .less, .styl 파일을 감지하면 자동으로 해당 전처리기를 실행해서 CSS로 변환하고, 결과를 브라우저에 주입합니다. 첫 번째로, 전처리기를 사용하려면 먼저 npm 패키지를 설치해야 합니다.
SCSS를 사용한다면 npm install -D sass, Less라면 npm install -D less, Stylus라면 npm install -D stylus를 실행합니다. 이게 전부입니다.
Vite는 이 패키지들이 설치되어 있으면 자동으로 감지해서 사용합니다. 별도의 vite.config.js 설정은 필요 없습니다.
두 번째로, SCSS 파일에서는 변수, 중첩, 믹스인, 함수 등을 자유롭게 사용할 수 있습니다. 예제 코드에서 $primary-color는 변수이고, @mixin button-style은 재사용 가능한 스타일 블록입니다.
&:hover는 중첩 문법으로, 부모 선택자(.btn-primary)의 hover 상태를 의미합니다. darken() 함수는 SCSS 내장 함수로 색상을 어둡게 만듭니다.
이런 기능들이 순수 CSS로는 불가능하거나 매우 장황한 코드를 간결하게 만들어줍니다. 세 번째로, 전처리기 파일을 JavaScript에서 import하면 Vite가 컴파일하고 스타일을 <style> 태그로 페이지에 주입합니다.
개발 중에는 파일이 변경될 때마다 HMR이 작동해서 전체 페이지를 새로고침하지 않고도 스타일이 업데이트됩니다. 빌드 시에는 모든 SCSS 파일이 CSS로 변환되고, 하나의 CSS 파일로 번들링되어 최적화됩니다.
네 번째로, 전처리기별 추가 옵션을 설정하고 싶다면 vite.config.js의 css.preprocessorOptions를 사용합니다. 예를 들어, 모든 SCSS 파일에서 공통으로 사용할 변수 파일을 자동으로 임포트하려면 additionalData: '@import "@/styles/variables.scss";'처럼 설정할 수 있습니다.
이렇게 하면 매번 수동으로 import하지 않아도 됩니다. 여러분이 전처리기를 사용하면 다음과 같은 이점이 있습니다: 1) 디자인 시스템을 변수로 중앙화해서 일관성을 유지할 수 있습니다, 2) 중첩 문법으로 HTML 구조를 반영해서 코드 가독성이 높아집니다, 3) 믹스인으로 반복 코드를 줄여서 유지보수가 쉬워집니다, 4) 수학 연산과 색상 함수로 동적인 스타일을 생성할 수 있습니다.
실전 팁
💡 글로벌 변수를 모든 SCSS 파일에 자동으로 주입하려면 vite.config.js에서 css.preprocessorOptions.scss.additionalData를 설정하세요. 단, 이 방법은 빌드 성능에 영향을 줄 수 있으니 꼭 필요한 변수만 포함하세요.
💡 SCSS의 @use와 @forward는 @import보다 권장되는 최신 문법입니다. 네임스페이스를 명시적으로 관리할 수 있어서 변수 충돌을 방지합니다. 예: @use 'variables' as v; 후 color: v.$primary-color;
💡 전처리기 파일이 많아지면 빌드 시간이 늘어납니다. 개발 중에는 괜찮지만, 프로덕션 빌드에서는 CSS-in-JS나 Tailwind 같은 대안도 고려해보세요.
💡 SCSS 소스맵이 작동하지 않으면 css.devSourcemap: true를 vite.config.js에 추가하세요. 브라우저 개발자 도구에서 원본 SCSS 파일의 정확한 줄 번호를 볼 수 있습니다.
💡 Less에서 JavaScript를 실행하는 기능(예: ~로 node_modules 참조)은 보안 이슈로 기본적으로 비활성화되어 있습니다. 필요하다면 preprocessorOptions.less.javascriptEnabled: true를 설정하되, 신뢰할 수 있는 코드만 사용하세요.
4. CSS_Modules_사용법
시작하며
여러분이 컴포넌트 기반 프레임워크(React, Vue 등)를 사용할 때, 다른 컴포넌트의 스타일과 클래스 이름이 충돌해서 의도하지 않은 스타일이 적용된 적 있나요? 특히 .button, .header 같은 일반적인 클래스명을 여러 곳에서 사용하면 전역 네임스페이스 오염 문제가 발생합니다.
이런 문제는 CSS의 전역 스코프 특성 때문에 발생합니다. 하나의 HTML 페이지에 모든 CSS가 로드되면, 같은 클래스명이 여러 번 정의되어 마지막에 로드된 스타일이 이전 스타일을 덮어씁니다.
BEM 같은 명명 규칙으로 해결할 수 있지만, 클래스명이 장황해지고 실수하기 쉽습니다. 바로 이럴 때 필요한 것이 CSS Modules입니다.
각 CSS 파일의 클래스를 로컬 스코프로 만들어서 자동으로 고유한 클래스명을 생성해줍니다.
개요
간단히 말해서, CSS Modules는 CSS 클래스명을 파일 단위로 스코프화해서 다른 파일과 이름 충돌을 방지하는 기술입니다. 왜 이 기능이 필요할까요?
첫째, 컴포넌트 단위로 스타일을 완전히 격리할 수 있어서 예측 가능한 스타일링이 가능합니다. 둘째, 클래스명을 간단하게 유지하면서도 충돌 걱정이 없습니다.
셋째, JavaScript에서 클래스명을 객체로 참조해서 타입 안전성을 높일 수 있습니다. 예를 들어, 대시보드 컴포넌트에서 .card 클래스를 사용하고, 프로필 컴포넌트에서도 .card를 사용해도 서로 영향을 주지 않습니다.
기존에는 .button-primary-large-disabled처럼 긴 BEM 클래스명을 작성해야 했다면, CSS Modules를 사용하면 .button이라고만 작성해도 자동으로 .button_a3f2b_1처럼 고유한 이름으로 변환됩니다. 핵심 특징은 다음과 같습니다: 1) .module.css 확장자를 사용하면 자동으로 활성화됩니다, 2) 클래스명이 {클래스명}_{해시}_{인덱스} 형식으로 변환됩니다, 3) JavaScript에서 객체로 클래스명을 참조합니다.
이러한 특징들이 대규모 애플리케이션의 스타일 관리를 크게 단순화합니다.
코드 예제
/* Button.module.css - CSS Modules 파일 */
.button {
padding: 12px 24px;
border-radius: 4px;
font-weight: 600;
}
.primary {
background: #3498db;
color: white;
}
.disabled {
opacity: 0.5;
cursor: not-allowed;
}
// Button.jsx - React 컴포넌트
import styles from './Button.module.css'
export function Button({ variant, disabled, children }) {
// styles.button은 실제로는 "button_a3f2b_1" 같은 고유한 클래스명
const className = `${styles.button} ${styles[variant]} ${disabled ? styles.disabled : ''}`
return <button className={className}>{children}</button>
}
설명
이것이 하는 일: Vite는 .module.css 파일을 감지하면 각 클래스명에 고유한 해시를 추가하고, 클래스명을 키로 하는 JavaScript 객체를 반환합니다. 첫 번째로, CSS Modules를 사용하려면 파일 이름을 Button.module.css처럼 .module.css로 끝나게 만들면 됩니다.
이것만으로 Vite가 자동으로 CSS Modules를 적용합니다. 파일 안에서는 평범하게 .button, .primary 같은 클래스를 작성하면 되고, 빌드 시에 Vite가 .button_a3f2b_1, .primary_a3f2b_2처럼 변환합니다.
해시는 파일 경로와 내용을 기반으로 생성되어 충돌 가능성이 0에 가깝습니다. 두 번째로, JavaScript에서 import하면 클래스명 객체를 받습니다.
import styles from './Button.module.css'를 실행하면 styles는 { button: 'button_a3f2b_1', primary: 'primary_a3f2b_2', disabled: 'disabled_a3f2b_3' } 같은 객체가 됩니다. 이 객체를 사용해서 className={styles.button}처럼 타입 안전하게 클래스를 적용할 수 있습니다.
만약 오타로 styles.buton이라고 쓰면 undefined가 되어 버그를 쉽게 발견할 수 있습니다. 세 번째로, 여러 클래스를 조합할 때는 템플릿 리터럴이나 라이브러리를 사용합니다.
예제 코드처럼 백틱으로 여러 클래스를 합치거나, classnames 라이브러리로 className={cn(styles.button, styles[variant], { [styles.disabled]: disabled })}처럼 조건부 클래스를 깔끔하게 작성할 수 있습니다. 네 번째로, 전역 클래스가 필요한 경우도 있습니다.
예를 들어, 외부 라이브러리의 클래스를 오버라이드하거나, 의도적으로 전역 스타일을 정의하고 싶을 때는 :global(.class-name)을 사용합니다. 예: :global(.tippy-tooltip) { z-index: 9999; }는 CSS Modules 변환을 우회해서 그대로 .tippy-tooltip로 출력됩니다.
여러분이 CSS Modules를 사용하면 다음과 같은 이점이 있습니다: 1) 스타일 격리로 예측 가능한 스타일링이 가능합니다, 2) 클래스명을 짧고 의미 있게 유지할 수 있습니다, 3) JavaScript에서 타입 체크를 통해 오타를 방지합니다, 4) 사용하지 않는 CSS를 자동으로 제거하는 tree-shaking이 가능합니다(일부 도구에서).
실전 팁
💡 CSS Modules의 해시 패턴을 커스터마이징하려면 vite.config.js에서 css.modules.generateScopedName을 설정하세요. 예: '[name]__[local]__[hash:base64:5]'는 Button__button__a3f2b 형식으로 생성합니다.
💡 TypeScript에서 CSS Modules의 타입을 자동 생성하려면 vite-plugin-dts나 typescript-plugin-css-modules를 사용하세요. 그러면 자동완성과 타입 체크가 가능해집니다.
💡 camelCase로 클래스명을 참조하고 싶다면 css.modules.localsConvention: 'camelCaseOnly'를 설정하세요. .button-primary가 styles.buttonPrimary로 변환됩니다.
💡 CSS Modules와 SCSS를 함께 사용하려면 Button.module.scss처럼 확장자를 바꾸면 됩니다. 변수, 중첩 등 SCSS 기능과 CSS Modules 스코프를 동시에 사용할 수 있습니다.
💡 개발 중에는 css.modules.generateScopedName: '[local]'로 설정해서 디버깅을 쉽게 하고, 프로덕션에서는 짧은 해시를 사용해서 파일 크기를 줄이세요. 환경변수로 분기 처리할 수 있습니다.
5. PostCSS_통합_및_Tailwind_설정
시작하며
여러분이 최신 CSS 기능(예: 중첩, 사용자 정의 미디어 쿼리)을 사용하고 싶거나, Autoprefixer로 벤더 프리픽스를 자동으로 추가하고 싶을 때, 어떻게 설정해야 할지 고민한 적 있나요? 또는 Tailwind CSS 같은 유틸리티 프레임워크를 프로젝트에 통합하려고 할 때 복잡한 설정 때문에 막막했던 경험이 있을 겁니다.
이런 문제는 CSS 후처리(post-processing) 도구를 수동으로 구성해야 하기 때문에 발생합니다. PostCSS는 강력한 CSS 변환 도구지만, webpack 같은 번들러에서는 로더와 플러그인 체인을 직접 설정해야 해서 진입 장벽이 높았습니다.
바로 이럴 때 필요한 것이 Vite의 PostCSS 통합입니다. Vite는 PostCSS를 기본적으로 지원하고, postcss.config.js 파일만 추가하면 자동으로 적용됩니다.
개요
간단히 말해서, PostCSS는 JavaScript 플러그인으로 CSS를 변환하는 도구이고, Vite는 이를 기본적으로 지원해서 별도 로더 없이 바로 사용할 수 있습니다. 왜 PostCSS가 필요할까요?
첫째, Autoprefixer로 구형 브라우저를 지원하는 벤더 프리픽스를 자동으로 추가할 수 있습니다. 예를 들어 display: flex를 작성하면 자동으로 -webkit-box, -ms-flexbox 같은 프리픽스가 추가됩니다.
둘째, 최신 CSS 문법을 구형 브라우저가 이해할 수 있는 형태로 변환할 수 있습니다(PostCSS Preset Env). 셋째, Tailwind CSS 같은 유틸리티 프레임워크를 통합할 수 있습니다.
Tailwind는 PostCSS 플러그인으로 동작해서 필요한 유틸리티 클래스만 생성하고 나머지는 제거합니다(PurgeCSS). 기존에는 webpack에서 postcss-loader와 여러 플러그인을 설정 파일에 나열하고 순서를 맞춰야 했다면, Vite에서는 postcss.config.js에 플러그인만 나열하면 자동으로 적용됩니다.
핵심 특징은 다음과 같습니다: 1) postcss.config.js 파일이 있으면 자동으로 PostCSS가 실행됩니다, 2) Tailwind, Autoprefixer 등 인기 플러그인과 완벽하게 호환됩니다, 3) 개발 중에도 빌드 시에도 동일하게 작동합니다. 이러한 특징들이 CSS 작업 흐름을 크게 개선합니다.
코드 예제
// postcss.config.js - PostCSS 설정 파일
export default {
plugins: {
// Tailwind CSS 플러그인
tailwindcss: {},
// 벤더 프리픽스 자동 추가
autoprefixer: {},
// 프로덕션 빌드에서 사용하지 않는 CSS 제거
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {})
}
}
// tailwind.config.js - Tailwind 설정
export default {
content: ['./index.html', './src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {
colors: { primary: '#3498db' }
}
}
}
// main.css - Tailwind 디렉티브
@tailwind base;
@tailwind components;
@tailwind utilities;
설명
이것이 하는 일: Vite는 CSS 파일을 처리할 때 프로젝트 루트의 postcss.config.js를 자동으로 찾아서, 정의된 플러그인들을 순서대로 실행해 CSS를 변환합니다. 첫 번째로, PostCSS를 사용하려면 먼저 필요한 플러그인을 설치해야 합니다.
가장 일반적인 조합은 npm install -D tailwindcss autoprefixer postcss입니다. Tailwind는 유틸리티 클래스 프레임워크이고, Autoprefixer는 벤더 프리픽스를 추가하고, postcss는 PostCSS 자체입니다.
설치 후 postcss.config.js 파일을 만들어서 플러그인을 나열하면 됩니다. 두 번째로, Tailwind를 설정할 때는 tailwind.config.js에서 스캔할 파일을 지정해야 합니다.
content 배열에 HTML과 JavaScript 파일 경로를 지정하면, Tailwind가 실제로 사용된 클래스만 추출해서 CSS를 생성합니다. 예를 들어 <div className="bg-blue-500">가 있으면 .bg-blue-500 스타일만 포함되고, 사용하지 않은 수천 개의 클래스는 제외됩니다.
이것이 PurgeCSS 기능으로, 최종 CSS 파일 크기를 수 MB에서 수십 KB로 줄입니다. 세 번째로, CSS 파일에서 @tailwind 디렉티브를 사용하면 Tailwind의 기본 스타일, 컴포넌트, 유틸리티가 주입됩니다.
@tailwind base는 CSS 리셋과 기본 스타일, @tailwind components는 버튼이나 카드 같은 컴포넌트 클래스, @tailwind utilities는 m-4, p-2 같은 유틸리티 클래스를 제공합니다. 이 디렉티브는 빌드 시에 실제 CSS 규칙으로 변환됩니다.
네 번째로, 프로덕션 빌드에서는 추가 최적화를 적용할 수 있습니다. cssnano 플러그인은 CSS를 압축하고 불필요한 공백, 주석, 중복 규칙을 제거합니다.
예제 코드처럼 환경 변수로 프로덕션에서만 cssnano를 활성화하면 개발 속도와 빌드 최적화를 모두 챙길 수 있습니다. 여러분이 PostCSS와 Tailwind를 사용하면 다음과 같은 이점이 있습니다: 1) 벤더 프리픽스를 신경 쓰지 않아도 됩니다(Autoprefixer가 자동 처리), 2) 유틸리티 우선 접근법으로 스타일링 속도가 빠릅니다, 3) PurgeCSS로 사용하지 않는 CSS가 자동으로 제거되어 번들 크기가 최소화됩니다, 4) PostCSS 플러그인 생태계를 활용해서 다양한 CSS 변환을 적용할 수 있습니다.
실전 팁
💡 Tailwind의 JIT(Just-In-Time) 모드는 기본적으로 활성화되어 있어서 필요한 스타일만 즉시 생성합니다. 커스텀 값도 사용할 수 있어서 w-[375px]처럼 임의의 값을 직접 지정할 수 있습니다.
💡 PostCSS 플러그인 순서가 중요합니다. 일반적으로 tailwindcss → autoprefixer → cssnano 순서로 실행됩니다. 순서가 잘못되면 일부 변환이 제대로 작동하지 않을 수 있습니다.
💡 Tailwind의 @apply 디렉티브로 유틸리티 클래스를 조합해서 커스텀 클래스를 만들 수 있습니다. 예: .btn { @apply px-4 py-2 bg-blue-500 text-white rounded; }. 하지만 과도하게 사용하면 Tailwind의 이점이 줄어드니 필요한 경우만 사용하세요.
💡 개발 중에 Tailwind 자동완성을 사용하려면 VS Code의 "Tailwind CSS IntelliSense" 확장을 설치하세요. 클래스명 자동완성, 색상 미리보기, 린팅 기능을 제공합니다.
💡 PostCSS 설정을 vite.config.js 안에 인라인으로 작성할 수도 있습니다. css.postcss에 플러그인 객체를 전달하면 됩니다. 작은 프로젝트에서는 파일을 하나 줄일 수 있어서 편리합니다.
6. 웹_워커와_WASM_파일_처리
시작하며
여러분이 무거운 계산 작업(예: 이미지 처리, 데이터 파싱)을 백그라운드에서 실행하고 싶거나, C/C++로 작성된 고성능 모듈을 웹에서 사용하고 싶을 때, 어떻게 해야 할까요? JavaScript는 싱글 스레드라서 무거운 작업이 UI를 멈추게 하고, 순수 JavaScript로는 성능 한계가 있습니다.
이런 문제는 웹 워커(Web Worker)와 WebAssembly(WASM)로 해결할 수 있습니다. 웹 워커는 별도의 스레드에서 코드를 실행해서 메인 스레드를 차단하지 않고, WASM은 네이티브에 가까운 속도로 코드를 실행할 수 있습니다.
하지만 전통적으로 이들을 번들러와 통합하는 것은 복잡했습니다. 바로 이럴 때 필요한 것이 Vite의 웹 워커와 WASM 지원입니다.
특별한 쿼리 파라미터를 사용해서 간단하게 임포트할 수 있습니다.
개요
간단히 말해서, Vite는 웹 워커를 ?worker 쿼리로 임포트하고, WASM 파일을 ?init로 임포트해서 별도의 로더 없이 바로 사용할 수 있습니다. 왜 이 기능이 필요할까요?
첫째, 웹 워커로 CPU 집약적 작업을 오프로드해서 UI 반응성을 유지할 수 있습니다. 예를 들어, 대용량 CSV 파일을 파싱하거나 이미지 필터를 적용하는 작업을 워커에서 처리하면 사용자가 여전히 버튼을 클릭하고 스크롤할 수 있습니다.
둘째, WASM으로 C/Rust 같은 언어로 작성된 고성능 라이브러리를 사용할 수 있습니다. 예를 들어, 암호화, 압축, 이미지 처리 같은 작업은 WASM으로 구현하면 JavaScript보다 5-10배 빠를 수 있습니다.
기존에는 worker-loader나 복잡한 webpack 설정이 필요했다면, Vite에서는 import Worker from './worker.js?worker'처럼 간단한 쿼리만 추가하면 됩니다. 핵심 특징은 다음과 같습니다: 1) ?worker 쿼리로 웹 워커를 즉시 사용 가능, 2) ?init로 WASM을 자동으로 초기화, 3) HMR과 소스맵이 워커에서도 작동합니다.
이러한 특징들이 고성능 웹 애플리케이션 개발을 단순화합니다.
코드 예제
// worker.js - 웹 워커 파일
self.addEventListener('message', (e) => {
const { data } = e
// 무거운 계산 작업 시뮬레이션
const result = data.numbers.reduce((sum, n) => sum + n, 0)
// 메인 스레드로 결과 전송
self.postMessage({ result })
})
// main.js - 워커 사용
import Worker from './worker.js?worker'
const worker = new Worker()
worker.postMessage({ numbers: [1, 2, 3, 4, 5] })
worker.addEventListener('message', (e) => {
console.log('Result:', e.data.result) // 15
})
// WASM 사용 예시 (Rust로 컴파일된 add 함수)
import init, { add } from './math.wasm?init'
await init()
console.log(add(10, 20)) // 30
설명
이것이 하는 일: Vite는 ?worker 쿼리를 감지하면 해당 파일을 별도의 워커 번들로 분리하고, Worker 생성자를 반환합니다. WASM은 ?init로 초기화 함수와 익스포트를 함께 제공합니다.
첫 번째로, 웹 워커를 만들려면 일반 JavaScript 파일을 작성하되, self.addEventListener('message')로 메시지를 받습니다. 워커는 메인 스레드와 분리된 별도의 컨텍스트에서 실행되므로 DOM에 접근할 수 없고, postMessage로만 통신합니다.
예제 코드에서 워커는 숫자 배열을 받아서 합계를 계산하고 결과를 반환합니다. 이 작업이 메인 스레드를 차단하지 않아서 UI가 멈추지 않습니다.
두 번째로, 메인 스레드에서 워커를 사용할 때는 import Worker from './worker.js?worker'로 임포트합니다. 이것은 Worker 클래스를 반환하므로 new Worker()로 인스턴스를 생성합니다.
그 다음 worker.postMessage(data)로 데이터를 보내고, worker.addEventListener('message', callback)으로 결과를 받습니다. 워커가 더 이상 필요 없으면 worker.terminate()로 종료해서 메모리를 해제해야 합니다.
세 번째로, WASM 파일을 사용할 때는 ?init 쿼리를 추가합니다. import init, { add } from './math.wasm?init'는 초기화 함수 init와 WASM 모듈이 익스포트한 함수들을 가져옵니다.
await init()을 먼저 호출해서 WASM 모듈을 메모리에 로드하고, 그 후에 add() 같은 함수를 호출할 수 있습니다. WASM 함수는 JavaScript 함수보다 훨씬 빠르게 실행됩니다.
네 번째로, 워커와 WASM을 함께 사용할 수도 있습니다. 워커 내부에서 WASM을 임포트하면 백그라운드에서 고성능 계산을 수행할 수 있습니다.
예를 들어, 이미지 처리 라이브러리를 WASM으로 컴파일하고 워커에서 실행하면, 대용량 이미지를 처리해도 UI가 반응성을 유지합니다. 여러분이 웹 워커와 WASM을 사용하면 다음과 같은 이점이 있습니다: 1) CPU 집약적 작업이 UI를 차단하지 않아서 사용자 경험이 향상됩니다, 2) WASM으로 네이티브 수준의 성능을 얻어서 복잡한 계산이 가능합니다, 3) 기존 C/C++/Rust 라이브러리를 재사용할 수 있습니다, 4) 민감한 로직을 워커로 분리해서 보안을 강화할 수 있습니다(메인 스레드와 격리됨).
실전 팁
💡 워커와 메인 스레드 간 통신은 데이터를 복사하므로 오버헤드가 있습니다. 대용량 데이터는 Transferable Objects(ArrayBuffer, ImageBitmap 등)를 사용해서 복사 없이 소유권을 이전하세요. 예: worker.postMessage(buffer, [buffer])
💡 여러 워커를 풀로 관리하려면 Comlink 라이브러리를 사용하세요. Promise 기반 API로 워커를 마치 일반 함수처럼 호출할 수 있어서 코드가 훨씬 간결해집니다.
💡 WASM 파일 크기가 크면 초기 로딩이 느려질 수 있습니다. wasm-opt 도구로 WASM을 최적화하거나, 스트리밍 인스턴스화(WebAssembly.instantiateStreaming)를 사용해서 다운로드와 컴파일을 동시에 진행하세요.
💡 워커에서 모듈을 임포트하려면 type: 'module' 옵션을 사용하세요. Vite는 자동으로 ESM 워커를 지원합니다. 예: new Worker('./worker.js', { type: 'module' })
💡 개발 중에는 워커 디버깅이 어려울 수 있습니다. Chrome DevTools의 Sources 탭에서 워커를 별도로 선택해서 브레이크포인트를 설정하고 콘솔을 사용할 수 있습니다. Vite의 소스맵이 워커에서도 작동합니다.
댓글 (0)
함께 보면 좋은 카드 뉴스
WebSocket과 Server-Sent Events 실시간 통신 완벽 가이드
웹 애플리케이션에서 실시간 데이터 통신을 구현하는 핵심 기술인 WebSocket과 Server-Sent Events를 다룹니다. 채팅, 알림, 실시간 업데이트 등 현대 웹 서비스의 필수 기능을 구현하는 방법을 배워봅니다.
API 테스트 전략과 자동화 완벽 가이드
API 개발에서 필수적인 테스트 전략을 단계별로 알아봅니다. 단위 테스트부터 부하 테스트까지, 실무에서 바로 적용할 수 있는 자동화 기법을 익혀보세요.
효과적인 API 문서 작성법 완벽 가이드
API 문서는 개발자와 개발자 사이의 가장 중요한 소통 수단입니다. 이 가이드에서는 좋은 API 문서가 갖춰야 할 조건부터 Getting Started, 엔드포인트 설명, 에러 코드 문서화, 인증 가이드, 변경 이력 관리까지 체계적으로 배워봅니다.
API 캐싱과 성능 최적화 완벽 가이드
웹 서비스의 응답 속도를 획기적으로 개선하는 캐싱 전략과 성능 최적화 기법을 다룹니다. HTTP 캐싱부터 Redis, 데이터베이스 최적화, CDN까지 실무에서 바로 적용할 수 있는 핵심 기술을 초급자 눈높이에서 설명합니다.
OAuth 2.0과 소셜 로그인 완벽 가이드
OAuth 2.0의 핵심 개념부터 구글, 카카오 소셜 로그인 구현까지 초급 개발자를 위해 쉽게 설명합니다. 인증과 인가의 차이점, 다양한 Flow의 특징, 그리고 보안 고려사항까지 실무에 바로 적용할 수 있는 내용을 다룹니다.