์„œ๋ฒ„๋ฆฌ์Šค Webpack vs Esbuild

geunsuryu-wonderwall
  • #serverless
  • #esbuild
  • #webpack
  • #swc


๋“ค์–ด๊ฐ€๋ฉฐ


์•ˆ๋…•ํ•˜์„ธ์š”! ์›๋”์›”์—์„œ ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ•˜๊ณ  ์žˆ๋Š” ์œ ๊ทผ์ˆ˜๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.
์›๋”์›” ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค๋Š” ๋Œ€๋ถ€๋ถ„ ์„œ๋ฒ„๋ฆฌ์Šค๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๊ณ , serverless-webpack๋ฅผ ํ™œ์šฉํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
์ตœ๊ทผ์— ts-loader/ts-jest ๋ฅผ swc-loader/swc-jest ๋กœ ๋ณ€๊ฒฝํ–ˆ์—ˆ๊ณ ,
์ด๋ฒˆ์— serverless-esbuild ๋กœ ๋ฐ”๊พธ๊ฒŒ ๋œ ๊ณผ์ •๊ณผ ํšจ๊ณผ๋ฅผ ์†Œ๊ฐœํ•ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.


๋น„๊ต ๊ฒฐ๊ณผ๋ถ€ํ„ฐ ๋ฏธ๋ฆฌ ๋ณด๊ณ  ์‹ถ์œผ์‹œ๋ฉด, ์•„๋ž˜ ๋งํฌ๋ฅผ ๋ˆŒ๋Ÿฌ๋ณด์„ธ์š”~

๋น„๊ต ๊ฒฐ๊ณผ ๋ณด๊ธฐ



SWC

SWC๋Š” Rust๋กœ ์ œ์ž‘๋œ ๋นŒ๋“œํˆด์ž…๋‹ˆ๋‹ค. ๋ฒˆ๋“ค๋Ÿฌ๋Š” ์•„์ง ์ž‘์—…์ค‘์œผ๋กœ ๋˜์–ด์žˆ์–ด์„œ, webpack์— swc-loader๋งŒ ์ ์šฉํ•ด์„œ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ์กด์— ์‚ฌ์šฉํ•˜๋˜ nest build์™€ ์†๋„๋ฅผ ๋น„๊ตํ•ด๋ณด๋ฉด, ์—„์ฒญ๋‚œ ์†๋„ ํ–ฅ์ƒ์„ ๋Š๋‚„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๊ฒŒ๋‹ค๊ฐ€ ts-jest๋„ swc-jest๋กœ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๊ต‰์žฅํžˆ ๋นจ๋ผ์ง„ ๊ฑธ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

nest buildswc
๋นŒ๋“œ ์‹œ๊ฐ„10.41s0.67s
ts-jestswc-jest
ํ…Œ์ŠคํŠธ ์‹œ๊ฐ„16s5s

์‚ฌ์šฉ๋œ package.json script๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

    "build:webpack": "nest build --webpack",
    "build:swc": "swc --ignore \"**/*.spec.ts\" --delete-dir-on-start --out-dir dist/src/ src/",

ํ…Œ์ŠคํŠธ๋Š” jest ์„ค์ •์˜ transform ๋ถ€๋ถ„๋งŒ ๋ณ€๊ฒฝํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

    "transform": {
        "^.+\\.(t|j)s$": "ts-jest"
    },
    "transform": {
        "^.+\\.(t|j)s$": "swc-jest"
    },

์‚ฌ์šฉํ•œ swc ์„ค์ • ํŒŒ์ผ(.swcrc)๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

{
    "sourceMaps": true,
    "module": {
        "type": "commonjs",
        "lazy": true
    },
    "jsc": {
        "target": "es2021",
        "parser": {
            "syntax": "typescript",
            "tsx": false,
            "decorators": true,
            "baseUrl": "./"
        },
        "transform": {
            "legacyDecorator": true,
            "decoratorMetadata": true
        },
        "paths": {
            "@src/*": ["./src/*"]
        },
        "keepClassNames": true
    }
}

๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ์˜ ๋นŒ๋“œ ์†๋„์™€ ํ…Œ์ŠคํŠธ ์†๋„๋Š” ํฐ ํ–ฅ์ƒ์ด ์žˆ์—ˆ์ง€๋งŒ, ์‹ค์ œ ๋ฐฐํฌ์—์„œ๋Š” ํฐ ์ฐจ์ด๋ฅผ ๋Š๋‚„ ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.



