Message Mockup Tool - Vue.js移行の必要性分析
結論
現時点では Vanilla JS のままで十分。 ただし、以下の条件に該当する場合は Vue.js への移行を検討する価値がある。
前提知識:「状態(state)」とは何か
プログラミングにおける「状態」とは、アプリが「今どうなっているか」を表すデータのことです。
身近な例で理解する
【ECサイトの状態の例】
┌─────────────────────────────────────────────────────┐
│ 状態(state)= アプリの「今の状況」を表すデータ │
├─────────────────────────────────────────────────────┤
│ │
│ ユーザー情報: │
│ ├── ログインしてる? → true / false │
│ ├── 誰がログインしてる? → { name: "田中", ... }│
│ └── カートに何が入ってる? → [商品A, 商品B] │
│ │
│ UI の状態: │
│ ├── メニューは開いてる? → true / false │
│ ├── ローディング中? → true / false │
│ └── エラーメッセージ → "在庫切れです" │
│ │
│ フォームの状態: │
│ ├── 入力された名前 → "山田太郎" │
│ ├── 入力されたメール → "yamada@example.com" │
│ └── バリデーションエラー → { email: "無効" } │
│ │
└─────────────────────────────────────────────────────┘
今回のMessage Mockupツールの「状態」
// message-mockup.js の state がまさに「状態」
const state = {
// ========== データの状態 ==========
sender: 'You', // 送信者の名前(今どうなってる?)
receiver: 'Friend', // 受信者の名前(今どうなってる?)
messages: [...], // メッセージ一覧(今何がある?)
nextId: 7, // 次のメッセージID(今いくつ?)
// ========== UI の状態 ==========
isAnimating: false, // アニメーション再生中?(今どうなってる?)
isRecording: false // 録画中?(今どうなってる?)
}
現状は6個の状態 → シンプルなので管理しやすい
状態が増えるとどうなるか
サービス化すると、こうなる:
const state = {
// ========== 元からある状態(6個)==========
sender: 'You',
receiver: 'Friend',
messages: [...],
nextId: 7,
isAnimating: false,
isRecording: false,
// ========== 認証で追加(4個)==========
user: { id, email, name }, // ログイン中のユーザー
isAuthenticated: false, // ログインしてる?
accessToken: '...', // 認証トークン
tokenExpiry: Date, // トークン有効期限
// ========== 課金で追加(5個)==========
plan: 'free', // 現在のプラン
usageCount: 3, // 今月の使用回数
usageLimit: 5, // 上限
subscriptionId: '...', // Stripe契約ID
billingCycleEnd: Date, // 次回請求日
// ========== 設定で追加(3個)==========
theme: 'light', // ダークモード?
language: 'ja', // 言語設定
defaultApp: 'x', // デフォルトのアプリ
// ========== UIで追加(4個)==========
isSettingsOpen: false, // 設定画面開いてる?
isUpgradeModalOpen: false, // アップグレードモーダル開いてる?
currentTab: 'editor', // 今どのタブ?
sidebarCollapsed: false, // サイドバー閉じてる?
// ========== 履歴で追加(2個)==========
savedMockups: [...], // 保存済みモックアップ
exportHistory: [...] // エクスポート履歴
}
// 合計: 6 + 4 + 5 + 3 + 4 + 2 = 24個の状態!
状態が多いと何が問題か
// 問題1: 状態を変えたら、UIを手動で更新しないといけない
function login(user) {
state.user = user
state.isAuthenticated = true
// 忘れると表示がおかしくなる!
updateHeader() // ヘッダーのログイン状態を更新
updateSidebar() // サイドバーのユーザー名を更新
updateUsageDisplay() // 使用回数表示を更新
checkPlanLimits() // プラン制限をチェック
// ... 他にも更新が必要な場所があるかも?
}
// 問題2: どこで状態が変わったか追跡困難
// 「なぜか isAnimating が true のままになるバグ」
// → コード全体を検索して、どこで変更してるか探す必要がある
// 問題3: 状態同士の依存関係
// 「プランが free なら usageLimit は 5、pro なら無制限」
// → この関係を手動で維持しないといけない
Vue.js なら自動で解決
// Vue.js: 状態を変えると、UIが自動で更新される
const user = ref(null)
const isAuthenticated = computed(() => !!user.value) // 自動計算
// user.value = { name: '田中' } と代入するだけで
// isAuthenticated が true になり
// 関連するUI(ヘッダー、サイドバー等)が全部自動更新される
「状態10個」の目安
| 状態の数 | 管理難易度 | 推奨 |
|---|---|---|
| 1-5個 | 簡単 | Vanilla JS で十分 |
| 6-10個 | まだ管理可能 | Vanilla JS でギリギリ |
| 11-20個 | 複雑化 | Vue.js 検討 |
| 20個以上 | カオス | Vue.js + 状態管理ライブラリ |
Vue.js移行が必要になる条件
1. 状態管理の複雑化
現状(Vanilla JS):
const state = {
sender: 'You',
receiver: 'Friend',
messages: [...],
isAnimating: false,
isRecording: false
}
問題が発生するケース:
- ログイン状態の追加
- 課金プラン・使用回数の管理
- ユーザー設定の永続化
- 複数画面間での状態共有
// 肥大化した状態の例
const state = {
// 元の状態
sender: 'You',
messages: [...],
// 認証関連
user: { id, email, name, avatar },
isAuthenticated: false,
// 課金関連
plan: 'free', // free | pro | enterprise
usageCount: 0,
usageLimit: 5,
billingCycle: 'monthly',
// 設定関連
preferences: { theme, language, defaultApp },
// UI状態
isAnimating: false,
isRecording: false,
isSettingsOpen: false,
isUpgradeModalOpen: false,
// 履歴
savedMockups: [...],
exportHistory: [...]
}
Vanilla JSの問題点:
- 状態変更時の UI 同期が手動(
renderMessages(),renderPreview()を都度呼ぶ) - 状態の依存関係が見えにくい
- デバッグが困難(どこで状態が変わったか追跡しづらい)
Vue.jsの解決策:
// composables/useAuth.ts
export const useAuth = () => {
const user = ref(null)
const isAuthenticated = computed(() => !!user.value)
// 状態変更 → UI自動更新
}
// composables/useBilling.ts
export const useBilling = () => {
const plan = ref('free')
const canExport = computed(() => plan.value !== 'free' || usageCount.value < 5)
}
判断基準: 状態が10個以上になったら移行を検討
2. 複数画面・ルーティングの必要性
現状: 単一HTMLファイルで完結
問題が発生するケース:
/ → ランディングページ
/app → メインツール
/app/history → 作成履歴
/app/settings → 設定
/pricing → 料金プラン
/dashboard → 使用状況ダッシュボード
/login → ログイン
/success → 決済完了
Vanilla JSでの実装:
// 自前でルーティング実装が必要
window.addEventListener('hashchange', () => {
const route = window.location.hash.slice(1)
if (route === '/history') showHistoryPage()
if (route === '/settings') showSettingsPage()
// ... どんどん増える
})
Vue.jsの解決策:
pages/
├── index.vue # ランディング
├── app/
│ ├── index.vue # メインツール
│ ├── history.vue # 履歴
│ └── settings.vue # 設定
├── pricing.vue
└── login.vue
判断基準: 3画面以上必要になったら移行を検討
3. コンポーネントの再利用
現状: 1つのアプリ(X/Twitter DM)のみ
問題が発生するケース: アプリ選択UIがあるが、現状はX DMのみ実装:
<div class="app-grid">
<button data-app="discord">Discord</button>
<button data-app="imessage">iMessage</button>
<button data-app="messenger">Messenger</button>
<button data-app="whatsapp">WhatsApp</button>
<button data-app="x" class="active">X</button>
</div>
複数アプリ対応時のVanilla JS:
// コードの重複が大量発生
function renderXPreview() { ... }
function renderDiscordPreview() { ... }
function renderIMessagePreview() { ... }
// 共通部分の抽出が難しい
Vue.jsの解決策:
<!-- 共通のMessageBubbleコンポーネント -->
<MessageBubble
:text="message.text"
:sender="message.sender"
:theme="currentApp" <!-- x | discord | imessage -->
/>
<!-- アプリごとのスタイルはpropsで切り替え -->
<style>
.bubble[data-theme="x"] { background: #1d9bf0; }
.bubble[data-theme="discord"] { background: #5865f2; }
.bubble[data-theme="imessage"] { background: #34c759; }
</style>
判断基準: 2つ以上のバリエーションを作る場合は移行を検討
4. フォーム・バリデーションの複雑化
現状: シンプルな入力のみ
senderInput.addEventListener('input', (e) => {
state.sender = e.target.value || 'You'
renderPreview()
})
問題が発生するケース:
- 課金フォーム(カード情報、住所)
- プロフィール編集(画像アップロード、複数フィールド)
- バリデーション(必須チェック、形式チェック)
Vanilla JSでの実装:
// バリデーションロジックが散乱
function validateEmail(email) { ... }
function validateCard(card) { ... }
function showError(field, message) { ... }
function clearError(field) { ... }
// 各フィールドにイベントリスナーを手動設定
// エラー表示の更新も手動
Vue.jsの解決策:
<template>
<form @submit.prevent="handleSubmit">
<input v-model="form.email" :class="{ error: errors.email }" />
<span v-if="errors.email">{{ errors.email }}</span>
</form>
</template>
<script setup>
const { form, errors, validate } = useForm({
email: { required: true, email: true },
name: { required: true, minLength: 2 }
})
</script>
判断基準: 入力フィールドが10個以上、またはバリデーションが必要な場合
5. テスト容易性
現状: テストなし(小規模なら問題ない)
問題が発生するケース:
- 課金機能の導入(お金が絡むのでバグは致命的)
- チーム開発(他人のコード変更で壊れないか確認)
- 継続的な機能追加
Vanilla JSのテスト:
// DOM操作のテストは困難
test('メッセージ追加', () => {
// DOMをセットアップ
document.body.innerHTML = `<div id="messages-list"></div>`
// グローバル変数を初期化
state.messages = []
// 関数を呼び出し
addMessage()
// DOMを検証
expect(document.querySelectorAll('.message-item').length).toBe(1)
})
Vue.jsのテスト:
// コンポーネント単位でテスト可能
import { mount } from '@vue/test-utils'
import MessageEditor from './MessageEditor.vue'
test('メッセージ追加', async () => {
const wrapper = mount(MessageEditor, {
props: { messages: [] }
})
await wrapper.find('.add-btn').trigger('click')
expect(wrapper.emitted('add')).toBeTruthy()
})
判断基準: 課金機能導入時、またはチーム開発時
6. 既存インフラの活用
現状のプロジェクト構成:
mdx-playground/
├── apps/web/ # Nuxt 3 プロジェクト(既存)
│ ├── nuxt.config.ts
│ ├── wrangler.toml # Cloudflare設定済み
│ └── content/ # コンテンツ
└── ...
Vanilla JS版のデプロイ:
- 別プロジェクトとして管理
- 別のCloudflare Pages設定
- 別のドメイン/サブドメイン
Vue.js版(Nuxtに統合):
apps/web/
├── app/pages/
│ ├── tools/
│ │ └── message-mockup.vue # ここに追加
│ └── ...
└── wrangler.toml # 既存の設定を流用
メリット:
- 認証システムの共有
- API Routesの共有
- デプロイパイプラインの統一
- ドメインの統一(
yoursite.com/tools/message-mockup)
判断基準: 既存のNuxtプロジェクトに他のツール/機能がある場合
判断フローチャート
サービス化したい
│
▼
ログイン・課金機能が必要?
│
├─ No → Vanilla JSのままCloudflare Pagesへ
│
▼ Yes
│
画面は3つ以上必要?(ダッシュボード、履歴、設定など)
│
├─ No → Vanilla JS + Supabase/Firebase Auth
│ (認証SDKだけ追加)
│
▼ Yes
│
複数のモックアップ種類を作る?(Discord, iMessage等)
│
├─ No → まだVanilla JSで頑張れる
│ (ただし保守コストは上がる)
│
▼ Yes
│
Vue.js移行を推奨
現実的なアドバイス
今すぐやること(Vanilla JS版)
# 1. Cloudflare Pagesにそのままデプロイ
wrangler pages deploy ./public
# 2. 認証はSupabaseを使う(SDK軽い)
npm install @supabase/supabase-js
# 3. StripeはCloudflare Functionsで
# functions/api/checkout.js を作成
移行のトリガー
以下のいずれかに該当したら、Vue.js移行を計画:
- 状態が10個以上 になった
- 画面が3つ以上 必要になった
- Discord/iMessage版 も作ることになった
- チームメンバー が増えた
- バグ修正が困難 になった(どこで何が起きてるかわからない)
移行コスト目安
| 項目 | 工数 |
|---|---|
| 基本構造の移行 | 1-2日 |
| 日時ピッカー | 0.5日 |
| アニメーション | 0.5日 |
| 動画エクスポート | 1-2日 |
| テスト | 1日 |
| 合計 | 4-6日 |
まとめ
| 観点 | Vanilla JS | Vue.js |
|---|---|---|
| 初期開発速度 | ✅ 速い | △ 設計必要 |
| 保守性(小規模) | ✅ 問題なし | △ オーバーキル |
| 保守性(大規模) | ❌ 困難 | ✅ 良好 |
| 状態管理 | △ 手動同期 | ✅ 自動同期 |
| テスト | ❌ 困難 | ✅ 容易 |
| 再利用性 | ❌ コピペ | ✅ コンポーネント |
| 学習コスト | ✅ なし | △ あり |
推奨: まずVanilla JSでリリースし、上記トリガーに該当したら移行を検討。