한국어
  • parcel
  • vite
  • js
  • esbuild
  • bundler
  • monorepo

Parcel 에서 Vite 로: 100K LOC 마이그레이션의 짧은 이야기

우리는 세 가지 프론트엔드 프로젝트를 Parcel 에서 Vite 로 마이그레이션했고, 그 과정은... 원활했습니다.

Gao
Gao
Founder

배경 이야기

우리는 Logto 에서 세 가지 주요 프론트엔드 프로젝트를 가지고 있습니다: 로그인 경험, 콘솔, 그리고 라이브 프리뷰. 이 프로젝트들은 모두 TypeScript, React, 그리고 SASS 모듈로 이루어져 있고, 총 약 100K 라인의 코드가 있습니다.

우리는 Parcel 을 그 단순함과 제로 설정 방식으로 사랑했습니다. 새로운 프로젝트를 Parcel 로 설정하는 것이 얼마나 쉬운지에 충격을 받은 날이 아직도 기억납니다. parcel index.html 명령을 실행하면 필요한 모든 종속성이 설치되고 프로젝트가 실행됩니다. "경험 있는" 개발자라면 Gulp 와 Webpack 으로 설정하던 예전 날들과 비교하며 동일한 감정을 느낄 수도 있습니다. Parcel 은 마치 마법 지팡이와도 같습니다.

Parcel 의 단순함은 가끔은 변덕스러울 때가 있어도 오랫동안 우리가 그것을 고수한 주요 이유였습니다. 예를 들어:

  • Parcel 은 때때로 실제로 존재하는 청크 파일을 찾지 못해서 프로젝트를 번들하지 못하곤 했습니다.
  • 우리의 모노레포 설정과 함께 작동시키기 위해서는 약간의 해킹 구성 작업이 필요했습니다.
  • Parcel 은 MDX 3 을 네이티브로 지원하지 않아서 이를 위해 커스텀 변환기를 만들어야 했습니다.
  • Parcel 은 수동 청크를 지원하지 않으며 (이 글을 쓰는 시점까지, 수동 청크 기능은 여전히 실험 단계에 있습니다), 이는 대부분의 경우 괜찮지만, 때로는 필요하기도 합니다.

그럼 왜 우리는 다른 것으로 마이그레이션하기로 결정했을까요?

  1. 우리는 2023년 6월에 출시된 Parcel 2.9.3 에 묶여 있었습니다. 이후 새로운 버전이 출시될 때마다 업그레이드를 시도했지만 항상 빌드 오류로 실패했습니다.
  2. Parcel 의 최신 버전은 2024년 2월에 출시된 2.12.0 이었습니다. 거의 매일 커밋이 있었지만, 그 이후로 새로운 릴리스가 없었습니다.

누군가는 Parcel 이 죽었는지 묻는 토론을 열기도 했습니다. 공식 답변은 아니지만, Parcel 은 여전히 살아있다고 하면서 "우리는 큰 리팩터링 작업 중이고, 사소한 릴리스에 시간을 할애하지 않고 있다" 고 했습니다. 우리에게는 그것이 마치 "오리죽음" 과 같았습니다: 우리가 사용할 수 있는 최신 버전이 1년 이상 전의 것이고, 다음 버전이 언제 출시될지 모르는 상태였습니다. 죽은 것처럼 보이고, 죽은 것처럼 행동하니, 우리에게는 죽은 것입니다.

Parcel 업그레이드 풀 요청

믿어봐요, 우리는 노력했어요.

왜 Vite?

우리는 Vitest 를 통해 Vite 를 알게 되었습니다. 몇 달 전, Jest 의 ESM 지원에 지쳐서 새로운 것을 시도하고 싶었습니다. Vitest 는 네이티브 ESM 지원과 Jest 호환성 덕분에 우리의 마음을 사로잡았습니다. 훌륭한 개발자 경험을 제공하며 Vite 의 힘을 빌려 만들어졌습니다.

현 상태

