웹팩 환경에서의 빌드 속도 개선기 - 1

Sming·2022년 10월 12일
4

너무 느린 빌드 속도

프로젝트 개발을 하던 도중 빌드 속도가 너무 느린것을 확인하였다.

development환경기준 yarn start를 할시 17초, auto save로 rebuild를 할시 7.5초가 소모 되었다.

첫 빌드속도는 크게 중요하진 않지만 개발할때 rebuild속도는 핵심적이라고 생각했다. 매번 저장할때마다 7.5초를 기다리고 결과를 확인하는것은 너무 생산성이 떨어지기 때문이다.

다음은 개선전 build와 rebuild속도이다.

build (yarn start)

rebuild (save를 통한 rebuild)

첫번째 개선 - 소스맵

가장 먼저 개선하기로 한 것은 소스맵이였다.

production모드에서는 devtool을 false로 주어서 생성하고 있지않지만 development에서 source-map을 사용하고 있었다. source-map은 빌드 속도와 rebuild속도 모두 매우 느리다.

webpack 공식문서를 보면 개발환경에서는 eval-cheap-module-source-map을 추천한다고 한다.

소스맵의 eval옵션은 자바스크립트의 eval 문법을 이용하여 rebuild 속도가 매우 빠르다고한다. 또한 cheap-module을 추가로 붙혀줬는데 이 옵션은 정확한 매핑을 포기하는 대신 빌드속도를 소량 빠르게 해준다.

그래서 rebuild를 최대한 빠르게 할 수 있으면서 디버깅까지 할 수 있는 eval-cheap-module-source-map으로 변경하였다.

다음은 소스맵을 적용한 후의 build, rebuild 속도이다.

build (yarn start)

rebuild (save를 통한 rebuild)

소스맵만 적재적소로 이용하더라도 build와 rebuild속도가 눈에 띄게 빨라진것을 볼 수 있다.

두번째 개선 - runtimeChunks

웹팩의 optimization에 있는 runtimeChunks옵션은 웹팩의 런타임 코드를 번들링된 코드와 분리를 하여 작업을 한다.

런타임 코드와 번들링된 코드끼리 별도의 프로세스에서 작동하기때문에 빌드속도 및 리빌드 속도가 빨라지게 됩니다.

분리할 시 vendor-runtime-chunks 파일과 나머지 번들링 코드들이 분리된 상태로 번들되는 것을 확인할 수 있습니다.

module.exports = {
	...
    
    optimization: {
    	runtimeChunks: true
    }
}

다음은 runtimeChunks를 적용한 후의 build, rebuild 속도입니다.

build (yarn start)

rebuild (save를 통한 rebuild)

build의 속도는 크게 줄지 않지만 rebuild의 속도가 크게 줄었다. 개발환경에서 중요한것은 rebuild이기에 개발환경시에 매우 유용한 옵션이다.

세번째 개선 - ts-loader

개발환경에서 ts-loader를 이용하였는데 babel-loader를 사용하면 빌드시 타입체킹을 해주지 않기때문에 ts-loader를 선택하였습니다.

하지만 babel-loader에서 해주지 않는 타입체킹을 해주는 만큼 빌드속도가 느려지게 되는데요.

이것을 해결하기위해서 transpileOnly:true , forkTsCheckerWebpackPlugin을 이용하여 개선을 진행하였습니다.

transpileOnly: true 옵션을 이용하면 ts-loader가 타입체킹을 제외하고 트랜스파일링만 진행해주기 때문에 빌드속도가 매우 빨라지게 됩니다.

여기서 forkTsCheckerWebpackPlugin을 이용하면 타입체킹을 별도의 프로세스에서 진행을 하여서 빠른 빌드속도를 유지하면서 타입체킹을 진행합니다.

실측 결과

babel-loader만을 사용했을때와 비교를 해보자면 rebuild 시점에서 babel-loader는 500ms ~ 600ms, ts-loader는 700ms ~ 800ms정도 나오게 된다.

