Logo

SWC(Speedy Web Compiler) 기본 사용법

최근에 자바스크립트가 아닌 다른 프로그래밍 언어로 작성된 자바스크립트 프로젝트를 위한 빌드 도구들이 많이 등장하고 있습니다.

이번 포스팅에서 괴물같은 성능을 자랑하며 기존 빌드 도구들의 자리를 위협하고 있는 차세대 트랜스파일러이자 컴파일러인 SWC에 대해서 알아보겠습니다.

SWC란?

SWC(Speedy Web Compiler)는 자바스크립트 코드를 트랜스파일(transpile)하거나 타입스크립트 코드를 컴파일(compile)하기 위해 사용할 수 있는 개발 도구입니다. 여기서 자바스크립트 코드를 트랜스파일한다는 것은 ES6 이상의 최신 문법으로 작성된 자바스크립트 코드를 ES5 이하의 문법으로 변환하는 과정을 뜻하며, 타입스크립트 코드를 컴파일한다는 것은 타입스크립트 문법으로 작성된 코드를 일반 자바스크립트 코드로 변환하는 과정을 의미하죠.

이러한 소위 빌드(build) 프로세스를 통해서 생성되는 자바스크립트 코드는 구형 웹 브라우저나 Node.js에서도 잘 돌아가게 됩니다. 따라서 개발자들은 코드 호환성에 대해서 크게 걱정하지 않고 자바스크립의 최신 문법이나 타입스크립트로 코드를 작성할 수 있습니다.

SWC가 등장하기 전에는 오랫동안 Babel이라는 트랜스파일러와 타입스크립트의 내장 컴파일러인 TSC가 함께 사용되었습니다. 하지만 Babel과 TSC는 둘 다 타입스크립트로 작성되었고 따라서 싱글 쓰레드로 실행되기 때문에 속도가 느리다는 단점이 있었습니다. 특히 대규모 프로젝트에서 빌드가 오래 걸려서 개발 생산성 문제를 야기하곤 했습니다.

Rust라는 저수준 프로그래밍 언어로 작성된 SWC는 Babel과 TSC와 같은 기존 빌드 도구에 비해 작게는 몇 배, 크게는 몇 십 배나 빠른 엄청난 성능을 자랑하는 새로운 빌드 도구입니다. SWC 하나로 Babel과 TSC를 모두 대체할 수 있으며 Babel에 비해서 설정이 단순하기 때문에 많은 신규 프로젝트에서 SWC를 채택해서 사용하고 있습니다. 대표적인 예로, React의 메타 프레임워크인 Next.js를 들 수 있겠습니다.

참고로 한국 개발자이신 강동윤님께서 SWC를 만드셨다고 하네요. 정말로 자랑스러운 일이 아닐 수 없습니다. 🇰🇷

SWC 설치

SWC는 자바스크립트 프로젝트에서 설치 후에 npx 명령어를 통해서 터미널에서 간단하게 사용해볼 수 있습니다.

우선 npm 저장소에서 @swc/cli@swc/core 패키지를 내려받아 설치해야합니다. SWC는 애플리케이션 실행 시에는 필요가 없기 때문에 개발 의존성으로 설치해줍니다.

Node.js 프로젝트에서는 터미널에서 npm add 명령어로 설치합니다.

$ npm add -D @swc/cli @swc/core

Bun을 사용하는 프로젝트에서는 bun add 명령어로 설치합니다.

$ bun add -D @swc/cli @swc/core

터미널에서 npx swc --version을 실행했을 때, 다음과 같이 버전이 출력되면 SWC가 잘 설치된 것입니다.

$ npx swc --version

@swc/cli: 0.1.63
@swc/core: 1.3.101

단일 파일 변환

SWC는 기본적으로 최신 문법으로 작성된 자바스크립트나 타입스크립트로 작성된 코드를 입력받아 트랜스파일 또는 컴파일한 결과 코드를 출력하는 프로그램입니다.

간단한 실습을 위해서 index.ts 파일에 다음과 같은 타입스크립트 코드를 작성해보겠습니다.

index.ts
export const multiplyByTwo = (arr: number[]) => {
  return arr.map((num) => num * 2);
};

터미널에서 index.ts 파일을 상대로 swc 명령어를 실행해보면 ES5 문법 기준으로 변환된 결과 코드가 터미널에 출력됩니다.

