🐡

今 Nuxt3×Vuetify に Storybook を導入するのは地獄だから辞めておけ(と思うけど導入したいなら)

2024/09/05に公開

はじめに

Storybookの導入を開始してからはや5日目、ようやく記事を書けます
今回は史上まれにみる地獄を見ました
以前動画を扱った際に「地獄」というワードを使いましたが、あちらは知識不足や社会的な環境不足からくる地獄でした
一方今回は「Storybook」という非常に強固なライブラリがあり、様々な情報もNuxt用のアドオンもなんでも揃っているのに、Nuxt公式のライブラリ、モジュールが杜撰でどうしようもないという、人為的な地獄を経験

もしかしたら無償で、ほぼ一人で、貢献してくださっている開発者様には絶対に言ってはいけないことですが、恐らく初めて、「開発者殴りてえ」と思いました。
一応公式(多分)ですからね、公式の裏で消えていったレガシーもあるわけです。それは「レガシー」の項で語りますが
もう恨みつらみたまりすぎて「はじめに」が長くなりましたが、正直Nuxt捨ててReact(Next)に行きたくなりましたが、

兎も角、今回Nuxt3のプロジェクトにStorybookを導入する機会がありましたが、大変に苦労しました。ので情報を共有いたします。とはいえ、公式のモジュールも少しずつ改善されてそのうち使えるようになるでしょうから、これはそれまでの一時しのぎです。

因みに、弊社で開発中のNuxt3プロジェクト自動生成WEBアプリ「N-Dev」も進化しておりまして、Vitestの追加とStorybookの追加ができるように次回なる予定(もしかしたらHistoireにするかも)ですので、さくっと使いたい方はそちらもおすすめです。機能公開はもう少しお待ちください。

環境

現状最新パッケージを用意しました。開発環境がWindowsなのでさらに面倒なことが色々起こり...

開発環境

  • Windows11
  • Bun.js

パッケージ

  • Nuxt3: 3.13.0
  • Vuetify: 3.7.1
  • Storybook: 8.2.9

まず答え

https://github.com/KoTanukiKyoso/nuxt3-vuetify-storybook-template
ここに作成したテンプレートを置いておきますのでご利用・或いは参考にされてください。
ほぼこれが全てです。
あれをインストールしてこれをインストールして、という手順で説明しないのは、それがほぼ無理だからです。あらゆるコマンドで躓きます。
というわけで本題終了。

既存のプロジェクトに追加される場合は、「./storybook」を丸ごとコピーし、package.jsonをもとに必要なパッケージを全部追加してください。また、「assets/scss/main.scss」の部分等を任意のパスに変更してください。「stories」フォルダは作成しておりませんが、監視対象には入れてますので、作成した場合きちんと表示されると思います。

Storybookとは(前提知識)

「UIカタログ」といわれるもので、例えばVueだとコンポーネント単位で切り出し、プロパティをいじって見た目を確認したり動作を確認できるものです。基本的にUIを中心に開発時に役立つツールですね。UIのテストなんかもできるようです。最近は割と開発の必須ツールとして考えられているようです。

使用にはパッケージのインストールと、.storybookディレクトリ下のmain.ts、preview.tsが最低限必要です。まあこの辺りは「init」で自動作成してくれます。
また、それぞれのコンポーネントについて、カタログを作成する場合はstoriesファイルが必要です。
一般的に、example.vueというコンポーネントのカタログはexample.stories.tsというファイルを作成します。

また、コントロールを使用し、例えばボタンであればカラープロパティを自由に選択し、自由な色のボタンを試すことができます。
参考:https://storybook.js.org/docs/essentials/controls#annotation

答えの説明

参考

まず、今回のテンプレートは「Archetipo95」様の「my-nuxt-starter」をかなり参考にしました。私が見る限り今Nuxt3×Storybookに開発者以外で一番詳しい方ではないかとさえ。御礼申し上げます。
本来この方のプロジェクトをフォークしようかと思ったのですが、MITとはいえライセンス付きでしたし、中身はほぼ異なりますので、参考という形になりました。

なお、上記のプロジェクトでは「import {join} from 'path'」の関数が使用されていますが、Windowsでpathを使用すると「/」ではなく「\」を使用してきて上手く動かないので、以下のようにして対応してます。

main.ts
__dirname.replace(/\\/g, '/')

難しい点

