🚄

IE11対応のnpmライブラリのビルドをvite+swcで爆速化した話

2022/06/12に公開

https://www.npmjs.com/package/warera

普段職場で作るアプリのジャンルが業務系ということで、西暦と和暦を相互に変換したくなることが多く、ちょうどよいライブラリが見つからなかったので「warera」というライブラリを自作しました。
IE11に対応することが必須であったため、従来はwebpack+babelでビルドしUMD形式で出力する方法を取っていたのですが、昨今のビルドツールブーム(?)に乗じて、vite+swcでビルドするように変更しましたので、その具体的な方法について書き残しておきます。
IE11対応でライブラリを作ることなんてもうないかもしれません(そうであってほしい)が、参考になれば幸いです。

TLDR

以下のような状態のリポジトリがあったとして、
https://github.com/kaito3desuyo/warera/tree/b3183002fd8190947827f9359c09ab166117b140

以下のように改修すればOKです。
https://github.com/kaito3desuyo/warera/pull/21/files

詳細な手順

ライブラリ

必要なライブラリは以下の通りです。

  • vite
  • @swc/core
  • unplugin-swc
  • core-js
  • (@swc/jest)
$ npm i -D vite @swc/core unplugin-swc core-js@3 @swc/jest

viteの設定

vite.config.jsを新規に作成します。

vite.config.js
import path from "path";
import swc from "unplugin-swc";
import { defineConfig } from "vite";

export default defineConfig({
  build: {
    lib: {
      entry: path.resolve(__dirname, "src/main.ts"),
      name: "Warera",
      formats: ["umd"],
      fileName: (format) => `warera.${format}.js`,
    },
    minify: "terser",
    sourcemap: true,
  },
  plugins: [swc.vite()],
});

viteというとReactだったりVue.jsアプリケーションのビルドに用いるイメージが強いですが、「Library Mode」というものがあり、素のTS/JSで書かれたnpmライブラリのビルドにも使用することができます。
https://vitejs.dev/guide/build.html#library-mode

おおむね公式ドキュメントに記載の内容に沿っていますが、formatsを明示的に指定することで、UMD形式のみが出力されるようにしています。(デフォルトではES ModulesとUMD両方出力されます)
ES Modules形式も一緒に出力するようにしてもよいのですが、swcでトランスパイルしている関係上、core-jsからのポリフィルがES Modules形式の出力ファイルにも含まれてしまいます。[1]
今回はIE11対応を主眼に置いていたので、UMDのみでも差し支えないだろうということで明示的に指定することにしました。

また、minifyオプションでterserを明示的に指定しています。
デフォルトではesbuildとなっており、esbuildが持っているminify機能をそのまま使います。しかし、今回はunplugin-swcを通じてビルドにswcを使うため、esbuildを使おうとしても使えませんので、terserとします。
注意点としては、以下にあるように、「formatsesの時は適用されない」ことです。これは配列で複数指定していても同様で、デフォルトの['es', 'umd']であれば、UMD形式のファイルは圧縮され、ES Module形式のファイルは圧縮されません。
https://vitejs.dev/config/#build-minify

npmライブラリを作るため、ソースマップ出力はtrueとしています。
また、pluginsswc.vite()を追加します。

swcの設定

.swcrcを新規に作成します。

.swcrc
{
  "jsc": {
    "parser": {
      "syntax": "typescript"
    }
  },
  "env": {
    "mode": "usage",
    "coreJs": 3
  }
}

TypeScriptを読み込ませるために、jsc.parser.syntaxtypescriptを指定し、ポリフィルを注入するためにenvを指定します。
https://swc.rs/docs/configuration/supported-browsers
envは本来対応させるブラウザの指定に用いられますが、.browserslistrcの自動読み込みに対応しているため、今回はenv.targetsの指定を省略し、別途.browserslistrcを用意します。

env.modeはbabelでもおなじみの設定項目ですが、「usageモードは現在Babelほど効率的ではありません」とのことです。

browserslistの設定

.browserslistrcを新規に作成します。

.browserslistrc
defaults
IE 11
maintained node versions 

今回は上記のようにしました。対応範囲によって変更してください。

TypeScriptの設定

tsconfig.jsonにも若干の変更を加えます。

tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "esnext",
    "lib": ["esnext"],
    "moduleResolution": "node",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "isolatedModules": true,
    "skipLibCheck": true,
    "emitDeclarationOnly": true, <-- ココ
    "declaration": true, 
    "declarationMap": true, <-- ココ
    "declarationDir": "./types" <-- ココ
  },
  "include": ["./src"]
}

webpackでは、TSの型情報も一緒に出力してくれていましたが、swcはトランスパイル後のJSファイルのみを出力するため、別途tscを使って型情報を出力する必要があります。そのため、tsconfig.jsonでも「指定フォルダに型情報のみを出力するようにする」必要があります。
具体的には矢印で指し示した3か所を指定する必要があります。

npmスクリプトの設定

最後に、package.jsonでビルドスクリプト等を変更します。

package.json
{
  "name": "warera",
  "version": "0.2.4",
  "description": "",
  "main": "./dist/warera.umd.js", <-- ココ
  "exports": {
    ".": "./dist/warera.umd.js" <-- ココ
  },
  "types": "./types/main.d.ts", <-- ココ
  "files": [
    "/dist", <-- ココ
    "/types" <-- ココ
  ],
  "scripts": {
    "build": "vite build", <-- ココ
    "build:prod": "tsc && vite build", <-- ココ
    "watch": "npm run build -- -w", <-- ココ
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  },
  … 以下略
}

(おまけ) jestの設定

どうせswcを使うなら、ついでにjestの実行も爆速化してしまいましょう。

jest.config.js
... 以上略
transform: {
    "^.+\\.(ts|tsx)$": "@swc/jest",
},
... 以下略

@swc/jestをインストール後、transformを書き換えるだけです。めっちゃ簡単。

不要なファイルを消す

webpack、babel関連のファイルは不要なので消してしまいましょう。

  • webpack.config.js
  • .babelrc

また、ライブラリも消してしまいます。

  • @babel/core
  • @babel/preset-env
  • babel-loader
  • ts-loader
  • tslib
  • webpack
  • webpack-cli
  • (ts-jest)

結果

具体的な計測は今のところしていませんが、明らかに差がわかるレベルでビルド時間が短縮されました。体感では半分以下になっています。

それよりも、複雑で煩雑なwebpackの設定ファイルからオサラバできたことがかなりのメリットに感じます。IE11対応という比較的めんどくさいことをしなければならない場合であっても、vite+swcならばこの程度の分量の設定ファイルで済むというのは、なかなか良いのではないでしょうか。

もし、みなさんも今後npmライブラリを作る機会があれば、vite+swcを試してみてはいかがでしょうか。IE11対応も行けますよ。[2]

参考にしたページ

https://ja.vitejs.dev/
https://swc.rs/
https://github.com/egoist/unplugin-swc
https://zenn.dev/drop_table_user/articles/7b043bef6cec29
https://miyauchi.dev/ja/posts/speeding-up-jest/
https://github.com/evanw/esbuild/issues/297

脚注
  1. ツリーシェイキングされてくれるかは未確認です。誰か確かめてほしい(他力本願寺) ↩︎

  2. IE11需要が可及的速やかに消滅することを心から祈っています ↩︎

Discussion