요즘IT
위시켓
최근 검색어
전체 삭제
최근 검색어가 없습니다.

*FEConf2023에서 발표한 <FEConf 2023 [B4] React Native, Metro를 넘어서>를 정리한 글입니다. 발표 내용을 2회로 나누어 발행합니다. 1회에서는 Metro에서 ESBuild로 번들러를 전환하게 된 배경을 소개하고 나아가 번들러가 무엇인지와 번들러의 역할을 수행하기 위한 기능인 Resolution과 Load, Optimization을 알아봅니다. 2회에서는 Optimization의 Minification과 Tree Shaking를 소개하고, 토스에서 Metro를 ESBuild로 바꿨던 여정을 이야기합니다. 본문에 삽입된 이미지의 출처는 모두 이 콘텐츠와 같은 제목의 발표 자료로, 따로 출처를 표기하지 않았습니다. 발표 자료는 FEConf2023 홈페이지에서 다운로드할 수 있습니다.

회원가입을 하면 원하는 문장을
저장할 수 있어요!

다음

회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!

확인

개발

React Native, Metro를 넘어서 (1)

년차,
어떤 스킬
,
어떤 직무
독자들이 봤을까요?
어떤 독자들이 봤는지 궁금하다면?
로그인

*FEConf2023에서 발표한 <FEConf 2023 [B4] React Native, Metro를 넘어서>를 정리한 글입니다. 발표 내용을 2회로 나누어 발행합니다. 1회에서는 Metro에서 ESBuild로 번들러를 전환하게 된 배경을 소개하고 나아가 번들러가 무엇인지와 번들러의 역할을 수행하기 위한 기능인 Resolution과 Load, Optimization을 알아봅니다. 2회에서는 Optimization의 Minification과 Tree Shaking를 소개하고, 토스에서 Metro를 ESBuild로 바꿨던 여정을 이야기합니다. 본문에 삽입된 이미지의 출처는 모두 이 콘텐츠와 같은 제목의 발표 자료로, 따로 출처를 표기하지 않았습니다. 발표 자료는 FEConf2023 홈페이지에서 다운로드할 수 있습니다.

 

2024년 FEConf 세션 발표자 모집 중

2024년 FEConf 라이트닝 토크 발표자 모집 중 

 

FEConf2023에서 발표된 ‘React Native, Metro를 넘어서 / 박서진 토스 프론트엔드 개발자

 

토스에서 프론트엔드 개발자로 일하고 있는 박서진이라고 합니다. 이 글에서는 저희가 매일 사용하고 있는 Metro, webpack 아니면 ESBuild와 같이 그런 번들러가 무엇인지에 대해서 자세하게 알아보겠습니다. 번들러가 무엇인지를 알면 Metro도 ESBuild도 생긴 것만 다른 똑같은 번들러라는 걸 알게 되고, 두 번들러를 (쉽지는 않습니다만) 갈아끼울 수 있다는 것도 알게 될 것입니다.

 

번들러 전환 배경: Metro에서 ESBuild로

토스에서는 다양한 서비스를 React Native로 개발하고 있습니다. 혜택, 피드 광고, 병원비 돌려받기와 같은 다양한 도메인의 제품이 React Native로 개발되고 있습니다.

 

React Native를 이용해서 개발하다 보면 자연스럽게 Metro라고 하는 것을 사용하게 됩니다. 웹에 웹팩이 있다면 React Native에는 Metro가 있습니다. 그야말로 개발 서버를 띄울 때부터 프로덕션에 배포할 때까지 정말 많은 곳에서 만나게 되는 도구입니다.

 

그런데 Metro를 쓰다 보면 되게 다양한 문제점을 만나게 됩니다.

 

