프로젝트 개발을 하던 도중 빌드 속도가 너무 느린것을 확인하였다.
development환경기준 yarn start
를 할시 17초, auto save로 rebuild를 할시 7.5초가 소모 되었다.
첫 빌드속도는 크게 중요하진 않지만 개발할때 rebuild속도는 핵심적이라고 생각했다. 매번 저장할때마다 7.5초를 기다리고 결과를 확인하는것은 너무 생산성이 떨어지기 때문이다.
다음은 개선전 build와 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와 rebuild속도가 눈에 띄게 빨라진것을 볼 수 있다.
웹팩의 optimization에 있는 runtimeChunks
옵션은 웹팩의 런타임 코드를 번들링된 코드와 분리를 하여 작업을 한다.
런타임 코드와 번들링된 코드끼리 별도의 프로세스에서 작동하기때문에 빌드속도 및 리빌드 속도가 빨라지게 됩니다.
분리할 시 vendor-runtime-chunks
파일과 나머지 번들링 코드들이 분리된 상태로 번들되는 것을 확인할 수 있습니다.
module.exports = {
...
optimization: {
runtimeChunks: true
}
}
다음은 runtimeChunks를 적용한 후의 build, 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
를 이용하지 않고 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
하나로 더 빠르게 할 수 있다.
하지만 우리 프로젝트에서는 개발환경에서는 ts-loader
, 배포환경에서는 babel-loader
를 이용했는데 그 이유는 다음과 같다.
현재 프로젝트에서는 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를 썻다면..
잘 읽었습니다. esbuild loader를 쓰지 않은 이유까지 제가 궁금한 이야기가 많이 담겨 있네요. runtimechunks는 찾아봤야겠네요. 처음 알게 되었네요. 감사합니다!!