T-CREATOR

Storybook × Nuxt/SvelteKit/VitePress:マルチフレームワーク併用の初期配線

Storybook × Nuxt/SvelteKit/VitePress:マルチフレームワーク併用の初期配線

モダンなフロントエンド開発では、単一のプロジェクトで複数のフレームワークを使い分けるケースが増えています。アプリケーション本体は Nuxt で構築し、ドキュメントサイトは VitePress、そしてコンポーネントカタログは Storybook で管理する、といった具合です。

この記事では、Storybook 環境に複数のフレームワーク(Nuxt、SvelteKit、VitePress)を併用して配置する際の初期設定手順と、それぞれのフレームワークを共存させるための配線方法を解説します。マルチフレームワーク構成の第一歩として、プロジェクトの土台作りに焦点を当ててご紹介しましょう。

背景

マルチフレームワーク構成の需要

近年のフロントエンド開発では、目的に応じて最適なフレームワークを選択するアプローチが一般的になっています。

たとえば、以下のような使い分けが考えられます:

#用途フレームワーク理由
1アプリケーション本体NuxtSSR/SSG に強く、Vue.js エコシステムが充実
2管理画面・ダッシュボードSvelteKit軽量で高速、シンプルな記述が可能
3ドキュメントサイトVitePressMarkdown ベースで簡単に構築できる
4コンポーネントカタログStorybookUI コンポーネントの開発・テスト・ドキュメント化

このような構成を採用すると、それぞれの強みを活かしながら、開発効率と保守性を両立できるのです。

Storybook の役割

Storybook は、UI コンポーネントを独立した環境で開発・テスト・ドキュメント化するためのツールです。複数のフレームワークで構築されたコンポーネントを一元管理できるため、マルチフレームワーク構成において中心的な役割を果たします。

以下の図は、Storybook を中心としたマルチフレームワーク構成の概要を示しています。

mermaidflowchart TB
  dev["開発者"] -->|コンポーネント開発| sb["Storybook<br/>統合環境"]
  sb -->|Nuxt用| nuxt["Nuxt アプリ"]
  sb -->|SvelteKit用| svelte["SvelteKit 管理画面"]
  sb -->|VitePress用| vite["VitePress ドキュメント"]
  nuxt -->|共有コンポーネント| shared[("共有ライブラリ")]
  svelte -->|共有コンポーネント| shared
  vite -->|共有コンポーネント| shared

Storybook が各フレームワークのコンポーネントを統合管理し、共有ライブラリとして再利用可能な形で提供します。この構成により、コンポーネントの一貫性を保ちながら、フレームワーク固有の最適化も実現できるわけですね。

課題

マルチフレームワーク併用時の技術的課題

複数のフレームワークを Storybook で管理する際、以下のような課題に直面します。

ビルドツールとバンドラーの競合

各フレームワークは独自のビルドツール(Vite、Webpack、Rollup など)を使用しています。これらを 1 つの Storybook プロジェクトで共存させるには、設定の調整が必要です。

依存関係の管理

Nuxt、SvelteKit、VitePress はそれぞれ異なる依存パッケージを必要とします。バージョンの競合や重複を避けながら、すべてのフレームワークが正常に動作する環境を構築しなければなりません。

TypeScript 設定の統一

フレームワークごとに TypeScript の設定(tsconfig.json)が異なる場合、型定義の整合性を保つのが難しくなります。

以下の図は、マルチフレームワーク構成における主な課題を示しています。

mermaidflowchart LR
  issues["マルチフレームワーク<br/>併用時の課題"]
  issues -->|課題1| build["ビルドツール<br/>の競合"]
  issues -->|課題2| deps["依存関係<br/>管理の複雑化"]
  issues -->|課題3| types["TypeScript設定<br/>の不整合"]
  build -->|解決策| vite_main["Vite統一化"]
  deps -->|解決策| yarn_ws["Yarn Workspaces"]
  types -->|解決策| shared_config["共通tsconfig"]

これらの課題を適切に解決することで、スムーズなマルチフレームワーク開発環境が実現します。

解決策

モノレポ構成によるプロジェクト管理

