Vue+shadcn-vue+Storybookのテンプレートを作ってみる検証スクラップ

目的
以下でNuxt+shadcn-vue+Storybookの環境を構築していたけどVueに切り替えたいため別リポでテンプレートを作成する。
目標
- 以下の技術スタックが同梱されている
- ✅Vue
- ✅TypeScript
- ✅ESLint
- ✅prettier
- ✅vitest
- ✅Sass
- ✅vue/test-utils
- ✅unplugin-auto-import
- unplugin-vue-componentsと合わせてコンポーネントのオートインポートをするもの
- ✅unplugin-vue-components
- ✅shadcn-vue
- Tailwindcss
- ✅Storybook
- husky
- hygen
- ✅Vue
- ✅上記のスタックでStorybookを公開
- ✅お試しで一つコンポーネントとstoryを公開(リンク)
- テストファイルの作成
- huskyでコンポーネントのテンプレートを作成
- ✅shadcn-vueでbuttonコンポーネントを追加
- ✅shadcn-vueで追加したコンポーネントをラップするコンポーネントの作成
- ✅上記コンポーネントのStory化
- (more)FigmaのvariablesをExport Filtered Variablesでjson化してコピペし、Style-Dictionaryでcss変数化する
- (more)storycapでVRTの実装
- (more)Laravelへの移植
- (more)Gitに絵文字プレフィクスをつける
フォルダ構成
フォルダ構成はvueのデフォルト(create-vue)+Nuxtを参考にしたい気持ち
リポジトリ

Vueのインストール
コマンド1つで質問に答えると以下のツールがインストールされる
- Vue
- TypeScript
- Vitest
- ESLint
- prettier
npm create vue@latest
Need to install the following packages:
create-vue@3.10.3
Ok to proceed? (y)
Vue.js - The Progressive JavaScript Framework
✔ Project name: … vue-template
✔ Add TypeScript? … Yes
✔ Add JSX Support? … Yes
✔ Add Vue Router for Single Page Application development? … No
✔ Add Pinia for state management? … No
✔ Add Vitest for Unit Testing? … Yes
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … Yes
✔ Add Prettier for code formatting? … Yes
✔ Add Vue DevTools 7 extension for debugging? (experimental) … Yes
Scaffolding project in /Users/erina-furusawa/Develop/study/vue-template...
Done. Now run:
cd vue-template
npm install
npm run format
npm run dev
デフォルトで作成されるコンポーネントファイルなどは削除する。

Sassの設定
vueのドキュメントを見るとviteの設定に従えばstyleタグの中で使えるとあるので、viteのドキュメントに従ってsassをインストールする。
npm add -D sass

shadcn-vueを設定する前に初めのコンポーネントを作成する
<script setup lang="ts">
const props = defineProps({
label: {
type: String,
required: true
}
})
</script>
<template>
<Button class="bg-blue-500">
{{ label }}
</Button>
</template>
<style scoped></style>
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import Button from './BasicButton.vue';
const defaultProps = {
label: 'Hello world',
};
describe('render button', () => {
it('is contain label', () => {
const wrapper = mount(Button, { props: defaultProps });
const buttonElement = wrapper.find('button');
const buttonText = buttonElement.text();
expect(buttonText).toBe(defaultProps.label);
});
});

@vue/test-utilsのインストール
DOMのテストは@vue/test-utilsでできるとのことなのでインストールする
npm install --save-dev @vue/test-utils