Esbuild

esbuild๋Š” Go ์–ธ์–ด๋กœ ์ž‘์„ฑ๋œ ๋นŒ๋“œํˆด์ž…๋‹ˆ๋‹ค. serverless-esbuild๋ผ๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ๋„ ์žˆ์–ด์„œ serverless-webpack์„ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


serverless.yml ์— esbuild ํ•ญ๋ชฉ ์„ค์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

plugins:
    - serverless-esbuild

custom:
    esbuild:
        bundle: true
        minify: false
        target: 'node16'
        packager: yarn
        plugins: plugins.js
        external:
            - '@nestjs/microservices'
            - '@nestjs/websockets/socket-module'
            - 'class-transformer/storage'
            - 'nock'
            - 'mock-aws-s3'
            - 'fastify-swagger'
            - 'pg-native'
  • ์ฒ˜์Œ์— external ์„ค์ •์—†์ด serverless package ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋ฉด Error๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ํ•˜๋‚˜์”ฉ external๋กœ ์„ค์ •ํ•ด์ฃผ๋ฉด ์ž˜ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

esbuild๋Š” decorator ์ง€์›์ด ๋˜์ง€ ์•Š์•„์„œ, esbuild-plugin-tsc๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
๋˜ .env ํŒŒ์ผ์„ ๋นŒ๋“œ ์‹œ์ ์— ํฌํ•จ์‹œํ‚ค๊ธฐ ์œ„ํ•œ plugin๋„ ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค.


plugins.js

const esbuildPluginTsc = require('esbuild-plugin-tsc');

module.exports = serverless => {
    const envCopyPlugin = {
        name: 'env-copy',
        setup(build) {
            build.onEnd(() => {
                const fs = require('fs');
                const path = require('path');
                fs.cpSync(`.env.${serverless.service.provider.stage}`, path.join('.esbuild/.build', '.env'), {
                    force: true,
                    dereference: true
                });
            });
        }
    };

    return [esbuildPluginTsc(), envCopyPlugin];
};

๋กœ์ปฌ ๋นŒ๋“œ๋ฅผ ์œ„ํ•œ esbuild.js ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

const esbuild = require('esbuild');
const path = require('node:path');
const esbuildPluginTsc = require('esbuild-plugin-tsc');

const cwd = process.cwd();
const outfile = path.resolve(cwd, 'dist/src/main.js');
const tsconfig = path.resolve(cwd, 'tsconfig.json');
const entryPoints = [path.resolve(cwd, 'src/main.ts')];
const config = {
    platform: 'node',
    target: ['node16'],
    bundle: true,
    keepNames: true,
    plugins: [esbuildPluginTsc()],
    tsconfig,
    entryPoints,
    outfile,
    external: ['./node_modules/*']
};

(async () => {
    await esbuild.build(config);
})();

package.json

    "build:esbuild": "node esbuild.js",
nest buildswcesbuild
๋นŒ๋“œ ์‹œ๊ฐ„10.41s0.67s1.1s

๋นŒ๋“œ ์‹œ๊ฐ„์€ swc๋ณด๋‹ค ์•ฝ๊ฐ„ ๋Š๋ฆฌ์ง€๋งŒ, ๊ธฐ์กด nest build์™€ ๋น„๊ตํ•˜๋ฉด ์—„์ฒญ ๋นจ๋ผ์ง„ ๊ฑธ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋˜ ํ•˜๋‚˜์˜ ํšจ๊ณผ๋Š” ๋ฒˆ๋“ค๋ง ์šฉ๋Ÿ‰์ด ๊ต‰์žฅํžˆ ์ค„์–ด๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.



NestJS ์ด์Šˆ

https://github.com/nestjs/nest-cli/issues/731#issuecomment-772330860

๋นŒ๋“œ ์‹œ๊ฐ„์ด ๋งŽ์ด ๋‹จ์ถ•๋์ง€๋งŒ, swc์™€ esbuild ๋ชจ๋‘ type checking์ด ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, nest์—์„œ ๊ณต์‹์ ์œผ๋กœ ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.



Webpack vs Esbuild


