홍승아블로그

esbuild

esbuild 특징

  • Go언어를 기반으로 작성되었고 정적(네이티브) 코드 방식으로 컴파일 프로그래밍 언어로 작성된 코드를 기계가 이해할 수 있는 기계어로 바뀌는 변환 방식은 총 3가지로 표현됩니다.
    • 정적 컴파일
      • 컴파일 진행 시 전체를 기계어로 변환되기 때문에 전체 실행시간이 짧아진다는 장점이 있음.
      • 단, 컴파일이 오래 걸리고 플랫폼(Mac, Window)에 맞게 다르게 컴파일이 진행해야하는 단점이 있음
    • 인터프리터
      • 프로그래밍 언어를 한 문장씩 읽고 기계어로 바꾸는 방식, 조금씩 바꾸고 바로 실행하기 때문에 초기 동작이 빠름(자바스크립트 채용)
      • 단, 전체 실행 시간은 정적 컴파일에 비해 드리며, 같은 코드를 발견하더라도 매번 같은 방식으로 번역해야해서 비효율적
    • JIT(Just in Time)
      • 번역했던 문장을 번역해서 캐시에 저장해놓고, 혹여 동일한 문장을 번역 시 캐시에 저장된 기계어를 사용해서 실행하는 방식
      • 정적 컴파일, 인터프리터 방식을 혼합해서 사용(V8 엔진) esbuild 방식은 GO 언어 기반으로 작성되었고, 정적 컴파일 코드 방식으로 컴파일을 진행함으로 전체 실행시간을 줄여줄 수 있다.
  • 병럴 처리에 많이 사용 성능 개선에 한 부분으로는 병렬 처리도 한 몫을 하고 있습니다. 자바스크립트 엔진은 싱글 스레이드 기반으로 동작하기 때문에 하나의 쓰레드에서만 동작하기에 처리속도에 제한이 있습니다. go 언어로 작성 된 esbuild는 운영 체제에 병렬 처리를 사용하고 있으며, cpu 사용량을 최대한 사용해서 번들링을 수행하고 있어서 성능이 높다고 하네요.
  • 메모리가 효율적으로 사용 CPU 캐시 사용량을 극대화 해서 AST 데이터 변환을 최대한 지양하고 재사용 진행

위에 3가지 특징을 통해서 전체 빌드 성능을 10-100 배 정도 빠르게 컴파일이 가능하다고 하네요^^

Untitled

esbuild-loader 사용 이유

서비스를 빌드 속도 개선을 위해서 어디서 시간이 많이 소요되는지 확인하기 위해서 speed-measure-webpack-plugin을 사용하여서 현재 빌드 시간을 측정해보기로 했습니다.

