capacitor-vue について
Vue 3 と Capacitor を使う開発者向けの開発スキルです。検出した Vue、Quasar、Nuxt の構成に合わせ、プラグインやライフサイクル、ルーティングの実装方法を案内します。
主な機能
- 01Vue、Capacitor、フレームワークの検出
- 02プラグインをコンポーザブルでラップ
- 03プラグインリスナーのライフサイクル管理
- 04ディープリンクと Vue Router の連携
- 05Android の戻るボタンとプラットフォーム判定
活用シーン
- 01既存の Vue プロジェクトへの Capacitor 導入
- 02プラグイン用の再利用可能なコンポーザブル作成
- 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 向けのフレームワーク固有のガイダンスです。
前提条件
- Vue 3 を使用する Capacitor 6、7、または 8 のアプリ。
- Node.js と npm がインストールされていること。
- iOS の場合:Xcode がインストールされていること。
- 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:プロジェクトを分析する
プロジェクトファイルを読み取り、次の項目を自動検出します。
- Vue のバージョン:
package.jsonからvueのバージョンを読み取ります。 - Capacitor のバージョン:
package.jsonから@capacitor/coreのバージョンを読み取ります。存在しない場合は、Capacitor がまだ追加されていないため、ステップ 2 に進みます。 - メタフレームワーク:次のファイルを順に確認してフレームワークを検出します。
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) プロジェクトです。以下の手順を続けます。
- プラットフォーム:存在するディレクトリ(
android/、ios/)を確認します。 - Capacitor の設定形式:
capacitor.config.ts(TypeScript)またはcapacitor.config.json(JSON)があるか確認します。 - ビルド出力ディレクトリ:
vite.config.tsまたはvite.config.jsからbuild.outDirを読み取ります。デフォルトはdistです。
ステップ 2:Vue プロジェクトに Capacitor を追加する
@capacitor/core がすでに package.json に含まれている場合はスキップします。プロジェクトが Quasar を使用している場合もスキップします(Quasar には独自の Capacitor 統合があります。references/quasar.md を参照してください)。
-
Capacitor のコアと CLI をインストールします。
npm install @capacitor/core npm install -D @capacitor/cli -
Capacitor を初期化します。
npx cap initプロンプトが表示されたら、web directory にステップ 1 で検出した Vue のビルド出力パスを設定します。Vite ベースの Vue プロジェクトでは、通常
distです。 -
生成された
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" } -
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 が必要です。プロジェクトでこれらのプラグインを使用し、ウェブを対象とする場合は、次の手順を行います。
-
PWA Elements をインストールします。
npm install @ionic/pwa-elements -
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 アプリを新しいメジャーバージョンにアップグレードします。