$ npx swc index.ts
Successfully compiled 1 file with swc.
export var multiplyByTwo = function(arr) {
    return arr.map(function(num) {
        return num * 2;
    });
};

결과 코드를 터미널에 쓰지 않고 파일에 쓰고 싶다면 -o 또는 --out-dir 옵션을 사용합니다.

$ npx swc index.ts -o output.js
Successfully compiled 1 file with swc.
```

`output.js` 파일을 열어보면 변환된 자바스크립트 코드가 보일 것입니다.

```sh
$ cat output.js
export var multiplyByTwo = function(arr) {
    return arr.map(function(num) {
        return num * 2;
    });
};

폴더 전체 변환

대부분의 프로젝트는 여러 파일로 구성되므로 폴더 안에 있는 모든 파일을 변환할 일이 더 많을 것입니다.

실습을 위해서 src 폴더를 만들고, index.ts 파일을 그 폴더 안으로 옮겨보겠습니다.

$ mkdir src
$ mv index.ts src/

그 다음 src 폴더를 상대로 swc 명령어를 실행하는데요. -d 또는 --out-dir 옵션으로 dist 폴더를 지정해주겠습니다.

$ npx swc src -d dist
Successfully compiled: 1 file with swc (4.95ms)

dist 폴더에는 index.js 파일이 생성되어 있을 것입니다. index.js 파일을 열어보면 변환된 코드가 확인됩니다.

$ ls dist
index.js
$ cat dist/index.js
export var multiplyByTwo = function(arr) {
    return arr.map(function(num) {
        return num * 2;
    });
};

SWC 설정 파일

SWC는 .swcrc 파일을 통해서 어떻게 코드를 변환할지를 상세하게 설정할 수 있습니다. .swcrc 파일은 프로젝트의 최상위 경로에 위치하며 아래와 같은 모습을 띕니다.

.swcrc
{
  "$schema": "https://json.schemastore.org/swcrc",
  "jsc": {
    /* ... */
  },
  "env": {
    /* ... */
  },
  "module": {
    /* ... */
  }
}

참고로 $schema 속성은 필수는 아니지만, 있으면 코드 에디터에서 자동 완성이 되기 때문에 편리하고 안전합니다.

SWC에서 제공하는 수 많은 설정 옵션은 SWC 공식 문서를 참조 바랍니다. 본 포스팅에서는 자주 사용되는 옵션만 다루겠습니다.

파서(parser) 설정

jsc.parser 옵션은 입력 코드가 타입스크립트로 작성되는지 자바스크립트로 작성되는지를 지정하는데 사용합니다.

타입스크립트를 사용하지 않는 프로젝트에서는 jsc.parser.syntax 옵션을 ecmascript로 설정해주면 됩니다.

.swcrc
{
  "$schema": "https://json.schemastore.org/swcrc",
  "jsc": {
    "parser": {
      "syntax": "ecmascript"
    }
  }
}

위와 같이 설정을 하고 index.ts를 상대로 SWC를 실행하면 문법 오류가 나는 것을 볼 수 있습니다.

$ npx swc src/index.ts

  × Expected ',', got ':'
   ╭─[src/index.ts:1:1]
 1 │ const multiplyByTwo = (arr: number[]) => {
   ·                           ─
 2return arr.map((num) => num * 2);
 3};
   ╰────


Caused by:
    Syntax Error
Error: Failed to compile 1 file with swc.

jsc.parser.syntax 옵션을 typescript로 설정하면 SWC가 타입스크립트 문법을 이해할 수 있게 됩니다.

{
  "$schema": "https://json.schemastore.org/swcrc",
  "jsc": {
    "parser": {
      "syntax": "typescript"
    }
  }
}

그러므로 이제 index.ts가 아무 문제없이 자바스크립트 코드로 컴파일됩니다.

$ npx swc src/index.ts
Successfully compiled 1 file with swc.
export var multiplyByTwo = function(arr) {
    return arr.map(function(num) {
        return num * 2;
    });
};

타켓(target) 설정

jsc.target 옵션을 통해서 출력 코드가 얼마나 최신의 자바스크립트 문법을 사용할지를 결정할 수 있는데 기본 값은 es5 입니다.

예를 들어, jsc.target 옵션을 es2015로 바꿔보겠습니다.