そもそも、何故Nuxt3にStorybook(を含めVitest等の外部ツール)の導入が難しいのかという話ですが、以下の点があると思います。

  1. オートインポート:Nuxtにはオートインポート機能があるため、それぞれのインポートを記載しません。しかし、この機能はNuxtとして使用する場合のみに利用できるため、外部ツールからアクセスする場合はパスを通してあげる必要があります。
  2. パスエイリアス:「#app」や「#buld」のようなパスエイリアスを使用します。その参照先もコンパイル時に動的?に生成されるファイル?なのかよくわかりませんが、兎も角、単純にtsconfigを参照してもパスが通りません。ちなみに「/.nuxt/tsconfig.json」で見るだけは見れます。(参考:https://thaim-til.hatenablog.jp/entry/2024/02/12/230259)
  3. 解説記事の問題:「レガシー」の項に示しますが、今は使わなくなったライブラリ等の情報が大量にあります。また、記事の質もさして良くないように感じました。根本的にReactのように情報が無いというのもありますが、Nuxt3自体が結構迷走して開発が遅れに遅れたために、Nuxt3の仕様が固まる前の情報などが散見されます。
  4. ユーザが少なく対応がされてない:以下のStorybook公式のページの画像を見てください。Reactは3タイプあります。Nextもあります。VueもSvelteもあります。...Nuxtはどうしたんですか?上記Nuxtがそもそもわかりにくいこと加え、ユーザが少ない事もあるのでしょう。残念ながらStorybookが対応してくれてません。


https://storybook.js.org/docs)

使用するパッケージ

使用するパッケージはVue3×Vite用の「@storybook/vue3-vite」です。
「公式(Nuxt)の方法は使えません(今は)」の項で詳しく紹介しますが、現在一番適正なNuxtモジュールの「@nuxtjs/storybook」は使用できません
Vue3用のStorybookパッケージをNuxt3用に改造していきます。

オートインポートとパスを通す設定

「.storybook/main.ts」で使用されている「import AutoImportComponents from 'unplugin-vue-components/vite'」と「import AutoImport from 'unplugin-auto-import/vite'」がそれぞれコンポーネントと関数等をオートインポートするためのパッケージです。こちらをインストールして、Viteに設定(viteFinalのpluginsに設定)します。
参考:https://zenn.dev/kazuwombat/articles/29c3252889f64c

ちなみに「@nuxtjs/storybook」ではオートインポートできますが、まともに動かないので。

モックの作成

さて、とはいえ全ての自動インポートが解決されるわけではないことは先にあげた通りです。そこで一部の関数、コンポーネントはモックを作成してあげる必要があります。今回作成しているのは「<nuxt-image>」「<nuxt-link>」「useState()」です。それぞれ「.storybook/mocks」の中に配置してあり、今回はほぼ参考にした「Archetipo95」様のものそのままです。
これを「main.ts」の上記オートインポートの部分に追加します。

ちなみに「@nuxtjs/storybook」ではオートインポートできますが、まともに動かないので。

Vuetifyの導入

Vuetifyについても、過去はStorybookのアドオンやNuxtのモジュールがあったようですが、今はありませんので、手動で追加していきます。

まずプロジェクトにVuetifyを追加して、公式の指示通りにnuxt.config.tsを変更します。この辺りはVuetify公式に従ってください。
その上で、一般的には「plugins」フォルダに「vuetify.ts」を作成すると思うのですが、「defineNuxtPlugin」関数がインポートできないため、別の場所に「defineNuxtPlugin」を使用しない形で作成したものを配置します(面倒ですね)。公開しているテンプレートでは「.storybook/utils/vuetify.ts」に作成しています。

ちなみに「@nuxtjs/storybook」ではそのまま「plugins」から読み込めますが、まともに動かないので。

次にStory Wrapperの設定を行います。「.storybook/StoryWrapper.vue」と「.storybook/withVeutifyTheme.decorator.ts」が該当します。Vuetifyには全ての要素は「v-app」内にある必要があるという決め事がありますが、それを実現するのがこのファイルです。
今回は「v-main」も挿入していますが、そうすると高さがバカでかくなるので以下のスタイルを適用しています。

:deep(.v-application__wrap) {
  min-height: 0;
  padding: 12px;
}

また、次項で説明しますが、テーマ(ダークテーマ、ライトテーマ)の切り替えを実装する機能も付与しています。

