Nuxt 3 + Vuetify 3 + Storybook 開発環境構築
本業でVueのアップデート周りを行っており、構成としてNuxt.js 3とVuetify 3を組み合わせて開発しています。
アップデートに伴って、いままでコンポーネントのドキュメントが整備されていなかったのでStorybookを利用して開発することにしました。
Vuetifyを利用するからStroybookは不要論はあるかもしれません。
しかし、コンポーネントを組み合わせたケースではStorybookがあると値の可視化ができるため、アンドキュメント部分の補完になります。
そこで Nuxt3 + Vuetify 3 + Storybook の開発環境を構築する方法を紹介します。
この構成での情報があまりなかったので覚書として記事にしました。
対象読者
- Nuxt.js 3を使ってStorybookを利用したい人
- Vuetify 3と組み合わせたい人
開発環境
- Node.js 20.11.0
- Nuxt.js 3以上
- Vuetify 3以上
- storybook 8系
検証リポジトリ
Install Nuxt3
まずはじめに、Nuxtをインストールしていきます。
Nuxtをインストールしたいディレクトリで nuxi init
でNuxtの雛形をインストールすることができます。
本記事のパッケージマネージャーは npm
を利用します。
npx nuxi@latest init my-app
✔ Which package manager would you like to use? › npm
cd my-app // プロジェクトディレクトリに移動
npm run dev // Nuxt プロジェクトを起動
Install ESLint
ESLintをインストールしていきます。
Prettier も合わせていれておくのが鉄板でしたが、Nuxt 3では、No Prettier
が推奨になっているので、今回はESLintのみでフォーマットさせておきます。
この記事では、ESLintのみでフォーマットを行っていきます。
npm install -D eslint eslint-plugin-nuxt @nuxtjs/eslint-config-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser
Config
// .eslintrc
{
"root": true,
"env": {
"browser": true,
"es6": true
},
"extends": [
"eslint:recommended",
"@nuxtjs/eslint-config-typescript",
"plugin:@typescript-eslint/recommended",
"plugin:vue/vue3-recommended"
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
"sourceType": "module"
},
"plugins": ["vue", "@typescript-eslint"],
"overrides": [
{
"files": ["layouts/*.vue", "pages/**/*.vue"],
"rules": { "vue/multi-word-component-names": "off" }
}
]
}
Add Script
package.jsonにESLintの実行スクリプトを追加します。
{
"script": {
"lint": "eslint --fix './**/*.vue'"
}
}
PhpStormやVSCodeの手元のエディタ・IDEで、保存時にフォーマットをかけておきます。
Vuetify 3
公式ドキュメントに Vuetify 3
をインストールしていきます。
- vuetify
- vite-plugin-vuetify
- @mdi/font
Vuetifyの本体・Nuxtで利用するためにモジュールのインストール・Vuetifyで利用するアイコンをインストールしていきます。
npm i -D vuetify vite-plugin-vuetify
npm i @mdi/font
Config
次にNuxtでVuetifyを利用するための設定を行っていきます。
nuxt.config.ts に組み込みます。
ビルドはトランスパイルにVuetifyを追加します。
モジュールには、autoInportを許可できるように設定します。
最後にViteのテンプレートでアセットURLを参照できるようにします。
import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
build: {
transpile: ['vuetify']
},
plugins: [
'~/plugins/vuetify.ts'
],
modules: [
(_options, nuxt) => {
nuxt.hooks.hook('vite:extendConfig', (config) => {
// @ts-expect-error
config.plugins.push(vuetify({ autoImport: true }))
})
}
// ...
],
vite: {
vue: {
template: {
transformAssetUrls
}
}
}
})
Plugins
~/plugins/vuetify.ts
を作成して プラグインで Vuetifyを呼び出すようにします。
// import this after install `@mdi/font` package
import '@mdi/font/css/materialdesignicons.css'
import 'vuetify/styles'
import { createVuetify } from 'vuetify'
export default defineNuxtPlugin((app) => {
const vuetify = createVuetify({
// ... your configuration
})
app.vueApp.use(vuetify)
})
公式では、defineNuxtPlugin
に createVuetify
を呼び出すようにしています。
この記事では、Storybookを利用するため、Vuetifyの設定値は Storybook の設定で呼び出すため別ファイルで管理しました。
utils/vuetify.ts
を作成して、Vuetifyの設定を管理します。
Iconの設定を除けば、componentsとdirectivesのみでもVuetifyのコンポーネントを呼び出せます。
ここでは、アイコンを設定した場合も含めてコードをいれました。
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
import { aliases, mdi } from 'vuetify/iconsets/mdi-svg'
import 'vuetify/styles'
const vuetify = createVuetify({
components,
directives,
icons: {
defaultSet: 'mdi',
aliases,
sets: {
mdi
}
}
})
export default vuetify
utils/vuetify.ts
でファイルを退避させておくことで、Nuxtの開発環境・Storybook環境で利用することができます。
import vuetify from '~/utils/vuetify'
export default defineNuxtPlugin((app) => {
app.vueApp.use(vuetify)
})
テンプレート部分にVuetifyのコンポーネントを配置
起動コマンドでVuetifyのコンポーネントが呼び出しができているか確認します。
npm run dev -- -o
app.vue
or laypouts/defaluts.vue
にVuetifyのコンポーネントを配置します。
ここでは簡易的にボタンをクリック後にコンテンツを表示するようにしました。
- クリックでshowContentsの真偽値を切り替えしコンテンツを表示
<script setup lang="ts">
import { ref } from 'vue'
const showContents = ref(false)
</script>
<template>
<NuxtLayout>
<v-app>
<v-sheet class="pa-4 text-center mx-auto">
<v-btn color="primary" @click="showContents = !showContents">
<template v-if="!showContents">
Open Content
</template>
<template v-if="showContents">
Close Content
</template>
</v-btn>
</v-sheet>
<v-sheet v-if="showContents" class="pa-4 text-center mx-auto">
コンテンツです。
</v-sheet>
<NuxtPage />
</v-app>
</NuxtLayout>
</template>
Nuxt環境でVuetifyのコンポーネントが利用できれば成功です。
Storybookの設定
ここからStorybookの環境構築をします。
公式のドキュメントに沿ってインストールします。
npx storybook@latest init
でStorybookの初期設定を行います。
初期設定では、利用するフレームワークを選択する画面が表示されます。Vue3を選択します。
ビルドツールはViteを選択します。
npx storybook@latest init
- Ok to proceed? (y) y
- ? Do you want to manually choose a Storybook project type to install? › y
- ? Please choose a project type from the following list: › - Use arrow-keys. Return to submit.
❯ react
react_scripts
react_native
react_project
webpack_react
nextjs
> vue3
angular
ember
↓ web_components
- We were not able to detect the right builder for your project. Please select one: › Vite
> Vite
- ? We have detected that you're using ESLint. Storybook provides a plugin that gives the best experience with Storybook and helps follow best practices: https://github.com/storybookjs/eslint-plugin-storybook#readme
> Yes
インストールが完了すると localhost:6006 でStorybookの画面が表示されます。
起動すると シンタックスエラーが発生します。
initで vite.config.jsが存在しないため vueファイルを読み込めないため起きます。(空ディレクトリで initする場合は、発生しません)
プロジェクトのルートに vite.config.js を作成して、Viteの設定を行います。
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
})
設定ファイルの書き換え
初期インストールすると vite の設定なっているため stories のパスを変更をかけて .stories.ts といったファイルすべて対象にさせます。
また、初期インストールでは .storybook/main.js や .storybook/preview.js が存在するため、 tsファイル に変更します。
.storybook/main.ts
を 修正します。
変更された部分は StorybookConfig 型をインポートして configの型定義します。
import type { StorybookConfig } from "@storybook/vue3-vite";
const config: StorybookConfig = {
stories: ["../**/*.mdx", "../**/*.stories.@(js|jsx|mjs|ts|tsx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@chromatic-com/storybook",
"@storybook/addon-interactions",
],
framework: {
name: "@storybook/vue3-vite",
options: {},
},
docs: {
autodocs: "tag",
},
};
export default config;
.storybook/preview.ts
にVuetifyのコンポーネントを呼び出すように修正します。
Previewではスタイルの適用などコンポーネント実際に描画するため @storybook/vue3
の setup
を利用して Vuetify を呼び出します。
また、Previewの型もmainと同様にインポートして型定義します。
import type { Preview } from "@storybook/vue3";
import { setup } from '@storybook/vue3'
// Styles
import vuetify from "../utils/vuetify";
setup((app) => {
if (app) {
app.use(vuetify)
}
})
export const decorators = [
(story: any) => ({
components: { story },
template: '<v-app><story /></v-app>',
}),
]
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;
再度、storybookを起動するとStorybookが表示されます。
npm run storybook
StorybookにVuetifyのコンポーネントを追加
最後にStorybook上でVuetifyのコンポーネントを追加して表示されることを確認します。
components/Button.stories.ts
と components/Button.vue
を配置します。
<script setup lang="ts">
defineProps<{
/**
* ボタンに表示するテキスト
*/
label: string
variant?: 'plain' | 'outlined' | 'tonal' | 'text'
color: 'primary' | 'success' | 'error' | 'info'
flat?: boolean
exact?: boolean
elevation?: string | number
disabled?: boolean
height?: string | number
href?: string
icon?: any
loading?: string | boolean
}>()
const emits = defineEmits<{
/**
* @param label The label of the button
*/
click: [label: string];
}>()
</script>
<template>
<v-btn
:label="label"
:color="color"
:variant="variant"
:flat="flat"
:exact="exact"
:elevation="elevation"
:disabled="disabled"
@click="() => emits('click', 'Event')"
>
{{ label }}
</v-btn>
</template>
import type { Meta, StoryObj } from '@storybook/vue3'
import Button from './Button.vue'
type Story = StoryObj<typeof Button>
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
label: {
control: {
type: 'text'
}
},
height: {
control: {
type: 'number'
}
},
elevation: {
control: {
type: 'number'
}
},
variant: {
control: {
type: 'select'
},
options: ['plain', 'outlined', 'tonal', 'text', undefined]
},
color: {
control: {
type: 'select'
},
options: ['primary', 'success', 'error', 'info']
},
href: {
control: {
type: 'text'
}
},
onClick: { action: 'click' }
},
render: args => ({
components: { Button },
setup () {
return { args }
},
template: '<Button v-bind="args" />'
})
}
export const Default: Story = {
args: {
label: 'Button',
variant: undefined,
color: 'primary',
flat: false,
rounded: false,
disabled: false,
loading: true,
exact: false,
elevation: 1000,
height: 1000
}
}
export default meta
再度、起動することで配置したVuetifyのボタンを表示されます。
npm run storybook
表示されれば開発環境構築は以上です。
ドキュメントだけではいくつかハマりどころがあるので、参考になればと思います。
補足:検証用のリポジトリと表示用URL
以下の検証用リポジトリとURLはStorybook7系で動いています。
再度、開発環境構築試したところStorybook 8がリリースされていました。
記事自体は、8系として検証しています。
以下の検証用は Stoybook 8にアップデートする予定です。
Discussion