次世代のWebプラットフォームSWCを学ぶ

# はじめに
[SWC](https://swc.rs/)は多くのライブラリやランタイムで利用されています。
[Next.js11.1のブログ](https://nextjs.org/blog/next-11-1#adopting-rust-based-swc)によると[Babel](https://babeljs.io/)や[Terser](https://github.com/terser/terser)で行っていた処理をパフォーマンス改善のためにSWCを利用していると書かれています。
他にも[Deno](https://deno-ja.vercel.app/manual@v1.9.1/typescript/overview#%E3%81%A9%E3%81%AE%E3%82%88%E3%81%86%E3%81%AB%E5%8B%95%E3%81%8D%E3%81%BE%E3%81%99%E3%81%8B)でTypescriptやTSX、JSXをJavaScriptに変換する際に利用されています。
最近では[Vite4.0がリリース](https://vitejs.dev/blog/announcing-vite4.html#new-react-plugin-using-swc-during-development)され、Reactの環境として開発中にSWCを用いるプラグインを新たに公開しました。
この記事ではそんなさまざまな主要ライブラリ、ランタイムで利用されているSWCについて紹介します。

# SWCとは
SWCはRustをベースに作成された高速で拡張が可能なWebプラットフォームで、JavaScriptとTypeScriptのコンパイルとバンドルをマルチコアで行うことが出来ます。シングルコアでもBabelの20倍高速で、4コア使用すると70倍も高速になるみたいです。

現在サポートされている機能は7つあります。
- コンパイル
- `swcpack`によるバンドル(🚧開発中🚧)
- minify
- WebAssemblyを用いた変換
- `swc-loader`を用いたwebpackでの使用
- `@swc/jest`を用いたJestのパフォーマンス向上
- プラグインのカスタマイズ

バンドルはまだ開発中なのでこの記事では触れないことにします。

提供されている[Playground](https://swc.rs/playground)でSWCが実際にどのようにコードを変換するかを見ることが出来ます(写真ではES2022にMinifyしています)。
![スクリーンショット 2022-12-18 14.54.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591069/8a4734e4-ffb6-b39e-d1d6-002d7c9b9861.png)

# インストール方法
pnpmやnpm、yarnを用いてインストールすることが出来ます。最近はpnpmをデフォルトとして紹介するライブラリが増えてきましたね(storybookでも7.0から正式に対応可能になるので、私は今後基本的にpnpmを使っていこうと考えています)。`@swc/core`がswcの核となるパッケージです。cliを利用する時は`@swc/cli`もインストールします。
```shell
# pnpm
pnpm i -D @swc/cli @swc/core
# npm
npm i -D @swc/cli @swc/core
# yarn
yarn add -D @swc/cli @swc/core
```

インストールが完了したら`sample.js`という足し算を行う関数が含まれるファイルを用意してトランスパイルしてみます。
```js:sample.js
export const add = (a, b) => a + b;
```
トランスパイルの詳細を設定しない場合は以下のコマンドで行うことが出来ます。
```shell
npx swc ./sample.js
```
結果は標準出力されます。
```
Successfully compiled 1 file with swc.
export var add = function(a, b) {
    return a + b;
};


//#sourceMappingURL=data:application/json;charset=utf-8;base64,IntcInZlcnNpb25cIjozLFwic291cmNlc1wiOltdLFwibmFtZXNcIjpbXSxcIm1hcHBpbmdzXCI6XCJcIixcImZpbGVcIjpcInN0ZG91dFwifSI=%                                          
```
1行目は結果のログ、2行目から4行目が結果です。最終行にはsourceMappingURL(ソースマップファイルの場所)が出力されます。

# cli
インストール方法は先ほどの通りです。ファイルをトランスパイルすることができるコマンドを提供します。
オプションとファイルを提供する仕組みなっています。
```
npx swc [options] <files ...>
```
optionsにはswcの設定ファイルである`.swcrc`のパスを指定する`--config-file`やenv名を設定する`--env-name`などさまざまな設定を行うことが出来ます。
この記事ではその中でもファイルを監視して自動でコンパイルする機能を紹介します。自動でコンパイルを行うオプションは`--watch`または`-w`です。これを利用するためには新たなパッケージ`chokidar`をインストールする必要があります。`chokidar`はファイル監視を行うパッケージです。
```shell
pnpm i -D chokidar
```
さらに自動でコンパイルを行った時の出力先を準備する必要があります。出力先を設定するオプションはファイルなら`-o`(`--out-file`)、フォルダなら`-d`(`--out-dir`)です。これらを利用すると以下のように書くことで自動コンパイルを行うことが出来ます。
```shell
npx swc -w  -o index.js ./sample.js
npx swc -w  -d dist ./sample.js
```
実行後に`sample.js`に(出力結果が変わるような)変更を加えてみると吐き出されたファイルに変化が起きることが確認できます。ファイルの出力先を設定したときはmapファイルも生成されましたが、フォルダにした時はされません。フォルダの出力の時も出したい時は`-s`(--source-maps)を利用すると出力されます。

# core
cliでも使用しました`@swc/core`パッケージです。ビルドツールを作成するときに役に立つAPIが詰まっています。
この記事では含まれるAPIから代表してとしてコードを変換する`transform`と解析する`parse`を紹介します。
これらは非同期な関数ですので同期的に扱う時のために`transformSync`なども提供されています。他にも`minify`や`print`、`parseFile`などのAPIが提供されていますのでぜひお手元で確認してみてください。
`transform`でsourcemapの作成やminifyの実行も含めてトランスパイルすると以下のようになります。
```js:index.js
const swc = require("@swc/core");

swc
  .transform("export const add = (a, b) => a + b;", {
    filename: 'sample.js',
    sourceMaps: true,
    minify: true,

    jsc: {
      parser: {
        syntax: "ecmascript",
      },
      transform: {},
      minify: {
        compress: {
          unused: true
        },
        mangle: true,
      },
    },
  })
  .then((output) => {
    console.log(output);
  });
```
第一引数には対象のソースコードを与えます。第二引数には対象のコードが書かれたファイル名やsourcemapを作成することやminifyを行うか否かなどの設定を書きます。jscはswcの設定です。結果は`Promise<Output>`が返されます。このように作成したファイルを`node index.js`で実行するとトランスパイルされたファイルと結果が得られます。
```shell
{
  code: 'export var add=function(r,n){return r+n};',
  map: '{"version":3,"sources":["sample.js"],"sourcesContent":["export const add = (a, b) => a + b;"],"names":["add","a","b"],"mappings":"AAAA,OAAO,IAAMA,IAAM,SAACC,EAAGC,UAAMD,EAAIC,EAAE"}'
}
```
先ほどcliを実行したときに出力されたファイルと似ています。先ほどはminifyしていないので異なる内容が出力されていますが、minifyを除区など同じ設定で行えば同じ結果が得られます。
次に`parse`です。コードの解析を行なってくれます。
```js:index.js
const swc = require("@swc/core");

swc
  .parse("export const add = (a, b) => a + b;", {
    syntax: "ecmascript",
    comments: false,
    script: true,

    target: "es2022",

  })
  .then((module) => {
    console.log(module);
    console.log(module.body[0]);
  });
```
第一引数にはソースコード、第二引数には設定を記述します。今回は`target`を`es2022`にしてみました。`console.log(module);`の出力は以下のようになります。
```shell
{
  type: 'Module',
  span: { start: 1, end: 36, ctxt: 0 },
  body: [
    {
      type: 'ExportDeclaration',
      span: [Object],
      declaration: [Object]
    }
  ],
  interpreter: null
}
```
1~36文字目まで(今回入力したソースコード全て)のブロックは種類が`Module`で中身には`ExportDeclaration`(exportの宣言)を持つコードがあることがわかります。`ExportDeclaration`にあたる部分の情報の多くが`[Object]`で不透明なので`console.log(module.body[0])`で見てみます。
```shell
{
  type: 'ExportDeclaration',
  span: { start: 1, end: 36, ctxt: 0 },
  declaration: {
    type: 'VariableDeclaration',
    span: { start: 8, end: 36, ctxt: 0 },
    kind: 'const',
    declare: false,
    declarations: [ [Object] ]
  }
}
```
`ExportDeclaration`によって宣言されたものを見ることが出来ました。8行目から36行目で変数を定数として定義していることがわかります。確かにコードの8行目から36行目はそのようなコードとなってますね。これ以上調べていくとキリがないのでこの記事では行いませんが、さらなる`declarations`の中身が気になる場合環境を作成して確認してみてください。

# wasm
`@swc/wasm-web`を使用すると、WebAssemblyを使用してブラウザ内でコードを同期的に変換することが出来ます。
```shell
pnpm i @swc/wasm-web
```
でインストールすることが出来ます。
`initSwc()`で初期化後に
```js
parseSync(`console.log('hello')`, {})
transformSync(`console.log('hello')`, {})
```
`のような関数をブラウザ内で呼び出すことが出来ます。

# jest
jestの設定で`@swc/jest`を用いることで`ts-jest`よりも高速にテストの実行を行うように設定することが出来ます(利用した感じ`esbuild-jest`くらいの速度が出ます)。`@swc/core`と合わせて利用する必要があります。
```shell
pnpm i -D @swc/jest
```
`ts-jest`を用いた設定は以下のようになっています(`pnpx ts-jest config:init`による生成)。
```js:jest.config.js
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};
```
これを利用して`@swc/jest`を利用するように変えたい場合は`ts-jest`となっているところを置き換えて、
```js:jest.config.js
module.exports = {
  preset: '@swc/jest',
  testEnvironment: 'node',
};
```
としたいところですが、`preset`に設定するとエラーが出ます。`preset`はjestの設定詰め合わせで`@swc/jest`には設定が存在しないため動きません。ファイルの変換の機能を表すtransformに設定する必要があります。
```js:jest.config.js
module.exports = {
  transform: {
    "^.+\\.(t|j)sx?$": ["@swc/jest"],
  },
  testEnvironment: 'node',
};
```
swcの設定ファイル`.swcrc`の設定を加えたい場合は以下のようにします。
```js:jest.config.js
const fs = require('fs')

const config = JSON.parse(fs.readFileSync(`${__dirname}/.swcrc`, 'utf-8'))
module.exports = {
  transform: {
    "^.+\\.(t|j)sx?$": ["@swc/jest", { ...config }],
  },
  testEnvironment: 'node',
};
```

# webpack
webpackでswcを利用したい場合は`swc-loader`を利用します。これも`@swc/core`と合わせて利用する必要があります。
```shell
pnpm i -D swc-loader
```
インストール後は下のようにwebpackを設定すると使えます。swcの設定ファイル`.swcrc`はloaderの設定として自動的に反映されます。
```js:webpack.config.js
module: {
  rules: [
    {
      test: /\.m?js$/,
      exclude: /(node_modules)/,
      use: {
        loader: "swc-loader"
      }
    }
  ]
}
```

# 設定
これまでにも何度か出てきましたが、SWCの設定は`.swcrc`で行います。デフォルトでは以下のような設定がされています。
```json:.swcrc
{
	"$schema": "https://json.schemastore.org/swcrc",
	"jsc": {
		"parser": {
			"syntax": "ecmascript",
			"jsx": false,
			"dynamicImport": false,
			"privateMethod": false,
			"functionBind": false,
			"exportDefaultFrom": false,
			"exportNamespaceFrom": false,
			"decorators": false,
			"decoratorsBeforeExport": false,
			"topLevelAwait": false,
			"importMeta": false
		},
		"transform": null,
		"target": "es5",
		"loose": false,
		"externalHelpers": false,
		"keepClassNames": false
	},
	"minify": false
}
```
多種多様な設定が存在するので利用するときは自分が欲している機能を追加、変更して利用してください。設定は[こちら](https://swc.rs/docs/configuration/swcrc)に書かれています


# さいごに
とても使いやすく、パフォーマンスも良い今後も使っていきたいライブラリに感じました。バンドラーの開発が落ち着くと今よりさらに猛威を振るうのではないかと考えています。これを気に利用する機会を増やしてみてはいかがでしょうか。