Lesson 21-Tauri Project Architecture and Engineering

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 AreaFrontend (Rust)Backend (Rust)
UI RenderingResponsibleNot Responsible
User InteractionResponsibleNot Responsible
System API CallsInvokes Rust via CommandsImplements System-Level Functionality
State ManagementLeads (with Rust State Sync)Assists (Provides State Storage)
Data PersistenceBusiness LogicUnderlying 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

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

  1. 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)
  2. Directory Naming:
    • Singular form (e.g., component instead of components)
    • Functional grouping (e.g., api/fs instead of api_filesystem)

Engineering Toolchain

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');
  });
});

Performance Optimization and Deployment

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
  }
}

Performance Monitoring Solution

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);
}

Multi-Platform Deployment

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/
Share your love