マルチフレームワーク構成を管理する最も効果的な方法は、モノレポ(monorepo)アーキテクチャの採用です。Yarn Workspaces を使用することで、複数のフレームワークを 1 つのリポジトリで効率的に管理できます。

プロジェクト構成の全体像

まず、推奨されるディレクトリ構成を確認しましょう。

csharpproject-root/
├── package.json              # ルートパッケージ設定
├── yarn.lock                 # 依存関係ロックファイル
├── .storybook/               # Storybook共通設定
│   ├── main.ts
│   └── preview.ts
├── packages/
│   ├── nuxt-app/             # Nuxtアプリケーション
│   │   ├── package.json
│   │   ├── nuxt.config.ts
│   │   └── components/
│   ├── sveltekit-app/        # SvelteKitアプリケーション
│   │   ├── package.json
│   │   ├── svelte.config.js
│   │   └── src/lib/
│   ├── vitepress-docs/       # VitePressドキュメント
│   │   ├── package.json
│   │   ├── .vitepress/
│   │   └── docs/
│   └── shared/               # 共有コンポーネント・ユーティリティ
│       ├── package.json
│       ├── components/
│       └── utils/
└── tsconfig.base.json        # TypeScript共通設定

この構成により、各フレームワークを独立したパッケージとして管理しつつ、共通の依存関係やツールを効率的に共有できます。

Yarn Workspaces の設定

プロジェクトルートに配置する package.json で、Yarn Workspaces を有効化します。

json{
  "name": "multi-framework-storybook",
  "private": true,
  "workspaces": ["packages/*"],
  "scripts": {
    "dev": "yarn workspace @app/nuxt-app dev",
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build"
  }
}

workspacesフィールドにpackages​/​*を指定することで、packages ディレクトリ配下のすべてのサブディレクトリがワークスペースとして認識されます。これにより、各パッケージ間で依存関係を簡単に共有できるのです。

具体例

ステップ 1:プロジェクトの初期化

まず、プロジェクトのルートディレクトリを作成し、Yarn Workspaces を初期化しましょう。

bashmkdir multi-framework-storybook
cd multi-framework-storybook
yarn init -y

次に、ルートの package.json を編集してワークスペースを有効化します。

json{
  "name": "multi-framework-storybook",
  "version": "1.0.0",
  "private": true,
  "workspaces": ["packages/*"]
}

private: trueを設定することで、このルートパッケージが npm レジストリに公開されないようにします。ワークスペース構成では必須の設定ですね。

ステップ 2:Storybook の導入

プロジェクトルートで Storybook を初期化します。

bashnpx storybook@latest init --type vue3

Storybook のインストールが完了したら、マルチフレームワークに対応できるように設定を調整します。

.storybook/main.ts を以下のように設定しましょう。

typescriptimport type { StorybookConfig } from '@storybook/vue3-vite';

const config: StorybookConfig = {
  // Storiesの検索パターンを複数フレームワークに対応
  stories: [
    '../packages/nuxt-app/**/*.stories.@(js|jsx|ts|tsx)',
    '../packages/sveltekit-app/**/*.stories.@(js|jsx|ts|tsx)',
    '../packages/shared/**/*.stories.@(js|jsx|ts|tsx)',
  ],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
  ],
  framework: {
    name: '@storybook/vue3-vite',
    options: {},
  },
};

export default config;

stories配列に複数のパッケージディレクトリを指定することで、各フレームワークのストーリーファイルを自動的に読み込めます。

ステップ 3:Nuxt アプリケーションの追加

packages ディレクトリ配下に Nuxt アプリケーションを作成します。

bashmkdir -p packages/nuxt-app
cd packages/nuxt-app
npx nuxi init .

Nuxt の初期化が完了したら、package.json にワークスペース用の名前を設定しましょう。

json{
  "name": "@app/nuxt-app",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "nuxt dev",
    "build": "nuxt build",
    "generate": "nuxt generate"
  }
}

@app​/​nuxt-appというスコープ付きの名前を使用することで、ワークスペース内での識別が明確になります。

次に、Nuxt コンポーネント用の Storybook ストーリーを作成します。