빌드 시 각 Loader, Plugin, 전체 사용 시간을 측정해서 알려주는 Plugin으로써 아래와 같이 설치해서 사용이 가능합니다.

  • Loader
    • 빌드 과정에서 각 파일을 import 혹은 load할 때 모듈의 소스코드를 변형시키는 전처리 과정을 수행
    • webpack은 기본적으로 JS / JSON 파일만 이해 → 그 외에 웹자원((HTML, CSS, Images, 폰트 등)에 대해서는 변환을 위한 작업
  • Plugin
    • 로더는 파일을 해석하고 변환하는 과정에 관여하는 반면, 플러그인은 해당 결과물의 형태를 바꾸는 역할
    • 기본적인 동작에 추가적인 기능을 제공하는 속성

빌드 속도 측정

Untitled

yarn add -D speed-measure-webpack-plugin

속도를 측정해보자.

측정을 해보니 빌드 시간 느린 순으로 babel-loader, Terser Plugin, OptimizeCssAssetsWebpackPlugin 정도가 오래 걸리는 다는 것을 확인 할 수 있었습니다.

  • 성능 측정에 대한 Loader, Plugin 정리
    • babel-loader
      • es6 이상의 문법을 es5 이하의 코드로 transpiling 해주는 것을 babel이라고 하며 이러한 babel과 webpack을 연동시켜주는 것이 babel-loader입니다.
    • optimize-css-assets-webpack-plugin
      • HtmlWebpackPlugin 이 html을 압축하는 plugin이라면, optimize-css-assets-webpack-plugin는 css를 압축시키는 plugin입니다.
    • terser-webpack-plugin
      • 코드를 mangle 하는 과정이나 compress 하는 과정과 같이, 우리가 작성한 코드를 동일한 기능을 제공하는 경량화된 코드로 변환해 주는 일련의 작업을 minify 혹은 minification(코드 경량화) 라고 부르고, 코드 경량화 작업을 해주는 툴을 우리는 minifier라고 합니다. terser-webpack-plugin은는 우리가 작성한 자바스크립트 코드를 프로덕션에서 더욱 경량화된 상태로 제공될 수 있도록 도와주는 plugin 입니다.
    • css-loader, postcss-loader, sass-loader
      • postcss-loader: js 플러그인을 통해 스타일을 변형하는 도구입니다.
      • sass-loader: sass-loader는 webpack에 의해 관리 및 유지되는 기능이 아닌 third-party 패키지로, 브라우저는 sass를 인식할 수 없기 때문에 css로 transpiling 해줍니다.
      • css-loader: css 파일을 js 코드로 변환합니다.
    • IgnorePlugin
      • 번들링시 무시할 파일들을 정규 표현식 또는 필터 함수로 설정합니다.
    • file-loader: 파일을 모듈로 사용할 수 있게 만들어주는 처리를 합니다. 실제로 사용하는 파일을 output directory로 옮깁니다.
    • url-loader: 파일을 base64 url로 변환하는 처리를 해줍니다. 파일을 옮기는 작업이 아닌 변환해서 output directory에 저장합니다.

webpack과 esbuild 장점을 살린 esbuild-loader 도입

위에서 언급한 것처럼 번들러를 esbuild로 도입하면 좋겠지만, 기존에 webpack에서 사용하는 loader, plugin을 esbuild에서 전부 다 지원하지 않고 있고, 또한 이슈가 생겼을 때 많은 래퍼런스 자료를 가지고 있는 webpack을 버리기는 어려운 사정으로 webpack을 유지한 채로 esbuild 속도만 지원받기 위해서 esbuild-loader 도입하게 되었습니다.

webpack + babel/plugin 사용보다는 webpack-[esbuild-loader/minifycss]를 사용할 경우 transpilation과 Minification 단계를 더 빠르게 할 수 있는 대안을 제공하고 있습니다.

Installation

npm install -D esbuild-loader
or
yarn add -D esbuild-loader
or
pnpm add -D esbuild-loader

Quick Setup

esbuild는 확장자에 따라 각 파일을 처리하는 방법을 자동으로 결정

// webpack.config.js
  module.exports = {
      module: {
          rules: [
-             // Transpile JavaScript
-             {
-                 test: /\.js$/,
-                 use: 'babel-loader'
-             },
-
-             // Compile TypeScript
-             {
-                 test: /\.tsx?$/,
-                 use: 'ts-loader'
-             },
+             // Use esbuild to compile JavaScript & TypeScript
+             {
+                 // Match `.js`, `.jsx`, `.ts` or `.tsx` files
+                 test: /\.[jt]sx?$/,
+                 loader: 'esbuild-loader',
+                 options: {
+                     // JavaScript version to compile to
+                     target: 'es2015'
+                 }
+             },
          ],
      },
  }

다른 파일 확장자에게 특정 로더를 강제 적용할 경우

// webpack.config.js
 {
     test: /\.js$/,
     loader: 'esbuild-loader',
     options: {
+        // Treat `.js` files as `.jsx` files
+        loader: 'jsx',

         // JavaScript version to transpile to
         target: 'es2015'
     }
 }

이전 자바스크립트 엔진과 호환을 위한 작업

// webpack.config.js
{
     test: /\.[jt]sx?$/,
     loader: 'esbuild-loader',
     options: {
+        target: 'es2015',
     },
 }

타입스크립트 지원

  • esbuild-loader는 타입스크립트 컴파일 지원
  • root 패스에 tsconfig.json이 있는경우 자동으로 esbuild-loader 로드
  • 단, config파일을 다르게 설정한 경우 아래와 같이 options에 설정
     // webpack.config.js
     {
         test: /\.tsx?$/,
         loader: 'esbuild-loader',
         options: {
    +        tsconfig: './tsconfig.custom.json',
         },
     },

EsbuildPlugin

Minification

  • Esbuild는 자바스크립트 축소를 지원하며 Terser or UglifyJs와 같은 코드 압축에 대한 기능 제공
  • 또한, 설정에 따라서 css 축소 기능도 지원하고 있습니다. 단, css-loader 과 같은 css 로딩 기능이 이미 설정되어 있어야합니다.
  • terser-webpack-plugin, mini-css-extract-plugin/optimize-css-assets-webpack-plugin 대안으로 사용이 가능합니다.
// webpack.config.js
const { EsbuildPlugin } = require('esbuild-loader')

  module.exports = {
      ...,

+     optimization: {
+         minimizer: [
+             new EsbuildPlugin({
+                 target: 'es2015'  // Syntax to transpile to (see options below for possible values)
+                 css: true  // Apply minification to CSS assets
+             })
+         ]
+     },
  }

CSS-in-JS

  • styled-components 나 emotion과 같은 CSS-in-JS 방식을 사용하고 계신다면 아래와 같은 방식으로 CSS를 최적화 제공
// webpack.config.js
module.exports = {
      // ...,

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

Defining constants

  • webpack DefindPlugin을 esbuild-loader의 define 속성을 대체가 가능,
- const { DefinePlugin } = require('webpack')
+ const { EsbuildPlugin } = require('esbuild-loader')

  module.exports = {
      // ...,

      plugins:[
-         new DefinePlugin({
-             'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
-         })
+         new EsbuildPlugin({
+             define: {
+                 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
+             },
+         }),
      ]
  }

CRA + react-app-rewired

esbuild-loader 적용

  • CRA 내부적으로 사용중인 babel-loader rules 삭제
  • esbuild-loader 적용
    • 내장된 babebel-older, babel-runtime plugin 삭제
// config-override.js
{
  // Match `.js`, `.jsx`, `.ts` or `.tsx` files
  test: /\.[jt]sx?$/,
  loader: 'esbuild-loader',
  options: {
    target: 'es2015',
  },
},

const removeBabelImportsFromExternalPaths = (webpackConfig) => {
  const oneOfRule = webpackConfig.module.rules.find((rule) => rule.oneOf);

  if (oneOfRule) {
    oneOfRule.oneOf = oneOfRule.oneOf.filter((rule) => !rule.test || !rule.test.toString().includes('js'))
  }
};

removeBabelImportsFromExternalPaths(modifiedConfig);

EsbuildPlugin 적용

  • CRA 내부적으로 사용중인 terser-webpack-plugin, optimize-css-assets-webpack-plugin 삭제
  • EsbuildPlugin 적용
// config-override.js

modifiedConfig.optimization.minimizer = webpackConfig.optimization.minimizer.filter((plugin) => ['TerserPlugin', 'OptimizeCssAssetsWebpackPlugin'].indexOf(plugin.constructor.name) < 0);;

{
	optimization: {
      minimize: isEnvProduction,
      minimizer: [
        new EsbuildPlugin({
          target: ['es2015'],
          css: true,
        }),
      ],
}

참고페이지

이전글
vite
다음글
react v19(next)