Vuetify2をStorybookで動かしてみた (後編)

こんにちは!フリージアの東山です。
本記事は「Vuetify2をStorybookで動かしてみた」の後編になります。

前編では、以下の実装を行いました。 詳細はVuetify2をStorybookで動かしてみた (前編)をご参照ください。

  • Storybookの導入
  • VueコンポーネントをStorybookに登録

後編では、Vuetify2のコンポーネントの登録を行います。
SASS(SCSS) 拡張している場合、通常の方法ではVuetify2のコンポーネントを登録することができません。
本記事では、通常の方法で登録した場合にエラーが発生することを確認し、それを解決する形でVuetify2のコンポーネントを登録します。

Storybookの設定 (Vuetify2)

Vuetify2をStorybookで読み込めるように以下を設定します。

  • VueのプラグインとしてVueitfy2を呼び出す
  • Vuetify2で使用しているGoogleフォント・アイコンフォントを読み込む
  • Webpackのsass-loaderの設定を変更する

それぞれ説明します。

VueのプラグインとしてVueitfy2を呼び出す

Vue.use()を用いてVuetify2を呼び出します。 A-la-carte (treeshaking)に詳細が纏められていますので、必要に応じて御覧ください。

「.storybook/config.js」に以下の内容を追記・修正してください。

import Vue from 'vue'
import Vuetify from 'vuetify/lib'
import 'vuetify/src/styles/main.sass'

Vue.use(Vuetify)

// addDecorator({ template: `...` }) を修正する
addDecorator(() => ({
  vuetify: new Vuetify(),
  template: `
<v-app>
  <v-content class="ma-5">
    <story/>
  </v-content>
</v-app>
`
}))

Vuetifyをインポートする場合、「vuetify」と「vuetify/lib」のいずれかをインポート元として指定します。
「vuetify」を指定した場合は、SASS(SCSS)がコンパイルされた状態のものを読み込むため、SASS(SCSS)拡張を反映することができません。 「vuetify/lib」を指定した場合は、Vuetify2のSASS(SCSS)ファイルをsass-loaderでコンパイルをする必要があるため、コンパイル時にSASS(SCSS)拡張を挟むことができます。
今回はVueitfy2のSASS(SCSS)拡張を行っているため、「vuetify/lib」を指定します。「vuetify/lib」を指定した場合は、合わせて「vuetify/src/styles/main.sass」を読み込む必要があります。

また、VuetifyのコンポーネントはV-Appタグ内で呼び出す必要があります。
V-Appに関連したクラス (スタイル) 定義が、Vueitfyのコンポーネントのスタイル付けに大きな影響を与えているからです。
ここでは、「addDecorator(() => { template: ... })」を拡張し、ストーリーのコンポーネントがV-Appタグ内で読み込まれるように修正しました。

Vuetify2で使用しているGoogleフォント・アイコンフォントを読み込む

スタイルファイルをヘッダーで読み込むよう設定します。 「.storybook/preview-head.html」に以下の内容を追記・修正してください。

<link
  href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900&display=swap"
  rel="stylesheet"
/>
<link
  href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css"
  rel="stylesheet"
/>

2つのlinkタグの内、前者はRobotoフォントの読み込みを、後者はマテリアルデザインのアイコンフォントの読み込みを行っています。

Webpackのsass-loaderの設定を変更する

Vuetify2をNuxt.jsで試してみたでは「@nuxtjs/vuetify」を使用しているため不要でしたが、Storybookでは下記の設定が必要になります。

  • dart-sass、fiberをsass-loaderのオプションとして指定する
  • sass-loaderのdataプロパティを用いて、SASS(SCSS)拡張しているスタイルファイルをインジェクトする

「webpack.config.js」に以下の内容を追記・修正してください。

// ...

const sassLoaderOptions = {
  implementation: require('sass'),
  fiber: require('fibers')
}

module.exports = ({ config }) => {
  // ...

  const sassExtensions = ['.sass', '.scss']
  sassExtensions.forEach(extension => {
    config.module.rules.push({
      test: new RegExp(extension + '$'),
      use: [
        'style-loader',
        'css-loader',
        {
          loader: 'sass-loader',
          options: Object.assign(
            {},
            sassLoaderOptions,
            { data: extension === '.sass' ? "@import 'src/assets/css/_variables.scss'" : "@import 'src/assets/css/_variables.scss';" }
          )
        }
      ]
    })
  })

  // ...

  return config
}