첫째, ‘--reset-cache’라고 하는 굉장히 유명한 옵션이 있습니다. 일반적인 웹 개발을 하는 것과 다르게 수동으로 매번 캐시를 날려줘야 하다 보니 좀 불편한 점이 있습니다. 둘째, 별거 아닌 서비스임에도 불구하고 데브 서버부터 프로덕션 빌드까지 상당하게 오랜 시간이 걸렸습니다. Metro의 로딩 인디케이터가 있는데, 그 인디케이터를 보다 보면 많이 답답하고는 했습니다. 셋째, 현대적인 어떤 그런 번들러와 다르게 Tree Shaking과 같은 최적화 도구를 지원하지 않아서, 빌드된 결과물이 필요 이상으로 크기도 했습니다.

 

토스에서는 이런 문제들을 ESBuild라고 하는 번들러를 도입함으로써 해결했습니다. 단순히 번들러를 교체한 것만으로도 reset-cache라고 하는 것을 더 이상 기억하지 않아도 됐고, 빌드 속도도 눈부시게 빨라졌으며, Tree Shaking을 통해서 사용자들이 내려받아야 하는 JavaScript의 파일 크기도 많이 줄일 수가 있었습니다.

 

그야말로 마법과 같은 결과라고도 할 수 있습니다. 그래서 앞서 소개해 드렸던 바와 같이 Metro와 ESBuild와 같은 그런 번들러가 구체적으로 어떤 역할을 하는지에 대해서 알아보면서, 토스가 어떻게 Metro에서 ESBuild로 전환할 수 있었는지를 소개해 드리려고 합니다.

 

 

번들러: Metro와 ESBuild (+Webpack)는 무엇인가? 

번들러가 무엇인지에 대해서 알기 위해서는, 먼저 번들러가 왜 필요한지를 먼저 알아야 합니다. React Native 아키텍처는 언뜻 보면 굉장히 복잡해 보일 수도 있습니다만, 사실은 굉장히 간단합니다.

 

먼저 토스는 JavaScript로 코드를 씁니다. 예를 들어서 어떤 뷰를 렌더링하는 코드를 쓰겠죠. 그러면 그 JavaScript 코드를 hermes나 아니면 v8과 같은 JavaScript 엔진이 실행되면서 네이티브 코드를 대신 호출해 줍니다.

 

예를 들어서, 뷰를 렌더링하는 거니까 iOS에서는 ‘UIView’를 생성하고 안드로이드에서는 ‘android.view’를 만드는 겁니다. 마치 도큐먼트의 ‘createElement’ div를 크롬에서 JavaScript 엔진이 실행하면 div가 새로 생기는 것과 유사합니다.

 

 

그런데 React Native 아키텍처에서는 특별한 조치 없이는 JavaScript 파일이 단일 파일이어야 합니다. 여러 개의 파일을 입력으로 넣을 수가 없고 단 하나의 파일만 실행이 가능하다는 뜻입니다. JavaScript에서 개발할 때 파일을 나눠서 개발하는 것은 너무 당연한데요. 특히 리액트인 경우에는 컴포넌트 당 하나의 파일로 만들기도 합니다. 예를 들어, 일반적으로 앱이 있고, 앱에서는 컴포넌트 1과 2가 있으며, 그리고 그 두 개를 합친 것을 렌더링하는 방식으로 개발하고 싶어 합니다.

 

 

그런데 React Native는 앞서 말씀드린 것과 같이 한 개의 파일만 입력으로 받습니다. 그래서 쪼개진 파일을 입력으로 넣을 수가 없습니다.

 

그러다 보니 딜레마가 생기게 됩니다. 1개 파일에 코드를 다 개발해서 10만 줄 되는 그런 파일을 만들 수도 없고, 그렇다고 여러 개의 파일로 쪼개서 개발하자니 React Native가 이해하지도 못합니다.

 

이때 번들러라고 하는 것이 등장하게 됩니다. 번들러가 하는 가장 중요한 역할은 저희가 이렇게 쪼개서 개발한 파일을 하나로 합쳐주는 것입니다. 예를 들어서 앞서 언급한 앱 컴포넌트 1과 2로 나누어서 개발된 프로젝트를 이렇게 하나로 합쳐서 만들어주는 것입니다.

 

