ozawaのブログ

本ブログは、PC関連について投稿します

webpackからviteに移行した際の備忘録

はじめに

webpackが開発終了をしてNext.jsのturbopackに開発チームが移ったそうで、
AWS Lambdaのnode16のサポート期限期限短縮でnode18へのランタイムのバージョンアップ対応を機にviteへの移行を検討始めました。

vitejs.dev

turbopackではなくviteに移行をするのは、turbopackはNext.jsがメインで他のフレームワークだったり汎用的に使えそうな仕様ではまだ無かったためです。
コミュニティによればturbopackは将来的に汎用的に使えるようになるようですが、
現時点ではwebpackの移行先として、turbopack vs viteのような比較がされるような対抗馬的な存在のviteが勢いがあるので採用しました 。

Turbopack We are in active discussions with other frameworks to bring Turbopack to their users. We're excited to see what we can build together!

turbo.build

webpackからviteへの移行時の問題点

大きな問題点ですと私の場合は下記がありました

  1. viteのビルド設定
  2. viteのデフォルトがesbuildでreflect-metadataを利用するライブラリ群が使えない

1. viteのビルド設定

webpackではいくつかのプラグインが世に出ていて、
例えばgulpとの連携プラグインだったりがありましたが、viteは2021年からβリリースされた比較的新しいのでそのあたりがまだ整っていません。 gulpのビルド/デプロイ周りを書き直すのが面倒だったので、webpackプラグインのビルド部分を削除して単純にシェルコマンドでvite buildを叩くような形で暫定対応しました。
個人的にはvite + gulpのプラグインは余裕があれば作りたいなとは思っていたりしますね。

あとは、lambdaは複数の関数ファイルを出力する形式にするところで詰まったり、
バンドルする際に特定のライブラリは動的インポートをするようにする設定だったりを探すのにドキュメント類が少ないので困りましたが、
下記のオプションでやれます。

  • build.rollupOptions.external
    • ここに指定したライブラリは動的インポート対象になりバンドルされないので別途node_moduleもデプロイが必要です。
  • build.rollupOptions.input
    • 複数のエントリーポイントでファイルを出力する際に利用。これはwebpackでも同じような感じですね
  • build.rollupOptions.output.chunkFileNames
    • ここでエントリーポイントから呼び出すviteでビルドして分割されたファイルを呼び出すファイル名を適当に記載

viteはrollupと呼ばれるバンドラーを利用しているようです。

rollupjs.org

2. viteのデフォルトがesbuildでreflect-metadataを利用するライブラリ群が使えない

この記事の本題で一番考えたところです。
viteではデフォルトでesbuildを利用していて、このesbuildがemitDecoratorMetadataのオプションをサポートしていません。
そのため、emitDecoratorMetadataを利用するreflect-metadataやそれを利用する各種ライブラリ群(TSyringeやTypeorm..etc)などは型情報を参照出来ない?のでビルドエラーになります。

これに対する対応策として下記の2つがとりあえずありました。

  1. esbuildに対してemitDecoratorMetadataを有効化するプラグインを利用
  2. esbuildを止めてviteのビルドをswcを利用して行う

諸々検討した結果ですが私は「2.」の方法を採用しました。
viteのswcを利用するプラグインも一応あってvite-plugin-react-swcというモノもありますが、
reactを利用していなかったのでunplugin-swcを参考に自前でviteでswcビルドをするプラグインを書きました。
unplugin-swcのそのまま使わなかったのは2022年からメンテがされておらず、私の環境では動かなかったからです。

github.com github.com

色々な設定がこのunplugin-swcにかかれていますが、
とりあえずビルドに必要そうなのは下記の部分です。(記憶を頼りに抜粋しているので動作確認はしていないです)

// unplugin自体はRollupのプラグインのユティリティ? or インターフェース? の漠然としたイメージで私はいます   

export default createUnplugin(
  (option : any = {}) => {

    const filter = createFilter(
      include || /\.[jt]sx?$/,
      exclude || /node_modules/,
    )
    return {
      name: 'swc',
     // resolveIdはいらなそう
          async transform(code, id) {
        if (!filter(id)) return null

        // swcのビルド設定
        let jsc: JscConfig = {
          parser: {
            syntax: 'typescript',
            decorators: true,
            legacyDecorator: true,
            decoratorMetadata: true
          },
          transform: {},
        };
        
        /// ... 省略
       
        const result = await transform(code, {
          filename: id,
          sourceMaps: true,
          ...options,
          jsc,
        })
        return {
          code: result.code,
          map: result.map && JSON.parse(result.map),
        }
     },

     vite: {
        config() {
          return {
            // これでesbuild無効
            esbuild: false,
          }
        },
      },

});


あとは、書いたプラグインをvite.configのbuild.rollupOptions.pluginsで指定すれば良いです。

おわりに

とりあえずviteに移行しました。
十秒程度、webpackよりもビルド時間が短縮された気がします。 webpack + typescript + ESMのセットでAlias解決がうまく出来ずに自前のwebpackプラグインを書いた時よりも、 vite移行は比較的楽に対応出来たなと思っています。

今はNode16 -> Node18になることでメンテが数年以上されていないライブラリがopensslの互換性エラーに苦しんでいて、
オプションの--openssl-legacy-providerで暫定対応は出来ますが、今後を考えて自前で書かないといけないかな〜と考えています。

以上です、ありがとうございました。