안녕하세요! 오픈소스컨설팅 Playce-Dev 팀의 FE 개발자 이정현입니다.

저번 포스팅에서는 CRA 없이 배우는 Webpack이라는 글로 Webpack을 직접 구성하고 설정해보는 시간을 가져보았는데요. 이번 포스팅에서도 Webpack을 다루게 되었습니다! Webpack을 공부하게 되며 저희 제품인 RoRo의 빌드 속도를 높일 수 있다면 좋을 거 같다는 의견을 받아 이번 포스팅을 진행하게 되었습니다!

과연 빌드 속도를 높이고 좋은 결과를 이끌어낼 수 있을지 알아보도록 하겠습니다. 🤔

Webpack을 Customizing 해보자

React App을 생성하기 위해 CRA(Create React App)를 사용하면 기본적인 Webpack 설정 파일들이 숨겨져 구성되어 있습니다. 개발을 하다 보면 Webpack 설정을 변경해야 하는 경우가 있는데요. 그럴 때 Webpack 설정을 어떻게 변경할 수 있는지 알아보도록 하겠습니다.

1. Eject

yarn eject

eject는 CRA로 생성된 프로젝트의 설정을 custom 하기 위한 방법 중 하나로, CRA를 통해 제공된 숨겨져 있는 모든 설정들과 패키지들의 의존성을 살펴볼 수 있습니다.

⚠️ 한번 eject를 수행하게 되면 이전 상태로 되돌아갈 수 없습니다.

Eject는 신중해야 한다

eject는 프로젝트를 자유롭게 customizing 가능하다는 강점이 있지만 CRA로 프로젝트를 생성한 몇 가지 이점을 포기하게 됩니다.

  1. CRA의 모든 configuration을 직접 유지보수 해야 합니다.
  2. One Build Dependency의 장점을 잃어버리게 됩니다. 작업 도중 필요한 패키지를 추가적으로 설치한다거나, 삭제할 때 항상 다른 패키지들과의 의존성을 고려해줘야 합니다.
  3. React App의 버전업에 대한 구성요소 업그레이드를 할 수 없습니다.
  4. 기존에 보이지 않던 많은 설정 파일들이 보이게 되어 디렉토리 구조가 복잡해지고 지저분해집니다.

eject를 하게 되면 이러한 문제들이 발생하게 되고 eject를 할지에 대한 결정은 번복할 수 없기 때문에 아주 신중해야 됩니다.

그렇다면 eject를 하지 않고 Webpack 설정을 변경할 수는 없을까요?

다행히도 eject를 하지 않고도 기존의 CRA 프로젝트를 customizing 할 수 있는 방법이 있습니다!

한번 그 방법들을 알아보도록 하겠습니다. 🧐 (물론, eject를 하는 것보다 자유롭지 못합니다)

2-1. react-app-rewired

우리는 eject 하지 않고 라이브러리의 도움을 받아 Webpackcustomizing 할 수 있습니다.

react-scripts를 대체하는 react-app-rewired를 이용하여 Webpack 설정에 새로운 설정을 주입시켜 custom 해보도록 하겠습니다.

yarn add react-app-rewired

프로젝트 루트 디렉토리에 config-overrides.js 파일을 생성하여 추가할 Webpack config들을 정의하고 확장시킬 수 있습니다.

/* config-overrides.js */

const rewireReactHotLoader = require('react-app-rewire-hot-loader');
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');

const smp = new SpeedMeasurePlugin();

module.exports = function (config, env) {
  config = smp.wrap(rewireReactHotLoader(config, env), { ...config });
  return config;
};

package.json의 스크립트 실행 명령어를 변경해줍니다.

/* package.json */

"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test": "react-app-rewired test"
}

이렇게 설정해줌으로써 react-app-rewired를 사용할 준비가 되었습니다. 이와 비슷한 라이브러리에 대해 한 가지 더 짚어가며 어떤 설정들을 바꿔볼지 알아보겠습니다!

2-2. CRACO

