9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Nuxt3 + Tailwind CSS + Storybook ver7 環境の作り方

Last updated at Posted at 2023-04-18

はじめに

少し前にNuxt3 + Tailwind CSS + Storybook ver7の環境を作ったことがありました。
storybookの導入時NuxtのAutoImportの対応が思いの外時間がかかったので備忘録として環境構築の流れを書いてみました。

環境

node v18.15.0
nuxt ^3.3.2
tailwindcss ^3.3.0
storybook ^7.0.5

Nuxt3プロジェクト作成

まずはNuxt3プロジェクトを作ります。

プロジェクト作成

公式ドキュメントに従ってプロジェクトを作成します。

$ npx nuxi init <project-name>
$ cd <project-name>
$ yarn install

インストールできたら動作確認します。

$ yarn dev

http://localhost:3000/にアクセスするとWelcome to Nuxt!とページが出ました。
これでNuxt3の土台はできました。

Tailwind CSS導入

こちらも公式ドキュメントに従って導入します。

$ yarn add -D tailwindcss postcss autoprefixer

$ npx tailwindcss init
// tailwind.config.jsが作成される

postcss.config.jsを作成

StorybookでTailwind CSSを読み込むためにpostcss.config.jsを作成します。

postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

nuxt.config.tsに追記

postcss.config.jsを作成しましたが、Nuxt3では読み込むためにnuxt.config.tsに書く必要があります。

nuxt.config.ts
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
+  postcss: {
+    plugins: {
+      tailwindcss: {},
+      autoprefixer: {},
+    },
+  },
});

tailwind.config.jsのcontentを書き換える

ここはディレクトリ構成によるので、自分の構成に合わせて書いてみてください。一般的な構成の場合は以下でいいと思います。

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
+   "./*.{vue,js,ts,jsx,tsx}",
+   "./components/**/*.{vue,js,ts,jsx,tsx}",
+   "./layouts/**/*.{vue,js,ts,jsx,tsx}",
+   "./pages/**/*.{vue,js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

Tailwind CSSをCSSファイルに追加します。
今回はassets/css/main.cssにしました。

main.css
@tailwind base;
@tailwind components;
@tailwind utilities;

nuxt.config.tsでmain.cssを読み込みます。

nuxt.config.ts
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  postcss: {
    plugins: {
      tailwindcss: {},
      autoprefixer: {},
    },
  },
+ css: ["~/assets/css/main.css"],
});

Tailwind CSSが適用されているかapp.vueを書き換えて確認しましょう。

app.vue
<template>
  <div class="text-red-500">Hello tailwind CSS!</div>
</template>

文字が赤くなったのでうまくいきました。

Storybook導入

一番沼ったところです。Nuxtの自動インポートをStorybookに適応したりする部分が割と面倒ですがやっていきましょう。

インストール

公式ドキュメントを見ながら以下コマンドでインストールします。

$ npx storybook@latest init --type vue3
// .storybookディレクトリとstoriesディレクトリが作成されます。

Storybookを起動して、インストールできているか確認します。

$ yarn dev

http://localhost:6006 にアクセスしてみるとintroductionページが開きました。

Storybookの設定ファイルを書く

今回componentsは以下のようなディレクトリ構成にします。

components/
  ├ <コンポーネント名>/
  |  ├ index.vue
  |  └ index.stories.ts

そのため.storybook/main.jsのstoriesを以下のようにします。
ここは自分の構成に合わせて適切に設定してください。

.storybook/main.js
/** @type { import('@storybook/vue3-vite').StorybookConfig } */
const config = {
+ stories: [
+   "../*.stories.@(js|jsx|ts|tsx)",
+   "../components/**/*.stories.@(js|jsx|ts|tsx)",
+   "../layouts/**/*.stories.@(js|jsx|ts|tsx)",
+   "../pages/**/*.stories.@(js|jsx|ts|tsx)",
+ ],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions",
  ],
  framework: {
    name: "@storybook/vue3-vite",
    options: {},
  },
  docs: {
    autodocs: "tag",
  },
};
export default config;

preview.jsでTailwind CSSを定義したCSSファイルを読み込みます。

preview.js
/** @type { import('@storybook/vue3').Preview } */
+ import "../assets/css/main.css";
const preview = {
  parameters: {
    actions: { argTypesRegex: "^on[A-Z].*" },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
  },
};

export default preview;

また自動で作成されたstoriesディレクトリは使わないので削除しましょう。

Storybookでも自動インポートに対応する

以下を参考にしました。

VueAPI等の自動インポート

Nuxt3では(Nuxt2もですが)自動インポート機能があります。
import文を書く手間がなくなるので開発がスピーディーに進むのですが、Storybook内では読み込んでくれないのでnot foundになってしまいます。
これを解決するためにunplugin-auto-importを使います。

$ yarn add -D unplugin-auto-import

以下のように設定し、Storybookでも自動インポートされる関数等を読み込めるようにします。

.storybook/main.js
/** @type { import('@storybook/vue3-vite').StorybookConfig } */
+ const AutoImport = require("unplugin-auto-import/vite");
const config = {
  stories: [
    "../*.stories.@(js|jsx|ts|tsx)",
    "../components/**/*.stories.@(js|jsx|ts|tsx)",
    "../layouts/**/*.stories.@(js|jsx|ts|tsx)",
    "../pages/**/*.stories.@(js|jsx|ts|tsx)",
  ],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions",
  ],
  framework: {
    name: "@storybook/vue3-vite",
    options: {},
  },
  docs: {
    autodocs: "tag",
  },