テーマ(切り替え)の導入

こちらも「.storybook/StoryWrapper.vue」と「.storybook/withVeutifyTheme.decorator.ts」に関連した内容です。Vuetifyでは以下画像のようにテーマを定めてテーマごとに色の切り替え等ができます。
「StoryWrapper.vue」の「v-app」にpropsの形でテーマを与えておき、それを「.storybook/preview.ts」内の「globalTypes」の箇所でStorybookに与えた画面上部のツールバーに与えます。


参考:
https://storybook.js.org/recipes/vuetify
https://github.com/storybookjs/storybook/blob/next/code/addons/themes/docs/api.md#writing-a-custom-decorator

ファイルでかくなる問題

以上で基本的な説明を終えました。
因みに、このサンプルだけを含んだ構成ですが、、、500MB弱あります(パッケージインストール時)。
Storybookはそこそこ重いです。
元々動かすためにはReactが必要だったのですがStorybook8のアップデートで必要なくなったのですが、多分内部では呼んでますね。内包しているだけでしょう。

Stories自動生成

さて、Storybookで表示するためにはそれぞれのコンポーネントについてstoriesファイルを作成する必要があります。これが割と単純作業なので自動化したいところなのですが、実はそのようなプログラムを作成されている方がいらっしゃいます。
詳細は「IIHARA議長」様の以下の記事を参照してください。いいね!しましょう。
https://qiita.com/Gityosan/items/e6570b8db8f90b351b3d

「create.bash」というコードがそれにあたりますが、一部そのままでは不都合な部分がありましたので、改造したものを「.storybook/create-story.bash」に入れてます。Bashはあまり詳しくないのですけれどとりあえずは動くと思います。なお、自環境で試したところ一つだけ生成に失敗したファイルがありましたが、Bashスクリプトよくわからないのでそのままにしてます...
使い方ですが、Bashが入っている場合はそれで、Windowsの方もGit Bashは入ってると思いますので、それで実行できます。実行したパス以下の「.view」のつくファイルに対して自動生成し、既にある場合はスキップされます。

私は開発に「WebStorm」を使用しておりますので、以下のように設定し、「作業ディレクトリ」を変更することで実行するパスを変更して使用してます。

公式(Nuxt)の方法は使えません(今は)

ここからが本題ですね(私的には)。
さてここでは私がこれが正道のやり方だろ、と思って取り組んだものの見事に失敗したやり方と、諸々気になった個所を上げます。もし「ここを修正したら動くのでは?」というのがあったら教えてください。この方法が使用出来たら便利なのは間違いないです。

無理やり使うなら

まずは取り合えずNuxt3 × Vuetifyの環境を作成します。Vuetifyのページに従うのが良いでしょう。
https://vuetifyjs.com/en/getting-started/installation/#manual-setup

そしてStorybookを追加していきますが、
正規のインストール方法は以下に記載があります。
https://storybook.nuxtjs.org/getting-started/setup

これによると「npx nuxi@latest module add storybook」を実行した後に「npx storybook-nuxt init」を実行してね、とのことです。

因みに、「npx nuxi@latest module add storybook」はパッケージの「dependencies」に「@nuxtjs/storybook」を追加して、「nuxt.config.ts」の「modules」に「@nuxtjs/storybook」を追加するだけのコマンドです(内部的にはnuxiでもう少ししてると思います)。まずここで2つ問題があります。

  1. なんで「dependencies」に追加する?「devDependencies」で良いはずです。ちなみにこれ次でさらに問題になります。
  2. Vuetifyを設定すると「modules」への追加がうまくいかない。Vuetifyでは「modules」に以下のように少し特殊な書き方で追加しますが、これが元で追加に失敗します。Vuetifyは最適解かは怪しいですが、Vue及びNuxt界隈では最有名なUIコンポーネントのはずです。何故想定されていないのでしょうか、、、
nuxt.config.ts
modules: [
    (_options, nuxt) => {
        nuxt.hooks.hook('vite:extendConfig', (config) => {
            config.plugins.push(vuetify({autoImport: true}))
        })
    }
],

次に「npx storybook-nuxt init」を実行しますが、以下のISSUEを見ていただきたいのですが、要約すると
ユーザ:インストールコマンド「npx storybook-nuxt init」が動かないんだけど
開発者:そのインストールコマンドは廃止されたよ、置き換えられるよ
他のユーザ:それはいつ頃動きますか?
https://github.com/nuxt-modules/storybook/issues/721