그래서 결과물이 하나의 번들로 만들어지기 때문에, 번들러라고 부르고 있습니다.

 

이렇게 JavaScript 코드를 하나의 파일로 만들면 이제 React Native에서는 이 파일을 처리할 수가 있습니다. 우리가 기대했던 대로 코드가 올바르게 동작하게 됩니다. 이것이 React Native에서 Metro의 주된 역할이라고 할 수 있겠습니다.

 

요약하고 넘어가 보겠습니다. 우선 브라우저나 React Native와 같은 개발 환경에서는 파일을 합쳐야 실행할 수 있는 경우가 많습니다. 그런데 일반적으로 개발자들은 쪼개서 개발하는 것을 선호합니다. 이 딜레마를 해결해 주는 것이 번들러이고, 이 번들러는 여러 쪼개진 JavaScript 파일을 합쳐주는 역할을 합니다.

 

 

번들러가 하는 일 - Resolution, Load, Optimization

이제 조금 더 깊은 내용으로 넘어가 보려고 합니다. 번들러는 여러 개의 쪼개진 파일을 하나로 합치는 역할을 하지만, 이런 역할을 수행하기 위해서 꼭 필요한 기능들이 있습니다. Resolution 그리고 Load가 그것입니다. 이제 각각에 대해서 알아보도록 하겠습니다.

 

Resolution

먼저 Resolution입니다. 번들러가 파일을 합치는 과정에서는 import 문이나 require 문의 경로를 정확히 아는 게 필수입니다. 예를 들어서 아래 예시에서 첫 번째 라인을 보면 ‘./App’에서 앱을 가져온다고 되어 있는데, 이 ‘./App’이라고 하는 게 정확하게 어떤 경로를 지정하는지를 알아야 합니다.

 

그런데 많은 경우에는 이게 모호하다고 할 수가 있습니다. 이 경우에도 ‘./App.js’, ‘./App.ts’, ‘./App.tsx’ 그리고 ‘./App/index.js’ 그렇게 해서 ‘./App/index.ios.js’까지 굉장히 많은 선택지들이 있다는 것을 알 수가 있습니다. 그래서 Resolution이란 이렇게 모호한 ‘./App.js’이라고 하는 요청을 정확한 파일 경로로 풀어내는 것이라고 할 수 있습니다.

 

이와 유사하게 굉장히 모호한 상황이 많습니다. 예를 들어, components에서 ‘component1’과 ‘component2’를 import하는 상황을 생각해 봅시다.

 

 

여기에서 components라고 하는 것이 npm에서 설치한 ‘components’라고 하는 패키지인지, 아니면 src 폴더 안의 ‘components’라고 하는 폴더인지, 아니면 lib 폴더 안에 있는 ‘components’ 폴더인지 일반적으로는 정확하게 알 수 있는 방법이 없습니다. 그래서 요청에 맞춰 파일 경로를 정확하게 찾아주는 방법이 잘 정의되어 있어야 합니다.

 

이렇게 번들러에서 Resolution이라고 하는 것은 import나 require되는 파일 위치를 정확하게 찾아주는 일이라고 할 수 있습니다. 일반적으로 번들러에서는 이것에 대한 굉장히 좋은 기본 설정을 제공합니다. 그래서 크게 신경 쓰지 않고 개발할 수 있습니다.

 

혹시라도 이것을 커스터마이즈하고 싶은 경우가 있을 수도 있습니다. 예를 들어, 일반적인 경우와 다르게 React Native에서는 ios.js, native.js라고 하는 특수한 확장자를 사용하기도 합니다.

 

그런 특수한 경우를 잘 처리하기 위해서는 번들러에 여러 가지 설정을 추가할 수 있습니다. 예를 들어, Metro에서는 ‘resolveRequest’라고 하는 굉장히 직관적인 이름의 함수를 정의할 수 있습니다. 말 그대로 요청을 정확한 파일 경로로 풀어내는 것입니다. 그래서 이 ‘resolveRequest’라고 하는 함수는 ‘moduleName’이라고 하는 인자로 요청된 것에 대해서 정확한 파일의 경로 문자열을 반환하는 함수입니다. 이 부분에 대해서 자세한 설명이나 스펙은 메트로 공식 문서를 참고하시면 이해하실 수 있습니다.

 

