이번에 기존 webpack을 사용하면서 vite에 대해 궁금해졌다. 엄청 빠르다고 얘기를 들었고 현재 webpack 기반으로 개발을 하면서 개발 환경이 느려서 vite를 도입해보면 어떨까해서 검토를 진행했다. (결국 webpack + esbuild-loader 사용으로 적용했다)
그러면 vite, webpack에 대해서 차례대로 알아보자
Vite는 개발 서버에서 Native ESM을 사용하여 소스를 제공하여 빠르게 서비스를 띄울 수 있다.
Vite는 dependencies와 source code 두 가지로 나누어 개발 서버의 시작 시간을 개선했다고 한다.
또한, 개발 서버를 시작 시에 현재 사용되고 있는 모듈만 가지고 오고(dynamic import), 변경점이 있는 모듈만 업데이트를 한다.

참고로 Vite는 개발 환경(개발 서버를 띄울 때)에서는 esbuild를 사용하고 배포 환경 즉, 단일 파일로 번들링할 때에는 rollup을 사용한다.
vite를 기반으로 프로젝트를 생성하는 방법은 공식 문서에 아주 잘 설명이 되어 있다.
나는 다음 명령어를 통해 프로젝트를 시작했다. (템플릿 참고)
pnpm create vite my-vite-project --template react-ts
그 다음 vite.config.js를 중심으로 살펴보자. 다음은 내가 구성한 설정 파일이다.
개발 서버 구동 시에 포트를 3000으로 사용하도록 하고 번들링할 때 rollupOptions를 통해 빌드 파일 명칭을 명시해주었다.(rollup 옵션 참고)
그리고 @vitejs/plugin-react를 plugins로 추가하여 react 환경에서 개발되도록 구성했다. 해당 플러그인이 해주는 역할을 다음과 같다.
기본적으로 이렇게만 구성해도 동작하는 데 아무 이상이 없다. 정말 간단하다.
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig(() => {
return {
plugins: [
react(),
],
server: {
port: 3000,
},
build: {
rollupOptions: {
output: {
entryFileNames: `myProject.js`
},
},
};
});
이제부터 내가 Vite를 적용하면서 맞닥뜨린 이슈 사항에 대해 말해보겠다.
현재 개발 중인 서비스는 사내 관리자 서비스로 코어 관리자에서 내가 개발한 서비스의 번들링된 빌드 파일을 참조해서 서비스가 제공되는 구조이다.
코어 관리자 쪽에는 jquery가 사용되고 있는데 여기서부터 문제가 발생한다.
먼저 vite를 통해 내가 개발한 서비스를 다음과 같이 코어 쪽에 노출되도록 했다.
"scripts": {
"dev": "concurrently \"tsc -b && vite build --watch -m development\" \"vite preview\"",
"build": "tsc -b && vite build -m production",
"preview": "vite preview"
},
--watch 옵션을 통해 변경점을 추적할 수 있게 했고 vite preview로 빌드 파일을 실행시켰다.
build와 preview를 동시에 실행하기 위해 concurrently 라이브러리를 활용했다.
이렇게 해서 서비스를 코어 관리자 내에서 띄우고 확인을 해보니 다음과 같은 오류가 발생하여 서비스가 동작하지 않았다.
Uncaught TypeError : $ is not a function
해당 문제 발생 원인에 대해 찾아보니 vite로 빌드 시에 global 변수 침범 이슈가 있다는 것을 알게되었다.
내가 초기에 빌드한 파일을 살펴 보면 $= 와 같이 $에 뭔가 할당하고 있는 것을 확인할 수 있다. 따라서 global 변수인 jquery의 $를 침범하게 되는 것이다.(관련 이슈)
서비스 파일을 정말 기본 코드만 구성한 후에 $=를 모두 지우고 확인해보면 위의 에러가 발생하지 않는다.

내가 찾은 해결 방안은 2가지다.
첫 번째
1. 내 서비스 내에 jquery 설치
2. @rollup/plugin-inject를 통해 jquery 전역 변수를 자동으로 주입
3. window 객체에 명시적으로 jquery 다시 명시
코드를 통해 살펴보자.
import inject from "@rollup/plugin-inject";
export default defineConfig({
plugins: [
inject({
$: 'jquery'
})
],
})
그리고 entry 파일에 다음과 같이 추가해준다.
import $ from 'jquery';
window.$ = $;
내 서비스에서는 사용하고 있지 않는 jquery를 설치하고 window 객체에 넣는 것은 상위 코어 서비스의 jquery를 오염시키는 행위이고 버전 또한 맞춰줘야 한다. 따라서 적합하지 않은 해결 방법이라 판단했다.
두 번째
1. terser를 설치
2. minify 시에 terser 사용하도록 적용 (클라이언트 빌드에는 'esbuild'가 기본값)
3. mangle (난독화) 옵션을 통해 코드 난독화 시에 "$"는 제외하도록 설정 (설정 참고)
빌드 시에 적용될 설정은 다음과 같다.
build: {
minify: "terser",
terserOptions:{
mangle:{
reserved: ["$"]
}
},
rollupOptions: {
input: "./src/index.ts",
output: {
entryFileNames: "myProject.js"
}
},
sourcemap: mode === "development"
}
}
다시 빌드된 파일을 확인해보면 $에 할당하는 구문은 보이지 않는다.

