[React] webpack에서 vite로 번들러 교체하기

이수빈·2024년 11월 17일
post-thumbnail

최근 webpack을 번들러로 사용 중인 React.js 서비스를 개발 환경에서 작업하면서 프로젝트 속도가 저하된 것이 피부로 와닫기 시작했다. 프로젝트의 규모가 커지다 보니 실행하는 시간도 그렇지만, 파일을 저장하면 바로바로 적용되는 hot reload 속도가 굉장히 느려졌다. 특히 최근에 tailwind를 적용해야 하는 일이 있었는데, 기존에 작성된 css를 tailwind 문법에 맞게 변경하는 퍼블리싱 작업이다보니 코드를 수정하고 정상적으로 반영 되었는지 확인하기 위해 저장을 자주하게 되는데, 저장 할 때마다 30초씩 걸리다 보니 상당한 DX 저하를 경험한 것 같다. 
이러한 문제를 해결하기 위해 번들러 교체를 하기로 마음 먹었다.

vite란

vite 공식 문서에서는 vite를 다음과 같이 소개한다.

vite는 빠르고 간결한 모던 웹 프로젝트 개발 경험에 초점을 맞춰 탄생한 빌드 도구이며, 두 가지 컨셉을 중심으로 하고 있습니다.

개발 시 네이티브 ES Module을 넘어 더욱 다양한 기능을 제공합니다. 가령, Hot Module Replacement (HMR)과 같은 것들 말이죠.

번들링 시, Rollup 기반의 다양한 빌드 커맨드를 사용할 수 있습니다. 이는 높은 수준으로 최적화된 정적(Static) 리소스들을 배포할 수 있게끔 하며, 미리 정의된 설정(Pre-configured)을 제공합니다.

vite는 로컬에서 개발할 때 번들링을 하지않고 ESM 방식을 사용해 구동 속도가 매우 빠르다. 또한 내장된 서버는 Hot Module Replacement(HMR)를 지원한다. 이 부분이 제일 만족했던 부분인데, 특정 컴포넌트를 수정하면 코드 전체가 다시 로딩되는 것이 아니라 수정한 컴포넌트만 다시 불러와서 그려주기 때문에 로딩 속도가 상당히 빨랐다.

또한, 프로덕션 빌드 시 Rollup을 사용하여 코드를 최적화한다. Rollup은 코드를 효과적으로 트리쉐이킹 하여 불필요한 코드를 제거하고, 결과물의 파일 크기를 최소화 한다.

사전작업

기존 프로젝트에 vite를 적용하는 방법을 검색해보면 대부분 커맨드로 vite 기반의 새로운 프로젝트를 생성해 생성된 설정들을 기존 프로젝트에 옮겨 적용하는 방식을 많이 사용하고 있었다.

npm create vite@latest

해당 커맨드를 실행하게 되면 사용할 프레임워크, 언어 등을 선택하게 되는데 vite를 적용할 프로젝트의 환경과 일치하게 선택해 주고 빈 프로젝트를 하나 생성했다.

"vite": "^5.3.1",
"vite-plugin-checker": "^0.7.1",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-svgr": "^4.2.0",
"@sentry/vite-plugin": "^2.21.1",
"@vitejs/plugin-react": "^4.3.1",
"rollup-plugin-visualizer": "^5.12.0"

그런 다음 vite 설정과 관련된 라이브러리와 추가적으로 필요한 라이브러리들을 기존 프로젝트의 package.json에 추가 후 설치해 주었다.

적용하기

Vite는 설정 파일을 defineConfig 함수로 감싸, 환경에 따라 동적으로 구성할 수 있다. webpack.config.js와 유사하게 vite.config.ts에서 환경 변수 및 플러그인을 설정할 수 있다.

설정파일

환경 변수 설정 (loadEnv)

vite에서는 loadEnv 함수를 사용하여 .env 파일에서 환경 변수를 로드할 수 있다. loadEnv를 사용하여 현재 프로젝트의 환경 변수를 불러온다. 그리고 command와 mode 매개변수를 사용해 로컬 개발 서버 환경인지 프로덕션 빌드인지 구분할 수 있다.

export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, join(process.cwd(), 'environment'), '');
const isLocal = command === 'serve';

