mcpField
C
capacitor-vue

capacitor-vue

github.com/capawesome-team開発52874 回インストール1 週間前に更新
リポジトリ

capacitor-vue について

Vue 3 と Capacitor を使う開発者向けの開発スキルです。検出した Vue、Quasar、Nuxt の構成に合わせ、プラグインやライフサイクル、ルーティングの実装方法を案内します。

主な機能

  1. 01Vue、Capacitor、フレームワークの検出
  2. 02プラグインをコンポーザブルでラップ
  3. 03プラグインリスナーのライフサイクル管理
  4. 04ディープリンクと Vue Router の連携
  5. 05Android の戻るボタンとプラットフォーム判定

活用シーン

  1. 01既存の Vue プロジェクトへの Capacitor 導入
  2. 02プラグイン用の再利用可能なコンポーザブル作成
  3. 03リスナーの後処理やプラットフォーム設定の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/capawesome-team/skills --skill capacitor-vue

インストール後、Claude Code で/capacitor-vueを入力して読み込まれたか確認します。

同梱ファイルreferences/nuxt.mdreferences/quasar.md

詳細

Capacitor と Vue

Capacitor アプリ開発における Vue 固有のパターンとベストプラクティス — Composition API、コンポーザブル、リアクティビティ、ライフサイクルフック、Vue Router との統合、Quasar と Nuxt 向けのフレームワーク固有のガイダンスです。

前提条件

  1. Vue 3 を使用する Capacitor 6、7、または 8 のアプリ。
  2. Node.js と npm がインストールされていること。
  3. iOS の場合:Xcode がインストールされていること。
  4. Android の場合:Android Studio がインストールされていること。

エージェントの動作

  • 質問する前に自動検出します。 プロジェクト内の vite.config.ts、vite.config.js、quasar.config.js、quasar.config.ts、nuxt.config.ts、package.json、capacitor.config.ts または capacitor.config.json と、既存のディレクトリ構造を確認します。検出できない場合に限り、ユーザーに質問します。
  • 手順を追って案内します。 プロセスを一度に1つのステップずつ案内します。
  • メタフレームワークを検出します。 プロジェクトがプレーンな Vue(Vite)、Quasar、Nuxt のいずれを使用しているかを判断し、それに応じて手順を調整します。

手順

ステップ 1:プロジェクトを分析する

プロジェクトファイルを読み取り、次の項目を自動検出します。

  1. Vue のバージョン:package.json から vue のバージョンを読み取ります。
  2. Capacitor のバージョン:package.json から @capacitor/core のバージョンを読み取ります。存在しない場合は、Capacitor がまだ追加されていないため、ステップ 2 に進みます。
  3. メタフレームワーク:次のファイルを順に確認してフレームワークを検出します。
    • quasar.config.js または quasar.config.ts — Quasar プロジェクトです。references/quasar.md に進みます。
    • nuxt.config.ts または nuxt.config.js — Nuxt プロジェクトです。references/nuxt.md に進みます。
    • vite.config.ts または vite.config.js — プレーンな Vue(Vite) プロジェクトです。以下の手順を続けます。
  4. プラットフォーム:存在するディレクトリ(android/、ios/)を確認します。
  5. Capacitor の設定形式:capacitor.config.ts(TypeScript)または capacitor.config.json(JSON)があるか確認します。
  6. ビルド出力ディレクトリ:vite.config.ts または vite.config.js から build.outDir を読み取ります。デフォルトは dist です。
ステップ 2:Vue プロジェクトに Capacitor を追加する

@capacitor/core がすでに package.json に含まれている場合はスキップします。プロジェクトが Quasar を使用している場合もスキップします(Quasar には独自の Capacitor 統合があります。references/quasar.md を参照してください)。

  1. Capacitor のコアと CLI をインストールします。

    npm install @capacitor/core
    npm install -D @capacitor/cli
    
  2. Capacitor を初期化します。

    npx cap init
    

    プロンプトが表示されたら、web directory にステップ 1 で検出した Vue のビルド出力パスを設定します。Vite ベースの Vue プロジェクトでは、通常 dist です。

  3. 生成された capacitor.config.ts または capacitor.config.json の webDir の値が Vue のビルド出力パスと一致していることを確認します。一致していない場合は更新します。

    capacitor.config.ts:

    import type { CapacitorConfig } from '@capacitor/cli';
    
    const config: CapacitorConfig = {
      appId: 'com.example.app',
      appName: 'my-app',
      webDir: 'dist',
    };
    
    export default config;
    

    capacitor.config.json:

    {
      "appId": "com.example.app",
      "appName": "my-app",
      "webDir": "dist"
    }
    
  4. Vue アプリをビルドしてプラットフォームを追加します。

    npm run build
    npm install @capacitor/android @capacitor/ios
    npx cap add android
    npx cap add ios
    npx cap sync
    