이와 비슷하게 ESBuild에 대해서도 Resolution에 대한 규칙을 정의할 수 있습니다. 플러그인을 통해서 정의할 수 있는데, ‘onResolve’라고 하는 Metro랑 비슷한 이름의 API를 제공합니다. 그래서 resolution 규칙을 설정하고 싶은 필터를 잘 걸고, 필터에 해당하는 요청에 대해서 경로를 찾는 방법을 함수로써 정의할 수 있습니다.

 

 

그리고 ESBuild에서는 꼭 이렇게 복잡하게 정의를 하지 않더라도 간단하게 확장자 정도만 추가하고 싶다면, ‘resolveExtensions’라고 하는 옵션도 제공합니다. 그래서 굳이 복잡하게 플러그인 형태로 만들지 않고도 간단하게 resolving할 파일 확장자 목록을 정의합니다. 예를 들어, 아래 예시 같은 경우에는 ‘./test’를 ‘.ios.js’, ‘.native.js’, ‘.js’ 순으로 찾도록 하는 것입니다.

 

이렇게 Resolution에 대해서 알아보았습니다. Resolution을 다 끝마친 뒤에는 어떤 일이 벌어진다고 생각하시나요? 굉장히 모호하게 작성되어 있었던 import 문들이 정확하게 어떤 파일들을 가리키는지 명확하게 나타나기 때문에, 이제 그 파일들을 합치기만 하면 최종적인 결과물을 만들어낼 수 있을 것입니다.

 

Load

그런데 번들러가 추가로 해주는 작업이 하나가 더 있습니다. 바로 Load입니다. 표준 JavaScript만 이용해서 개발하고 있다면 정말 좋겠지만, 사실 대부분의 경우에는 꼭 그렇지만은 않습니다. TypeScript는 JavaScript가 아니기 때문에 아쉽게도 브라우저나 React Native 환경에서 바로 사용할 수 없습니다. 번들러는 합치기만해서는 안 된다는 것이죠. TypeScript를 JavaScript로 바꿔주기도 해야 합니다.

 

비슷하게, React Native에서는 TypeScript는 아니지만 Flow를 이용하고 있는데요. 특이하게 라이브러리를 제공할 때 표준 JavaScript로 바꾸지 않고 Flow 코드를 그대로 npm에 올리고 있습니다. 이 경우에도 Flow는 표준 JavaScript가 아니기 때문에, 단순히 합치기만 하면 에러가 발생합니다. 이 경우에도 Flow 코드를 JavaScript로 변환시켜줘야 한다는 뜻입니다.

 

이처럼 번들러는 단순히 파일을 찾는 것에만 그치는 것이 아니라 JavaScript가 아닌 것을 JavaScript로 바꿔줘야 합니다. 이런 작업을 해주는 게 바로 Load입니다. 정확하게 말하자면 저희의 프로젝트 안에서 import되는 것들을 실제로 표준 JavaScript로 옮기는 작업을 Load라고 합니다. 번들러를 쓸 때 이 설정도 정확하게 잘 설정을 해줘야 합니다.

 

세상에는 굉장히 다양한 Loader들이 있습니다만, 일반적으로 TypeScript나 Flow, 아니면 최신 JavaScript, 옛날에는 optional chaining이나 async await 같은 문법도 완벽하게 표준화가 되어 있지 않은 때가 있었습니다. 그래서 이런 비표준 JavaScript를 Babel이나 SWC와 같은 컴파일러를 이용해서 표준 JavaScript로 바꾸는 Loader를 사용합니다. 특히 React Native에서는 Flow나 TypeScript와 같이 비표준 JavaScript를 쓰는 경우가 많아서 이것을 JavaScript로 바꾸는 방법을 잘 정의해 줘야 합니다.

 

 