???
まずインストールが動かない、、、なんで?
代替策がまだ使用できないのに既に廃止されてる、、、なんで?

意味不明ですね。
ですが、このコマンド、無理やり実行できます。パッケージ管理に「Bun.js」を使うことで。
、、、なんで?

そして実行することでパッケージに必要な諸々を追加し、「.stories」フォルダ下や「stories」フォルダを自動作成してくれるのですが、その後のpackage.jsonを見てみましょう。

...公式通りにやってるんですけどね
同じパッケージでしかもバージョンが異なるものが設定されています。この状態でインストールするとバージョンの低い新しく追加されたほうが優先されます。
では、「dependencies」に追加された方を削除して実行してみましょう、、、
はい、動きません。動かないんですよ。

何が悪いのかはわからないんですけど、取りあえず以下のようにパッケージを設定して実行したら最低限動きます。理由はありません、試行錯誤の賜物です。「@nuxtjs/storybook」は先にインストールされた8.2.0を使用し、「@storybook-nuxt」も合わせます。その他storybook関連は8.0.8がインストールされますが、8.2.9辺りに揃えます。8.2.7とかでも良いです(因みに@nuxtjs/storybookのバージョンとstorybookのバージョンが近いですが、偶然だと思います。特に連動しているようには見えません)。
また、実行すると「sass-embedded」が足りないと言われるのでこれも入れます。

こうやってインストールしたStorybookですが、コンポーネントを複数種類exportすると「Context conflict」というエラーが出て最後の一つのみしか表示されません。1コンポーネント辺り1つしか表示しないならギリ動きますが、動作はするもののVuetifyと合わせて使用するとエラーが出力される点や、composablesを呼び出すと止まるなどの問題がありまして、試行錯誤しましたが解決不能でしたので、断念しました。

因みに、自動追加された「main.ts」ですが、パスに「\」が使用されているためWindowsでは動きませんので修正しましょう。開発者の皆様はWindowsをないがしろにし過ぎだと思います。

Nuxt/Storybookモジュールのダメなところ(恨み)

まああげきれないほどありますが、まずは既に上げたそもそもインストールができない点、無理やりインストールしても動かない点ですね
ちなみに本当に誰が悪いか、は謎で、実はVuetifyが悪いとかStorybookが悪いとか、そういう可能性もなくはないんですけど。

ホームページの問題

その他例えば公式のページで、このページでは「main.ts」に

main.ts
import { StorybookConfig } from '@nuxtjs/storybook' // not from '@storybook/core-common'

と記載があるのですが、次のページには

main.ts
import type { StorybookConfig } from '@storybook-vue/nuxt'

と記載されてます。1ページで矛盾していくのやめてもらっていいですかね。因みに次ページの方が正解です(少なくとも現バージョンでは)。きちんとtypeインストールしてるし。逆に前の記述は何なのか。

また、Examplesのページには昨日まで「StackBlitz」が表示されており、クッソ狭い画面でなぜか公式で表示しようとすると表示されず、また、Storybookのページのサンプルのはずが、作成した画面(3000ポート)の方しか表示されず、無理やりポートを変更し(6006ポート)て閲覧するもコンポーネントが表示されないという惨状だったのですが、今日見るとなんか「We're sorry, but something went wrong.」の画面でそもそも表示されてませんでした。

まあ後は、ISSUEがどれも比較的新しくしかも結構クリティカルな問題なのと、Vuetifyのデモが動かない、というISSUEに開発者が「Vuetify使ったことない、誰かやって」って言ってるのも気になりますね。Storybookを扱う以上人並み以上にはUIフレームワークに触れてると思うのですが、Nuxt関係のことをしていてVuetify触ったこともないってことがあるのでしょうか。

まあまあ事情は色々あるとして、中々先行きが不安です。

環境にゴミを残していく

さて、これだけならまだ色々検証を繰り返しせばという部分もあるのですが、それだけではない要素、目に見えない要素も色々問題になってきます。
まずは、エラー停止時にプロセスが死なずに常駐することがある問題ですね。
Windowsの場合は以下のように使用しているポートでプロセスを見つけて殺してください。

netstat -ano | find "6006"
taskkill /f /pid 