craco (Create-React-App Configuration Override) 또한 앞서 언급한 react-app-rewired처럼 React 프로젝트를 eject 하지 않고 customizing 가능하게 해주는 라이브러리 중 하나입니다.

yarn add @craco/craco

프로젝트 루트 디렉토리에 craco.config.js파일을 생성하여 추가할 Webpack config들을 정의하고 확장시킬 수 있습니다.

const smp = new SpeedMeasurePlugin()

module.exports = {
  webpack: smp.wrap({
    configure
  })
}

package.json의 스크립트 실행 명령어를 변경해줍니다.

/* package.json */

"scripts": {  
	"start": "craco start",  
	"build": "craco build",  
	"test": "craco test",  
	"eject": "craco eject"
}

이렇게 react 프로젝트를 eject 하지 않고 Webpack 설정들을 오버라이딩할 수 있는 유용한 라이브러리들에 대해 알아보았습니다.

이제 이 포스팅의 주목적인 어떻게 빌드 속도를 향상시킬지 알아보도록 하겠습니다! Webpack을 custom 하여 속도를 향상시키는 방법으로 eject를 이용한 경우와 craco 라이브러리를 사용한 두 가지 방법으로 소개 드리려고 합니다.

먼저 프로젝트를 직접 eject 하여 빌드 속도를 개선하는 방법을 살펴보도록 하겠습니다.


속도가 느린 이유가 뭘까? 원인 파악부터!

먼저 속도를 개선하기 위해서 어디서 시간이 많이 소요되는 것인지 알아야 될 필요가 있다고 생각하였습니다.

다행히도 Webpack에 적용되어 있는 많은 기능들 중 어떤 게 가장 많은 시간을 잡아먹으며 각 기능이 이루어지는데 시간이 얼마나 걸리는지 알려주는 plugin이 존재한다 하여 얼른 적용해 확인해보았습니다.

Webpack에 설정된 plugin들과 loader들의 속도를 측정해주는 plugin으로 speed-measure-webpack-plugin을 사용해보았습니다.

yarn add -D speed-measure-webpack-plugin

해당 플러그인을 사용하기 위해 위의 명령어를 입력하여 설치해줍니다.

1. eject 한 webpack.config.js에 SMP 적용하기

/* webpack.config.js */

const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();

module.exports = function (webpackEnv) {
	return smp.wrap({
		mode: ...,
		entry: ...,
		...
	});

}

react에서 만들어준 webpack.config.js 파일에 SMP를 어떻게 적용시키는지 코드를 살펴보겠습니다.

방법은 간단합니다. mode나 entry, output, plugin들이 정의되어 있는 return 함수에 smp.wrap 을 이용하여 감싸주기만 하면 됩니다!

2. eject 하지 않고 react-app-rewired에 SMP를 적용하기

/* config-overrides.js */

const rewireReactHotLoader = require('react-app-rewire-hot-loader');
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');

const smp = new SpeedMeasurePlugin();

module.exports = function (config, env) {
  config = smp.wrap(rewireReactHotLoader(config, env), { ...config });
  return config;

};

기존에 추가적인 로더를 적용시키기 위해 react-app-rewired를 사용하고 있는 것을 확인하였고 rewireReactHotLoader도 사용중이었습니다.

해당 설정 파일에 SMP를 적용하는 코드를 작성해주었습니다.

2. eject 하지 않고 CRACO에 SMP를 적용하기

/* craco.config.js */

const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');

const smp = new SpeedMeasurePlugin();

module.exports = {
  webpack: {
    configure: (webpackConfig) => {
      return smp.wrap(webpackConfig);
    },
  },
};

속도를 확인해보자

저희 roro의 빌드 속도를 체크해보았습니다. 측정해보니 5mins 44.95secs 로 빌드할 때마다 너무 느리다 생각했던 것이 실제로도 엄청 오랜 시간이 걸린다는 것을 확인할 수 있습니다.

뭐가 이렇게 느려?

