Viteのライブラリモードを使ってブラウザで動く(Vue/React/Svelteの)単一のjsを生成する

Viteのライブラリモードを使ってブラウザで動く(Vue/React/Svelteの)単一のjsを生成する

author potpro(ぼとぷろ)
2022/03/02

Viteのライブラリモードを使ってブラウザで動く(Vue/React/Svelteの)単一のjsを生成する

Viteとは

Vite、皆さん使ってますでしょうか。

Viteは次世代フロントエンドツールと称しており、フロントエンドの大変な依存やライブラリの利用によるインストールなど、開発環境構築に労力を割くことなく、かつ高速に動作するビルドツールとして、知名度を上げているビルドツールです。

実際、VueやSvelteではこのViteを使うことが最近では推奨されていることから、かなり有力になってきている気がします。

実は自分は割と最近使い始めて、確かにかなり開発体験がいいなあと思いました。WebPackやbabel系loaderの地獄に苦しめられた人たちはみんなそう思うんじゃないでしょうか。

esbuildを使用しているのでビルド時間が早いのもいいですが、vue-cliなどを使用した時に生成されたパッケージなどの大量の依存みたいなのが無くなったのもすごくありがたいです。npmインストールも早い。

Viteのライブラリモード(library-mode)

ViteはそのままVue/React/SvelteなんかをSPA目的で使うと、本番ビルド時はhtmlとchunkされたjsとcssファイル、画像などの静的ファイルを吐き出します。

例として、npm create vite@latestでreact-tsを生成してnpm run buildした場合、デフォルトではこんなファイルがビルドされて出力されます。

vite v2.8.6 building for production...
✓ 33 modules transformed.
dist/assets/favicon.17e50649.svg   1.49 KiB
dist/assets/logo.ecc203fb.svg      2.61 KiB
dist/index.html                    0.52 KiB
dist/assets/index.a72fc762.js      1.59 KiB / gzip: 0.82 KiB
dist/assets/index.cd9c0392.css     0.75 KiB / gzip: 0.48 KiB
dist/assets/vendor.c10f0117.js     129.52 KiB / gzip: 41.79 KiB

しかし、今回は吐き出されたjsをライブラリとしていろんなページで使用するような物を想定しています。

この形式で生成したほうが読み込みも効率的なhtmlが生成されているので、通常はこのまま使えばいいのですが、ライブラリとして使いたいという話になると1ファイルですべてまとめられたファイルが欲しいとなるわけです。

そのような場合、ViteにはLibrary Modeというモードが存在します。

ライブラリモードは、vite.config.tsを少し変更するだけで使用可能です。

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  build: {
    lib: {
      entry: 'src/main.tsx',
      formats: ['umd'],
      name: 'reactTs',
      fileName: 'react-ts',
    }},
})

設定はこんな感じです。

✓ 31 modules transformed.
dist/style.css         0.75 KiB / gzip: 0.48 KiB
dist/react-ts.umd.js   133.92 KiB / gzip: 44.12 KiB

こうすると、umd形式の単一ファイルがビルドされます。iifeでの出力も可能ですが、ブラウザで動作させるということを考えるとumdで問題なく動作するはずです。その分容量は膨らむ可能性がありますが、ビルドした感じだとほぼ変わらなかったです。

実際、この時点で134KBもあります。Reactを含んでるので仕方ないとはいえ、まあ軽くは無いですね・・・。

cssもjsに含めたい

実はまだ単一のファイルになっていません。見て気づくと思いますが、style.cssだけは別ファイルとして出力されています。実際のところ、これもjsの中にcssをインジェクトして含めたいという話になるわけです。

この問題について、同じことを思っている人たちはいるようで、issueが立てられていろんな解決策が出ていましたが、これが一番簡単な解決法かなと思いました。

https://github.com/vitejs/vite/issues/1579#issuecomment-1037811957

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  build: {
+   cssCodeSplit: true,
    lib: {
      entry: 'src/main.tsx',
      formats: ['umd'],
      name: 'reactTs',
      fileName: 'react-ts',
    }},
})

これを追加してビルドしてみると、cssもjsの中に入れられてビルドが可能です。

> npm run build
dist/react-ts.umd.js   134.79 KiB / gzip: 44.60 KiB

ただし、グローバルを汚染してしまうという問題が書かれており、実際その点は注意点です。

コメントにはUMD形式なら問題ないよと書かれていましたが、自分が生成されたコードはglobal.__vite_style__を使っており、やっぱりグローバル汚染はしている感じがしました。

こんなコードが生成されます。

var __vite_style__=document.createElement("style");__vite_style__.innerHTML=`body{margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.App{text-align:center}.App-logo{height:40vmin;pointer-events:none}@media (prefers-reduced-motion: no-preference){.App-logo{animation:App-logo-spin infinite 20s linear}}.App-header{background-color:#282c34;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:calc(10px + 2vmin);color:#fff}.App-link{color:#61dafb}@keyframes App-logo-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}button{font-size:calc(10px + 2vmin)}
`;document.head.appendChild(__vite_style__);

まあこのあたりは一緒にするのもあまり良くないんだと思います。最近議論されているようなのでもっといい方法が出てくるかもしれません。

React/Vue/Svelteでサイズを比較してみる

1ファイルにまとめちゃうと、サイズの増加が気になりますよね。

せっかくなので、フロントエンドライブラリでどれくらいサイズが変わるのかを試してみようと思います。

比較対象はVue/React/Svelteです。

基本的にデフォルトのままで、vite.config.ts以外は変更していません。

viteのnpm create vite@latestを行った初期状態はどれも同じような機能(ロゴとクリックカウンター)なので、同じ機能でVue/React/Svelveを使うとどれだけサイズが違うのかがわかりやすいかと思います。

結果は以下の通り。

# react-ts
dist/react-ts.umd.js   134.79 KiB / gzip: 44.60 KiB

# vue-ts
dist/vue-ts.umd.js   60.87 KiB / gzip: 28.12 KiB

# svelte-ts
dist/svelte-ts.umd.js   12.29 KiB / gzip: 8.02 KiB

こうやってみると、かなり明確に差が出ましたね。reactとvueが2倍差があるのが驚きですね。

Vueは3になってかなり色々改善されたと聞きましたが、サイズもかなり減ってるようです。

そしてSvelteはライブラリを付属しない作りなのでやっぱり断トツで軽い。こういったライブラリとして開発するとしたら、Svelteに軍配が上がる気がします。

react-vue-svelte

ちなみに表示はこんな感じです。微妙に違いますが機能はほぼ同じ。ちゃんとクリックカウンタも動作しているのでライブラリが含まれていないということはなさそうです。

終わりに

これを見てみると、サイトで使いまわせるようなライブラリを作るなら、Svelteでの開発を考えるかな?

実際、自分はReactが好きなので使いたいと思っていたのですが、134KBもあるのか・・・って気持ちです。

Svelteもちょっと癖はありますがそれなりにいい開発体験が出来そうな感じはします。

とにかく、現状ではViteでの開発体験は最高ですね。こういったモードも用意されていたりするのが素敵です。

そして私はWebPackやGulpの呪縛から早く逃れたい・・・。