주로 cloud 환경에서 메모리 용량이 1GB이하일 때 build fail이 날 수 있다고 한다(https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#npm-run-build-exits-too-early). 우리는 CI 서버에 2GB 설정해두었다고 하는데 최근들어 build fail 횟수가 점점 늘어났다.
CTO님이 조만간 3GB로 늘려주신다고 했지만, 2GB도 작은 것은 아니기에 원인을 파악해서 수정하는 것이 좋겠다고 하셨다.
[0mCreating an optimized production build...
The build failed because the process exited too early. This probably means the system ran out of memory or someone called `kill -9` on the process.
항상 멈추는 시점은 “Creating an optimized production build… “ 메시지가 뜨고 몇 분 후이다. 여기에서 js파일들을 bundle하고 source map을 만드는 구간인 것 같다.
관련 이슈
많이들 비슷한 경우가 있나보다. CRA(Create React App) issue 답변에서 해결방법을 찾을 수 있었다.
해결해보자.
- GENERATE_SOURCEMAP=false
source map을 disable한다. 원래도 development 환경일 때만 디버깅을 했고, build하고 production에 올릴 때는 소스를 공개하면 안 되기 때문에 아래의 명령어로 따로 source map을 삭제하고 있었다.
"build": "react-scripts build && rm build/static/**/*.map"
위같이 삭제하지 않아도 source map을 disable하는 환경설정이 추가 됐다.
react-script 1.0.11 버전부터 가능하다고 하여, 이참에 1.1.4 버전으로 올렸다. 프로젝트에 충돌이나 dependency 버전 문제는 없는 듯 하다.
root 디렉토리의 .env 파일에 추가해도 되고, script에 추가해도 된다.
"build": "GENERATE_SOURCEMAP=false react-scripts build"
2. code splitting
CRA는 build 과정에서 모든 js파일을 하나로 합친다. 프로젝트 규모가 커질 수록 component가 많아지면 아무래도 bundle하기 위해 파일간 dependency 계산에 오래걸릴 수밖에 없을 것이다.
두 번째 해결방법은 code splitting인데 dynamic import를 통해서 파일을 나누는 것이다. 보통 route를 설정한 곳에서 하는데, dynamic import를 하면 각 route마다 필요한 component(page)에 dependency된 부분만 chunk 파일이 생기며 해당 path로 들어가면 그 chunk js만 로드하게된다.
dynamic import를 위해 react-loadable을 사용했다.
import ReactLoadable from 'react-loadable';
import LoadingIndicator from './LoadingIndicator';const Loadable = options => ReactLoadable({
loading: LoadingIndicator,
delay: 300,
...options
});const AsyncMainPage = Loadable({
loader: () => import('./MainPage')
});const AsyncLoginPage = Loadable({
loader: () => import('./LoginPage')
});<Route path="/main" component={AsyncMainPage}/>
<Route path="/login" component={AsyncLoginPage}/>
모든 route에 하려니 연이은 Loadable 호출때문에 코드가 지저분해졌는데, 맞는 것인지 잘 모르겠다. 결국은 component가 많은 부분만 async로 가져오기로 했다.
3. analyze
2번에 async할 페이지를 결정하기 위해 analyze를 돌렸다. component가 많고 js logic이 많은 페이지가 용량이 클 줄 알았는데 한글이 몇 백줄 되는 약관페이지 류가 용량이 상당히 컸다 ㅎㅎ
메모리 용량때문에 build fail난 시점이 emoji 관련 라이브러리를 추가하고부터 인데 역시나 수많은 모듈이 있음에도 emoji 모듈이 20% 정도의 크기를 차지하고 있었다 -_-
앞으로 추가할 모듈이 많아질 것 같은데, 어떻게 해야하는지 잘 모르겠다. 용량 검사를 하고 그나마 가벼운 것을 골라야 하는지. 일단은 emoji 모듈은 삭제하고 다른 것으로 대체하기로 했다.
build success!
부족한 지식과 해석일 수 있으니 틀린 내용, 다른 의견이 있다면 댓글 남겨주시면 감사하겠습니다!