위의 결과를 토대로 속도가 유독 느린 pluginloader들에 대해 어떤 기능을 하는 것들인지 살펴보도록 하겠습니다.

  • IgnorePlugin

번들링시 무시할 파일들을 정규 표현식 또는 필터 함수로 설정합니다.

  • optimize-css-assets-webpack-plugin

HtmlWebpackPlugin 이 html을 압축하는 plugin이라면, optimize-css-assets-webpack-plugin는 css를 압축시키는 plugin입니다.

  • babel-loader

es6 이상의 문법을 es5 이하의 코드로 transpiling 해주는 것을 babel이라고 하며 이러한 babel과 webpack을 연동시켜주는 것이 babel-loader입니다.

  • css-loader, postcss-loader, resolve-url-loader, sass-loader
    • postcss-loader: js 플러그인을 통해 스타일을 변형하는 도구입니다.
    • sass-loader: sass-loader는 webpack에 의해 관리 및 유지되는 기능이 아닌 third-party 패키지로, 브라우저는 sass를 인식할 수 없기 때문에 css로 transpiling 해줍니다.
    • css-loader: css 파일을 js 코드로 변환합니다.
  • @svgr/webpack: 원래 React에서는 svg 파일을 바로 렌더링할 수 없습니다. 하지만 우리는 svg를 간편하게 리액트 컴포넌트로 사용하고 있는데요. 이런 일이 가능한 건 svgr 덕분입니다!
  • file-loader: 파일을 모듈로 사용할 수 있게 만들어주는 처리를 합니다. 실제로 사용하는 파일을 output directory로 옮깁니다.
  • url-loader: 파일을 base64 url로 변환하는 처리를 해줍니다. 파일을 옮기는 작업이 아닌 변환해서 output directory에 저장합니다.

어떻게 속도를 개선할 수 있을까

웹팩보다 100배 빠른 번들러가 있다?!

🎉 esbuild 등장 🎉

esbuild는 새롭게 떠오르는 차세대 번들러로 Go와 JS/TS로 개발되었으며 2020년 초에 처음 출시되었습니다.

Github: https://github.com/evanw/esbuild

공식 문서: https://esbuild.github.io/

최신 버전 (2022.06.29 기준): v0.14.47

독보적인 스피드를 자랑합니다…

어떻게 이렇게 빠를 수 있는 거죠?

그 이유로는 이러합니다.

  • 네이티브 코드로 컴파일되는 “Go” 언어로 작성되었습니다.
  • 코드 파싱, 출력과 소스맵 생성을 모두 병렬로 처리합니다.
  • 불필요한 데이터 변환과 할당이 없습니다.

⚠️ 주의할 점은 esbuild자바스크립트를 위한 번들러로 타입스크립트의 타입 체킹이나 프론트엔드 프레임워크의 지원, 핫 모듈 리로딩 등을 포함한 개발 서버 등 번들링과 관계 없는 기능들은 지원하고 있지 않습니다.

Webpack과 ESbuild, 왜 고민해? 둘 다 쓰면 되지!

저희 팀에서 해결하고자 했던 속도 이슈를 단번에 해결할 수 있는 번들러로 Webpack 대신 ESBuild를 새로 도입하면 좋겠지만 기존에 있던 것을 새로 도입해 사용한다는 게 말처럼 쉽지 않습니다. 또한 이미 다양한 지원이 존재하고 래퍼런스가 넘쳐나는 Webpack 생태계를 버리기에 아쉽다는 점도 있습니다. 그에 비해 ESBuild는 래퍼런스도 아직 많지 않고 지원되는 기능의 범위도 필수적인 것 이외에 한정적입니다.

하지만 이 좋은 ESBuild 존재를 이미 알아버린 이상 사용하지 않기엔 너무너무 아깝다면?

이럴 때 Webpack과 함께 사용할 수 있도록 esbuild-loader 라는 로더를 지원하고 있기 때문에 속도가 빠른 ESBuild의 이점과 Webpack을 함께 사용하여 윈윈할 수 있습니다! 😎

