사내에서 발표했던 것을 정리한 내용이다
개발자는 코드를 수정하자마자 바로 반영되기를 원한다. 하지만 그 시간 차가 20분 정도 난다고 하면 얼마나 짜증이 날까..? 이 시리즈는 배포 속도를 최대한 끌어올려 본 경험을 정리한 것이다.
보통 프론트엔드에서의 배포라고 함이라면,
master
에 머지 (PR이든 직접 Push든)CI
에서 해당 레포가 머지됬단 걸 확인(Webpack)
를 실행하여 빌드 시작위의 작업을 실행하는데 현재 7분이 걸린다. dev(alpha)
, production
순으로 동기적으로 하면 총 14분이 배포에만 걸리는 시간이다. 끔찍하지 않은가?
전 회사에선 배포까지 20분이 넘게 걸렸었던걸로 기억하는데.. 꽤나 고통받았던 기억 밖에 없다.
(CI 스크립트를 테스트 하기 위해선 정말 시간과의 싸움이었다)
어쨌든, 배포 하는 시간 중에서 빌드
할 때가 너무 느려서 골치를 앓던 중(배포 속도 꼴지였음 ㅎㅎ)
사내 슬랙에서 동료 프론트 엔지니어분이 흥미로운 번들러를 알려주셔서 한 번 시도해보기로 했다.
내가 모든걸 결정할 수 있기 때문에 아무런 방해없이 과감하게 시도해 보기로 하였다.
참고, 기술스택:
- Webpack 4, React, Typescript
💡 (Speed Measure Plugin을 설치하면, 웹팩 빌드 소요시간을 파악할 수 있다)
⚠️ 위 사진은 이미 esbuild를 적용하고 난 뒤의 모습. 그럼에도 순수 빌드 타임만 2분 25초가 걸린다.
문제의 이유는 명확했다. 빌드할 때 쓸데없는 수많은 플러그인과 로더가 돌아가고 있기 때문이다.
그 중에서 유독 시간을 잡아 먹는 것들이 있었는데 다음과 같았다:
import A from 'a/b/c';
의 구문은 case sensitive 하지 않아서 실제로 폴더 이름이 A/B/C
여도 import
가 정상적으로 동작하는데, docker와 같은 리눅스 환경에서 돌아갈 때엔 case-sensitive해서 대소문자가 다르면 에러를 내뿜는다. 이를 사전에 알려주는 플러그인👀 SMP 플러그인을 사용하지 않더라도, 대부분 로더나 플러그인에 debug 옵션이 있어서, 해당 옵션을 켜서 어디에 시간이 많이 소요되는지 파악할 수 있다.
많은 로더, 플러그인들을 구글링하여 배포할 때 필요없는 것들을 골라낼 수 있었다. 사실 이 부분은 배포할 때 안정성을 추구한다면, 포함시켜도 상관없다.
husky
를 통해 commit 할 때에도 lint가 돌도록 하였다. esbuildMinifyPlugin
이 존재하므로 옮겨보자해당 플러그인, 로더들을 교체하고 난 뒤 시간을 다시 재보았다.
🤨 2분 26초 -> 11초
418개의 모듈을 5초만에 처리하는 esbuild-loader는 다른 typescript loader와 비교해서 얼마나 빠른걸까?
한 번 비교해봤다.
개발 환경 세팅을 다시 되돌리기 귀찮아서 esbuild가 적용된 결과 스크린샷만 있는데, ts-loader, babel-loader와 esbuild-loader 속도비교를 해보았다.
1번 주자는 모두가 잘 쓰고있는 ts-loader이다. minify, uglify 플러그인은 terser를 사용했다.
1분 10초
만약 transpileOnly
옵션을 키고 babel-loader를 사용한다해도 이것보다 더 느려질 것이다.
(TS => ts-loader => js => babel-loader => js) (으악 끔찍..)
- 웬만하면 ts-loader보다는, 유용성이 더 높은 babel-loader를 사용해보자
- ts-loader: 별다른 옵션을 설정하지 않으면 타입체킹을 하기 때문에 굉장히 느리다. 또한 개발 환경에서는 캐시 플러그인을 따로 사용해야지 속도가 어느정도 보장이 된다.
- awesome-typescript-loader: 일부 작업의 부하로 컴파일 속도가 느리다고 함
- babel-loader: babel은 버전 7부터 typescript도 이해하는데 심지어 babel 전용 플러그인과 폴리필을 제공할 수 있으므로 그 유용성은 ts-loader보다 높다.
42초
11초
순수 esbuild 환경(CLI)
이 아닌 웹팩 환경에서 실행했음에도 이렇게나 빠른 속도를 보장하고 있다. 만약 웹팩 환경을 벗어날 수 있다면 아마 1초만에 끝났을 것이라 생각한다. 이 11초라는 수치는 esbuild 입장에선 굴욕 수준..
ts-loader, babel-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()
]
}
tsc —noEmit
을 실행하거나, fork-ts-checker-webpack-plugin
을 사용하여야 함 /** @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)
babel-loader
를 사용하여야 한다)지원이 되는지는 확인해보지 못했다.. 갖고 있는 레포가 SSR을 사용하고 있지 않다보니 확인할 길이 없었다. webpack 보다 빠른 snowpack은 지원한다!
빌드 속도로 고생받고 있다면,
1. 빌드할 때 필요없는 플러그인, 로더를 줄이는 것을 1차 목표로 하자
2. babel-loader, ts-loader
의 속도가 너무 느리다면 esbuild-loader
를 한 번 적용해보자
안녕하세요!
Speed Measure Plugin, Esbuild-loader, 불필요한 플러그인 비활성화한 것을 테스트해보고싶은데!
테스트하실때 사용하신 레포지토리나 config정보좀 볼 수 있을까요~?