Nuxt コンポーネントの作成

components/HelloWorld.vue を作成しましょう。

vue<template>
  <div class="hello-world">
    <h1>{{ message }}</h1>
    <p>{{ description }}</p>
  </div>
</template>

<script setup lang="ts">
// Propsの型定義
interface Props {
  message: string;
  description?: string;
}

// デフォルト値を持つPropsの定義
withDefaults(defineProps<Props>(), {
  description: 'Nuxtで構築されたコンポーネントです',
});
</script>

このコンポーネントは、メッセージと説明文を表示するシンプルなコンポーネントです。TypeScript の型定義も含めているため、型安全性が確保されます。

Storybook ストーリーの作成

components/HelloWorld.stories.ts を作成します。

typescriptimport type { Meta, StoryObj } from '@storybook/vue3';
import HelloWorld from './HelloWorld.vue';

// メタデータの定義
const meta: Meta<typeof HelloWorld> = {
  title: 'Nuxt/HelloWorld',
  component: HelloWorld,
  tags: ['autodocs'],
};

export default meta;
type Story = StoryObj<typeof HelloWorld>;

メタデータでは、ストーリーのタイトルやコンポーネント、自動ドキュメント生成のタグを設定しています。titleNuxt​/​というプレフィックスを付けることで、Storybook 上でフレームワークごとに分類されるのです。

次に、具体的なストーリーを定義しましょう。

typescript// デフォルトストーリー
export const Default: Story = {
  args: {
    message: 'こんにちは、Nuxtの世界へ!',
  },
};

// カスタムストーリー
export const WithCustomDescription: Story = {
  args: {
    message: 'Nuxt × Storybook',
    description:
      'マルチフレームワーク構成でコンポーネントを管理しています',
  },
};

複数のストーリーを定義することで、コンポーネントの様々な状態を視覚的に確認できます。

ステップ 4:SvelteKit アプリケーションの追加

続いて、SvelteKit アプリケーションを packages ディレクトリに追加します。

bashcd ../../
mkdir -p packages/sveltekit-app
cd packages/sveltekit-app
yarn create svelte@latest .

対話形式のプロンプトでは、以下のように選択しましょう:

  • Skeleton project を選択
  • TypeScript を有効化
  • ESLint、Prettier などのツールを必要に応じて追加

SvelteKit の初期化が完了したら、package.json を編集します。

json{
  "name": "@app/sveltekit-app",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "vite dev",
    "build": "vite build",
    "preview": "vite preview"
  }
}

SvelteKit コンポーネントの作成

src/lib/components/Counter.svelte を作成しましょう。

svelte<script lang="ts">
  // カウンターの状態管理
  let count = 0;

  // カウントアップ関数
  function increment() {
    count += 1;
  }

  // カウントダウン関数
  function decrement() {
    count -= 1;
  }

  // リセット関数
  function reset() {
    count = 0;
  }
</script>

<div class="counter">
  <h2>カウンター: {count}</h2>
  <div class="buttons">
    <button on:click={increment}>+1</button>
    <button on:click={decrement}>-1</button>
    <button on:click={reset}>リセット</button>
  </div>
</div>

Svelte のリアクティブシステムを活用した、シンプルなカウンターコンポーネントです。状態管理がとても直感的で理解しやすいのが特徴ですね。

Storybook ストーリーの作成

src/lib/components/Counter.stories.ts を作成します。

typescriptimport type { Meta, StoryObj } from '@storybook/svelte';
import Counter from './Counter.svelte';

const meta: Meta<typeof Counter> = {
  title: 'SvelteKit/Counter',
  component: Counter,
  tags: ['autodocs'],
};

export default meta;
type Story = StoryObj<typeof Counter>;

SvelteKit 用のストーリーでは、@storybook​/​svelteを使用します。Nuxt と同様に、タイトルにフレームワーク名を含めることで分類します。

typescriptexport const Default: Story = {};

カウンターコンポーネントは Props を受け取らないため、デフォルトストーリーは空のオブジェクトで十分です。

ステップ 5:VitePress ドキュメントの追加