+ viteFinal: async (config) => {
+    if (config.plugins !== undefined) {
+     config.plugins.push(
+       AutoImport({ imports: ["vue"], dts: ".storybook/auto-imports.d.ts" })
+     );
+   }
+   return {
+     ...config,
+     define: {
+       ...config.define,
+       global: "window",
+     },
+   };
+ },
};
export default config;

コンポーネントの自動インポートに対応する

Nuxtにはコンポーネントの自動インポートもあり、こちらはunplugin-auto-componentsでは解決できません。
コンポーネントの自動インポートに対応するためにはunplugin-vue-componentsを使います。
なお、コンポーネントの自動インポートは無効にもできるので無効にする場合、こちらの設定は不要です。

$ yarn add -D unplugin-vue-components

以下のように追記し、Storybookでも自動インポートされる関数等を読み込めるようにします。

.storybook/main.js
/** @type { import('@storybook/vue3-vite').StorybookConfig } */
const AutoImport = require("unplugin-auto-import/vite");
+ const AutoImportComponents = require("unplugin-vue-components/vite");
const config = {
  stories: [
    "../*.stories.@(js|jsx|ts|tsx)",
    "../components/**/*.stories.@(js|jsx|ts|tsx)",
    "../layouts/**/*.stories.@(js|jsx|ts|tsx)",
    "../pages/**/*.stories.@(js|jsx|ts|tsx)",
  ],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions",
  ],
  framework: {
    name: "@storybook/vue3-vite",
    options: {},
  },
  docs: {
    autodocs: "tag",
  },
  viteFinal: async (config) => {
    if (config.plugins !== undefined) {
      config.plugins.push(
        AutoImport({ imports: ["vue"], dts: ".storybook/auto-imports.d.ts" })
      );
+     config.plugins.push(
+       AutoImportComponents({
+         dirs: ["components"],
+         directoryAsNamespace: false, // trueの場合ディレクトリ名もコンポーネント名に含む
+         dts: ".storybook/components.d.ts",
+       })
+     );
+   }
    return {
      ...config,
      define: {
        ...config.define,
        global: "window",
      },
    };
  },
};
export default config;

directoryAsNamespaceはディレクトリが層階構造になっているときにディレクトリ名をコンポーネント名に含むかどうかを決めます。nuxt.comfig.ts側のpathPrefixをfalseにする場合はdirectoryAsNamespaceもfalseにします。

// ディレクトリ名をコンポーネント名に含むとき
// components/Fizz/Bass/index.vueのコンポーネント名は<FizzBuzz />になる
// nuxt.comfig.ts
pathPrefix: true
// .storybook/main.js
directoryAsNamespac:true


// ディレクトリ名をコンポーネント名に含まないとき
// components/Fizz/Bass/index.vueのコンポーネント名は<Buzz />になる
// nuxt.comfig.ts
pathPrefix: false
// .storybook/main.js
directoryAsNamespac:false

自動インポートに対応した際に作成されるファイルはコミットの中にあると若干邪魔にもなるのでgitignoreに追記することをおすすめします。

.gitignore
...
+ .storybook/auto-imports.d.ts
+ .storybook/components.d.ts

ストーリーを作ってみる

ストーリーを作るためにまずコンポーネントを作ります。
今回はかんたんなボタンコンポーネントを作ります。

components/Button/index.vue
<script setup lang="ts">
type ButtonColor = "green" | "red";
interface Props {
  color?: ButtonColor;
}
const props = withDefaults(defineProps<Props>(), {
  color: undefined,
});
const { color } = toRefs(props);
</script>

<template>
  <button
    class="px-4 py-2 rounded-lg transition-colors"
    :class="{
      'text-white bg-green-500 hover:bg-green-700': color === 'green',
      'text-white bg-red-500 hover:bg-red-700': color === 'red',
      'text-black bg-gray-200 hover:bg-gray-400': !color,
    }"
  >
    <slot></slot>
  </button>
</template>

なお本記事ではvue3の大きな変更点であるcomposition apiの解説は省略します。
以下のドキュメント等を見てみてください。

コンポーネントができたのでstoriesも作ります。

components/Button/index.stories.ts
import Button from "./index.vue";

import type { Meta, StoryObj } from "@storybook/vue3";

const meta: Meta<typeof Button> = {
  title: "button",
  component: Button,
  render: (args) => ({
    components: { Button },
    tags: ["autodocs"],  // docsが不要だったら削除してOK
    setup() {
      return { args };
    },
    template: '<Button v-bind="args">ボタン</Button>',
  }),
  argTypes: {
    color: {
      control: {
        type: "inline-radio",
      },
      options: ["green", "red", undefined],
    },
  },
};
export default meta;
type Story = StoryObj<typeof Button>;

export const Component: Story = {
  args: {
    color: "green",
  },
};

最後に http://localhost:6006 にアクセスして確認します。
スクリーン ショット 2023-04-19 に 01.58.22 午前.png
うまく行きました。ControlsでPropsに設定した色の選択もできます。

最後に

今回はNuxt3 + Tailwind CSS + Storybook ver7の環境構築をしました。
Storybook ver7も2023/04/03に正式リリースされ、今後はスタンダートになると思います。
今回紹介した構成を参考にしていただければ幸いです。

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?