이 방식으로 환경을 구분해 VITE_ 접두어가 붙은 환경 변수에 접근할 수 있다.

플러그인 설정 (plugins)

vite는 다양한 플러그인 지원을 통해 빌드 프로세스를 확장할 수 있다. 이번 설정에서는 svgr, react, visualizer, checker, sentryVitePlugin을 사용하여 프로젝트에 필요한 기능을 추가했다.

  • 플러그인 목록 및 설명
    • @vitejs/plugin-react: React 프로젝트의 기본 플러그인으로, React JSX 구문을 지원한다.
    • vite-plugin-svgr: .svg 파일을 React 컴포넌트로 사용하도록 지원한다.
    • rollup-plugin-visualizer: 번들 분석을 위한 시각화 도구로, webpack bundle analyzer와 유사하게 report.html 파일을 생성해 번들 파일 크기를 시각적으로 확인할 수 있다.
    • vite-plugin-checker: 타입스크립트 및 ESLint 오류를 실시간으로 체크해주는 플러그인이다.
    • @sentry/vite-plugin: Sentry와 연동하여 배포 시 소스맵 업로드 및 에러 추적을 가능하게 한다.
plugins: [
  svgr(),
  react(),
  visualizer({
    filename: './dist/report.html',
  }),
  checker({
    typescript: true,
    eslint: { lintCommand: 'eslint ./src --ext .ts,.tsx', dev: { logLevel: ['error'] } },
  }),
  !isLocal && mode !== 'development'
    ? sentryVitePlugin({
        url: env.VITE_SENTRY_BASE_URL,
        org: 'sentry',
        project: env.VITE_SENTRY_PROJECT,
        authToken: env.VITE_SENTRY_API_AUTH_TOKEN,
        release: {
          name: env.VITE_PROJECT_VERSION,
          deploy: {
            env: mode,
          },
        },
        sourcemaps: {
          assets: './dist/**',
          filesToDeleteAfterUpload: './dist/**/*.map',
        },
      })
    : null,
]

모듈 경로 별칭(alias)

vite에서는 resolve.alias 옵션을 통해 경로 별칭을 지정할 수 있다. 별칭을 사용하면 import 경로를 간결하게 작성할 수 있어 가독성을 높일 수 있다.

resolve: {
  alias: {
    '@': path.resolve(__dirname, 'src'),
    'asset': path.resolve(__dirname, 'asset'),
  },
},

webpack에서 설정한 값과 동일하게 @는 src 디렉터리를, asset은 에셋 폴더를 참조하도록 설정했다.

빌드 설정(build)

빌드 설정에서는 outputDir, sourcemap, rollupOptions 등을 지정하여 빌드 결과를 조정할 수 있다.

주요 빌드 옵션

• outDir: 빌드 결과물을 저장할 디렉터리 경로이다. 나는 dist/${env.VITE_PROJECT_VERSION || ''} 형식으로 설정하여, 프로젝트 버전에 따라 빌드 폴더를 지정할 수 있게 했다. 프로젝트 버전에는 브랜치 이름이나 태그 이름을 사용하는 편이다.
• sourcemap: true로 설정하여 디버깅을 위한 소스맵을 생성한다. Sentry에서 난수화 된 빌드 코드와 원본 코드를 매핑해 정확한 디버깅이 가능하도록 사용된다.
• rollupOptions: 번들링 시 필요한 특정 모듈을 분리하거나 로딩 순서를 지정한다.

build: {
  outDir: `dist/${env.VITE_PROJECT_VERSION || ''}`,
  sourcemap: true,
  cssCodeSplit: true,
  rollupOptions: {
    output: {
      manualChunks(id: string) {
        if (id.includes('node_modules/react/') || id.includes('node_modules/react-dom/')) {
          return '@react-vendor';
        }
        if (id.includes('node_modules/@mailplug-inc/design-system') && id.includes('.css')) {
          return '@MDS-style';
        }
        if (id.includes('node_modules/@mailplug-inc/design-system')) {
          return '@MDS';
        }
        return null;
      },
    },
  },
}

로컬 개발 서버 설정(server)