이것을 Metro에서 어떻게 설정할 수 있는지를 살펴보겠습니다. Metro에서는 ‘transformerPath’라고 하는 명령어를 통해서 파일을 transform, 다시 말해, Load하는 방법을 지정할 수가 있습니다. 코드에서와 같이 ‘transformFile’이라고 하는 함수는 첫 번째 인자로 ‘filePath’를 받아서 해당 ‘filePath’에 있는 내용을 읽은 뒤에 결과물을 코드로 반환합니다. 예를 들어서 TypeScript 파일을 읽은 다음에 ‘Babel’이나 ‘SWC’로 컴파일 한 다음에 JavaScript 파일을 반환하는 것입니다. 자세한 Load에 관한 설정은 Metro 공식문서에서 볼 수 있습니다.

 

 

이것과 비슷하게 ESBuild에 대해서도 Load 설정을 변경할 수가 있습니다. 이전의 Metro 설정과 매우 유사합니다. 첫 번째 인자의 ‘args.path’를 통해서 Load 파일을 지정하면 이것을 읽어서 변환된 결과를 JavaScript로 반환하는 모습을 볼 수가 있습니다.

 

지금까지 Resolution과 Load 과정에 대해서 살펴보았습니다. 번들러는 이처럼 Resolution 과정을 거치면 import 되는 파일을 모두 찾아볼 수가 있고, 로드 과정을 거치면 import되는 파일이 TypeScript나 Flow라고 하더라도 표준적인 JavaScript로 모두 바꿀 수 있습니다. 그래서 import되는 JavaScript 파일들을 모두 다 준비한 상태이므로 번들러는 이 파일들을 모두 합치기만 하면 됩니다. 그러면 완전한 1개의 JavaScript 파일이 완성됩니다.

 

Optimization

그런데 번들러가 하나의 파일로 합쳐졌다고 하더라도 이 상태로는 프로덕션에 배포할 수가 없습니다. 왜냐하면 번들링을 하는 것만으로는 파일이 너무 커서 성능이 떨어지기 때문입니다. 예를 들어, 일반적인 React Native나 웹 프로젝트에서는 상당한 양의 의존성을 사용하게 됩니다. 토스만 하더라도 당연하게 사용하는 리액트 외에도 디자인 시스템, 스타일링, 애니메이션, 날짜 계산 이런 것들을 위해서 굉장히 다양한 라이브러리들을 사용하고 있는데, 특별한 최적화 없이 이 파일들을 단순히 합치는 것만으로는 파일이 너무 커져 성능이 떨어지게 됩니다.

 

그래서 번들러는 Resolution과 Load 외에 Optimization, 다시 말해서 최적화 작업을 수행합니다. 파일 크기를 작게 하기 위해서 다양한 작업을 수행한다고 할 수가 있습니다.

 

파일 크기를 줄이는 테크닉에는 크게 Minification과 Tree Shaking이 있습니다. Metro와 ESBuild의 차이를 조금 더 명확하게 알기 위해서, 이 각각의 테크닉이 구체적으로 어떤 역할을 하는지, 2회에서 알아보도록 하겠습니다.

 

요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.

좋아요

댓글

공유

공유

댓글 0
FEConf
485
명 알림 받는 중

작가 홈

FEConf
485
명 알림 받는 중
프론트엔드 컨퍼런스 FEConf 공식 계정입니다.

좋아요

댓글

스크랩

공유

공유

지금 회원가입하고,
요즘IT가 PICK한 뉴스레터를 받아보세요!

회원가입하기
요즘IT의 멤버가 되어주세요! 요즘IT의 멤버가 되어주세요!
요즘IT의 멤버가 되어주세요!
모든 콘텐츠를 편하게 보고 스크랩해요.
모든 콘텐츠를 편하게 보고 스크랩 하기
매주 PICK한 콘텐츠를 뉴스레터로 받아요.
매주 PICK한 콘텐츠를 뉴스레터로 받기
로그인하고 무료로 사용하기