배포 속도를 올려보자 - 빌드

lingodingo·2020년 12월 29일
45
post-thumbnail

사내에서 발표했던 것을 정리한 내용이다

개발자는 코드를 수정하자마자 바로 반영되기를 원한다. 하지만 그 시간 차가 20분 정도 난다고 하면 얼마나 짜증이 날까..? 이 시리즈는 배포 속도를 최대한 끌어올려 본 경험을 정리한 것이다.

😎 현재 배포 환경

보통 프론트엔드에서의 배포라고 함이라면,

  1. master에 머지 (PR이든 직접 Push든)
  2. CI에서 해당 레포가 머지됬단 걸 확인
  3. 인스턴스(VM) 부팅, 리눅스 및 노드 설치, 레포내의 코드 옮겨오기
  4. 패키지 설치
  5. package.json에 설정되어 있는 커맨드를 통해 번들러(Webpack)를 실행하여 빌드 시작
  6. 결과물을 S3에 파일 업로드

위의 작업을 실행하는데 현재 7분이 걸린다. dev(alpha), production순으로 동기적으로 하면 총 14분이 배포에만 걸리는 시간이다. 끔찍하지 않은가?

전 회사에선 배포까지 20분이 넘게 걸렸었던걸로 기억하는데.. 꽤나 고통받았던 기억 밖에 없다.
(CI 스크립트를 테스트 하기 위해선 정말 시간과의 싸움이었다)

어쨌든, 배포 하는 시간 중에서 빌드할 때가 너무 느려서 골치를 앓던 중(배포 속도 꼴지였음 ㅎㅎ) 사내 슬랙에서 동료 프론트 엔지니어분이 흥미로운 번들러를 알려주셔서 한 번 시도해보기로 했다.

내가 모든걸 결정할 수 있기 때문에 아무런 방해없이 과감하게 시도해 보기로 하였다.

참고, 기술스택:

  • Webpack 4, React, Typescript

🤔 왜 이렇게 빌드가 느려터진거야?

💡 (Speed Measure Plugin을 설치하면, 웹팩 빌드 소요시간을 파악할 수 있다)
⚠️ 위 사진은 이미 esbuild를 적용하고 난 뒤의 모습. 그럼에도 순수 빌드 타임만 2분 25초가 걸린다.

문제의 이유는 명확했다. 빌드할 때 쓸데없는 수많은 플러그인과 로더가 돌아가고 있기 때문이다.
그 중에서 유독 시간을 잡아 먹는 것들이 있었는데 다음과 같았다:

  • fork-ts-checker-webpack-plugin: 타입체크용 플러그인
  • case-sensitive-paths-plugin: Mac에서 import A from 'a/b/c';의 구문은 case sensitive 하지 않아서 실제로 폴더 이름이 A/B/C여도 import가 정상적으로 동작하는데, docker와 같은 리눅스 환경에서 돌아갈 때엔 case-sensitive해서 대소문자가 다르면 에러를 내뿜는다. 이를 사전에 알려주는 플러그인
  • eslint-loader: 린트 체크
  • ts-loader / babel-loader: js, jsx, ts, tsx 파일 읽어서 -> js로 바꿈
  • minify/uglify Plugin(ex, terser, uglify): 자바스크립트 파일 번들링 사이즈 줄이는 플러그인
  • source-map-loader: 소스맵 추출

👀 SMP 플러그인을 사용하지 않더라도, 대부분 로더나 플러그인에 debug 옵션이 있어서, 해당 옵션을 켜서 어디에 시간이 많이 소요되는지 파악할 수 있다.

🎢 빌드에 필요없는 플러그인, 로더를 줄이자