sass-loaderオプションのimplementationは、デフォルトではnode-sassになっています。 Vuetify2ではdart-sassを使用しているため、sass-loaderオプションのimplementationをdart-sassに上書きします。
また、Vuetify2ではfiberを使用しているため、sass-loaderオプションのfiberプロパティも合わせて設定します。

最後に、sass-loaderのdataプロパティを用いて、SASS(SCSS)拡張しているスタイルファイルをインジェクトします。
sass-loaderの対象ファイルが「.sass」か「.scss」かでセミコロンの必要可否が変わるため、三項演算子でそれぞれ設定しています。

以上で、Storybookの設定 (Vuetify2) は完了です。

Vuetify2のコンポーネントの登録

早速、Vuetify2のコンポーネントを登録してみましょう。

「stories/vuetify/VBtn.stories.ts」を以下の内容で作成してください。

import { storiesOf } from '@storybook/vue'
import { text } from '@storybook/addon-knobs'

storiesOf('Vuetify Components', module).add(
  'VBtn',
  () => ({
    props: {
      color: {
        default: text('color', 'indigo')
      }
    },
    template: `
<div>
  <v-btn class="text-none" :color="color">Button</v-btn>
  <v-btn class="text-none" :color="color" dark>Dark Button</v-btn>
  <v-btn class="text-none" :color="color" text>Flat Button</v-btn>
  <v-btn class="text-none" :color="color" outlined>Outline Button</v-btn>
  <v-btn class="text-none" :color="color" depressed>Depressed Button</v-btn>
  <v-btn class="text-none" :color="color" rounded>Round Button</v-btn>
  <v-btn class="text-none" :color="color" disabled>Disabled Button</v-btn>
  <v-btn class="text-none" :color="color" fab icon><v-icon>mdi-heart</v-icon></v-btn>
  <v-btn class="text-none" :color="color" fab dark><v-icon>mdi-heart</v-icon></v-btn>
  <v-btn class="text-none" :color="color" icon outlined><v-icon>mdi-heart</v-icon></v-btn>
</div>
    `
  }),
  {
    info: {
      summary: 'https://vuetifyjs.com/ja/components/buttons#buttons'
    }
  }
)

作成できましたら、前回同様、下記コマンドでStorybookを起動してください。

$ yarn storybook

Storybook画面の左メニューに「Vuetify Components > VBtn」があれば、登録はバッチリです。
後は動作を確認して〜、というところなのですが、実はこの状態ではVBtnは表示されません。以下のような画面になってると思います。 storybook-2-1

結論を申し上げますと、Vuetify ComponentをStorybookで利用する場合、Vuetify Componentをインポートする必要があります。
@nuxtjs/vuetifyでVuetify Componentを描画している部分では不要ですので、StorybookのストーリーにVuetify Componentのインポート文を追記します。

  • .storybook/config.js
// ...

import Vue from 'vue'
// ↓ Vuetifyのimport文を修正
import Vuetify, { VApp, VContent } from 'vuetify/lib'
// ↑ Vuetifyのimport文を修正
import 'vuetify/src/styles/main.sass'

// ...

addDecorator(() => ({
  vuetify: new Vuetify(),
// ↓ Vuetifyコンポーネントの指定
  components: { VApp, VContent },
// ↑ Vuetifyコンポーネントの指定
  template: `
<v-app>
  <v-content class="ma-5">
    <story/>
  </v-content>
</v-app>
`
}))
  • stories/vuetify/VBtn.stories.ts
import { storiesOf } from '@storybook/vue'
import { text } from '@storybook/addon-knobs'
// 追記
import { VBtn, VIcon } from 'vuetify/lib'
`
}))


storiesOf('Vuetify Components', module).add(
  'VBtn',
  () => ({
    // 追記
    components: { VBtn, VIcon },
    props: {
      color: {
        default: text('color', 'indigo')
      }
    },

    // ...
  })
)

ここまで実装できましたら、再度、下記コマンドでStorybookを起動してください。

$ yarn storybook

storybook-2-2

上記画像のように、Vbtnコンポーネントが正常に描画されていれば、動作検証完了です。

ここまでのソースコードはfreegian/nuxtjs-templateにプッシュしています。 正常に動作しないなどありましたら、ご確認ください。

最後に

今回は、StorybookでVuetify2のコンポーネントを登録し、動作を検証しました。
Vuetify2のコンポーネントの登録に関しては、少し強引な設定になっていたかと思います。よりよい方法がありましたら、是非、御教授いただけますと幸いです。

本記事を見て、ご意見やご指摘等ございましたら、「[email protected]」まで是非ご連絡ください。