.swcrc
{
  "$schema": "https://json.schemastore.org/swcrc",
  "jsc": {
    "parser": {
      "syntax": "typescript"
    },
    "target": "es2015"
  }
}

다시 index.ts를 대상으로 SWC를 실행해보면 출력 코드에서 타입만 제거되었지 문법은 입력 코드와 동일하다는 것을 볼 수 있습니다. (화살표 함수가 그대로 보존되고, constvar로 바뀌지 않았습니다.)

$ npx swc src/index.ts
Successfully compiled 1 file with swc.
export const multiplyByTwo = (arr)=>{
    return arr.map((num)=>num * 2);
};

모듈 시스템 설정

module.type 옵션은 출력 코드의 모듈 시스템을 설정하는데 쓰입니다. 기본 값은 es6이며 모듈 시스템으로 ESM(ES Modules)가 사용됩니다. 모듈 시스템으로 아직 CJS(CommonJS)를 사용하는 프로젝트에서는 module.type 옵션을 commonjs로 설정해줘야 합니다.

.swcrc
{
  "$schema": "https://json.schemastore.org/swcrc",
  "jsc": {
    "parser": {
      "syntax": "typescript"
    }
  },
  "module": {
    "type": "commonjs"
  }
}

설정을 바꾼 후에 SWC를 실행해보면 모듈 시스템이 CommonJS로 바뀌어 출력 코드가 장황해진 것을 볼 수 있습니다.

$ npx swc src/index.ts
Successfully compiled 1 file with swc.
"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "multiplyByTwo", {
    enumerable: true,
    get: function() {
        return multiplyByTwo;
    }
});
var multiplyByTwo = function(arr) {
    return arr.map(function(num) {
        return num * 2;
    });
};

최소화, 난독화, 압축

minify 옵션과 jsc.minify 옵션을 조합하여 출력 코드의 최소화나 난독화, 압축 여부를 설정할 수 있습니다.

{
  "$schema": "https://json.schemastore.org/swcrc",
  "jsc": {
    "parser": {
      "syntax": "typescript"
    },
    "minify": {
      "mangle": true,
      "compress": true
    }
  },
  "minify": true
}

위와 같이 설정 후 index.ts를 변환해보면 가독이 어려운 코드가 얻어질 것입니다.

$ npx swc src/index.ts
Successfully compiled 1 file with swc.
export var multiplyByTwo=function(n){return n.map(function(n){return 2*n})};

소스 맵 생성

sourceMaps 옵션을 true로 설정하면 변환된 자바스크립트 코드와 더불어 소스 맵도 만들어낼 수 있습니다.

{
  "$schema": "https://json.schemastore.org/swcrc",
  "jsc": {
    "parser": {
      "syntax": "typescript"
    }
  },
  "sourceMaps": true
}

출력 풀더를 dist로 지정하고 SWC를 실행해보겠습니다.

$ npx swc src -d dist
Successfully compiled: 1 file with swc (5.99ms)

dist 디렉토리에 소스 맵을 담고 있는 index.js.map 파일이 생성된 것을 볼 수 있습니다.

$ ls dist
index.js     index.js.map
$ cat dist/index.js.map
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export const multiplyByTwo = (arr: number[]) => {\n  return arr.map((num) => num * 2);\n};\n"],"names":["multiplyByTwo","arr","map","num"],"mappings":"AAAA,OAAO,IAAMA,gBAAgB,SAACC;IAC5B,OAAOA,IAAIC,GAAG,CAAC,SAACC;eAAQA,MAAM;;AAChC,EAAE"}%

마치면서

지금까지 실습을 통해서 SWC로 타입스크립트/자바스크립트 코드를 변환하는 방법에 대해서 알아보았습니다. 참고로 SWC Playground를 활용하면 웹 브라우저에서 간편하게 SWC를 체험해볼 수 있습니다.

실제 프로젝트에서는 SWC를 Webpack, Parcel, Vite와 같은 번들러(bundler)와 함께 쓰는 경우가 많아서 본 포스팅에서 다룬 것처럼 터미널 상에서 SWC만 단독으로 사용할 일을 많지 않을 것 같습니다. SWC를 다른 빌드 도구와 통합해서 사용하는 방법에 대해서는 추후 포스팅을 통해서 다뤄보도록 하겠습니다.