많은 로더, 플러그인들을 구글링하여 배포할 때 필요없는 것들을 골라낼 수 있었다. 사실 이 부분은 배포할 때 안정성을 추구한다면, 포함시켜도 상관없다.

  • fork-ts-checker-webpack-plugin: 개발할 때만 타입을 체크하지, 빌드할 때 필요할까? 삭제
  • eslint-loader: 린트 체크는 개발 중일때 적용하였고 2중 안전 장치로 husky를 통해 commit 할 때에도 lint가 돌도록 하였다. 삭제
  • case-sensitive-paths-plugin: 이것 또한 개발 중일때만 적용하면 된다. 삭제
  • ts-loader / babel-loader: esbuild-loader로 옮겨보자
  • minify/uglify Plugin(ex, terser, uglify): esbuildMinifyPlugin이 존재하므로 옮겨보자

해당 플러그인, 로더들을 교체하고 난 뒤 시간을 다시 재보았다.

🤨 2분 26초 -> 11초

오잉..? 성공!


esbuild-loader는 다른 Loader와 비교해서 얼마나 빠를까?

418개의 모듈을 5초만에 처리하는 esbuild-loader는 다른 typescript loader와 비교해서 얼마나 빠른걸까?

한 번 비교해봤다.

esbuild-loader vs ts-loader/babel-loader

개발 환경 세팅을 다시 되돌리기 귀찮아서 esbuild가 적용된 결과 스크린샷만 있는데, ts-loader, babel-loader와 esbuild-loader 속도비교를 해보았다.

ts-loader + terser@4.2.3

1번 주자는 모두가 잘 쓰고있는 ts-loader이다. minify, uglify 플러그인은 terser를 사용했다.

1분 10초

만약 transpileOnly 옵션을 키고 babel-loader를 사용한다해도 이것보다 더 느려질 것이다.
(TS => ts-loader => js => babel-loader => js) (으악 끔찍..)

babel-loader + terser@4.2.3

  • 웬만하면 ts-loader보다는, 유용성이 더 높은 babel-loader를 사용해보자
  1. ts-loader: 별다른 옵션을 설정하지 않으면 타입체킹을 하기 때문에 굉장히 느리다. 또한 개발 환경에서는 캐시 플러그인을 따로 사용해야지 속도가 어느정도 보장이 된다.
  2. awesome-typescript-loader: 일부 작업의 부하로 컴파일 속도가 느리다고 함
  3. babel-loader: babel은 버전 7부터 typescript도 이해하는데 심지어 babel 전용 플러그인과 폴리필을 제공할 수 있으므로 그 유용성은 ts-loader보다 높다.

42초

esbuild-loader + esbuildMinifyPlugin

11초

순수 esbuild 환경(CLI) 이 아닌 웹팩 환경에서 실행했음에도 이렇게나 빠른 속도를 보장하고 있다. 만약 웹팩 환경을 벗어날 수 있다면 아마 1초만에 끝났을 것이라 생각한다. 이 11초라는 수치는 esbuild 입장에선 굴욕 수준..

🔥 esbuild

ts-loader, babel-loader와 비교해서 왜 이렇게 속도 차이가 심할까? 공식 홈페이지에서는 이렇게 설명하고 있다.

  1. native code로 작성됨(Go)
  2. 파싱, 프린팅, 소스맵 추출 모든 과정이 완벽하게 동시(Parallel)하게 진행됨
  3. 빌드에 불필요한 단계를 없애거나 줄임

같은 노드 패키지를 다른 번들러랑 비교하여 속도를 나타낸 표가 있다.

정말 미친듯이 빠르다.

esbuild-loader

esbuild가 정말 빠른 빌드 툴임을 알았지만 도입에 망설여지는 여러 이유 중 하나는 webpack 생태계를 버릴 수 없어서 그럴 것이다. 그런 이들을 위해 esbuild-loader가 있다. 이 로더를 통해 웹팩에서도 esbuild를 사용할 수 있다.

기존에 ts-loader/babel-loader를 대체할 수 있는 방법은 다음과 같다(너무나 쉽다):

+ const { ESBuildPlugin } = require('esbuild-loader')

  module.exports = {
    module: {
      rules: [
-       {
-         test: /\.tsx?$/,
-         use: 'ts-loader'
-       },
+       {
+         test: /\.tsx?$/,
+         loader: 'esbuild-loader',
+         options: {
+           loader: 'tsx', // Or 'ts' if you don't need tsx
+           target: 'es2015'
+         }
+       },

        ...
      ]
    },
    plugins: [
+     new ESBuildPlugin()
    ]
  }

