- Published on
Cloudforet Console App build 최적화 (vite + vue)
- Authors
- Name
- sulmo
문제사항
우리 팀은 vercel을 이용한 개발 환경 배포를 진행 중인 상황. 콘솔 앱 배포 진행 중 필요로하는 힙 메모리가 9GB를 넘어감. 배포 중 Vercel의 컨테이너의 메모리 스펙을 넘겨 OOM에러가 발생 -> Vercel dev 환경배포 이용 불가
해결 방안
당장 해결할 시간이 없어 가장 1차로 빠르게 dev 환경을 이용하기위한 조치 후, 근본적인 메모리 최적화를 진행
1차 급한불 처리
각 개발자가 본인 피처를 로컬(맥북)에서 빌드 진행 후, 빌드된 파일을 그대로 vercel 환경에 배포. vercel project 명과 branch 명을 선택 후 github action(Github Action Job process)을 돌리는 형식으로 제공.
2차 Build process 최적화
로컬에서 빌드하는 방식은 개발자에게 생각보다 (매우많이)불편함
운이 좋게 내 노트북은 32GB램이지만 동료개발자의 경우 16GB로 근근히 버티고 있는 상황에서 빌드시간(약 20분)동안 WebStorm과 그외 툴(슬랙, 노션, 브라우저 등등) 죽여달라고 소리친다.
Vercel은 Team플랜으로 사용중이나 전혀 돈값을 못한다.
원인 분석
작업 시작
리서치에서 나온 방식을 순차적으로 진행
node --inspect 역시 client를 위한 실시간 검사기로서, 빌드 과정에서의 리소스는 볼 수 없었음.
- vite --profile profile파일을 받아 분석해봤지만, 눈에 띄는 병목지점이 존재하진 않음
- node에서 제공하는
—heap-prof
도 사용 메모리 사용량을 확인해보았지만, 사용량은 보이나 특정부분이 크게 누수되어보이는 지점은 없음.
- 메모리 누수 지점을 확인하긴 좋다. 하지만 객체의 위치추적이나 메모리가 유지되는 이유를 추적하기 어렵다.
- rollup의 plugin 세팅으로 빌드를 진행하는 동안 변환하는 파일 별로 로깅되도록 추가.
plugins: [
{
name: 'memory-logger',
buildStart() {
console.log('Build started:', logMemoryUsage());
},
transform(code, id) { // 각 모듈별 번들링 하기전의 훅, 코드나 id를 넘겨준다.
console.log(`Transforming ${id}:`, logMemoryUsage());
return code;
},
generateBundle() {
console.log('Generating bundle:', logMemoryUsage());
},
closeBundle() {
console.log('Bundle closed:', logMemoryUsage());
},
},
]
...
const logMemoryUsage = () => {
const mem = process.memoryUsage();
return {
// Resident Set Size: 프로세스가 실제 물리렘에 상주하는 양
rss: `${(mem.rss / 1024 / 1024).toFixed(2)} MB`,
// heapTotal: V8 엔진에서 JS 힙으로 예약한 메모리 총량
heapTotal: `${(mem.heapTotal / 1024 / 1024).toFixed(2)} MB`,
// heapUsed: V8 엔진에서 JS 실제 사용량
heapUsed: `${(mem.heapUsed / 1024 / 1024).toFixed(2)} MB`,
// external: V8 외부에서 관리하는 메모리 (버퍼, 네이티브 모듈 등)
external: `${(mem.external / 1024 / 1024).toFixed(2)} MB`,
};
};
지표 | 의미 | 포함 내용 |
---|---|---|
rss | 실제 메모리에 올라간 물리적 메모리 사용량 | 프로그램 코드, 힙, 스택, 외부 모듈 |
heapTotal | JavaScript 힙의 총 크기 (할당된 공간, 실제 사용 여부와 무관) | V8 힙 메모리 |
heapUsed | 실제 JavaScript 힙에서 사용 중인 메모리 | 객체, 배열 등 동적 데이터 |
external | V8 외부에서 관리하는 메모리 (버퍼, 네이티브 모듈 등) | Node.js 버퍼, 외부 C++ 확장 등 |
찍힌 로그
web:build: Transforming /Users/mzc01-sulmo/dev/console/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableGlobalVariableViewButton.vue?vue&type=script&setup=true&lang.ts: {
web:build: rss: '4604.48 MB',
web:build: heapTotal: '4398.41 MB',
web:build: heapUsed: '4294.66 MB',
web:build: external: '86.93 MB'
web:build: }
web:build: Transforming /Users/mzc01-sulmo/dev/console/packages/mirinae/dist/style.css: {
web:build: rss: '8780.17 MB',
web:build: heapTotal: '8922.13 MB',
web:build: heapUsed: '8749.96 MB',
web:build: external: '32.47 MB'
web:build: }
web:build:
web:build: A PostCSS plugin did not pass the `from` option to `postcss.parse`. This may cause imported assets to be incorrectly transformed. If you've recently added a PostCSS plugin that raised this warning, please contact the package author to fix the issue.
web:build: Transforming /Users/mzc01-sulmo/dev/console/packages/mirinae/css/light-style.css: {
web:build: rss: '8787.55 MB',
web:build: heapTotal: '8927.05 MB',
web:build: heapUsed: '8756.20 MB',
web:build: external: '32.50 MB'
web:build: }
갑자기 mirinae/dist/style.css가 변환되고 나서부터 메모리 4GB가 튐을 확인 + PostCss 플러그인 관련 경고 문구
postcss plugin 전수조사 시작.
module.exports = ({
tailwindcssOptions,
postcssEasyImportOptions,
postcssSimpleVarsOptions,
disablePostcssImport,
}) => ({
parser: 'postcss-comment',
plugins: [
require('tailwindcss')(tailwindcssOptions),
require('postcss-easy-import')(postcssEasyImportOptions),
...(disablePostcssImport ? [] : [require('postcss-import')]),
require('postcss-hexrgba'),
require('postcss-mixins'),
require('postcss-conditionals'),
require('postcss-nested'),
require('postcss-simple-vars')(postcssSimpleVarsOptions),
require('autoprefixer'),
require('postcss-preset-env')({ stage: 3 }),
],
})
어떤 플러그인이 문제인지 몰라서 하나씩 제거하면서 빌드 진행.
tailwind 관련 postcss 플러그인('tailwindcss')을 제거하면 램이 2GB 정도로 빌드가능했음..
web:build: Generating bundle: {
web:build: rss: '2358.75 MB',
web:build: heapTotal: '2001.36 MB',
web:build: heapUsed: '1707.34 MB',
web:build: external: '49.57 MB'
web:build: }
tailwindcss 버전 올리기 (1.x -> 3.x) 진행
- npm package에서 사라지는 이슈 발생
- tailwind는 mirinae에서도 사용하는 라이브러리라 루트로 올림(root package.json에 의존 추가)
Tailwind 버전 업그레이드 이후 변경사항
bg-gray가 원래 default값으로 사용되었는데, 이제 불가능하여 default인 500으로 수정 (or bg-gray-default를 사용하시면 됩니다.)
rounded도 DEFAULT 로 직접 정의필요로 변경되어 config 추가
row-gap-{n}
이 사라짐, 대신gap-y-{n}
으로 대체, col도 동일at-rule(@media, @import 등)은 중첩해서 사용할 수 없다고 함. 대신 아래와 같이 사용
@apply lg:min-w-16; // 아래와 같이 적용됨 @media (min-width: 1024px) { .lg\:min-w-16 { min-width: 4rem; } }
중첩이 가능했다가 불가능해진 이유는 3점대로 넘어가며 JIT(Just-In-Time)엔진을 이용한 빌드로 변경됐기 때문이다. JIT 모드는 정적 스캔을 진행하며 이에 부합하지 않는 케이스의 경우 문법적으로 작성이 불가능하다.
이에 따른 콘솔에서 변경 작업
- 같은 이슈로 중첩된거 해제함.
- define-mixin 도 마찬가지였는데, 이건 따로 플러그인이 있는지 추후 알아봐야 함.
- console의 모든 tailwindcss 변경사항 적용
정적 스캔의 범위
Tailwind JIT은 사용자가 실제 HTML/JS/TS/Vue 파일에 작성한 class 이름을 "문자열 그대로" 정적으로 분석(스캔) 합니다.
정적 스캔으로 인해 접근이 불가능한 중첩 범위 -> 클래스명이 정적으로 드러나지 않는 내부 중첩이나 at-rule(@media, @import) 중첩의 경우 스캔이 불가능
JIT 모드가 스캔 가능한 조건 정리
- 클래스명이 문자열 형태로 드러날 것
- 중첩 구조일 경우에도, 전개(flatten) 시점에 클래스가 명확할 것
- 동적 클래스 조합, 동적 조건문, JS 계산 등은 인식 불가
결과
빌드 성공 최종 결과