最後に、VitePress によるドキュメントサイトを追加しましょう。

bashcd ../../
mkdir -p packages/vitepress-docs
cd packages/vitepress-docs
yarn init -y
yarn add -D vitepress vue

package.json にスクリプトを追加します。

json{
  "name": "@app/vitepress-docs",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "docs:dev": "vitepress dev docs",
    "docs:build": "vitepress build docs",
    "docs:preview": "vitepress preview docs"
  },
  "devDependencies": {
    "vitepress": "^1.0.0",
    "vue": "^3.4.0"
  }
}

VitePress 設定ファイルの作成

.vitepress/config.ts を作成し、基本設定を行います。

typescriptimport { defineConfig } from 'vitepress';

export default defineConfig({
  title: 'マルチフレームワーク ドキュメント',
  description:
    'Nuxt、SvelteKit、VitePressの統合ドキュメント',
  themeConfig: {
    nav: [
      { text: 'ホーム', link: '/' },
      { text: 'ガイド', link: '/guide/' },
      { text: 'コンポーネント', link: '/components/' },
    ],
  },
});

この設定により、ナビゲーションメニューが表示され、各セクションへのアクセスが容易になります。

次に、サイドバーの設定を追加しましょう。

typescriptexport default defineConfig({
  // ... 前述の設定
  themeConfig: {
    // ... nav設定
    sidebar: [
      {
        text: 'はじめに',
        items: [
          { text: '概要', link: '/guide/' },
          { text: 'セットアップ', link: '/guide/setup' },
        ],
      },
      {
        text: 'コンポーネント',
        items: [
          { text: 'Nuxt', link: '/components/nuxt' },
          { text: 'SvelteKit', link: '/components/svelte' },
        ],
      },
    ],
  },
});

サイドバー設定により、ドキュメントの構造が明確になり、ユーザーが必要な情報を素早く見つけられます。

ドキュメントページの作成

docs/index.md を作成し、トップページを作成します。

markdown# マルチフレームワーク構成へようこそ

このプロジェクトでは、Nuxt、SvelteKit、VitePress を組み合わせた
マルチフレームワーク構成を採用しています。

# 特徴

- **Nuxt**: アプリケーション本体の構築
- **SvelteKit**: 管理画面やダッシュボード
- **VitePress**: ドキュメントサイト
- **Storybook**: コンポーネントカタログ

各フレームワークの強みを活かした、効率的な開発環境です。

Markdown 形式でドキュメントを記述できるため、技術ドキュメントの作成が非常に簡単ですね。

ステップ 6:TypeScript 共通設定の作成

プロジェクトルートに tsconfig.base.json を作成し、共通の TypeScript 設定を定義します。

json{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "moduleResolution": "bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true
  }
}

この基本設定により、型チェックの厳密性と互換性が確保されます。各フレームワークの tsconfig.json では、この設定を継承することで一貫性を保てるのです。

各パッケージの tsconfig.json で基本設定を継承しましょう。

Nuxt の tsconfig.json

packages/nuxt-app/tsconfig.json を作成します。

json{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "types": ["@nuxt/types"]
  }
}

SvelteKit の tsconfig.json

packages/sveltekit-app/tsconfig.json を編集します。

json{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "types": ["vite/client", "@sveltejs/kit"]
  },
  "include": ["src/**/*.ts", "src/**/*.svelte"]
}

これにより、すべてのフレームワークで一貫した TypeScript 設定が適用されます。

ステップ 7:Storybook の起動と動作確認

すべての設定が完了したら、Storybook を起動して動作を確認しましょう。

bashcd /path/to/project-root
yarn storybook

Storybook が起動すると、ブラウザでhttp:​/​​/​localhost:6006にアクセスできます。サイドバーには以下のように各フレームワークのコンポーネントが分類表示されるはずです:

#カテゴリコンポーネントフレームワーク
1NuxtHelloWorldVue 3 / Nuxt
2SvelteKitCounterSvelte / SvelteKit

以下の図は、Storybook を中心とした開発フローを示しています。