esbuild 자체가 minify, uglify도 지원하므로 terser와 같은 플러그인을 대체할 수 있다.

Minification 속도 비교 페이지에서는 속도도 빠른데 성능도 아주 준수하다고 소개하고 있다.

(대충 속도는 최소 10배이상 빠른데 압축 결과물의 번들 사이즈는 1등이랑 3~5% 차이)

또한 ESBuildMinifyPlugin만 따로 쓸 수 있으니, babel-loader를 포기할 수 없다면 한 번 도입해보자.

+ const {
+   ESBuildPlugin,
+   ESBuildMinifyPlugin
+ } = require('esbuild-loader')

  module.exports = {
    ...,

+   optimization: {
+     minimize: true,
+     minimizer: [
+       new ESBuildMinifyPlugin({
+         target: 'es2015' // Syntax to compile to (see options below for possible values)
+       })
+     ]
+   },

    plugins: [
+     new ESBuildPlugin()
    ]
  }

써보면서

장점

  1. 진짜 엄청 빠름
  2. esbuild 때문에 빌드 실패한적 없음
  3. ts-loader / babel-loader에서 마이그레이션 하기 쉬움
  4. 타입스크립트도 지원
  5. 커스텀한 플러그인을 제작할 수 있게 힘 많이 쏟고 있음 [플러그인 리스트 링크]
  6. tree-shaking 지원
  7. documentation 나름 잘 되어 있음
  8. wasm으로도 작성되어 있음
  9. 욕심 많은 1인 maintainer (뚝심있는 모습은 👍 )

단점

  1. 타입체크 안함. 타입 체크 하려면 parallel 하게 tsc —noEmit을 실행하거나, fork-ts-checker-webpack-plugin을 사용하여야 함
  2. 타입스크립트 문법을 완벽하게 지원하지는 않음 (문법 틀려도 컴파일되는 경우 있으므로 위의 플러그인을 꼭 사용하자)
  3. css loader는 아직 문제가 있어서 작업중
    /** @jsx jsx */
    import { jsx, css } from '@emotion/core';

jsx pragma boilerplate 문법은 babel 플러그인의 하나인 @emotion/babel-preset-css-prop으로 쉽게 없앨 수 있는데 esbuild에서는 preact의 h와 같은 전역 jsx pragma만 설정할 수 있음 (독자적인 플러그인 개발 필요)

또는 babel-loader 달아서 해결해야함(TS → esbuild-loader → JS → babel-loader → JS)

  1. webpack 생태계가 아직까지 넓어서 webpack에다 얹혀서 쓰는 걸 추천.. 또는 snowpack으로 옮기거나..
  2. 버전 1.0이 아직 안나왔다
  3. es5로 target 해서 빌드하면 에러 발생할 수 있음 (이럴땐 어쩔 수 없이 babel-loader를 사용하여야 한다)

SSR?

지원이 되는지는 확인해보지 못했다.. 갖고 있는 레포가 SSR을 사용하고 있지 않다보니 확인할 길이 없었다. webpack 보다 빠른 snowpack은 지원한다!

결론

빌드 속도로 고생받고 있다면,
1. 빌드할 때 필요없는 플러그인, 로더를 줄이는 것을 1차 목표로 하자
2. babel-loader, ts-loader의 속도가 너무 느리다면 esbuild-loader를 한 번 적용해보자

profile
Frontend developer

3개의 댓글

comment-user-thumbnail
2021년 9월 29일

안녕하세요!
Speed Measure Plugin, Esbuild-loader, 불필요한 플러그인 비활성화한 것을 테스트해보고싶은데!
테스트하실때 사용하신 레포지토리나 config정보좀 볼 수 있을까요~?

1개의 답글
comment-user-thumbnail
2022년 5월 16일

패키지매니저는 npm을 사용하셨나요? 저는 yarn3 인데 esbuild-loader를 못찾아 zero-install을 못하네요ㅠ

답글 달기