Project Architecture Design
Modular and Component-Based Design Principles
Layered Modular Architecture
src/
├── api/ # System API encapsulation module
│ ├── fs.ts # Filesystem operations
│ ├── dialog.ts # System dialogs
│ └── notification.ts # System notifications
├── components/ # Reusable UI components
│ ├── FileList.vue # File list component
│ └── Notification.vue # Notification component
├── stores/ # State management
│ ├── fileStore.ts # File state management
│ └── uiStore.ts # UI state management
├── utils/ # Utility functions
│ ├── crypto.ts # Encryption utilities
│ └── validator.ts # Data validation
└── views/ # Page views
├── Home.vue # Home page
└── Settings.vue # Settings page
Rust Modular Design
// src-tauri/src/
├── api/ # Commands exposed to frontend
│ ├── fs.rs # Filesystem commands
│ └── notification.rs # Notification commands
├── utils/ # Rust utility functions
│ ├── crypto.rs # Encryption implementation
│ └── validator.rs # Data validation
├── main.rs # Application entry point
└── config.rs # Configuration management
Separation of Responsibilities Between Frontend and Rust Backend
Responsibility Boundary Division
| Responsibility Area | Frontend (Rust) | Backend (Rust) |
|---|
| UI Rendering | Responsible | Not Responsible |
| User Interaction | Responsible | Not Responsible |
| System API Calls | Invokes Rust via Commands | Implements System-Level Functionality |
| State Management | Leads (with Rust State Sync) | Assists (Provides State Storage) |
| Data Persistence | Business Logic | Underlying Storage Implementation |
Communication Specification Example
// Rust-side command definition specification
#[tauri::command]
/// Retrieves file list (called by frontend)
/// @param path Target path
/// @returns Array of file information
fn get_file_list(path: String) -> Result<Vec<FileInfo>, String> {
// Implementation details...
}
// Frontend invocation specification
import { invoke } from '@tauri-apps/api/tauri';
/**
* Fetches file list (frontend encapsulation)
* @param path Target path
* @returns Promise<FileItem[]>
*/
async function fetchFileList(path) {
try {
return await invoke('get_file_list', { path });
} catch (error) {
console.error('Failed to fetch file list:', error);
throw error;
}
}
State Management Architecture Design
Pinia State Management Example
// stores/fileStore.ts
import { defineStore } from 'pinia';
import { fetchFileList } from '@/api/fs';
export const useFileStore = defineStore('file', {
state: () => ({
files: [] as FileItem[],
loading: false,
error: null as string | null
}),
actions: {
async loadFiles(path: string) {
this.loading = true;
this.error = null;
try {
this.files = await fetchFileList(path);
} catch (error) {
this.error = error instanceof Error ? error.message : 'Unknown error';
} finally {
this.loading = false;
}
}
},
getters: {
filteredFiles: (state) => (extension: string) => {
return state.files.filter(file => file.name.endsWith(extension));
}
}
});
Rust State Synchronization
// src-tauri/src/api/state.rs
use std::sync::{Arc, Mutex};
use tauri::State;
struct AppState {
counter: i32,
}
#[tauri::command]
fn increment_counter(state: State<Arc<Mutex<AppState>>>) -> i32 {
let mut app_state = state.lock().unwrap();
app_state.counter += 1;
app_state.counter
}
fn main() {
let app_state = Arc::new(Mutex::new(AppState { counter: 0 }));
tauri::Builder::default()
.manage(app_state.clone())
.invoke_handler(tauri::generate_handler![increment_counter])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Routing Architecture Design
Vue Router Configuration Example
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import Home from '@/views/Home.vue';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
name: 'home',
component: Home,
meta: { requiresAuth: true }
},
{
path: '/settings',
name: 'settings',
component: () => import('@/views/Settings.vue'), // Lazy loading
meta: { title: 'Settings' }
}
]
});
// Global route guard
router.beforeEach((to, from, next) => {
document.title = to.meta.title as string || 'Tauri App';
next();
});
export default router;
Routing Integration with Tauri Commands
// Execute Rust command before route navigation
router.beforeEach(async (to, from, next) => {
if (to.meta.requiresAuth) {
const isAuthenticated = await invoke('check_auth');
if (!isAuthenticated) {
next('/login');
return;
}
}
next();
});
Project Directory Structure and Conventions
Recommended Directory Structure
my-tauri-app/
├── public/ # Static assets (not bundled)
│ ├── favicon.ico
│ └── robots.txt
├── src/ # Frontend source code
│ ├── api/ # System API encapsulation
│ ├── assets/ # Assets to be bundled
│ ├── components/ # Reusable components
│ ├── router/ # Routing configuration
│ ├── stores/ # State management
│ ├── utils/ # Utility functions
│ ├── views/ # Page views
│ ├── App.vue # Root component
│ └── main.ts # Frontend entry point
├── src-tauri/ # Rust backend code
│ ├── src/ # Rust source code
│ ├── Cargo.toml # Rust dependency configuration
│ └── tauri.conf.json # Tauri configuration
├── index.html # HTML template
├── package.json # Frontend dependency configuration
├── tsconfig.json # TypeScript configuration
└── vite.config.ts # Vite build configuration
Naming Conventions
- File Naming:
- Vue Components:
PascalCase.vue (e.g., FileList.vue)
- TypeScript Files:
camelCase.ts (e.g., fileList.ts)
- Rust Modules:
snake_case.rs (e.g., file_system.rs)
- Directory Naming:
- Singular form (e.g.,
component instead of components)
- Functional grouping (e.g.,
api/fs instead of api_filesystem)
Advanced Tauri CLI Configuration
Custom Build Scripts
// package.json
{
"scripts": {
"tauri:dev": "cross-env NODE_ENV=development tauri dev",
"tauri:build": "cross-env NODE_ENV=production tauri build",
"tauri:preview": "tauri dev --preview"
}
}
Environment Variable Management
// src-tauri/src/main.rs
fn main() {
tauri::Builder::default()
.setup(|app| {
let window = app.get_window("main").unwrap();
// Read environment variable
let api_url = std::env::var("API_URL")
.unwrap_or_else(|_| "http://localhost:3000".into());
// Inject into frontend
window.emit("init-config", { api_url }).unwrap();
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Webpack/Vite Advanced Configuration
Vite Multi-Process Build Configuration
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
output: {
manualChunks: {
// Split node_modules into separate chunk
vendor: ['vue', 'pinia', 'axios'],
// Split large libraries separately
lodash: ['lodash-es'],
// Split by route
'route-home': ['src/views/Home.vue'],
'route-settings': ['src/views/Settings.vue']
}
}
},
// Enable multi-threaded compression
minify: 'terser',
terserOptions: {
parallel: true,
cpuCount: 4
}
}
});
TypeScript Integration
Enhanced Type Declarations
// src/types/tauri.d.ts
declare module '@tauri-apps/api' {
interface Window {
__TAURI__: {
event: {
listen<T>(event: string, handler: (event: T) => void): () => void;
};
fs: {
readTextFile(path: string): Promise<string>;
};
};
}
}
// Extend invoke types
declare module '@tauri-apps/api/tauri' {
interface TauriInvokeOptions {
timeout?: number;
}
}
Code Style Configuration
ESLint + Prettier Configuration
// .eslintrc.js
module.exports = {
root: true,
env: {
browser: true,
node: true,
es2021: true
},
extends: [
'plugin:vue/vue3-recommended',
'eslint:recommended',
'@vue/typescript/recommended',
'prettier'
],
rules: {
'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-explicit-any': 'warn'
}
};
// .prettierrc
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"printWidth": 100
}
Automated Testing Framework
Jest Unit Testing Example
// tests/api/fs.test.ts
import { fetchFileList } from '@/api/fs';
jest.mock('@/api/fs', () => ({
fetchFileList: jest.fn()
}));
describe('Filesystem API', () => {
it('Should correctly handle file list response', async () => {
(fetchFileList as jest.Mock).mockResolvedValue([
{ name: 'test.txt', size: 1024 }
]);
const result = await fetchFileList('/test');
expect(result).toEqual([
{ name: 'test.txt', size: 1024 }
]);
});
});
Cypress E2E Testing
// cypress/e2e/home.spec.ts
describe('Home Page Tests', () => {
it('Should correctly display file list', () => {
cy.visit('/');
// Mock Rust command response
cy.window().then((win) => {
win.__TAURI__.invoke.mockResolvedValue([
{ name: 'document.pdf', size: 2048 }
]);
});
cy.get('.file-list').should('contain', 'document.pdf');
});
});
Code Splitting and Lazy Loading
Route-Level Lazy Loading
// router/index.ts
const Settings = () => import(/* webpackChunkName: "settings" */ '@/views/Settings.vue');
const About = () => import(/* webpackChunkName: "about" */ '@/views/About.vue');
const routes = [
{ path: '/settings', component: Settings },
{ path: '/about', component: About }
];
Component-Level Lazy Loading
<template>
<div>
<button @click="showModal = true">Open Dialog</button>
<LazyDialog v-if="showModal" @close="showModal = false" />
</div>
</template>
<script setup>
import { ref } from 'vue';
import { defineAsyncComponent } from 'vue';
const showModal = ref(false);
const LazyDialog = defineAsyncComponent(() =>
import('@/components/Dialog.vue')
);
</script>
Resource Compression and Caching
Vite Resource Processing Configuration
// vite.config.ts
export default defineConfig({
build: {
assetsInlineLimit: 4096, // Inline assets smaller than 4KB
cssCodeSplit: true, // CSS code splitting
brotliSize: true, // Enable Brotli compression
chunkSizeWarningLimit: 1500 // Chunk size warning threshold
},
server: {
headers: {
'Cache-Control': 'public, max-age=31536000, immutable'
}
}
});
Application Packaging and Release
Auto-Update Configuration
// src-tauri/src/main.rs
fn main() {
tauri::Builder::default()
.system_tray(tray::build_tray()) // System tray support
.updater(|app| {
tauri::updater::Builder::new()
.check_update_callback(|app_handle| {
// Custom update check logic
Ok(tauri::updater::UpdateCheck::Available(
tauri::updater::UpdateInfo {
version: "1.0.1".into(),
release_notes: Some("Fixed several bugs".into()),
}
))
})
.build(app)
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Version Management Strategy
# Cargo.toml
[package]
version = "1.0.0" # Follow semantic versioning
# tauri.conf.json
{
"package": {
"version": "1.0.0",
"increment": "patch" # Configurable auto-version increment
}
}
Sentry Integration
// src/utils/sentry.ts
import * as Sentry from '@sentry/vue';
import { Integrations } from '@sentry/tracing';
export function initSentry(app) {
Sentry.init({
app,
dsn: import.meta.env.VITE_SENTRY_DSN,
integrations: [
new Integrations.BrowserTracing({
routingInstrumentation: Sentry.vueRouterInstrumentation(router),
tracingOrigins: ['localhost', 'https://myapp.com']
})
],
tracesSampleRate: 1.0
});
}
// Error capture example
try {
await riskyOperation();
} catch (error) {
Sentry.captureException(error);
}
CI/CD Configuration Example (GitHub Actions)
# .github/workflows/deploy.yml
name: Deploy Tauri App
on:
push:
tags: ['v*']
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: ${{ matrix.target }}
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm install
- run: npm run tauri build
- uses: actions/upload-artifact@v3
with:
name: release-${{ matrix.os }}
path: src-tauri/target/release/bundle/