mermaidsequenceDiagram
  participant Dev as 開発者
  participant SB as Storybook
  participant Nuxt as Nuxt コンポーネント
  participant Svelte as SvelteKit コンポーネント

  Dev->>SB: yarn storybook
  SB->>SB: ビルド設定読み込み
  SB->>Nuxt: ストーリー検索
  Nuxt-->>SB: HelloWorld.stories.ts
  SB->>Svelte: ストーリー検索
  Svelte-->>SB: Counter.stories.ts
  SB->>Dev: localhost:6006 起動完了
  Dev->>SB: コンポーネント選択
  SB->>Dev: プレビュー表示

開発者が Storybook を起動すると、各フレームワークのストーリーが自動的に読み込まれ、統合された UI で閲覧できます。コンポーネントの開発と確認がとてもスムーズになるわけです。

ステップ 8:共有コンポーネントライブラリの作成

複数のフレームワークで再利用可能な共有コンポーネントを管理するため、shared パッケージを作成します。

bashmkdir -p packages/shared
cd packages/shared
yarn init -y

package.json を編集します。

json{
  "name": "@app/shared",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "types": "./dist/index.d.ts"
    }
  }
}

exportsフィールドを使用することで、ES モジュールとしてパッケージをエクスポートできます。

共有ユーティリティ関数の作成

utils/format.ts を作成し、共通のユーティリティ関数を定義しましょう。

typescript/**
 * 日付を指定フォーマットで文字列に変換
 */
export function formatDate(
  date: Date,
  format: string = 'YYYY-MM-DD'
): string {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(
    2,
    '0'
  );
  const day = String(date.getDate()).padStart(2, '0');

  return format
    .replace('YYYY', String(year))
    .replace('MM', month)
    .replace('DD', day);
}

この関数は、各フレームワークから共通して利用できる日付フォーマット機能を提供します。

次に、数値フォーマット関数も追加しましょう。

typescript/**
 * 数値をカンマ区切りの文字列に変換
 */
export function formatNumber(num: number): string {
  return num.toLocaleString('ja-JP');
}

utils/index.ts でエクスポートします。

typescriptexport { formatDate, formatNumber } from './format';

これらのユーティリティ関数は、Nuxt でも SvelteKit でも同じように利用できるため、コードの重複を避けられます。

動作確認のポイント

マルチフレームワーク構成が正しく動作しているか、以下の点を確認しましょう:

#確認項目期待される結果
1Storybook の起動エラーなく起動し、localhost:6006 にアクセス可能
2Nuxt ストーリー表示HelloWorld コンポーネントが正しくレンダリングされる
3SvelteKit ストーリー表示Counter コンポーネントが動作し、カウントが増減する
4型チェックyarn tsc でエラーが発生しない
5ホットリロードコンポーネント編集時に自動更新される

これらすべてが正常に動作すれば、マルチフレームワーク併用の初期配線は完了です。

まとめ

本記事では、Storybook を中心としたマルチフレームワーク(Nuxt、SvelteKit、VitePress)併用環境の構築手順を解説しました。

重要なポイントをおさらいしましょう:

環境構築の要点

  • Yarn Workspaces を使用したモノレポ構成により、複数のフレームワークを効率的に管理
  • Storybook の設定で各フレームワークのストーリーパスを指定し、統合的なコンポーネントカタログを実現
  • 共通の TypeScript 設定を継承することで、型安全性と一貫性を確保

各フレームワークの役割

  • Nuxt はアプリケーション本体の構築に適しており、SSR/SSG の強みを活かせる
  • SvelteKit は軽量で高速な管理画面構築に最適
  • VitePress は Markdown ベースのドキュメントを簡単に作成できる
  • Storybook はすべてのコンポーネントを一元管理し、開発効率を向上させる

この初期配線を土台として、各フレームワークの特性を活かした開発を進められます。コンポーネントの共有やスタイルの統一、状態管理の最適化など、さらなる発展的な実装にも対応可能です。

マルチフレームワーク構成は、一見複雑に思えるかもしれませんが、適切な設定と構造化により、各技術の利点を最大限に引き出せる強力なアプローチになります。ぜひ、この記事を参考に、プロジェクトに最適な構成を見つけてください。

関連リンク