반응형
vite는 사전 번들링은 esbuild, 번들링 시에는 rollup을 기반으로 속도 개선과 module, ssr 지원 등 모던한 개발 구성을 갖출 수 있는 빌드 도구입니다.
차세대 프런트엔드 개발 툴
이라고 소개하는 vite로 React 환경을 구성해 보고 장단점과 개인 의견까지 덧붙여 보겠습니다.
개요
- 구성 및 주의점
- 장단점
- 결론
1. 구성 및 주의점
react, scss, webpack, babel, typescript로 구성되어 있던 것을 vite로 재구성하였습니다.
1) index.html
은 기본적으로 프로젝트의 root 경로에 있어야 합니다.
- root 경로가 아닌 다른 곳에 위치하고 싶다면
root
옵션에 해당 경로를 추가해 주어야 합니다.
// vue.config.js
export default defineConfig({
root: 'public',
...,
});
2) index.html
정적 코드가 dev와 prod일 경우가 다릅니다.
- 개발 모드일 경우에는 localhost에서 받아올 수 있도록 스크립트 설정을 해주어야 하고, 프로덕션 모드일 경우에는 entry src를 빌드되기 전 기준에 맞게 작성해 주면 됩니다.
- 웹팩에서는 root id를 가지고 있는 tag(
ex. <div id="root"></div>
)만 가지고 있으면 빌드할 때 자동으로 삽입되는데 html에서 스크립트를 작성해 주어야 한다는 점이 다릅니다. - dev/prod 모드를 구분하기 위해서 vite.config.js의 작업과 플러그인 사용이 필요합니다.
- vite에서는
<% if (MODE === 'development') { %><% } %>
와 같은 문법을 사용하면 html parse 에러가 발생합니다. 방법을 제대로 못찾은 것일 수도 있지만,transformIndexHtml
라는 플러그인 API를 활용해 간단히 로컬로 HTML 변환 플러그인을 만들어 사용했습니다.
// plugin
function transformHtml(mode) {
return {
name: 'html-transform',
transformIndexHtml(html) {
const dev = `<script type="module">
import RefreshRuntime from "http://localhost:3000/@react-refresh"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>
<script type="module" src="http://localhost:3000/@vite/client"></script>
<script type="module" src="http://localhost:3000/src/index.tsx"></script>`;
const prod = `<script type="module" src="/src/index.tsx"></script>`;
return html.replace('<div id="mode"></div>', mode === 'development' ? dev : prod);
},
};
}
<!-- index.html -->
<div id="mode"></div>
vite-plugin-html
을 설치하고 createHtmlPlugin를 통해 inject data에 Mode 값을 삽입해 줍니다. mode 값은 vite.config를 작성할 때 인자에서 꺼내 쓸 수 있습니다.
// vue.config.js
plugins: [
...,
transformHtml(mode),
createHtmlPlugin({
minify: true,
inject: {
data: {
MODE: mode,
},
},
}),
],
3) scss, less 등의 post css 를 사용할 경우 별도의 postcss.config.js
설정이 필요합니다.
- post css 설정을 위한 라이브러리를 dev dependency로 설치해 줍니다.
yarn add -D autoprefixer cssnano postcss postcss-import postcss-load-config postcss-loader postcss-nested
- vite의 css 옵션에서 scss entry 파일과 postcss option을 설정하면 됩니다.
// vite.config.js
...,
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "./src/assets/scss/style.scss";`,
},
},
postcss: (ctx) => ({
parser: ctx.parser ? 'sugarss' : false,
map: ctx.env === 'development' ? ctx.map : false,
plugins: {
'postcss-import': {},
'postcss-nested': {},
cssnano: ctx.env === 'production' ? {} : false,
autoprefixer: { overrideBrowserslist: ['defaults'] },
},
}),
},
4) 환경변수(env)는 VITE_
를 붙이고 쓸 때는 import.meta.env
키워드로 사용합니다.
- VITE_ prefix를 붙이면 env는 별도 설정 없이 mode에 따라 작동합니다. prefix를 원하는 대로 변경할 수도 있는데, 빈 문자열은 안된다고 하니 유의하세요.
// vite.config.js
envPrefix: 'HELLO_',
- 환경변수를 사용할 때는 기존에 웹팩으로 사용하던
process.env
키워드가 아닌import.meta.env
키워드로 작성해야 올바른 값을 불러올 수 있습니다.
전체 설정 보기
import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
import { createHtmlPlugin } from 'vite-plugin-html';
import path from 'path';
import { dependencies } from './package.json';
function renderChunks(deps) {
let chunks = {};
Object.keys(deps).forEach((key) => {
if (['react', 'react-router-dom', 'react-dom'].includes(key)) return;
chunks[key] = [key];
});
return chunks;
}
function transformHtml(mode) {
return {
name: 'html-transform',
transformIndexHtml(html) {
const dev = `<script type="module">
import RefreshRuntime from "http://localhost:3000/@react-refresh"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>
<script type="module" src="http://localhost:3000/@vite/client"></script>
<script type="module" src="http://localhost:3000/src/index.tsx"></script>`;
const prod = `<script type="module" src="/src/index.tsx"></script>`;
return html.replace('<div id="mode"></div>', mode === 'development' ? dev : prod);
},
};
}
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, 'env');
return {
server: { hmr: true },
plugins: [
react({
include: ['**/*.tsx', '**/*.ts'],
}),
tsconfigPaths(),
transformHtml(mode),
createHtmlPlugin({
minify: true,
inject: {
data: {
...env,
MODE: mode,
},
},
}),
],
resolve: {
alias: { '@': path.resolve(__dirname, 'src/') },
},
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "./src/assets/scss/style.scss";`,
},
},
postcss: (ctx) => ({
parser: ctx.parser ? 'sugarss' : false,
map: ctx.env === 'development' ? ctx.map : false,
plugins: {
'postcss-import': {},
'postcss-nested': {},
cssnano: ctx.env === 'production' ? {} : false,
autoprefixer: { overrideBrowserslist: ['defaults'] },
},
}),
},
build: {
sourcemap: false,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-router-dom', 'react-dom'],
...renderChunks(dependencies),
},
},
},
},
};
});
2. 장단점
장점
- serve, build 시 체감속도가 웹팩보다 빠릅니다. serve 시에는 실제로 1초 내외로 결과물이 나오는 것을 볼 수 있었습니다.
- 웹팩에 비해 config 라인 수가 적습니다. vite는 기본으로 지원되는 기능이 많아서 굳이 옵션으로 적지 않아도 실행이 됩니다. env, public copy, 기본 es6로 트랜스파일된다는 것이 그 예입니다.
- 빌드 최적화와 SSR 설정에 대해 지원해 주고 있어 간편하게 설정 가능합니다.
- 웹팩 설정에 대한 경험이 있다면 vite도 빠르게 구성 가능합니다.
단점
- 초기 serve 시 사이트 로딩을 시작하면서부터 디펜던시들을 받아 시간이 다소 오래 걸렸습니다. 첫 실행 시에만 로딩이 길고 이후부터는 빠르긴 했습니다.
- React의 lazy component를 적용할 수 없습니다. dynamic import는 rollup 설정을 통해 사용 가능하지만 lazy component의 경우 vite/rollup 구성에서 지원이 안된다고 합니다.
- 일반적으로 env를 사용할 때 쓰는
process.env.
가 아닌import.meta.env.
로 사용해야 한다는 것이 webpack -> vite로 마이그레이션하는 경우라고 한다면 다소 불편할 것 같습니다. - index.html을 dev/prod로 나누어 분기처리해야 하는 것이 번거로웠습니다.
3. 결론
저는 esbuild가 굉장히 빠른 속도로 serve, build를 진행해 준다고 하여 webpack에 esbuild-loader를 사용해 보기도 했는데요.
이번에 vite를 직접 구성해 보면서 production에서도 충분히 사용 가능하겠다는 생각을 하게 되었습니다.
프론트엔드는 최근 빌드 도구부터 프레임워크까지 가벼운 것으로의 변화가 느껴지는데 프로덕트에 맞춰 적절하게 변화를 반영하는 것이 필요할 것 같습니다.
반응형