프로젝트에 따라 다르겠지만, Vite 생태계가 성장함에 따라 플러그인 교체를 쉽게 찾을 수 있습니다. 다음은 마이그레이션 시 우리의 설정입니다:

  • Monorepo: 우리는 모노레포 관리를 위해 PNPM (v9) 워크스페이스를 사용했습니다.
  • 모듈: 모든 프로젝트에서 ESM 모듈을 사용합니다.
  • TypeScript: 프로젝트 전반에 TypeScript (v5.5.3) 와 경로 별칭을 사용합니다.
  • React: 프론트엔드 프로젝트 전체에 React (v18.3.1) 를 사용합니다.
  • 스타일링: 스타일링을 위해 SASS 모듈을 사용합니다.
  • SVG: 우리는 SVG 를 React 컴포넌트로 사용합니다.
  • MDX: GitHub Flavored Markdown 과 Mermaid 지원과 함께 MDX 를 사용합니다.
  • 지연 로딩: 일부 페이지와 컴포넌트를 지연 로드해야 합니다.
  • 압축: 프로덕션 빌드를 위해 gzip 및 brotli 압축 자산을 생성합니다.

마이그레이션

우리는 새로운 Vite 프로젝트를 생성하고 어떻게 작동하는지 알아보기 위해 실험하면서 마이그레이션을 시작했습니다. 과정은 원활했고 실제 마이그레이션은 며칠 만에 완료되었습니다.

즉시 사용 가능한 지원

Vite 는 모노레포, ESM, TypeScript, React, 그리고 SASS 에 대해 즉시 사용 가능한 지원을 제공합니다. 필요한 플러그인과 구성 설정만 설치하면 됩니다.

경로 별칭

Vite 는 경로 별칭에 대해 내장 지원을 제공하며, 예를 들어 우리의 tsconfig.json 에서는 다음과 같이 설정되어 있습니다:

우리는 단지 동일한 해결 방식을 vite.config.ts 에 추가하기만 하면 됩니다:

대체 경로는 프로젝트 루트 기준으로 상대 경로가 아닌 절대 경로여야 함을 유념하세요. 또는 vite-tsconfig-paths 플러그인을 사용하여 tsconfig.json 에서 경로 별칭을 읽을 수도 있습니다.

React 빠른 새로고침 및 HMR

Vite 는 HMR 을 내장 지원하지만, React 빠른 새로고침을 활성화하려면 플러그인을 설치해야 합니다. 우리는 Vite 팀이 제공하는 @vitejs/plugin-react 플러그인을 사용했으며, 이는 React 기능의 빠른 새로고침과 같은 뛰어난 지원을 제공합니다:

SVG 를 React 컴포넌트로 사용하기

우리는 SVG 를 React 컴포넌트로 변환하기 위해 vite-plugin-svgr 플러그인을 사용합니다. 이 플러그인을 Vite 구성에 추가하면 간단합니다:

그러나 SVG 가 어떤 조건에서 React 컴포넌트로 변환될지 명시하지 않았기 때문에 모든 import 가 변환되었습니다. 이 플러그인은 기본적으로 .svg?react 확장자로 import 된 SVG 만 변환하는 것을 제안합니다. 이에 따라 우리도 import 를 업데이트했습니다.

SASS 모듈

Vite 는 SASS 모듈에 대해 내장 지원을 제공합니다. 하지만 클래스 이름이 어떻게 포맷되는지 주의해야 합니다. 클래스 이름이 일관되게 포맷되지 않으면 사용자와 통합 테스트에 문제가 될 수 있습니다. vite.config.ts 에 있는 한 줄의 구성으로 문제를 해결할 수 있습니다:

참고로 Parcel 과 Vite 는 SASS 파일 import 에 대해 다른 방식을 가지고 있습니다:

* as 문법은 Vite 에서 작동하지만, styles 객체에 동적 키를 사용하여 클래스 이름을 접근할 때 모듈화된 클래스 이름을 잃게 됩니다. 예를 들어:

MDX 지원

Vite 는 Rollup 을 기본으로 사용하므로, 공식 @mdx-js/rollup 플러그인을 사용하여 MDX 및 그 플러그인을 지원할 수 있습니다. 구성은 다음과 같습니다:

remarkGfm 플러그인은 GitHub Flavored Markdown 을 지원하기 위해 사용되고, rehypeMdxCodeProps 플러그인은 MDX 파일 내 코드 블록에 props 를 전달하는 데 사용됩니다. 이는 Docusaurus 가 하는 방식과 유사합니다.

MDX 내부에서의 Mermaid 지원

우리는 다른 프로그래밍 언어처럼 MDX 파일에서 Mermaid 다이어그램을 사용하고 싶습니다. 사용법은 다른 코드 블록과 마찬가지로 간단해야 합니다:

위의 코드가 다음과 같이 렌더링됩니다:

우리의 앱은 라이트와 다크 테마를 지원하기 때문에, Mermaid 다이어그램을 다크 테마와 함께 작동하도록 약간의 코드를 작성했습니다. React 컴포넌트를 생성했습니다:

useTheme 은 현재 테마를 컨텍스트에서 가져오는 커스텀 훅입니다. mermaid 라이브러리는 초기에 로드되는 페이지의 크기를 줄이기 위해 비동기로 가져옵니다.

MDX 파일 내 코드 블록을 위해, 우리는 이를 처리하기 위해 통합 컴포넌트를 가지고 있습니다:

마지막으로 우리는 MDX 제공자를 다음과 같이 정의합니다:

지연 로딩

이 부분은 Vite 에 특화된 사항은 아니지만, 우리는 마이그레이션 중에 페이지들을 지연 로드하도록 업데이트했으므로 언급할 가치가 있습니다. 이후 아무 것도 망가지지 않았습니다.

React 에는 컴포넌트를 지연 로드하기 위한 내장 함수인 React.lazy 가 있습니다. 그러나 빠르게 작업을 진행할 때 몇 가지 문제가 발생할 수 있습니다. 우리는 이러한 문제를 해결하기 위해 react-safe-lazy 라는 작은 라이브러리를 만들었습니다. 이는 React.lazy 의 대체품으로 사용할 수 있으며 자세한 설명은 이 블로그 포스트 에서 볼 수 있습니다.

압축

gzip 및 brotli 압축을 지원하는 간단한 플러그인이 있으며, vite-plugin-compression 플러그인을 사용하여 압축된 자산을 생성할 수 있습니다. 설정은 매우 간단합니다:

수동 청크

Vite (또는 기본적으로 Rollup) 의 멋진 기능 중 하나는 수동 청크입니다. React.lazy 는 컴포넌트를 지연 로드하는 데 사용되지만, 수동 청크를 지정하여 어떤 컴포넌트나 모듈을 함께 번들로 묶어야 하는지 결정함으로써 청크에 대한 더 큰 제어를 가질 수 있습니다.

예를 들어, vite-bundle-visualizer 를 먼저 사용하여 번들 크기와 종속성을 분석할 수 있습니다. 그런 다음 청크를 나누기 위한 적절한 함수를 작성할 수 있습니다:

개발 서버

프로덕션 빌드와 달리, Vite 는 개발 모드에서 소스 코드를 번들로 묶지 않습니다 (동일한 모노레포 내의 링크된 종속성을 포함하여) 그리고 모든 모듈을 파일로 취급합니다. 우리의 경우, 브라우저가 처음에 수백 개의 모듈을 로드하게 되어 미친 것처럼 보일 수 있지만 대부분의 경우 실제로 괜찮습니다. 여기서 논의를 확인할 수 있습니다.

이것이 중요한 문제라면, 완벽하지 않지만 대체 가능한 해결책으로 optimizeDeps 옵션에 링크된 종속성을 나열하는 것입니다 vite.config.ts 에서:

이 작업은 링크된 종속성을 사전 번들링하여 개발 서버를 더 빠르게 만듭니다. 단점은 HMR 이 링크된 종속성에서 예상대로 작동하지 않을 수 있다는 점입니다.

또한, 우리는 프로덕션 환경에서 정적 파일을 서빙하는 프록시를 사용하고 개발 환경에서는 요청을 Vitest 서버로 프록시합니다. 충돌을 피하기 위해 일부 특정 포트를 구성해두었으며, 이는 vite.config.ts 에서 설정하기 쉽습니다:

환경 변수

Parcel 과 달리, Vite 는 import.meta.env 를 사용하여 환경 변수를 처리하는 현대적인 접근 방식을 사용합니다. .env 파일을 자동으로 로드하고 코드에서 변수를 대체합니다. 그러나 모든 환경 변수는 VITE_ 로 시작되어야 합니다 (구성 가능).

우리가 Parcel 을 사용할 때는 접두사를 확인하지 않고 process.env 변수들을 단순히 대체했습니다. 그래서 우리는 마이그레이션을 더 쉽게 하기 위해 define 필드를 사용하는 해결책을 생각해냈습니다:

이를 통해 환경 변수에 접두사를 점진적으로 추가하고 define 필드를 제거할 수 있었습니다.

결론

이것으로 끝입니다! 우리는 세 가지 프론트엔드 프로젝트를 Parcel 에서 Vite 로 성공적으로 마이그레이션했으며, 이 짧은 이야기가 여러분의 마이그레이션에 도움이 되기를 바랍니다. 여기 최종 구성은 다음과 같습니다: