はじめに
SWCは多くのライブラリやランタイムで利用されています。
Next.js11.1のブログによるとBabelやTerserで行っていた処理をパフォーマンス改善のためにSWCを利用していると書かれています。
他にもDenoでTypescriptやTSX、JSXをJavaScriptに変換する際に利用されています。
最近ではVite4.0がリリースされ、Reactの環境として開発中にSWCを用いるプラグインを新たに公開しました。
この記事ではそんなさまざまな主要ライブラリ、ランタイムで利用されているSWCについて紹介します。
SWCとは
SWCはRustをベースに作成された高速で拡張が可能なWebプラットフォームで、JavaScriptとTypeScriptのコンパイルとバンドルをマルチコアで行うことが出来ます。シングルコアでもBabelの20倍高速で、4コア使用すると70倍も高速になるみたいです。
現在サポートされている機能は7つあります。
- コンパイル
-
swcpack
によるバンドル(🚧開発中🚧) - minify
- WebAssemblyを用いた変換
-
swc-loader
を用いたwebpackでの使用 -
@swc/jest
を用いたJestのパフォーマンス向上 - プラグインのカスタマイズ
バンドルはまだ開発中なのでこの記事では触れないことにします。
提供されているPlaygroundでSWCが実際にどのようにコードを変換するかを見ることが出来ます(写真ではES2022にMinifyしています)。
インストール方法
pnpmやnpm、yarnを用いてインストールすることが出来ます。最近はpnpmをデフォルトとして紹介するライブラリが増えてきましたね(storybookでも7.0から正式に対応可能になるので、私は今後基本的にpnpmを使っていこうと考えています)。@swc/core
がswcの核となるパッケージです。cliを利用する時は@swc/cli
もインストールします。
# 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
という足し算を行う関数が含まれるファイルを用意してトランスパイルしてみます。
export const add = (a, b) => a + b;
トランスパイルの詳細を設定しない場合は以下のコマンドで行うことが出来ます。
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
はファイル監視を行うパッケージです。
pnpm i -D chokidar
さらに自動でコンパイルを行った時の出力先を準備する必要があります。出力先を設定するオプションはファイルなら-o
(--out-file
)、フォルダなら-d
(--out-dir
)です。これらを利用すると以下のように書くことで自動コンパイルを行うことが出来ます。
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の実行も含めてトランスパイルすると以下のようになります。
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
で実行するとトランスパイルされたファイルと結果が得られます。
{
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
です。コードの解析を行なってくれます。
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);
の出力は以下のようになります。
{
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])
で見てみます。
{
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を使用してブラウザ内でコードを同期的に変換することが出来ます。
pnpm i @swc/wasm-web
でインストールすることが出来ます。
initSwc()
で初期化後に
parseSync(`console.log('hello')`, {})
transformSync(`console.log('hello')`, {})
`のような関数をブラウザ内で呼び出すことが出来ます。
jest
jestの設定で@swc/jest
を用いることでts-jest
よりも高速にテストの実行を行うように設定することが出来ます(利用した感じesbuild-jest
くらいの速度が出ます)。@swc/core
と合わせて利用する必要があります。
pnpm i -D @swc/jest
ts-jest
を用いた設定は以下のようになっています(pnpx ts-jest config:init
による生成)。
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
これを利用して@swc/jest
を利用するように変えたい場合はts-jest
となっているところを置き換えて、
module.exports = {
preset: '@swc/jest',
testEnvironment: 'node',
};
としたいところですが、preset
に設定するとエラーが出ます。preset
はjestの設定詰め合わせで@swc/jest
には設定が存在しないため動きません。ファイルの変換の機能を表すtransformに設定する必要があります。
module.exports = {
transform: {
"^.+\\.(t|j)sx?$": ["@swc/jest"],
},
testEnvironment: 'node',
};
swcの設定ファイル.swcrc
の設定を加えたい場合は以下のようにします。
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
と合わせて利用する必要があります。
pnpm i -D swc-loader
インストール後は下のようにwebpackを設定すると使えます。swcの設定ファイル.swcrc
はloaderの設定として自動的に反映されます。
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules)/,
use: {
loader: "swc-loader"
}
}
]
}
設定
これまでにも何度か出てきましたが、SWCの設定は.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
}
多種多様な設定が存在するので利用するときは自分が欲している機能を追加、変更して利用してください。設定はこちらに書かれています
さいごに
とても使いやすく、パフォーマンスも良い今後も使っていきたいライブラリに感じました。バンドラーの開発が落ち着くと今よりさらに猛威を振るうのではないかと考えています。これを気に利用する機会を増やしてみてはいかがでしょうか。