LoginSignup
38
15

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-12-18

はじめに

SWCは多くのライブラリやランタイムで利用されています。
Next.js11.1のブログによるとBabelTerserで行っていた処理をパフォーマンス改善のために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しています)。
スクリーンショット 2022-12-18 14.54.11.png

インストール方法

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という足し算を行う関数が含まれるファイルを用意してトランスパイルしてみます。

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なども提供されています。他にもminifyprintparseFileなどのAPIが提供されていますのでぜひお手元で確認してみてください。
transformでsourcemapの作成やminifyの実行も含めてトランスパイルすると以下のようになります。

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で実行するとトランスパイルされたファイルと結果が得られます。

{
  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です。コードの解析を行なってくれます。

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]);
  });

第一引数にはソースコード、第二引数には設定を記述します。今回はtargetes2022にしてみました。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による生成)。

jest.config.js
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};

これを利用して@swc/jestを利用するように変えたい場合はts-jestとなっているところを置き換えて、

jest.config.js
module.exports = {
  preset: '@swc/jest',
  testEnvironment: 'node',
};

としたいところですが、presetに設定するとエラーが出ます。presetはjestの設定詰め合わせで@swc/jestには設定が存在しないため動きません。ファイルの変換の機能を表すtransformに設定する必要があります。

jest.config.js
module.exports = {
  transform: {
    "^.+\\.(t|j)sx?$": ["@swc/jest"],
  },
  testEnvironment: 'node',
};

swcの設定ファイル.swcrcの設定を加えたい場合は以下のようにします。

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と合わせて利用する必要があります。

pnpm i -D swc-loader

インストール後は下のようにwebpackを設定すると使えます。swcの設定ファイル.swcrcはloaderの設定として自動的に反映されます。

webpack.config.js
module: {
  rules: [
    {
      test: /\.m?js$/,
      exclude: /(node_modules)/,
      use: {
        loader: "swc-loader"
      }
    }
  ]
}

設定

これまでにも何度か出てきましたが、SWCの設定は.swcrcで行います。デフォルトでは以下のような設定がされています。

.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
}

多種多様な設定が存在するので利用するときは自分が欲している機能を追加、変更して利用してください。設定はこちらに書かれています

さいごに

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

38
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
15