로컬 개발 서버에서는 proxy 설정을 통해 API 요청을 특정 URL로 프록시 처리할 수 있다. 이 기능은 CORS 문제를 해결하거나, API 요청을 리디렉션하는 데 유용한 옵션이다.
webpack을 사용할때도 보통 로컬 개발 환경에서는 host 값이 localhost, 127.0.0.1로 설정 되기에 CORS 오류가 발생해 proxy 설정으로 이를 우회하는 설정을 해주었는데, vite에서도 동일하게 적용이 가능했다.
글을 작성하는 시점에서 해당 설정에 대해 알아보니 로컬 환경에서만 적용되는 환경이라 빌드 시점에서는 사용되지 않는다고 한다. isLocal에 의한 분기 처리는 제거 해도 될 것 같다.

server: isLocal
  ? {
      proxy: {
        '/api/v1/worknote': {
          target: env.VITE_WORKNOTE_BASE_URL,
          changeOrigin: true,
        },
        '/api/v1': {
          target: env.VITE_TASK_BASE_URL,
          changeOrigin: true,
        },
        '/api/v2/member': {
          target: env.VITE_MEMBER_BASE_URL,
          changeOrigin: true,
        },
        '/common': {
          target: env.VITE_MAIL_BASE_URL,
          changeOrigin: true,
        },
      },
      host: '127.0.0.1',
    }
  : undefined,

이 외에 설정

// index.html
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no" name="viewport" />
<meta name="robots" content="noindex, nofollow" />
<title></title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

webpack과 다른 점은 index.html 파일이 루트 경로에 존재해야 한다는 것이다. 기존엔 public 폴더 내부에 위치 시키고 사용했는데 위치를 옮겨주고 index.tsx의 위치도 잡아주었다.

// .env.test
✖ MAIL_BASE_URL = mail.com
✔ VITE_MAIL_BASE_URL = mail.com

env 설정에서도 차이점이 존재한다. .env로 시작하는 파일명으로 설정해 주어야 하며, VITE_ 라는 접두사를 붙여준다. 코드에서는 import.meta.env 객체를 통해 문자열 형태로 접근이 가능하다. (env 관련 설정 링크)

트러블슈팅

컴포넌트에 dynamic import가 적용되지 않는 현상

초기 로딩 속도 향상을 위해 첫 화면을 그릴 때 굳이 필요하지 않은 컴포넌트들(ex. 모달)을 dynamic import를 적용 시켜, 첫 로딩시에 불러오지 않고 접근 시에 로드 시키도록 분리하려 했다.
개발자 도구를 통해 분리 시킨 컴포넌트의 js 파일이 접근 시에 로드 되는지 확인하려 했지만 여전히 첫 로딩 시점에서 해당 컴포넌트를 불러오고 있었다.

문제 원인

해당 문제에 대한 원인은 빌드를 실행 시킨 커맨드라인에서 찾을 수 있었다.

dynamic import를 적용한 컴포넌트가 index.ts에서도 역시 정적으로 import 되어서 chunk 분리가 불가능 하다는 것이다.
해당 프로젝트에는 import의 가독성을 높이기 위해 Barrel Pattern이 적용되어 있는데, 이게 dynamic import를 방해하고 있었다.

해결 방법

기존 index.ts 파일에서 dynamic import를 적용하기 위한 컴포넌트들은 주석 처리를 하고 import 경로를 수정해 주었다.
문제 해결은 되었지만 Barrel Pattern에 대해 검색을 해보니 전체 파일에 해당 패턴을 적용 시키는 것은 코드를 느리게 만든다는 등 부정적인 의견이 많아 dynamic import를 적용한 컴포넌트만이 아닌, 프로젝트 전체에서 해당 패턴을 제거해야 하나라는 고민이 든다.

결론

vite 적용 전

vite 적용 후

vite 번들러 마이그레이션 작업은 로컬 환경에서의 속도 뿐만 아니라, 위에서 작성한 여러 최적화 작업들로 인해 첫 화면에 대한 로딩 속도가 약 58% 향상 되었다.
마이그레이션 이후에도 특정 js 파일이 기준보다 크게 나누어져서 노란색으로 표시 되는 파일이 존재하는데, 조금 더 세밀하게 chunk 기준을 세워 추가적인 성능 향상을 노려봐야겠다.

profile
내가 나중에 보려고

0개의 댓글