ステップ 3:プロジェクト構造

Capacitor を使用する Vue プロジェクト(Vite ベース)の構造は次のとおりです。

my-app/
├── android/                  # Android native project
├── ios/                      # iOS native project
├── public/
├── src/
│   ├── assets/
│   ├── components/
│   ├── composables/          # Custom composables for Capacitor plugins
│   ├── router/
│   │   └── index.ts          # Vue Router configuration
│   ├── views/
│   ├── App.vue
│   └── main.ts
├── capacitor.config.ts       # or capacitor.config.json
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts

ポイント:

  • android/ と ios/ ディレクトリにはネイティブプロジェクトが含まれており、バージョン管理にコミットする必要があります。
  • src/ ディレクトリには Vue アプリが含まれます。これは Capacitor アプリのウェブレイヤーです。
  • Capacitor プラグインをラップするカスタムコンポーザブルは src/composables/ に配置します。
  • Capacitor プラグインは、src/ 内の Vue コンポーネントまたはコンポーザブルから呼び出します。
ステップ 4:Vue で Capacitor プラグインを使用する

Capacitor プラグインはプレーンな TypeScript API です。Composition API を使用して、Vue コンポーネントから直接インポートして呼び出します。

コンポーネントで直接使用する
<script setup lang="ts">
import { ref } from 'vue';
import { Geolocation } from '@capacitor/geolocation';

const latitude = ref<number | null>(null);
const longitude = ref<number | null>(null);

async function getCurrentPosition() {
  const position = await Geolocation.getCurrentPosition();
  latitude.value = position.coords.latitude;
  longitude.value = position.coords.longitude;
}
</script>

<template>
  <div>
    <p>Latitude: {{ latitude }}</p>
    <p>Longitude: {{ longitude }}</p>
    <button @click="getCurrentPosition">Get Location</button>
  </div>
</template>
プラグインをコンポーザブルでラップする(推奨)

Capacitor プラグインをコンポーザブルでラップすると、再利用性、カプセル化されたリアクティブな状態、自動クリーンアップが得られます。

// src/composables/useCamera.ts
import { ref } from 'vue';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import type { Photo } from '@capacitor/camera';

export function useCamera() {
  const photo = ref<Photo | null>(null);
  const error = ref<string | null>(null);

  async function takePhoto(): Promise<void> {
    try {
      error.value = null;
      photo.value = await Camera.getPhoto({
        quality: 90,
        allowEditing: false,
        resultType: CameraResultType.Uri,
        source: CameraSource.Camera,
      });
    } catch (e) {
      error.value = e instanceof Error ? e.message : String(e);
    }
  }

  async function pickFromGallery(): Promise<void> {
    try {
      error.value = null;
      photo.value = await Camera.getPhoto({
        quality: 90,
        allowEditing: false,
        resultType: CameraResultType.Uri,
        source: CameraSource.Photos,
      });
    } catch (e) {
      error.value = e instanceof Error ? e.message : String(e);
    }
  }

  return {
    photo,
    error,
    takePhoto,
    pickFromGallery,
  };
}

コンポーネントでコンポーザブルを使用します。

<script setup lang="ts">
import { useCamera } from '@/composables/useCamera';

const { photo, error, takePhoto } = useCamera();
</script>

<template>
  <div>
    <button @click="takePhoto">Take Photo</button>
    <p v-if="error">Error: {{ error }}</p>
    <img v-if="photo?.webPath" :src="photo.webPath" alt="Captured photo" />
  </div>
</template>
ステップ 5:ライフサイクルフックを使ったプラグインのイベントリスナー

Capacitor プラグインのイベントリスナーは、メモリーリークを防ぐために onMounted で登録し、onUnmounted で削除する必要があります。Vue のリアクティビティシステムは ref の変更を自動的に検出するため、NgZone 相当の問題はありません。ただし、クリーンアップは依然として重要です。