이렇게 적용하고 보니 minify시에 esbuild를 사용할 때보다 확실히 빌드 시간이 느린 것을 확인할 수 있었고 공식 문서에서도 다음과 같은 내용이 있다.

현재 개발 환경과 배포 환경(production)의 webpack 설정 분리를 위해서 파일을 3개로 다음과 같이 나누고 있다.
그리고 webpack-merge를 사용해서 각 config 파일을 합쳐서 사용한다.
기존 프로젝트는 babel-loader를 사용하고 있었는데 이번 기회에 빌드 시간을 단축시키고자 esbuild-loader를 사용하기로 결정했다.
참고
loader란? https://webpack.kr/concepts/loaders/
esbuild-loader는 webpack 환경에서 esbuild를 활용할 수 있도록 해준다.
esbuild-loader 공식 문서를 기반으로 설정하면 다음과 같이 webpack 설정 파일을 구성할 수 있을 것이다.
export default {
entry: "./src/index.tsx", // 엔트리 파일 설정
output: {
filename: "reactApp.js",
path: path.resolve(__dirname, "dist"),
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx"],
alias: {
"@": path.resolve(__dirname, "src/"),
},
},
devtool: "eval-source-map",
module: {
rules: [
{
test: /\.(ts|tsx)$/,
loader: "esbuild-loader",
options: {
target: "es2015",
},
}
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html",
})
],
devServer: {
static: path.join(__dirname, "dist"),
compress: true,
port: 3000,
},
};
해당 esbuild-loader를 적용하게 되면 빌드 시에 타입 체크가 안되는데 이를 보완하기 위해서 fork-ts-checker-webpack-plugin를 적용해줬다.
결론적으로 로컬 환경에서 빌드할 때 시간이 기존보다 대략 절반으로 줄어들었다.
CI 환경에서는 speed-measure-webpack-plugin를 사용해서 측정했을 때 loader에 소요되는 시간이 절반 가량 줄었다.


현재 개발 환경은 빌드 파일을 실시간으로 생성하고 이를 catch해야 되는데 vite에서 빌드 파일을 watch 옵션을 통해 한 결과 webpack과 빌드 시간이 크게 차이가 나지 않았다.
확실히 vite serve로 띄우는 개발 서버는 거의 즉시 구동되지만 해당 개발 환경을 사용할 수 있는 경우가 많지 않을 것 같아 vite의 장점을 활용할 수 없을 것 같았다. 내가 개발한 서비스를 번들링한 단일 빌드 파일을 다른 서비스에서 참조하는 형식이기 때문이다.
결론적으로 번들링(빌드) 시간이 esbuild-loader를 적용한 webpack과 비교했을 때 크게 차이가 나지 않았다.
외부 라이브러리(antd) 적용 시에 source-map을 사용할 때 알 수 없는 warning이 발생했는데 이를 해결하기 위해 설정 코드를 추가해줘야 하는데 불필요한 코드라고 느끼게 되었고 추후에 계속해서 이슈가 발생할 시에 시간 소모가 많이 될 수 있다고 판단했다.
Vite를 적용하기 위해 Vite에 대해서 알아봤지만 결국 적용을 하지 못했다.
하지만 이번 기회를 통해 webpack과 Vite에 대해 더 자세히 알 수 있었고 추후 기회가 된다면 vite를 적극 도입해보고 싶다.
무엇보다 webpack에 비해 설정하는 데 간단하다고 느꼈고 개발 서버를 띄울 때는 거의 즉시 띄어지기 때문에 개발 시에 효율성이 많이 증대될 것 같다.
참고
https://1ilsang.dev/posts/vite-dev-server#4-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%84%EC%86%8D-%EC%A7%84%ED%96%89with-websocket
https://fe-developers.kakaoent.com/2022/220707-webpack-esbuild-loader/
https://ui.toast.com/weekly-pick/ko_20181220