오르막길 잊기전에 기록하기

웹팩 빌드 속도 개선하기 (feat. webpack-bundle-analyzer가 너무 오래걸릴때)

들어가며

얼마전 부쩍 프로덕트에 빌드속도가 느려지는 일이 있었다. 평소 5분정도면 넉넉히 끝나던게 20분 가까이 소요되기 시작했다. 이는 많은 삶의질의 하락을 가져왔고 이를 개선하는 과정을 기록으로 남겨두려 한다.

TL; DR

웹팩 빌드 성능 측정하기

모든 성능 트러블 슈팅은 측정부터란 말이 있다. webpack 의 빌드 성능을 측정하는데는 speed-measure-webpack-plugin 플러그인을 사용하면 좋다.

다만 vue-cli를 사용한다면, vue-cli가 webpack을 wrapped 하고 있으므로 다음 아티클을 참고한다.

webpack-chain을 사용하고 있어 vue.config.jschainWebpackspeed-measure-plugin을 적용하고 얻은 결과이다.

img1

분명 빌드는 20분가량 걸리는데, 수집된 지표상으로는 6분만에 끝났다고 나온다. “chainWebpack이 적용된 이후 vue-cli에서 추가로 플러그인이나 로더를 추가하는건가?” 하고 의심할 수 있었다.

node_modules에 있는 vue-cli 의 소스코드를 직접 수정하여 webpack 을 실행시키기 직전 webpack config에 speed-measure-plugin 을 적용시켜보았다.

img2

이전과는 다르게 webpack-bundle-analyzer 플러그인이 추가되고, 이게 15분을 잡아먹는 병목의 범인인것을 확인할 수 있었다.

webpack-bundle-analyzervue-cli--report-json 옵션을 받았을 때 빌드로 인해 생성된 번들 정보를 json 파일로 생성하기 위해 추가한다.

프로덕트에서 빌드 후 생성된 번들들을 CDN에서 warmup 시키기위해 생성된 번들정보가 필요했고, 이를 위해 사용했던 report-json 옵션이 성능 저하를 가져온 것이다.

디버깅

범인을 특정한 후 원인을 찾기 위해 본격적인 디버깅을 시작했다.

우선 크롬 프로파일러를 통해 정확히 어느 지점이 병목인지 찾으려 했다. 다음과 같이 ProfilingPlugin을 추가해주자. 마찬가지로 vue-cli가 webpack을 호출하기 전에 추가해주면 편하다.

if (args.clean) {
  await fs.remove(targetDir)
}

webpackConfig.plugins.push(new webpack.debug.ProfilingPlugin()); // here!
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();

webpackConfig = smp.wrap(webpackConfig);

return new Promise((resolve, reject) => {
  webpack(webpackConfig, (err, stats) => {

해당 플러그인은 크롬 개발자도구의 Profile 탭에서 열 수 있는 event.json 파일을 생성해 준다. 자세한 내용은 레퍼런스 를 참고한다.

플러그인을 추가하고 빌드를 돌렸더니 150MB짜리 (..) event.json이 생성되었고, profile탭에 넣었더니 용량이 너무 커서 그런지 크래시가나며 개발자도구가 죽어버렸다.

결국 webpack-bundle-analyzer의 곳곳에 break point를 찍고 한땀한땀 쫓아가며 병목지점을 찾기로 했다.

Node.js 는 실행시 --inspect-brk 옵션을 주면 크롬 개발자도구를 열어 왼쪽 상단 노드 아이콘을 선택하면 개발자도구로 디버깅이 가능하다.

마찬가지로 자세한 내용은 레퍼런스를 참고한다.

다음과 같은 명령어로 vue-cli 를 디버깅 할 수 있다.

$ node --inspect-brk node_modules/.bin/vue-cli-service build —-report-json

한땀한땀 디버깅한 결과 generateStatsFile 함수의 json 파일을 쓰는 bfj.write 에서 대부분의 시간이 소요되는것을 확인할 수 있었다.

src/BundleAnalyzerPlugin.js

async generateStatsFile(stats) {
  const statsFilepath = path.resolve(this.compiler.outputPath, this.opts.statsFilename);
  mkdir.sync(path.dirname(statsFilepath));

  try {
    await bfj.write(statsFilepath, stats, {
      space: 2,
      promises: 'ignore',
      buffers: 'ignore',
      maps: 'ignore',
      iterables: 'ignore',
      circular: 'ignore'
    });

    this.logger.info(
      `${bold('Webpack Bundle Analyzer')} saved stats file to ${bold(statsFilepath)}`
    );
  } catch (error) {
    this.logger.error(
      `${bold('Webpack Bundle Analyzer')} error saving stats file to ${bold(statsFilepath)}: ${error}`
    );
  }
}

다음 버전 릴리즈를 앞두고 많은 기능이 추가되어 번들 사이즈가 커져 생성해야 할 report.json의 크기도 같이 커졌고 (150MB가 넘는다), 이 bfj 모듈이 특이점을 넘으면 성능이 급격히 저하되는가? 하는 의구심이 생겼다.

우선 관련되어 리포팅된 이슈가 있는지 찾아보았고, bfj 모듈이 deprecated 되어 custom json writer로 변경하는 PR과 함께 테스트 결과 6배의 성능향상이 있었다는 코멘트를 확인할 수 있었다.

custom json writer가 적용된 버전인 4.1.0이 얼마전 릴리즈가 되었고 vue-cli가 의존하는 webpack-bundle-analyzer의 버전을 4.1.0으로 변경하여 다시 측정해보았다.

img3

변경한 결과 webpack-bundle-analyzer의 소요시간은 어마하게 줄어들었고 빌드에 걸리는 시간도 예전처럼 5분 남짓으로 돌아오게 되었다.

vue-cli가 의존하는 webpack-bundle-analyzer의 버전을 올려야 한다는걸 리포팅 하고, PR을 만들어 보냈다. 아마 다음 버전에 반영될것으로 보인다.

다음 버전이 릴리즈 되면 프로덕트에서 의존하는 vue-cli의 버전을 업그레이드하면 자연 해소될것이다. 당분간 답답해도 참아내야겠다.

마치며

처음 webpack을 디버깅하려 했을 때, 많이 막막한 느낌을 받았다. 이번 이슈를 통해 webpack의 성능을 측정하고, 디버깅하고, 이슈를 리포팅하고 PR을 보내 수정하는 일련의 과정이 좋은 경험이 됐다.

필자와 비슷한 상황에 놓인 사람들에게 이 글이 도움이 됐으면 한다.

comments powered by Disqus