自動クリーンアップ付きコンポーザブル
// src/composables/useNetwork.ts
import { ref, onMounted, onUnmounted } from 'vue';
import { Network } from '@capacitor/network';
import type { ConnectionStatus } from '@capacitor/network';
import type { PluginListenerHandle } from '@capacitor/core';

export function useNetwork() {
  const status = ref<ConnectionStatus | null>(null);
  let listenerHandle: PluginListenerHandle | null = null;

  onMounted(async () => {
    status.value = await Network.getStatus();
    listenerHandle = await Network.addListener('networkStatusChange', (newStatus) => {
      status.value = newStatus;
    });
  });

  onUnmounted(async () => {
    await listenerHandle?.remove();
  });

  return {
    status,
  };
}

コンポーネントでの使用例:

<script setup lang="ts">
import { useNetwork } from '@/composables/useNetwork';

const { status } = useNetwork();
</script>

<template>
  <p v-if="status">Network: {{ status.connected ? 'Online' : 'Offline' }}</p>
</template>
App.vue を使用したアプリ全体のリスナー

アプリのライフサイクル全体で維持する必要があるリスナー(アプリの状態変更など)は、App.vue に登録します。


<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue';
import { App } from '@capacitor/app';
import type { PluginListenerHandle } from '@capacitor/core';
import { RouterView } from 'vue-router';

let appStateListener: PluginListenerHandle | null = null;

onMounted(async () => {
  appStateListener = await App.addListener('appStateChange', (state) => {
    console.log('App state changed. Is active:', state.isActive);
  });
});

onUnmounted(async () => {
  await appStateListener?.remove();
});
</script>

<template>
  <RouterView />
</template>
ステップ 6:プラットフォームの検出

Capacitor.isNativePlatform() と Capacitor.getPlatform() を使用して、ネイティブ専用コードを条件付きで実行します。再利用できるよう、これをコンポーザブルでラップします。

// src/composables/usePlatform.ts
import { Capacitor } from '@capacitor/core';

export function usePlatform() {
  const platform = Capacitor.getPlatform() as 'web' | 'ios' | 'android';
  const isNative = Capacitor.isNativePlatform();
  const isIos = platform === 'ios';
  const isAndroid = platform === 'android';
  const isWeb = platform === 'web';

  return {
    platform,
    isNative,
    isIos,
    isAndroid,
    isWeb,
  };
}

コンポーネントで使用して、ネイティブ専用機能の表示と非表示を切り替えます。

<script setup lang="ts">
import { usePlatform } from '@/composables/usePlatform';

const { isNative } = usePlatform();
</script>

<template>
  <button v-if="isNative" @click="openNativeSettings()">Open Device Settings</button>
</template>
ステップ 7:Vue Router によるディープリンクのルーティング

Capacitor の App.addListener('appUrlOpen', ...) イベントを Vue Router のナビゲーションにマッピングして、ディープリンクを処理します。App.vue または専用のコンポーザブルで設定します。

