Published on

frontend application build 최적화 (vite + vue)

Authors
  • avatar
    Name
    sulmo
    Twitter

문제사항

우리 팀은 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플랜으로 사용중이나 전혀 돈값을 못한다.

원인 분석

작업 시작

리서치에서 나온 방식을 순차적으로 진행

  1. node --inspect 역시 client를 위한 실시간 검사기로서, 빌드 과정에서의 리소스는 볼 수 없었음.

  2. vite --profile profile파일을 받아 분석해봤지만, 눈에 띄는 병목지점이 존재하진 않음 image.png
  3. node에서 제공하는 —heap-prof도 사용 메모리 사용량을 확인해보았지만, 사용량은 보이나 특정부분이 크게 누수되어보이는 지점은 없음. image.png
  • 메모리 누수 지점을 확인하긴 좋다. 하지만 객체의 위치추적이나 메모리가 유지되는 이유를 추적하기 어렵다.

4. 마지막 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실제 메모리에 올라간 물리적 메모리 사용량프로그램 코드, 힙, 스택, 외부 모듈
heapTotalJavaScript 힙의 총 크기 (할당된 공간, 실제 사용 여부와 무관)V8 힙 메모리
heapUsed실제 JavaScript 힙에서 사용 중인 메모리객체, 배열 등 동적 데이터
externalV8 외부에서 관리하는 메모리 (버퍼, 네이티브 모듈 등)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)엔진을 이용한 빌드로 변경됐기 때문이며, postcss와의 의존을 끊었다고 함. ("postcss와 의존이 끊겼다"라는 말이 이해되지 않음. 추가 리서치 필요)

이에 따른 콘솔에서 변경 작업

  • 같은 이슈로 중첩된거 해제함.
    • define-mixin 도 마찬가지였는데, 이건 따로 플러그인이 있는지 추후 알아봐야 함.
    • console의 모든 tailwindcss 변경사항 적용

3버전의 tailwindcss에 맞게 코드 수정

빌드 성공 최종 결과

릴리즈를 위한 빌드 액션 시간감소 Lake

빌드진행 시 필요한 램 총 용량 감소

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 JIT 컴파일 엔진으로 상향된 퍼포먼스(매우 작아진 사전 빌드 css 사이즈)
  • 작아진 사전 빌드 css사이즈와 함께 증분 빌드된 컴포넌트 개선 tailwindcss JIT 컴파일 + postcss 플러그인 변경사항

이후 액션

  • 문서 정리
  • QA 환경 세팅 및 모든 페이지 UI 깨짐 확이 진행.
  • vercel 복구 프로젝트 생성 (테스트 완료)
  • 순환 참조 개선
    • 의존성 그래프가 단순 트리가이나라 복잡한 그래프형태로 변한다. 이에 따른 메모리 증가가 발생
    • console의 경우 store로인한 순환 참조 코드가 있는데, 레거시 vuex 제거 작업을 진행하면 해결 예정.