빌드진행 시 필요한 램 총 용량 감소
Bundle closed: {
web:build: rss: '2180.02 MB',
web:build: heapTotal: '1863.22 MB',
web:build: heapUsed: '1738.34 MB',
web:build: external: '78.00 MB'
web:build:
}
아니 왜? tailwindcss 버전업으로 이렇게 빨라졌나..?
1. 전체 CSS 생성 방식
- 2.x
- 모든 가능한 클래스(수천~수만 개)를 미리 생성
- 실제로 사용하지 않는 클래스까지도 전부 CSS로 렌더링됨
- 빌드 시 CPU, 메모리 사용량이 매우 큼
- 3.x (JIT모드 반영)
- 사용된 클래스만 동적으로 CSS로 생성 (build-time scan)
2. PostCss의 AST 생성 개선
PostCSS 자체는 기본적으로 플러그인 단위로 AST를 생성하고 처리하는데, Tailwind 2.x 시절엔:
- 많은 @import가 있을 경우 → 각각 별도 AST 처리 (메모리 낭비)
- 플러그인 사이에서 AST를 공유하지 않거나, 파일 경로가 없을 경우 캐시가 무효화됨
- 로깅된 메세지는 이 공유 실패를 뜻함 → 캐시 재사용 불가 → 더 많은 메모리 소비 Tailwind 3.x는 JIT 내장 덕분에 AST 처리를 최소화하고, PostCSS에게 넘기는 CSS 크기 자체가 작음 → 캐시/공유 부담도 줄어듦
A PostCSS plugin did not pass the `from` option to `postcss.parse`
이후 액션
- 문서 정리
- QA 환경 세팅 및 모든 페이지 UI 깨짐 확이 진행.
- vercel 복구 프로젝트 생성 (테스트 완료)
- 순환 참조 개선
- 의존성 그래프가 단순 트리가이나라 복잡한 그래프형태로 변한다. 이에 따른 메모리 증가가 발생
- console의 경우 store로인한 순환 참조 코드가 있는데, 레거시 vuex 제거 작업을 진행하면 해결 예정.