参考:https://qiita.com/C_HERO/items/c2e9ab1a7bb3537d4cb4

その他、nuxiにデータが残っていて、パッケージから削除してもエラーが出続ける場合もありました。
その場合は以下のコマンドで綺麗にしてあげましょう。

npx nuxi cleanup

参考:https://nuxt.com/docs/api/commands/cleanup

その他、原因不明でプロジェクトごと作り直すこと、2,3度では足りません。

また例えば、pagesフォルダに何か(何でもよい)のファイルが存在していればVuetifyのテーマが適用されるが、pagesフォルダが無いか空であれば適用されない、という謎の挙動もあり、、、
Storybookのインストール時、新規プロジェクトだとインストールに失敗するが、既存プロジェクトだとうまくいく現象もありましたが、恐らく原因は同様だと思います。
Nuxt3プロジェクトは作成時pagesフォルダは作成されませんし、必須ではないはずなのですが、どういった判定をしているのでしょうか。

レガシー

ここからは、公式の陰で?何故か?消えていったパッケージ達をまとめてみたいと思います。こいつらが生きていれば、、、

storybook-addon-vuetify3

https://github.com/mikinovation/storybook-addon-vuetify3/tree/main
2年前にアーカイブになってます
Vuetify3が出たばかりのころだと思うのですが、何があったのでしょうか。因みにアーカイブになったのは今年に入ってからです、最近です。
Storybook公式にはまだ記事は残ってます。
https://storybook.js.org/addons/storybook-addon-vuetify3

nuxt-vuetify

昨年まで開発が続いていて、昨年末にアーカイブになったこれも結構最近のプロジェクトなのですが、何故なくなったのでしょうか。以下記事ではこれを使用してStorybookの環境作成を紹介していましたが、このことにより実現不可となりました。
https://dev.to/chakas3/tutorial-storybook-with-vuetify-in-nuxt-app-4b9p

storybook-addon-nuxt

正に今回欲しかった代物です。これも去年まで開発が続けられており、1年ほど前にアーカイブになってます。「nuxt-modules」を使用してくださいとのことで、あの公式が出なければ続いていたと思うのですが、その公式は悲惨なんですが
Storybookの記事は残ってます
https://storybook.js.org/addons/storybook-addon-nuxt
以下の記事でも使用されていますね
https://zenn.dev/jackmiwamiwa/articles/nuxt3-storybook

追加情報

以下の記事では「export const decorators = [withVuetifyTheme];」という書き方は使用できるが古い(chatgptに聞いたところ)、とあるのですが、私の環境ではこちらの記法では動作しませんでしたので古いほう?の記法で書いてます。検証方法が悪かったのかChatGPTがウソついたのか。
https://qiita.com/hideya670/items/94ca86190a72c17b1aad

Storybookのツールバーを作成する際にアイコンを設定できますが、使用できるアイコンの一覧は以下にあります。正直充実してはいません。
https://storybook-design-system.netlify.app/?path=/docs/icon--basic

Storybookに類似し、初期からViteを前提としたシステムとしてHistoire(イストワール)があるようです。Vue中心で開発されており、Nuxt用のプラグインもVuetifyのサンプルもあるみたいですので、こちらの方が導入が楽かもしれません。
Storybookの縦長で使いにくい画面とは違って、非常に使いやすいように見えます。
が、Vue系の緑ってちょっとダサいかな、、、あとどのくらい大きなプロジェクトになっていくか、いつまで続くか少し気にはなります。
説明も少なくて、ぱっと見使い方がよくわからんすよねえ
今度使ってみて記事にしようかな。

参考:
https://zenn.dev/azukiazusa/articles/histoire-vite-ui-component-cataloging-tool-for-vue
https://zenn.dev/ormair/articles/158f62f4787c11

教訓

Nuxtはヤバい、Nuxt4が出るらしいが、これも遅れてるが、、、大丈夫か?
フレームワーク、ツールは少ないほうが良い。結局皆で同じものを使わないと辛い。フレームワークもだが、例えばパッケージ管理やランタイムもnpm、yarn、pnpm、bun、node、denoとかありすぎて、どれでは動くけどどれでは動かない、とか

環境揃えてみませんかね、その方がみんなの得になると思うけど
ま、何にせよ実はViteに集約されていっている感じはするが、、、

取り合えず私は次世代はNue.jsに期待してるんですけどね。
どうなるやら

Discussion