unplugin-auto-importとunplugin-vue-componentsのインストール
unplugin-auto-import
npm i -D unplugin-auto-import
vite.configの変更
import { fileURLToPath, URL } from 'node:url'
+import AutoImport from 'unplugin-auto-import/vite'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import VueDevTools from 'vite-plugin-vue-devtools'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
vueJsx(),
VueDevTools(),
+ AutoImport({
+ imports: ['vue']
+ })
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
ESLintのための設定を行う
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting',
+ './.eslintrc-auto-import.json'
],
parserOptions: {
ecmaVersion: 'latest'
}
}
unplugin-vue-components
npm i unplugin-vue-components -D
vite.configの変更
+
...
export default defineConfig({
plugins: [
vue(),
vueJsx(),
VueDevTools(),
AutoImport({...}),
+ Components({
+ dts: true,
+ })

auto importは効くけど、コンポーネントの型参照が効かない・・・

shadcn-vueのインストール
npm install -D tailwindcss autoprefixer
vite.configの修正
...
+import tailwind from "tailwindcss"
+import autoprefixer from "autoprefixer"
...
export default defineConfig({
+ css: {
+ postcss: {
+ plugins: [tailwind(), autoprefixer()],
+ },
+ },
...
tsconfigに依存関係解決の記載を追記
{
"compilerOptions": {
// ...
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
// ...
}
}
viteの依存関係解決
npm i -D @types/node
...
export default defineConfig({
...
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src"),
+ },
+ },
...
shadcnのinit
$ npx shadcn-vue@latest init
✔ Would you like to use TypeScript (recommended)? … yes
✔ Which framework are you using? › Vite
✔ Which style would you like to use? › Default
✔ Which color would you like to use as base color? › Slate
✔ Where is your global CSS file? … src/assets/index.css
✔ Would you like to use CSS variables for colors? … yes
✔ Where is your tailwind.config located? … tailwind.config.js
✔ Configure the import alias for components: … @/components
✔ Configure the import alias for utils: … @/lib/utils
✔ Write configuration to components.json. Proceed? … yes
[0:29:44]
✔ Writing components.json...
✔ Initializing project...
✔ Installing dependencies...
[0:29:50]
[0:29:50] ℹ Success! Project initialization completed.
[0:29:50]
main.tsにcssのimport
import { createApp } from 'vue'
import App from './App.vue'
+import './src/assets/index.css'
createApp(App).mount('#app')
試しにbuttonの追加
npx shadcn-vue@latest add button
そのままだとvue/multi-word-component-namesの規約に引っかかるのでインストールしたボタンのファイル名を変更
src/components/ui/button/Button.vue
→ src/components/ui/button/ShadButton.vue
前に作っていたButtonの中をShadButtonに差し替えて表示確認
<script setup lang="ts">
const props = defineProps({
label: {
type: String,
required: true
}
})
</script>
<template>
+ <ShadButton>
{{ label }}
+ </ShadButton>
</template>
<style scoped></style>

Storybookのインストール
npx storybook@latest init
そのままの設定だと依存関係が解決できないので、main.tsを書き換えてStorybookのviteの設定を上書きする。
import type { StorybookConfig } from '@storybook/vue3-vite'
import { fileURLToPath, URL } from 'node:url'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.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'
},
+ async viteFinal(config, options) {
+ config.base = '/'
+ config.assetsInclude = ['/sb-preview/runtime.js']
+ if (config.resolve) {
+ config.resolve.alias = {
+ ...config.resolve.alias,
+ '@': fileURLToPath(new URL('../', import.meta.url))
+ }
+ } else {
+ config.resolve = {
+ alias: {
+ '@': fileURLToPath(new URL('../', import.meta.url))
+ }
+ }
+ }
+
+ if (config.plugins) {
+ config.plugins.push(
+ Components({
+ dts: true,
+ dirs: ['../src/components/common', '../src/components/ui']
+ }),
+ AutoImport({
+ imports: ['vue']
+ })
+ )
+ } else {
+ config.plugins = [
+ Components({
+ dts: true,
+ dirs: ['../src/components/common', '../src/components/ui']
+ }),
+ AutoImport({
+ imports: ['vue']
+ })
+ ]
+ }
+ return config
+ }
}
export default config
また、shadcnで使っているtailwindが読み込めないのでcssの読み込みをpreview.tsに追記する。
import type { Preview } from '@storybook/vue3'
+import '../src/assets/index.css'
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i
}
}
}
}
export default preview
さらにshadcnのコンポーネント内でradix-vueが呼び出せないのでimport文を修正する。
...
+import { Primitive, type PrimitiveProps } from '@/node_modules/radix-vue'
import { type ButtonVariants, buttonVariants } from '.'
+import { cn } from '@/src/lib/utils'
...
storybookファイルを作成して動作確認をする
import type { Meta, StoryObj } from '@storybook/vue3'
import BasicButton from './BasicButton.vue'
const meta = {
title: 'Common/Button',
component: BasicButton,
tags: ['autodocs'],
args: {
label: 'Hello world'
}
} satisfies Meta<typeof BasicButton>
export default meta
type Story = StoryObj<typeof meta>
export const Primary: Story = {}