그렇다면 esbuild-loader를 활용해 빌드 속도 향상을 위한 Webpack + ESbuild 방식을 적용해보도록 하겠습니다.


1. Eject로 Webpack Custom 하기

먼저, Webpack을 Customizing 하기 위해 react project를 eject 해주었습니다.

yarn eject

앞서 알려드렸듯이 저희 RoRo 프로젝트에서는 Webpack에 추가적인 설정을 하기 위해 react-app-rewired를 사용 중에 있었습니다. 하지만 프로젝트를 eject 해버렸으니 react-app-rewired도 필요없게 되었죠!

scripts 실행 명령어 수정

package.json의 스크립트 실행 명령어를 react-app-rewired를 적용하기 전으로 수정해줍니다.

/* package.json */

"scripts": {  
-	"build": "react-app-rewired build"
+	"build": "node scripts/build.js"

}

esbuild-loader 설치

yarn add esbuild-loader

먼저 babel-loader 또는 ts-loader를 대신하게 될 esbuild-loader를 설치해줍니다.

ESBuild-loader 적용하기

Javacript & JSX

/* webpack.config.js */

module.exports = {
    module: {
      rules: [
-       {
-         test: /\\.js$/,
-         use: 'babel-loader',
-       },
+       {
+         test: /\\.js$/,
+         loader: 'esbuild-loader',
+         options: {
+           loader: 'jsx',  // Remove this if you're not using JSX
+           target: 'es2015'  // Syntax to compile to (see options below for possible values)
+         }
+       },
        ...
      ],
    },
  
}

TypeScript & TSX

/* webpack.config.js */

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'
+			tsconfigRaw: require('./tsconfig.json')     // tsconfig.json 사용 중이면 추가해주세요
+         }
+       },
        ...
      ]
    },
  
}

JS를 사용하는지 TS를 사용하는지에 따라 맞는 설정을 해주시면 됩니다.

한 가지 주의해야 할 점은 앞서 알려드렸듯이 esbuild자바스크립트를 위한 번들러로 번들링과 관계 없는, 타입스크립트의 타입 체킹을 지원하고 있지 않습니다. 그렇기 때문에 타입 체킹을 위한 ForkTsCheckerWebpackPlugin 플러그인을 사용하길 권고드립니다!

const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');

module.exports = {
    ...,

    plugins: [
      new ForkTsCheckerWebpackPlugin()
    ]
  
}

ForkTsCheckerWebpackPlugin 플러그인은 분리된 프로세스에서 타입 체크를 실행합니다. CRAwebpack.config.js 에서는 이미 적용되어져 있는 플러그인이기 때문에 따로 추가하지 않아도 됩니다!

ESBuildMinifyPlugin으로 코드 최적화하기

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

  module.exports = {
    ...,

    optimization: {
      minimizer: [
+       new ESBuildMinifyPlugin({
+         target: 'es2015'  // Syntax to compile to (see options below for possible values)
+         css: true  // optimize-css-assets-webpack-plugin 대체
+       })
      ]
    
},

esbuild의 또다른 장점으로는 자체적으로 빠르다는 점도 있지만 Minification도 함께 지원한다는 것입니다.

기존에는 transpiling을 위해 babel-loader를 사용하고 파일 축소를 위해 Terser이나 css-minimizer-plugin을 적용해야 했습니다. 하지만 esbuild를 사용한다면 Terser이나 UglifyJs 같은 minifiers 들도 ESBuildMinifyPlugin으로 대체하여 사용할 수 있기 때문에 여러가지 로더와 플러그인을 수행하는 것보다 더 좋은 속도를 낼 수 있습니다.

💁🏻‍♀️ minify란?

코드를 축소하는 행위로, 코드 최소화는 코드를 최적화하여 공간을 절약하고 페이지 로드 시간을 줄이며 웹사이트 대역폭 사용량을 줄이는 것을 의미합니다. 코드 경량화로 가장 중요한 것은 기능을 변경하지 않고 코드를 최소화한다는 것입니다.

위의 코드를 보시면 css: true 옵션 하나를 설정해주는 것만으로도 기존에 사용하던 css 최적화를 위한 CssMinimizerWebpackPlugin 또는 OptimizeCSSAssetsPlugin을 사용하지 않고 같은 성능을 낼 수 있습니다.

하지만 이것은 아래와 같이 MiniCssExtractPluginwebpack.config.js에서 사용되고 있을 때 가능한 이야기라는 것을 알아야 합니다!

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  module: {
        rules: [
          {
            test: /\.css$/i,
            use: [
              MiniCssExtractPlugin.loader,
              'css-loader'
            ]
          }
        ]
      },
    }
  plugins: [
      new MiniCssExtractPlugin()
  
]

