애초에 Vite나 Next.js가 아닌 CRA(Create React App)을 선택한 것 자체가 문제의 발단이긴 했지만, 프로젝트 회고에도 적었듯이 재작년의 나에겐 뭐.. 겁쟁이였던 나름의 이유가 있었다.
프로젝트가 어느 정도 안정된 작년 4월 말, 고구마 빌드 시간과 자잘한 이슈가 신경 쓰여 Vite로의 마이그레이션을 백로그에 적어두었으나, 바쁘다는 핑계로 거짓말처럼 잊혀졌다..
(아니 뭐 묵은지냐고)
이 백로그를 다시 꺼내든 건 그해 여름, CRA 환경에선 동적 메타 데이터 요청에 대응하기 어렵다는 걸 확인한 시점이었다. 예를 들어 동적 오픈 그래프 설정이 되어 있는 상품 링크로 카톡에 공유하면 상품마다 다른 썸네일과 제목이 떠야 하는데, CSR 환경의 CRA에서는 여간 까다로운 일이 아니었다. 각 서비스의 파싱을 수행하는 봇은 클라이언트에서 자바스크립트가 렌더링 되기를 기다려주지는 않기 때문이다.
(오픈 그래프 파싱을 통한 표기)
react-helmet
같은 라이브러리는 이미 클라이언트에서 렌더링이 끝난 이후에 메타 태그를 슬쩍 갈아치우는 방식이라, 육안으로는 바뀐 것처럼 보여도 오픈 그래프 파싱에 동적으로 대응할 수 없었다. 명확한 의미에서의 동적 오픈 그래프라고 보기가 어려웠고, react-helmet-async
도 현재 버전에서는 온전한 대응이 불가능한 상황이었다.
다음 대안은 puppeteer
와 prerenderer
플러그인이었다. Vite는 CRA와는 다르게 vite.config.js
설정을 통해 번들링 옵션은 직접 조정하고 프리렌더링으로 동적 오픈 그래프에 대응할 수 있다는 리서치 결과도 있었기에, 마이그레이션 자체는 충분히 유의미했다.
하지만 회사 업무라는 게 당장 해야 할 일만이 전부가 아닌 것이, 우선 내부의 요구가 강하지 않았다. 당장 대응 필요한 기능이 아니었다는 것 때문에 우선순위에서 늘 다른 업무에 밀렸다. 그리고 React 19에서 Document Metadata가 도입된다는 소식도 있었기 때문에 선임 개발자분과 논의 결과, 추후 필요할 때 대응하는 것으로 마무리되어 다시 봉인되었다..
끝내 CRA는 운명하셨습니다.. 올해 2월 초 공식적으로 deprecated 되었다. 해당 내용을 선임 개발자분들께 보고드리고, 이미 알려진 마이그레이션 가이드 문서도 체크해 뒀지만.. 일단 CRA가 적용된 프로젝트가 사내에서 비중이 크지 않고, 당시 메인으로 진행하던 두 개의 프로젝트에 치여 또다시 기억 저편으로 사라지고 계셨다..
최근에 와서야 다시 이 미뤄둔 이슈를 꺼내 드는 일이 생겼다. 외부에서 Next.js로 개발된 기능을 CRA 기반의 프로젝트에 붙여야 하는 작업이 생긴 것이었다. 기능을 떼와야 한다는 건 얼핏 들었으나 신규 Next.js 환경을 구성할 줄 알았지, CRA 환경에 녹여야 할 줄은 몰랐다.
코드를 까보니 단순히 프레임워크만 다른 게 아니라 스타일 라이브러리, 상태 관리, 디자인 패턴 등 뭐 하나 비슷한 게 없었다. 생각보다 이슈가 많은 작업이고 현재에도 진행하고 있지만, 어쨌든 이번 글에는 CRA의 webpack에 관련된 이야기만 언급하려고 한다.
물론 장기적으로 볼 땐 Next.js로 새 프로젝트 환경을 구성하거나 최소한 VIte로 마이그레이션부터 하는 게 나아 보였다. 하지만 전자는 로그인과 결제 기능, 그 외 필수 기능들을 다시 구현해야 했고, 후자는 마이그레이션 자체의 공수와 리스크를 감내해야 한다는 선행 이슈가 있었다. 어쨌든 고민할 여유가 없는 시점에선 작업이 우선이었기 때문에 일단 기존 프로젝트에 붙이기로 했다.
alias
설정이 외않되..?작업 중에 이슈 파악을 하게 된 건 alias
설정이 안 되는 걸 확인한 이후였다. TypeScript 환경을 새로 구성하고 tsconfig.json
을 통해 paths
를 잡아뒀다. 메인 진입점이 되는 컴포넌트를 붙이지 않은 채 import
를 수정할 때는 IDE가 alias
에 맞게 경로를 잘 잡아주고 있었는데, 막상 메인 진입점의 컴포넌트를 붙이고 나서 CRA를 통해 로컬에 띄우거나 빌드할 때 에러를 뱉는 것이었다. 물론 별칭이 아닌 상대 경로로 잡을 수도 있겠지만, 마이그레이션 과정에서 폴더 구조가 어떻게 바뀔지 모르는 데다, 무엇보다 모듈이.. 정말.. 오지게 많았다..
(아니, 재사용성도 낮은 모듈을 왜 이렇게까지 쪼갠 거지.. 저한테 왜 이러세요..)
eject
대신 CRACO로 webpack 설정 변경하기먼저 Agent에 문의하니 CRACO(Create React App Configuration Override)를 사용해서 webpack 설정을 오버라이드할 것을 권유했다. 더블 체크를 위해 구글링으로 에러 메시지와 CRA, alias 키워드 함께 때려 보니 CRACO 관련 글들이 보였다. CRA는 프로젝트 시작할 때 특정 설정을 초기화하는데, 이때 tsconfig.json
에서 설정된 paths
등의 설정이 무시되는 것이 원인이었다. strict
나 allowJs
등 일부 존중되는 옵션도 있지만, paths
나 outDir
등 주로 컴파일된 결과물의 생성과 관련된 옵션들을 대부분 무시된다.
앞서 살짝 언급되기도 했지만 Vite는 vite.config.js
를 통해 번들러 설정을 할 수 있다. 하지만 CRA는 디렉토리의 간결함을 위해 webpack이나 babel 등 관련 설정들을 숨겨 두었는데, 개발자는 신경 끄고 코딩에만 집중하라는 깊은 뜻(?)이었다. 물론 eject
명령어를 통해 webpack.config.js
과 같은 설정 파일들의 확인 및 수정이 가능하지만, 이전 상태로 돌아갈 수도 없고 딱히 권고하지 않는 사항이다. CRACO는 eject
대신 사용하기 위해 만들어진 셈인데, 이름에서부터 추측할 수 있듯 CRA 설정을 오버라이드해서 사용자 정의할 수 있도록 해주는 도구이다.
설정 방법 자체는 간단하다. npm i -D @craco/craco
혹은 yarn add -D @craco/carco
를 통해 devDependencies
로 해당 패키지를 설치하고, 루트에 craco.config.js
라는 파일을 생성한다. 난 아래와 같이 설정했다.
// craco.config.js
const path = require('path');
module.exports = {
webpack: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
};
그리고 터미널 명령어를 관리하는 package.json의 script 객체를 수정했다.
// package.json
...
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
},
...
설정 자체는 간단한데 내 경우엔 순탄치만은 않았다. private npm registry로 배포된 패키지를 통으로 떼어오는 과정에서의 이슈, CRA가 TypeScript 5버전을 지원하지 않는 이슈와 4버전이 Web MIDI API를 지원하지 않는 이슈 등이 신나게 어우러지면서 꽤나 삽질을 했다. js-synthesizer
가 stream
모듈을 사용하기에 브라우저에서 사용할 수 있도록 polyfill을 제공하는 패키지인 stream-browserify
까지 설치하면서 최종적으론 이런 형태의 설정이 되었다.
// craco.config.js
const path = require('path');
module.exports = {
webpack: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
configure: (webpackConfig) => {
webpackConfig.resolve.fallback = {
...webpackConfig.resolve.fallback,
stream: 'stream-browserify',
};
return webpackConfig;
}
},
};
붙이는 작업은 여전히 하고 있어서 또 어떤 이슈가 나올지 모르겠지만, CRACO로 alias
문제를 해결하고 나니 예상치 못한 복병이 나타났다. 안 그래도 느렸던 CRA의 빌드 시간이 호우야.. 오버라이드 과정까지 거치면서 저세상으로 가버리신 것 같다.
이 글은 미래의 누군가가 참고할 일이 없었으면 하는 바람으로 쓴다. 어차피 이제 와서 신규 프로젝트를 CRA로 구성할 일은 없을 테고, 혹시 지금 ‘언젠가를 하겠지..’ 싶은 마음으로 낡은 기술 스택을 안고 가는 사람이 있다면 잠시 이 글을 다시 한번 안쓰럽게 읽어보자. 오늘의 미뤄둔 작업이 내일의 발목을 잡아채기 전에 역사의 뒤안길로 사라지는 애들은 바로 거둬내야 한다는 교훈을 되새기며.. 아직 남은 기술 부채 털어내러 갑니다..