// src/composables/useDeepLinks.ts
import { onMounted, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import { App } from '@capacitor/app';
import type { PluginListenerHandle } from '@capacitor/core';

export function useDeepLinks() {
  const router = useRouter();
  let listenerHandle: PluginListenerHandle | null = null;

  onMounted(async () => {
    listenerHandle = await App.addListener('appUrlOpen', (event) => {
      const url = new URL(event.url);
      const path = url.pathname;

      // Navigate to the route matching the deep link path.
      // Adjust the path parsing logic to match the app's URL scheme.
      if (path) {
        router.push(path);
      }
    });
  });

  onUnmounted(async () => {
    await listenerHandle?.remove();
  });
}

App.vue でコンポーザブルを使用します。


<script setup lang="ts">
import { useDeepLinks } from '@/composables/useDeepLinks';

useDeepLinks();
</script>

<template>
  <RouterView />
</template>
ステップ 8:戻るボタンの処理(Android)

App.addListener('backButton', ...) と Vue Router を組み合わせて、Android のハードウェアの戻るボタンを処理します。

// src/composables/useBackButton.ts
import { onMounted, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import { App } from '@capacitor/app';
import { Capacitor } from '@capacitor/core';
import type { PluginListenerHandle } from '@capacitor/core';

export function useBackButton() {
  const router = useRouter();
  let listenerHandle: PluginListenerHandle | null = null;

  onMounted(async () => {
    if (Capacitor.getPlatform() !== 'android') {
      return;
    }

    listenerHandle = await App.addListener('backButton', ({ canGoBack }) => {
      if (canGoBack) {
        router.back();
      } else {
        App.exitApp();
      }
    });
  });

  onUnmounted(async () => {
    await listenerHandle?.remove();
  });
}

App.vue でコンポーザブルを使用します。


<script setup lang="ts">
import { useBackButton } from '@/composables/useBackButton';

useBackButton();
</script>

<template>
  <RouterView />
</template>
ステップ 9:PWA Elements のセットアップ

一部の Capacitor プラグイン(Camera、Toast など)では、ウェブ用のフォールバック UI に @ionic/pwa-elements が必要です。プロジェクトでこれらのプラグインを使用し、ウェブを対象とする場合は、次の手順を行います。

  1. PWA Elements をインストールします。

    npm install @ionic/pwa-elements
    
  2. src/main.ts で createApp() より前にカスタム要素を登録します。

    import { createApp } from 'vue';
    import { defineCustomElements } from '@ionic/pwa-elements/loader';
    import App from './App.vue';
    import router from './router';
    
    defineCustomElements(window);
    
    const app = createApp(App);
    app.use(router);
    app.mount('#app');
    
ステップ 10:ビルドと同期のワークフロー

Vue アプリを変更した後、ネイティブプラットフォーム向けにビルドして同期します。

npm run build
npx cap sync

デバイスまたはエミュレーターで実行するには、次のようにします。

npx cap run android
npx cap run ios

高度な設定やデバッグのためにネイティブ IDE を開くには、次のようにします。

npx cap open android
npx cap open ios

開発中にライブリロードを行うには、次のようにします。

npx cap run android --livereload --external
npx cap run ios --livereload --external

これにより Vite 開発サーバーが内部で起動し、ネイティブアプリが開発サーバーから読み込むよう設定されます。

エラー処理

  • webDir の不一致:npx cap sync で誤ったファイルがコピーされる場合は、capacitor.config.ts または capacitor.config.json の webDir が Vue のビルド出力パスと一致していることを確認してください。Vite ベースの Vue プロジェクトでは、デフォルトの出力ディレクトリは dist です。
  • 実行時にプラグインが見つからない:新しいプラグインをインストールした後は、npx cap sync を実行してください。プラグインが package.json の依存関係に含まれていることを確認してください。
  • リスナーによるメモリーリーク:onUnmounted で必ずプラグインのリスナーを削除してください。addListener が返す PluginListenerHandle を保存し、アンマウント時に handle.remove() を呼び出します。
  • ディープリンクが機能しない:アプリの URL スキームまたはユニバーサルリンクがネイティブプロジェクトで設定されていることを確認してください(Android の場合は android/app/src/main/AndroidManifest.xml、iOS の場合は ios/App/App/Info.plist と関連付けられたドメインのエンタイトルメント)。useDeepLinks() が App.vue で呼び出されていることを確認してください。
  • 戻るボタンでアプリが予期せず終了する:戻るボタンのリスナーが、App.exitApp() を呼び出す前に canGoBack を確認するようにしてください。終了するのは、ナビゲーション履歴がない場合のみです。
  • PWA Elements がウェブでレンダリングされない:createApp() より前に src/main.ts で defineCustomElements(window) が呼び出されていることを確認してください。@ionic/pwa-elements がインストールされていることも確認してください。
  • Quasar プロジェクト — Capacitor が検出されない:Capacitor を手動でインストールするのではなく、quasar mode add capacitor を使用してください。references/quasar.md を参照してください。
  • Nuxt プロジェクト — ビルド出力に互換性がない:Nuxt のデフォルトの SSR 出力は Capacitor と互換性がありません。静的 SPA を生成するには、プロジェクトで nuxt.config.ts の ssr: false を使用する必要があります。references/nuxt.md を参照してください。

関連スキル

  • capacitor-app-creation — Capacitor アプリをゼロから作成します。
  • capacitor-app-development — Vue に特化しない、Capacitor 開発全般のガイダンスです。
  • capacitor-plugins — 公式およびコミュニティのソースから Capacitor プラグインをインストールして設定します。
  • ionic-vue — Vue 対応の Ionic Framework(Capacitor を基盤とする UI コンポーネント、ナビゲーション、テーマ設定)です。
  • capacitor-app-upgrades — Capacitor アプリを新しいメジャーバージョンにアップグレードします。

コメント