MiniCssExtractPlugin 또한 CRAwebpack.config.js 에서는 이미 적용되어져 있는 플러그인이기 때문에 따로 추가하지 않아도 됩니다!

CSS-in-JS

만약 styled-componentsemotion과 같은 CSS-in-JS 방식을 사용하고 계신다면 아래와 같은 방식으로 CSS를 최적화 할 수 있습니다.

  module.exports = {
    ...,

    module: {
      rules: [
        {
          test: /\.css$/i,
          use: [
            'style-loader',
            'css-loader',
+           {
+             loader: 'esbuild-loader',
+             options: {
+               loader: 'css',
+               minify: true
+             }
+           
}

결과를 확인해보자

  • babel-loader (5 mins, 0.538 secs) → esbuild-loader (1 min, 40.13 secs)
  • TerserPlugin (38.36 secs), OptimizeCSSAssetsPlugin (3.93 secs) → ESBuildMinifyPlugin (2.71 secs)

ESBuild를 사용하게 되면서 babel-loaderesbuild-loader로 대체되고 ESBuildMinifyPlugin으로 TerserPluginOptimizeCSSAssetsPlugin 을 대체함으로써 속도가 엄청나게 향상된 것을 확인할 수 있습니다.


2. CRACO로 Webpack Custom 하기

앞서 CRA 프로젝트를 eject 하여 webpack.config.js 를 다뤄 esbuild-loader를 적용하는 방법을 알아보았습니다.

하지만 eject를 하게 되면 CRA로 프로젝트를 생성한 이점을 포기하게 되고 그에 따른 불편함을 안고 가야 하는데요. 저희 팀에서는 eject에 대한 risk를 감수하기보다 Webpack 설정을 오버라이딩 할 수 있는 라이브러리를 사용해 적용하기로 하였습니다!

기존에는 react-app-rewired를 사용해보았는데요 이번에는 CRACO를 사용해보겠습니다.

CRACO, CRACO-ESBuild 설치

 yarn add --dev craco-esbuild @craco/craco

Webpackesbuild-loader를 적용함에 있어 eject를 했을 땐 더이상 사용하지 않을 babel-loader 등을 실행하지 않도록 하기 위해 지우거나 주석 처리를 하면 됐는데, Webpack config를 overriding 하는 라이브러리를 사용할 땐 이와 같은 기능들을 어떻게 실행되지 않도록 하는지 의문이었습니다.

create-react-app-esbuild 라는 패키지가 있는 것을 알게 되었고 이 라이브러리를 적용시킴으로써 ESBuild를 사용하며 실행되지 않아도 되는 기능들을 알아서 대체해줄 수 있었습니다.

https://github.com/pradel/create-react-app-esbuild

CRACO를 이용해 Config 를 Custom 해보자

프로젝트 루트 디렉토리에 craco.config.js 파일을 생성하여 추가할 webpack config들을 정의하고 확장시킬 수 있습니다.

esbuild

const CracoEsbuildPlugin = require('craco-esbuild');

module.exports = {
  plugins: [
    {
      plugin: CracoEsbuildPlugin,
      options: {
        // includePaths: ['/external/dir/with/components'], // Optional. If you want to include components which are not in src folder
        esbuildLoaderOptions: {
          // Optional. Defaults to auto-detect loader.
          loader: 'tsx', // typeScript인 경우 tsx로 설정
          target: 'es2015',
        },
        esbuildMinimizerOptions: {
          // Optional. Defaults to:
          target: 'es2015',
          css: true, // if true, OptimizeCssAssetsWebpackPlugin will also be replaced by esbuild.
        },
        skipEsbuildJest: false, // Optional. Set to true if you want to use babel for jest tests,
        esbuildJestOptions: {
          loaders: {
            '.ts': 'ts',
            '.tsx': 'tsx',
          },
        },
      },
    },
  ],

};

plugin 선언부에 CracoEsbuildPlugin를 이용하여 esbuild를 적용시켜 줄 수 있습니다.

@svgr/webpack

저는 esbuild를 적용하는 과정에서 이러한 오류를 마주하게 되었는데요.

저희는 svg를 ReactComponent화 하여 사용하고 있었습니다. 원래 React에서는 svg 파일을 바로 렌더링할 수 없지만 svgr/webpack 덕분에 현재처럼 svg를 ReactComponent화 하여 사용할 수 있었습니다. 해당 로더가 제대로 적용되지 않는 거 같아 아래의 코드처럼 추가해주었습니다.

module.exports = {
  webpack: {
    configure: (webpackConfig) => {
      webpackConfig.module.rules.unshift({
        test: /\.(svg)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: '@svgr/webpack',
            options: { esModule: false },
          },
        ],
      });
  
      return webpackConfig;
    },
  },

}

이렇게 적용해주니 더이상 svg 파일들이 오류 없이 잘 보여지는 것을 확인하였습니다!

하지만 위와 같은 오류 외에도 다른 예상치 못한 Side Effect가 발생했는데요. 🥲

기존에 알맞은 크기로 예쁘게 보여지던 svg 이미지들의 크기가 엄청나게 커지면서 사이즈가 상이해진 이슈가 있었습니다. 이를 해결하기 위해서 아래와 같은 코드를 추가해주니 다행히 다시 정상적으로 예쁘게 보여질 수 있었습니다!

options: {
  svgoConfig: {
    plugins: [
      {
        removeViewBox: false,
      },
    ],
  },

},

SMP (speed-measure-webpack-plugin)

속도 측정을 위해 SMP를 적용하여 확인해보기로 하였습니다.

const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();

module.exports = {
  webpack: smp.wrap({
    configure: (webpackConfig) => {
      return webpackConfig;
    }
  })

}

처음에는 smp.wrapWebpack 선언부에 감싸주었는데요. 이렇게 하니 아래와 같은 문제가 있었습니다.

CRACO를 사용하는 건 새로 정의하거나 변경된 cofing들을 오버라이딩 하는 방식인데 기존에 잘 작동되던 Plugin들이 실행되지 않는 문제를 마주쳤습니다.

그래서 해당 문제를 해결하기 위해 smp.wrap을 감싸주는 위치를 수정해보았습니다.

const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();

module.exports = {
  webpack: {
    configure: (webpackConfig) => {
      return smp.wrap(webpackConfig);
    },
  },

}

이렇게 수정해주니 기존의 Plugin들도 정상 작동하는 걸 확인했습니다!

전체 코드

/* craco.config.js */

const CracoEsbuildPlugin = require('craco-esbuild');
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');

const smp = new SpeedMeasurePlugin();

module.exports = {
  webpack: {
    configure: (webpackConfig) => {
      webpackConfig.module.rules.unshift({
        test: /\.svg$/,
        use: [
          {
            loader: '@svgr/webpack',
            options: {
              svgoConfig: {
                plugins: [
                  {
                    removeViewBox: false,
                  },
                ],
              },
            },
          },
        ],
      });

      return smp.wrap(webpackConfig);
    },
  },
  plugins: [
    {
      plugin: CracoEsbuildPlugin,
      options: {
        // includePaths: ['/external/dir/with/components'], // Optional. If you want to include components which are not in src folder
        esbuildLoaderOptions: {
          // Optional. Defaults to auto-detect loader.
          loader: 'tsx', // typeScript인 경우 tsx로 설정
          target: 'es2015',
        },
        esbuildMinimizerOptions: {
          // Optional. Defaults to:
          target: 'es2015',
          css: true, // if true, OptimizeCssAssetsWebpackPlugin will also be replaced by esbuild.
        },
        skipEsbuildJest: false, // Optional. Set to true if you want to use babel for jest tests,
        esbuildJestOptions: {
          loaders: {
            '.ts': 'ts',
            '.tsx': 'tsx',
          },
        },
      },
      plugin: CracoEsbuildPlugin,
    },
  ],
};

실행하기

package.json의 스크립트 실행 명령어를 변경해줍니다.

/* package.json */

"scripts": {  
	"start": "craco start",  
	"build": "craco build",  
	"test": "craco test",  
	"eject": "craco eject"

}

명령어를 수정했다면 빌드 명령어를 입력해 실행해봅시다.

yarn build

결과를 확인해보자

ESBuild가 적용되어 속도가 빨라지고 정상 작동하는 것을 확인할 수 있습니다!


과연 속도가 개선되었을까?

ESBuild를 적용하기 전후의 속도를 비교해보도록 하겠습니다.

esbuild 적용 전
esbuild 적용 후

babel-loaderTerserPlugin, OptimizeCSSAssetsPlugin을 사용하던 기존 설정에서는 빌드 속도가 5mins, 44.95 secs 라는 엄청나게 긴 시간이 소요되었습니다.

하지만 ESBuild를 사용해보니 3min, 59.39 secs 라는 시간이 소요되고 빌드 시간이 2분이나 단축된 걸 확인 할 수 있습니다.

ESBuild를 사용하게 되면서 babel-loaderesbuild-loader로 대체되고 ESBuildMinifyPlugin으로 TerserPluginOptimizeCSSAssetsPlugin 을 대체함으로써 속도가 엄청나게 향상된 것을 확인할 수 있습니다.


마무리하며..

빌드 속도를 높이는데 시간을 2분이나 단축시키면서 나름 성공적인 결과를 이끌어냈다고 생각합니다.

하지만 ESBuild를 사용하기에 아직 이른 감도 없지 않습니다. ESBuild아직 버전 1.0도 나오지 않은 상태이고 적용하기 어렵진 않았지만 래퍼런스가 정말 적어 참고할 문서가 많지 않다는 게 마음이 좋지 않았습니다… 또한 타입스크립트를 지원하지만 타입 체크를 해주지 않아 ForkTsCheckerWebpackPlugin 라는 별도의 플러그인을 사용해야 하고 문법을 완벽하게 지원하지 않습니다.

이러한 이유로 ESBuild를 단독으로 사용하기엔 시기상조라 생각하지만 Webpackesbuild-loader 조합으로 함께 사용하는 것은 꽤 괜찮은 방법 같아 보입니다.

결론적으로는 ESBuild를 사용하고 로더와 플러그인을 줄임으로써 속도가 향상될 수 있었습니다. 웹팩 공식 문서에서도 최대한 로더와 플러그인을 줄여야한다고 하는데요. 먼저, 빌드시 필요없는 플러그인, 로더를 줄이는 것으로 속도를 향상시키는 것을 목표로 하면 좋을 것 같습니다!

참고:

https://webpack.js.org/

https://github.com/privatenumber/esbuild-loader

https://github.com/stephencookdev/speed-measure-webpack-plugin

https://github.com/pradel/create-react-app-esbuild#readme

https://velog.io/@lingodingo/%EB%B9%8C%EB%93%9C-%EC%86%8D%EB%8F%84%EB%A5%BC-%EC%98%AC%EB%A0%A4%EB%B3%B4%EC%9E%90feat.-esbuild

이정현
Developer

오픈소스컨설팅의 Front-End 개발자 이정현입니다. 한걸음 꾸준히 나아가며 한계를 뛰어넘기 위해 도전합니다.

Leave a Reply

Your email address will not be published. Required fields are marked *