์ €ํฌ API ์„œ๋น„์Šค๋“ค์€ ๋„๋ฉ”์ธ ๋ณ„๋กœ ๋ถ„๋ฆฌ๊ฐ€ ๋˜์–ด ์žˆ๋Š”๋ฐ, ๊ทธ ์ค‘ ๊ฐ„๋‹จํ•œ ๋„๋ฉ”์ธ ํ•˜๋‚˜๋ฅผ ๋น„๊ตํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

serverless-webpack
(ts-loader)
serverless-webpack
(swc-loader)
serverless-esbuild
๋นŒ๋“œ+๋ฐฐํฌ ์‹œ๊ฐ„ (serverless deploy)82s82s61s
ํ…Œ์ŠคํŠธ(57 tests) ์‹œ๊ฐ„16s (ts-jest)5s (swc-jest)5s (swc-jest)
์šฉ๋Ÿ‰14.9MB14.9MB2.9MB
  • ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ
    • MacBook Pro 2019
    • 2.6 GHz 6-Core Intel Core i7
    • 16 GB 2667 MHz DDR4
    • macOS Ventura 13.0


๋งˆ๋ฌด๋ฆฌ


๋นŒ๋“œ ์‹œ๊ฐ„์€ ํฐ ์ฐจ์ด๋Š” ๋‚˜์ง€ ์•Š์•˜์ง€๋งŒ, ๋ฒˆ๋“ค๋ง๋œ ์šฉ๋Ÿ‰์ด ํฌ๊ฒŒ ์ค„์–ด๋“  ํšจ๊ณผ๊ฐ€ ์žˆ์–ด์„œ ์ตœ์ข…์ ์œผ๋กœ serverless-esbuild ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

test ํˆด๋„ esbuild๋กœ ๊ฐ€๋Šฅํ•œ๊ฒŒ ์žˆ์„๊นŒ ํ•ด์„œ esbuild-jest๋ฅผ ์‹œ๋„ํ•ด๋ดค์ง€๋งŒ, ์„ค์ •์— ์–ด๋ ค์›€์„ ๊ฒช๋‹ค๊ฐ€ ์ด๋ฏธ ์ž˜ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋˜ swc-jest๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

์—ฌ๋‹ด์ด์ง€๋งŒ ์›๋ž˜๋Š” esbuild ๋„์ž…์€ ์šฐ์—ฐํžˆ ์ด๋ฃจ์–ด์กŒ์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ ๋ชฉํ‘œ๋Š” ์›๋ž˜ yarn berry์˜ PnP ๋„์ž…์ด ๋ชฉ์ ์ด์—ˆ๊ณ , webpack์—์„œ yarn berry ์„ค์ •์˜ ์–ด๋ ค์›€์„ ๊ฒช๋‹ค๊ฐ€ esbuild๊ฐ€ yarn berry ์ง€์›์ด ์ž˜๋œ๋‹ค๊ณ  ํ•ด์„œ esbuild๋ฅผ ํ…Œ์ŠคํŠธํ•ด๋ณด๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๊ณผ์ ์œผ๋กœ yarn berry ๋„์ž…์€ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ๋นŒ๋“œ์™€ ๋ฐฐํฌ๊นŒ์ง€ ์„ฑ๊ณตํ–ˆ์ง€๋งŒ, runtime์—์„œ ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•œ ์˜ค๋ฅ˜๋“ค์ด ๊ณ„์† ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ๋•๋ถ„์— esbuild๋ฅผ ํ…Œ์ŠคํŠธํ•ด ๋ณธ ๊ณ„๊ธฐ๊ฐ€ ๋๊ณ , ๋ฒˆ๋“ค๋ง ์šฉ๋Ÿ‰์ด ํฌ๊ฒŒ ์ค„์–ด๋“  ํšจ๊ณผ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์–ด์„œ ์‚ฝ์งˆ์˜ ์‹œ๊ฐ„๋“ค์ด ํ—›๋˜์ง„ ์•Š์•˜๋˜ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋๊นŒ์ง€ ์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค~ ๐Ÿ˜†

โ† ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ

Art Changes Life

๋…ธ๋จธ์Šค์™€ ํ•จ๊ป˜ ์—”ํ„ฐํ…Œํฌ ์‚ฐ์—…์„ ํ˜์‹ ํ•ด๋‚˜๊ฐˆ ๋ฉค๋ฒ„๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.

์ฑ„์šฉ ์ค‘์ธ ๊ณต๊ณ  ๋ณด๊ธฐ