실제로 개발하는데 rebuild에서의200ms정도 차이는 크게 신경쓸 부분은 아니라고 생각하였고 그것에 비해서 얻는 타입 체킹이라는 이점이 훨씬 더 좋다고 생각된다.

번외) babel-loader의 cache

실제로 저는 개발환경에서 babel-loader를 이용하지 않고 ts-loader를 이용하였기 때문에 babel-loader의 cache 기능을 이용하지 않았지만 개발환경에서 babel-loader를 이용한다면 다음과 같은 옵션을 이용할 수 있을것 같다.

  options: {
    cacheCompression: false,
    cacheDirectory: true,
  }

cacheCompression: 기본값은 false입니다. 설정되면 지정된 디렉토리가 로더의 결과를 캐시하는 데 사용됩니다.

후에 웹팩이 rebuild 될 경우 각 실행에서 잠재적으로 비용이 많이 드는 babel의 reTranspile 프로세스를 실행할 필요가 없도록 캐시에서 읽기를 시도합니다.

cacheDirectory: 기본값은 true입니다. 설정하면 각 Babel 변환 출력이 Gzip으로 압축됩니다.

production 환경에서는 압축을 해주기 때문에 true 해주는것이 좋지만 개발환경에서는 굳이 gzip으로 줄일 필요가 없기에 불필요한 빌드 시간을 늘리게 됩니다.

만능 - esbuild-loader

실제로 빌드 속도에 관련해서는 어떤 로더보다 빠르게 빌드된다. 아마 웹팩 빌드속도 올리기를 검색할 시 가장 많이 나오는 해결법일 것이다.

그도 그럴것이 위에서 사용한 모든 방법을 esbuild-loader 하나로 더 빠르게 할 수 있다.

하지만 우리 프로젝트에서는 개발환경에서는 ts-loader, 배포환경에서는 babel-loader를 이용했는데 그 이유는 다음과 같다.

배포 환경에서의 tree-shaking 이슈

현재 프로젝트에서는 react-icons를 이용하고 있는데 esbuild-loader를 사용할 시 react-icons가 tree-shaking이 되지 않는 문제를 확인하였다.

원래는 production 모드에서 기본으로 terserPlugin이 쓰지 않는 코드를 제거하는 행동을 해주는데 esbuild는 terserPlugin대신 ESBuildMinifyPlugin를 이용하기 때문에 되지 않는것 같았다.

그렇다면 개발환경에서는?

개발환경에서 역시 esbuild-loader 대신에 ts-loader를 이용하였는데 그 이유는 눈에 띄는 향상이 없었기 때문이다.

개발환경에서는 rebuild속도가 중요하다고 하였는데 실제 esbuild-loader를 개발환경에서 이용하면 500ms~600ms 정도 나오게 되었다.

실제로 ts-loader를 이용했을때와 큰 차이가 없어서 esbuild-loader를 이용하지 않았다. 또한 가끔 써드파티 라이브러리와 잘 맞지 않는 경우가 있어서 그러한 리스크를 가지면서까지 사용할 이유가 없어서 적용하지 않았다.

그외 여러가지 방법들..😎

공식문서에 위에 있는 내용을 포함하여 여러가지 방법들이 추가적으로 있다. 예를 들어 불필요한 로더, 플러그인을 제거하면 빌드속도가 올라가게 된다.

webpack5에서 부터 나온 asset Modules를 이용하면 js외의 외부 파일을 관리할때 사용하는 file-loader, url-loader들을 제거할 수 있다.

웹팩 asset Modules
웹팩 빌드속도 개선 공식 문서

결론

개발환경 기준으로 다음과 같은 개선을 이루어 냈다.

🌸 build: 17초 -> 5.5초
🌼 rebuild: 7.5초 -> 0.7초

사실 웹팩말고 vite를 썻다면..

profile
딩구르르

1개의 댓글

comment-user-thumbnail
2022년 10월 21일

잘 읽었습니다. esbuild loader를 쓰지 않은 이유까지 제가 궁금한 이야기가 많이 담겨 있네요. runtimechunks는 찾아봤야겠네요. 처음 알게 되었네요. 감사합니다!!

답글 달기