Capacitor Template
The Capacitor template provides a cross-platform native app development solution using web technologies, perfect for building mobile applications that can run on iOS and Android.
Technical Stack
- Capacitor - Cross-platform native runtime
- Vue 3 - Progressive JavaScript framework
- TypeScript - Type-safe development
- Vite - Fast build tool and dev server
- Vue Router - Client-side routing
- Pinia - State management library
- Vue i18n - Internationalization
- Tailwind CSS - Utility-first CSS framework
Quick Start
1. Create Project
bash
# Initialize project
vup init my-capacitor-project
cd my-capacitor-project
# Add Capacitor template
vup add my-capacitor-app
2. Install Dependencies
bash
# Install dependencies
pnpm install
3. Start Development
bash
# Start development server
cd apps/my-capacitor-app
pnpm dev
The application will be available at http://localhost:9301
.
Project Structure
apps/my-capacitor-app/
├── src/
│ ├── assets/ # Static assets
│ │ └── images/ # Image files
│ ├── locales/ # Internationalization
│ │ ├── index.ts # i18n configuration
│ │ ├── en_US.ts # English translations
│ │ └── zh_CN.ts # Chinese translations
│ ├── router/ # Vue Router
│ │ ├── index.ts # Router configuration
│ │ └── routes.ts # Route definitions
│ ├── views/ # Page components
│ │ ├── demo/ # Demo pages
│ │ ├── docs/ # Documentation pages
│ │ ├── empty/ # Empty state pages
│ │ └── index/ # Home page
│ ├── App.vue # Root component
│ ├── main.ts # Application entry point
│ └── vue-shim.d.ts # Vue type declarations
├── ios/ # iOS native project
│ └── App/ # iOS app files
├── android/ # Android native project
│ └── app/ # Android app files
├── public/ # Public assets
├── capacitor.config.json # Capacitor configuration
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
└── vite.config.js # Vite configuration
Core Features
Vue 3 with Capacitor
vue
<template>
<div class="container">
<h1>{{ title }}</h1>
<p>{{ description }}</p>
<button @click="handleNativeAction">Call Native Function</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Capacitor } from '@capacitor/core';
const title = ref('Capacitor App');
const description = ref('Cross-platform native app with web technologies');
const handleNativeAction = async () => {
if (Capacitor.isNativePlatform()) {
// Call native function
const { Toast } = await import('@capacitor/toast');
await Toast.show({
text: 'Hello from native!',
duration: 'short',
});
} else {
// Fallback for web
alert('Hello from web!');
}
};
</script>
<style scoped>
.container {
@apply p-4 bg-white rounded-lg shadow-md;
}
</style>
Native Plugin Integration
typescript
// utils/native.ts
import { Capacitor } from '@capacitor/core';
import { Camera, CameraResultType } from '@capacitor/camera';
import { Geolocation } from '@capacitor/geolocation';
import { Device } from '@capacitor/device';
export class NativeService {
static async takePicture(): Promise<string> {
if (!Capacitor.isNativePlatform()) {
throw new Error('Camera is only available on native platforms');
}
const image = await Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.Uri,
});
return image.webPath || '';
}
static async getCurrentPosition(): Promise<GeolocationPosition> {
if (!Capacitor.isNativePlatform()) {
throw new Error('Geolocation is only available on native platforms');
}
const coordinates = await Geolocation.getCurrentPosition();
return coordinates;
}
static async getDeviceInfo(): Promise<any> {
const info = await Device.getInfo();
return info;
}
}
Internationalization
typescript
// locales/en_US.ts
export default {
common: {
title: 'Capacitor Application',
description: 'Cross-platform native app with web technologies',
button: 'Call Native Function',
},
navigation: {
home: 'Home',
demo: 'Demo',
docs: 'Documentation',
},
};
Tailwind CSS Styling
vue
<template>
<div class="min-h-screen bg-gray-100">
<header class="bg-white shadow-sm">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h1 class="text-3xl font-bold text-gray-900">
{{ $t('common.title') }}
</h1>
</div>
</header>
<main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div class="px-4 py-6 sm:px-0">
<div class="border-4 border-dashed border-gray-200 rounded-lg h-96">
<p class="text-center text-gray-500 mt-20">
{{ $t('common.description') }}
</p>
</div>
</div>
</main>
</div>
</template>
Development Tools
Capacitor Configuration
json
// capacitor.config.json
{
"appId": "com.example.myapp",
"appName": "My Capacitor App",
"webDir": "dist",
"bundledWebRuntime": false,
"server": {
"androidScheme": "https"
},
"plugins": {
"SplashScreen": {
"launchShowDuration": 2000,
"backgroundColor": "#ffffff",
"androidSplashResourceName": "splash",
"androidScaleType": "CENTER_CROP",
"showSpinner": true,
"androidSpinnerStyle": "large",
"iosSpinnerStyle": "small",
"spinnerColor": "#999999",
"splashFullScreen": true,
"splashImmersive": true
}
}
}
Vite Configuration
javascript
// vite.config.js
import path from 'path';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import AutoImport from 'unplugin-auto-import/vite';
import { defineConfig, mergeConfig } from 'vite';
export default mergeConfig(
defineConfig({
root: '.',
plugins: [
vue(),
vueJsx(),
AutoImport({
imports: ['vue', 'vue-router', 'pinia', 'vue-i18n'],
dts: path.resolve(__dirname, 'auto-imports.d.ts'),
vueTemplate: true,
}),
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@shared': path.resolve(__dirname, '../../_shared'),
},
},
css: {
postcss: path.resolve(__dirname, '../../postcss.config.js'),
},
build: {
target: 'es2015',
minify: 'esbuild',
sourcemap: false,
outDir: 'dist',
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'vue-router', 'pinia'],
utils: ['lodash-es', 'dayjs'],
},
},
},
},
server: {
host: '0.0.0.0',
port: 9301,
open: false,
cors: true,
},
})
);
TypeScript Configuration
json
// tsconfig.json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@shared/*": ["../../_shared/*"]
}
},
"include": [
"src/**/*",
"src/**/*.vue",
"src/*.vue",
"./auto-imports.d.ts",
"src/vue-shim.d.ts"
]
}
Available Scripts
json
{
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vite build",
"preview": "vite preview",
"cap:add:ios": "cap add ios",
"cap:add:android": "cap add android",
"cap:sync": "cap sync",
"cap:run:ios": "cap run ios",
"cap:run:android": "cap run android",
"cap:open:ios": "cap open ios",
"cap:open:android": "cap open android",
"lint": "eslint src/ --ext .vue,.ts,.js",
"lint:fix": "eslint src/ --ext .vue,.ts,.js --fix",
"format": "prettier --write \"src/**/*.{js,ts,vue,json,css,scss}\"",
"format:check": "prettier --check \"src/**/*.{js,ts,vue,json,css,scss}\""
}
}
Platform Requirements
iOS Development
- Xcode 14.0 or later
- iOS SDK 12.0 or later
- CocoaPods for dependency management
Android Development
- Android Studio with Android SDK
- Java Development Kit (JDK) 11 or later
- Android SDK API level 21 or later
Development Workflow
1. Web Development
bash
# Start web development server
pnpm dev
# Build for production
pnpm build
2. Add Native Platforms
bash
# Add iOS platform
pnpm cap:add:ios
# Add Android platform
pnpm cap:add:android
3. Sync Changes
bash
# Sync web assets to native platforms
pnpm cap:sync
4. Run on Devices
bash
# Run on iOS simulator
pnpm cap:run:ios
# Run on Android emulator
pnpm cap:run:android
5. Open in Native IDEs
bash
# Open iOS project in Xcode
pnpm cap:open:ios
# Open Android project in Android Studio
pnpm cap:open:android
Best Practices
Platform Detection
typescript
import { Capacitor } from '@capacitor/core';
// Check if running on native platform
if (Capacitor.isNativePlatform()) {
// Native-specific code
console.log('Running on native platform');
} else {
// Web-specific code
console.log('Running on web platform');
}
// Check specific platform
if (Capacitor.getPlatform() === 'ios') {
// iOS-specific code
} else if (Capacitor.getPlatform() === 'android') {
// Android-specific code
}
Error Handling
typescript
import { Capacitor } from '@capacitor/core';
export async function safeNativeCall<T>(
nativeFunction: () => Promise<T>,
fallback: T
): Promise<T> {
try {
if (Capacitor.isNativePlatform()) {
return await nativeFunction();
} else {
return fallback;
}
} catch (error) {
console.error('Native function failed:', error);
return fallback;
}
}
Responsive Design
vue
<template>
<div class="app-container">
<div class="content">
<h1>{{ title }}</h1>
<p>{{ description }}</p>
</div>
</div>
</template>
<style scoped>
.app-container {
@apply min-h-screen bg-gray-100;
}
.content {
@apply max-w-md mx-auto p-4;
}
/* Mobile-first responsive design */
@media (min-width: 768px) {
.content {
@apply max-w-2xl;
}
}
@media (min-width: 1024px) {
.content {
@apply max-w-4xl;
}
}
</style>
Build and Deployment
Web Build
bash
# Build for web
pnpm build
# The built files will be in the dist directory
Native Build
bash
# Sync web assets to native platforms
pnpm cap:sync
# Build iOS app
pnpm cap:open:ios
# Then build in Xcode
# Build Android app
pnpm cap:open:android
# Then build in Android Studio