웹 개발자에게 익숙한 V8(크롬 엔진)은 PC 환경에 최적화되어 있다. 반면, 모바일은 배터리, CPU, 메모리가 훨씬 열악하다. 이 문제를 해결하기 위해 Facebook(Meta)이 직접 만든 엔진이 Hermes이다.
이 개념이 성능의 핵심이다.
Web (V8 엔진 - JIT 컴파일)
RN (Hermes 엔진 - AOT 컴파일):
웹 브라우저는 메모리가 넉넉한 PC를 가정하고 메모리 청소(GC)를 한다. 하지만 Hermes는 모바일을 위해 설계되었다.
V8은 코드가 좀 엉성해도 실행 시점에 JIT가 알아서 최적화해주거나 봐주는 경우가 있다. 하지만 Hermes는 미리 컴파일을 해야 하므로 문법적 오류나 표준에 맞지 않는 코드에 더 민감할 수 있다.
eval() 사용 불가 : 문자열을 코드로 실행하는 eval() 함수는 보안상, 성능상 Hermes에선 막혀있다. AOT 컴파일 모델에서 런타임에 임의 코드를 생성/실행하는 eval()이 구조적으로 맞지 않기 때문이다.
디버깅 : 크롬 개발자 도구를 띄우면 예전엔 변수명이 이상하게(최적화된 이름으로) 보였다. 하지만 최신 Hermes는 'Source Map' 기술이 좋아져서, 원본 코드를 보며 디버깅하는 데 문제가 없다.
웹의 Webpack/Vite와 역할은 같지만, 네이티브 앱의 특성을 처리하기 위해 훨씬 더 복잡한 일을 한다.
웹은 JS 파일을 여러 개로 쪼개서(Code Splitting) 필요할 때 로딩하는 게 좋은 최적화 방법이다. 하지만 RN은 기본적으로 하나의 거대한 JS 파일(index.bundle)을 만든다.
이유 : 모바일 파일 시스템에서 수천 개의 작은 파일을 읽는 것보다, 큰 파일 하나를 읽는 게 훨씬 빠르기 때문이다.
Metro의 역할 : 프로젝트 내의 모든 JS 파일(node_modules 포함)을 긁어모아서, 순서를 정렬하고 하나의 파일로 직렬화(Serialization)한다.
이게 웹팩보다 Metro가 강력한 점이자, RN 개발의 핵심 패턴이다. 만약 Button.js라는 컴포넌트를 만들었는데, iOS와 안드로이드 디자인이 완전히 달라야 한다면?
import Button from './Button'; (확장자 없이 import)
빌드할 때, iOS 빌드면 .ios.js를 가져오고, 안드로이드면 .android.js를 자동으로 가져간다. 덕분에 코드가 아주 깔끔해진다.
Metro는 npm start를 하자마자 프로젝트의 모든 파일 관계도(그래프)를 그린다. A 파일이 B를 import하고, B는 C를 import하고... 이 지도를 메모리에 갖고 있다.
델타 번들링 : 소스 코드 한 줄을 고치면, 전체를 다시 묶는 게 아니라 변경된 부분(Delta)만 계산해서 앱으로 쏴준다. 그래서 수정 사항이 0.1초 만에 폰에 반영된다.
캐시 이슈의 원인 : 이 '그래프'를 빠르게 하려고 캐싱을 엄청나게 한다. 그런데 가끔 꼬이면(예: 라이브러리 버전 업 후), Metro는 옛날 그래프를 보고 있어서 에러가 난다. 그래서 RN 개발자들은 "뭔가 이상하면 캐시 리셋(npm start -- --reset-cache)"이 습관을 들이면 좋다. 어쩔 수 없는 숙명이다...