ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Kasra Khosravi] Why you should use SWC (and not Babel) 한글 번역
    Frontend 2022. 3. 6. 15:56
     

    Why you should use SWC (and not Babel) - LogRocket Blog

    In this post, we'll cover the basics of transpilers and we'll compare Babel and SWC based on setup, execution, and speed.

    blog.logrocket.com

     

    아래 글은 Kasra Khosravi가 작성한 Why you should use SWC (and not Babel)이란 글을 한글로 번역한 글입니다.

    오역이 있을 수 있으니 정확한 내용을 읽고 싶다면 원문을 읽어보시길 바랍니다.

     


    왜 여러분은 (바벨이 아니라) SWC를 사용해야 하는가?

     

    바벨은 무엇인가?

    BabelES6와 같은 자바스크립트 최신 버전의 코드를 이 전 버전의 코드로 변환시켜주는 도구이며, 심지어 TypeScript를 변환시켜 주기도 한다.

    바벨은 여러분이 정의한 설정들을 바탕으로 소스 코드를 읽은 후, arrow functions 또는 optional chaining과 같은 신규 피쳐를 컴파일한다. 이 작업은 바벨의 세 가지 메이저 툴과 함께 진행된다.

    • 첫 째로, 바벨의 parser가 자바스크립트 코드를 컴퓨터가 이해할 수 있는 소스 코드 구조인 Abstract Syntax Tree (AST)로 변환한다.
    • 그 다음, 바벨의 traverser가 AST를 탐색하며 바벨 설정에 정의된 대로 코드를 수정한다.
    • 마지막으로 바벨의 generator가 수정된 AST를 보통의 코드로 번역한다.

     

    Source:  https://www.sitepoint.com/understanding-asts-building-babel-plugin/

     

     

     

    (Rust로 쓰여진) 바벨의 대체품

    SWC 또한 자바스크립트를 위한 트랜스파일러이고, Rust로 쓰여졌으며, Babel보다 훨씬 더 빠르다. Rust는 뛰어난 성능과 신뢰성으로 잘 알려져있으며, 여러 업무 코드에 부분적으로나 전체적으로 사용되도록 권장되어 왔다. 예를 들어:

     

    Rust가 매우 성능이 뛰어난 이유 중 하나는 Rust만의 가비지 컬렉션을 다루는 방법인, 더 이상 사용되지 않는 데이터 객체에 의한 메모리 사용을 해소하는 메모리 관리 접근법에 있다. Rust는 컴파일 단계에서 어떤 리소스가 더 이상 필요하지 않은지 결정하여 계속해서 실행할 필요가 없고, 따라서 프로세싱 시간은 줄어들고 성능은 향상된다.

     

    모두가 알다시피, 코드 트랜스파일링은 비용이 많이 드는 프로세스이고, 그것이 바로 Rust로 쓰여진 트랜스파일러를 사용하면 더 성능이 향상될 수 있는 이유이다. 우리는 더 멀리 살펴볼 것이지만, 우선 우리가 트랜스파일러를 필요로 하는지부터 결정해야 한다.

     

     

    왜 우리는 트랜스파일러를 필요로 하는가?

    다음은 트랜스파일러를 굳이 필요로 하지 않는 경우에 대한 예시이다:

    • 만약 여러분이 대부분을 ES3와 같이 잘 지원되는 자바스크립트 버전을 사용하는 간단한 프로젝트를 구축하고 있는 경우이다. 예를 들어, 이러한 코드를 실행하면 거의 대부분의 브라우저에서 잘 작동할 것이므로, 여러분의 자바스크립트 사용이 대체로 이렇다면, 트랜스파일러 없이 괜찮을 것이다.
    // we print it, but we don't agree with it
    function saySomething (something) {
        console.log(`${something}. But don't tell anyone.`);
    }
    
    saySomething("I don't like Javascript!");

     

    • 여러분이 구축하는 간단한 프로젝트가 화살표 함수와 같이 자바스크립트 최신 버전의 것에 의존하지만, 지원해야 하는 브라우저  또한 그 새로운 피쳐들을 지원하는 경우이다. 예를 들어, 아래의 코드를 Chrome 최신 버전(45+)에서 실행하는 것은 괜찮을 것이다:

    // we print it, but we don't agree with it
    const saySomething = something => {
      console.log(`${something}. But don't tell anyone.`);
    };
    
    saySomething("I don't like Javascript!");

     

    위 경우를 제외하고는 어플리케이션에서 트랜스파일러는 꼭 필요하다. 브라우저는 V8 (Chrome), SpiderMonkey (Firefox), 그리고 Chakra (IE)와 같이 여러 다른 종류의 자바스크립트 엔진을 사용한다. 이 것은 준준바스크립트 명세를 사용하더라도, 여러 다른 브라우저들이 해당 표준을 지원하는 타이밍과 지원해주는 정도가 광범위하게 다를 것이라는 것을 의미한다.

     

    이 것이 바로 무언가를 깨뜨리거나 새로운 피쳐 사용 기회를 잃지 않으면서 여러 다른 브라우저를 넘어 일관적으로 자바스크립트 코드를 핸들링하는 것이 필요한 이유이다.

     

    우리의 트랜스파일러에 대한 의존은 ES6 또는 TypeScript를 ES5로 변환하는 것에 그치지 않는다; 트랜스파일러는 자바스크립트의 미래를 오늘날의 우리들에게 가져다 주며, 우리가 ES2019와 같은 다양한 케이스의 자바스크립트 전환을 다룰 수 있게 해준다. 이 것은 오늘날의 자바스크립트 개발자를 위한 매우 강력한 도구이다.

     

    이제 우리는 왜 우리가 트랜스파일러를 필요로 하는지 정리해보았다. 이제 간단한 셋업과 함께 SWC 사용을 테스트해보고 바벨과의 상대적인 성능, 스피드의 차이를 비교해 볼 시간이다.

     

     

    SWC 사용법

    SWC는 NPM 패키지 매니저로 설치해서 사용할 수 있다.

    첫째로, 여러분의 디렉토리 루트에 다음 명령어를 실행한다.

     

    // use `cd` to go to the right directory and then run
    mkdir swc_project
    
    // initialize a package.json
    npm init
    
    // install swc core as well as its cli tool
    npm install --save-dev @swc/core @swc/cli

     

    이걸 실행함으로써, 이제 우리는 SWC core와 CLI를 함께 설치할 수 있다. CLI 패키지는 터미널에서 명령어로 실행될 수 있고, 코어 패키지는 빌드 셋업 단계에서 우릴 도울 것이다.

     

    첫 단계로, 자바스크립트 파일을 컴파일 하기 위해 CLI 도구에 집중하자. 루트 디렉토리에 아래와 같은 자바스크립트 파일이 존재한다고 생각해보자.

     

    //  async.js
    const fetch = require("node-fetch");
    
    async function getData() {
        let res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
        let json = await res.json();
        console.log('data', json);
    }
    
    getData();
    // result:
    // ▶Object {userId: 1, id: 1, title: "delectus aut autem", completed: false}

     

    이 파일을 트랜스파일하기 위해 아래의 명령어를 실행할 수 있다.

     

    // 이 커맨드를 실행하면 stdout으로 트랜스파일된 데이터가 만들어질 것이다.
    // 그리고 터미널에 프린트될 것이다.
    npx swc async.js
    
    // 아래 명령어를 실행하면 `output.js`라고 불리는 새로운 파일을
    // 트랜스파일된 데이터와 함께 생성할 것이다.
    npx swc async.js -o output.js
    
    // 아래 명령어를 실행하면 `transpiledDir`라는 이름의 디렉토리가 생성되고
    // 기존 dir에 있는 모든 파일이 트랜스파일될 것이다.
    npx swc src -d transpiledDir

     

    노트 — 트랜스파일된 파일이 어떻게 생겼는지 보기 위해 SWC playground를 사용할 수 있다.

    이제 두 번째 단계로, 빌드 시스템 안에 SWC를 도구로써 포함시킬 것이다. 이를 위해, 우리는 더 향상된 설정 가능한 빌더로써 Webpack을 사용할 수 있다.

     

    우선 첫째로, Webpack과 SWC 셋업 시 package.json이 어떻게 생겼을지 살펴보자. 이 셋업과 함께, npm run-script build 를 실행해서 Webpack이 우리의 패키지를 빌드하도록 만든다; 추가적으로 우리는 Webpack이 우리 어플리케이션을 서빙하도록 npm run-script start 를 실행할 수 있다.

     

    {
      "name": "swc-project",
      "version": "1.0.0",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "rm -rf ./dist && webpack",
        "start": "webpack-dev-server"
      },
      "license": "MIT",
      "devDependencies": {
        "@swc/core": "^1.1.39",
        "css-loader": "^3.4.0",
        "html-loader": "^0.5.5",
        "html-webpack-plugin": "^3.2.0",
        "sass-loader": "^8.0.0",
        "style-loader": "^1.1.1",
        "swc-loader": "^0.1.9",
        "webpack": "^4.41.4",
        "webpack-cli": "^3.3.10",
        "webpack-dev-server": "^3.10.1"
      },
      "dependencies": {
        "node-fetch": "2.6.0",
        "react": "^16.12.0",
        "react-dom": "^16.12.0",
        "regenerator-runtime": "^0.13.5"
      }
    }

     

    어플리케이션을 빌드하고 시작하기 위한 위 설정은 webpack.config.js 파일에 저장되어, Webpack에 의해 자동적으로 픽업될 것이다. 이 파일에서 다음과 같은 몇 가지 것들이 설정된다:

    • [output]: Webpack이 번들 파일들, assets, 그리고 모든 트랜스파일된 파일을 포함한 파일들을 산출할 장소와 이름을 세팅한다.
    • [devServer]: 어디에 컨텐츠를 서빙할 것인지부터 요청을 listen할 포트 정의까지 설정하여 Webpack app을 이 서빙한다.
    • [HTMLWebpackPlugin]: 우리는 이 플러그인을 정의하여 Webpack 번들이 포함된 HTML 파일을 서빙하는 프로세스를 쉽게 만든다.

    그러나 이 설정에서 가장 중요한 부분은 .js 또는 .jsx 파일 확장자를 가지는 자바스크립트 파일을 컴파일 할 수 있게 해주는 swc-loader이다.

     

    // global dependencies
    const path = require('path');
    const HTMLWebpackPlugin = require("html-webpack-plugin");
    
    module.exports = {
      mode: "development",
      output: {
        path: path.resolve(__dirname, './dist'),
        filename: 'index_bundle.js'
      },
      devServer: {
        contentBase: path.join(__dirname, 'dist'),
        compress: true,
        port: 9000
      },
      module: {
        rules: [
            {
            test: /\.jsx?$/ ,
            exclude: /(node_modules|bower_components)/,
            use: {
                // `.swcrc` in the root can be used to configure swc
                loader: "swc-loader"
            }
          },
          {
            test: /\.html$/,
            use: [
              {
                loader: "html-loader",
                options: { minimize: true }
              }
            ]
          },
          {
            test: /\.scss/i,
            use: ["style-loader", "css-loader", "sass-loader"]
          }
        ]
      },
      plugins: [
        new HTMLWebpackPlugin({
          filename: "./index.html",
          template: path.join(__dirname, 'public/index.html')
        })
      ]
    };

     

    Webpack 설정에 셋업된 swc-loader와 함께, 우리는 자바스크립트 파일을 트랜스파일하는 여정의 절반은 지나왔다. 그러나, 우리는 아직 SWC가 어떻게 우리 파일들을 트랜스파일하는지 알 필요가 있다. SWC는 .swcrc라고 불리는 설정 파일을 루트 디렉토리에 정의하는 방식으로 바벨과 비슷한 접근법이라고 할 수 있다. TypeScript를 트랜스파일하는 프로젝트에서 이 설정들이 어떻게 보이는지 살펴 보자.

     

    이 설정에서, 우리는 .ts 확장자 파일만 매칭되도록 하는 Regex로써 test 설정을 사용할 것이다. 추가적으로 jsx.parser 설정과 함께 SWC에서 어떤 parser가 (typescript / ecmascript 등의) 트랜스파일에 사용되는지 살펴볼 것이다.

     

    그러나 우리의 이용 사례를 위해 어떤 트랜스파일 옵션들이 의도적으로 사용되었는지 정의함으로써, syntax 파싱에 여전히 더 많은 통제권을 가지고 있다. 예를 들어, 이 예시에서 우리는 Typescript decoratorsdynamic imports 트랜스파일에 관심을 보이고 있지만, .tsx 확장자 파일을 트랜드파일하는 것은 무시하고 있다.

     

    // .swcrc
    
    {
      "test": ".*.ts$",
      "jsc": {
        "parser": {
          "syntax": "typescript",
          "tsx": false,
          "decorators": true,
          "dynamicImport": true
        }
      }
    }

     

    이제 위 webpack SWC 예시에서 React를 사용하길 원한다고 생각해보자. 알다시피 리액트에서는 리액트 컴포넌트를 작성하기 위해 .jsx라는 특정한 파일 확장자를 사용할 수 있다.

     

    // App.jsx
    
    // global dependencies
    import React from 'react';
    import ReactDOM from 'react-dom';
    
    const App = () => {
      return <h1>My SWC App</h1>;
    };
    
    ReactDOM.render(<App />, document.querySelector("#root"));

     

    Webpack을 통해 이 파일을 서빙하기 위해서는 우리가 이미 위에서 정의한 것과 같은 올바른 webpack loader가 필요하다. 또한 .swcrc 파일에 올바른 트랜스파일 세팅을 하는 것 또한 필요하다. 이제 이 접근법과 함께, 우리는 트랜스파일 시에 모던 자바스크립트 (ES2019) 최신 피쳐를 사용하면서 그와 더불어 .jsx 파일 또한 지원할 수 있다. 게다가 리액트 프로젝트를 위해 추가적인 트랜스파일 세팅이 필요하다면, 손쉽게 사용할 수 있는 다양한 세팅이 준비되어 있다.

     

    // .swcrc
    
    {
        "jsc": {
          "parser": {
            "syntax": "ecmascript",
            "jsx": true
          }
        }
      }

     

     

    바벨과 SWC의 속도 비교

    이 전에 얘기했듯이, 트랜스파일러 속도는 이 것이 빌드 프로세스에 반영되기 때문에 매우 중요하다. 그리고 많은 개발자들에게 이 단계에 걸리는 시간을 아끼는 것은 매우 중요한 문제이다. 이제 이 두 가지 도구가 스피드 면에서 어떻게 다른지 비교해보자.

     

    첫째로, 우리는 바벨과 SWC로 동기적으로 코드 변환을 실행시키는 인위적인 방법으로 두 개를 비교했다. 알다시피 자바스크립트는 싱글 스레드이고 실생활 어플리케이션에서 동기적으로 무거운 계산을 실행하는 것은 불가능하다. 그러나 이 방식은 여전히 우리에게 속도 비교의 지표를 제공해줄 것이다. (SWC 프로젝트의 메인테이너에 의해 실행된 테스트에서) 싱글 코어 CPU로 실행했을 때 벤치마크 비교를 살펴보자.

     

    Transform Speed(operation/second) Sample Runs
    SWC (ES3) 616 ops/sec 88
    Babel (ES5) 34.05 ops/sec 58

     

    위 결과는 SWC로 더 비용이 큰 ES3 변환 프로세스를 진행했음에도 불구하고, SWC 트랜스파일 속도가 바벨에 비해 월등히 눈에 띈다는 것을 보여준다.

     

    이제, 더 현실적인 시나리오를 벤치마크해보면, 자바스크립트에서 다뤄지는 실제 시나리오이면서 더 비용이 큰 await Promise.all() 샘플을 실행해볼 수 있다. 이 벤치마크에서, CPU 코어의 수와 그에 평행하는 연산이 작용한다. 다른 벤치마크를 보면, 두 가지 실험이 진행되었다. 두 가지 모두 8 CPU 코어 컴퓨터가 사용되었으며, 4개의 평행 연산이 수행되었다.

     

     

    첫 번째 실험에서는 4개의 프로미스가 실행되었다:

    Transform Speed(operation/second) Sample runs
    SWC (ES3) 1704 ops/sec 73
    Babel (ES5) 27.28 ops/sec 40

     

     

    두 번째 실험에서는 100개의 프로미스가 실행되었다:

    Transform Speed(operation/second) Sample runs
    SWC (ES3) 2199 ops/sec 54
    Babel (ES5) 32 ops/sec 6

     

    노트 — 여러분이 직접 테스트를 실행하고 벤치마크 비교하는 것에 관심이 있다면, 이 repository를 클론하고 터미널에 다음 커맨드를 실행해보길 바란다:

     

    // clone and cd into the cloned repository
    cd node-swc
    
    // Node.js benchmark runner, modelled after Mocha and bencha, based on Benchmark.js.
    npm i benchr -g
    
    // run the multicore.js or any other benchmarks under /benches directory
    benchr ./benches/multicore.js

     

    이 숫자들에서 우리가 주목해야 할 부분은 바벨의 성능이 비동기 작업을 수행할 때 떨어진다는 것이다. 이 것은 SWC가 worker thread에서 실행되면서 CPU 코어의 수를 잘 늘리는 것과 대조된다.

     

    전반적으로 두 가지 도구의 속도 차이를 분명히 보았다. SWC는 싱글 스레드 CPU 코어 베이스에서 바벨보다 20배 더 빨랐고, 멀티 코어 비동기 작업 프로세스에서는 약 60배 정도 더 빨랐다.

     

     

    결론

    이 글에서 트랜스파일러에 대한 기본 지식을 다루었고, 셋업, 실행 그리고 속도 측면에서 자바스크립트의 두 가지 트랜스파일러를 비교해보았다. 이 글에서 우리는 다음의 것들을 배웠다.

    • 두 가지 방식의 빌드 워크플로우가 매우 비슷하다.
    • 그러나 SWC는 바벨에 비해 커다란 속도 이점이 있다.

     

    따라서 여러분이 바벨을 사용하고 있고 더 빠른 빌드 시간을 얻기 위해 SWC로의 전환을 고려하고 있다면 다음의 것들을 확인해보자:

    • 모든 피쳐가 SWC에서 이미 전체적으로 또는 부분적으로 지원되는지, 혹은 전혀 지원되지 않는지 확인해 보자.
    • 여러분의 빌드 시스템이 SWC를 지원하는지 확인해 보자. (Webpack에선 지원해주더라도 Parcel과 같은 다른 툴에선 지원해주지 않을 수 있다.)

    아무튼 SWC와 같은 아이디어는 매우 유망해보이며, 앞으로의 발전에 계속 눈을 기울여야 할 것이다.

     

     

    Resources

    반응형

    COMMENT