Skip to content

Electron Desktop Template

The Electron Desktop template provides a modern desktop application development environment with Electron + Vue 3 + TypeScript + Vite. It's perfect for building cross-platform desktop applications using web technologies.

Technical Stack

  • Electron - Build cross-platform desktop apps with web technologies
  • Vue 3 - Progressive JavaScript framework
  • TypeScript - Type-safe development
  • Vite - Fast build tool and dev server
  • Vue Router - Official routing library (Hash mode for Electron)
  • Pinia - State management library
  • Vue i18n - Internationalization
  • Tailwind CSS - Utility-first CSS framework
  • SCSS - CSS preprocessor
  • Electron Forge - Application packaging and distribution

Quick Start

1. Create Project

bash
# Initialize project
vup init my-desktop-project
cd my-desktop-project

# Add Electron template
vup add my-electron-app

2. Install Dependencies

bash
# Install dependencies
pnpm install

3. Start Development

bash
# Start Electron development server
cd apps/my-electron-app
pnpm dev

The Electron application will launch automatically with hot reload support.

Project Structure

apps/my-electron-app/
├── src/
│   ├── main.ts                    # Main process entry
│   ├── preload.ts                 # Preload script
│   └── renderer/                  # Renderer process (Vue app)
│       ├── 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 (Hash mode)
│       │   ├── 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                # Renderer entry point
│       └── index.css              # Global styles
├── .vite/                         # Vite build cache
├── out/                           # Build output directory
├── forge.config.ts                # Electron Forge configuration
├── vite.main.config.ts            # Main process Vite config
├── vite.preload.config.ts         # Preload script Vite config
├── vite.renderer.config.ts        # Renderer process Vite config
├── package.json                   # Dependencies and scripts
└── tsconfig.json                  # TypeScript configuration

Core Features

Electron Main Process

typescript
// src/main.ts
import { app, BrowserWindow } from 'electron';
import { createWindow } from './window';

app.whenReady().then(() => {
  createWindow();

  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow();
    }
  });
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

Electron Preload Script

typescript
// src/preload.ts
import { contextBridge, ipcRenderer } from 'electron';

// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld('electronAPI', {
  // Example API
  getVersion: () => ipcRenderer.invoke('get-version'),
});

Vue 3 Renderer Process

vue
<template>
  <div class="container">
    <h1>{{ title }}</h1>
    <p>{{ description }}</p>
    <button @click="increment">Count: {{ count }}</button>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue';

const count = ref(0);
const title = ref('Electron + Vue 3 App');

const description = computed(() => `Current count is ${count.value}`);

const increment = () => {
  count.value++;
};
</script>

Hash Mode Routing

Important: Electron applications must use Hash mode routing, not History mode.

typescript
// router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router';

export default createRouter({
  history: createWebHashHistory(), // Use Hash mode for Electron
  routes,
});

Desktop-Optimized 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

Vite Configuration

The template uses separate Vite configurations for different processes:

typescript
// vite.main.config.ts - Main process
export default defineConfig({
  build: {
    lib: {
      entry: path.resolve(__dirname, 'src/main.ts'),
      formats: ['cjs'],
      fileName: () => 'main.js',
    },
    rollupOptions: {
      external: ['electron'],
    },
  },
});

// vite.renderer.config.ts - Renderer process
export default defineConfig({
  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'),
    },
  },
});

Electron Forge Configuration

typescript
// forge.config.ts
import type { ForgeConfig } from '@electron-forge/shared-types';

const config: ForgeConfig = {
  packagerConfig: {
    asar: true,
  },
  rebuildConfig: {},
  makers: [
    {
      name: '@electron-forge/maker-squirrel',
      config: {},
    },
    {
      name: '@electron-forge/maker-zip',
      platforms: ['darwin'],
    },
    {
      name: '@electron-forge/maker-deb',
      config: {},
    },
    {
      name: '@electron-forge/maker-rpm',
      config: {},
    },
  ],
  plugins: [
    {
      name: '@electron-forge/plugin-auto-unpack-natives',
      config: {},
    },
    {
      name: '@electron-forge/plugin-vite',
      config: {
        build: [
          {
            entry: 'src/main.ts',
            config: 'vite.main.config.ts',
            bundleConfig: {
              external: ['electron'],
            },
          },
          {
            entry: 'src/preload.ts',
            config: 'vite.preload.config.ts',
          },
          {
            entry: 'src/renderer/main.ts',
            config: 'vite.renderer.config.ts',
          },
        ],
      },
    },
  ],
};

export default config;

Available Scripts

json
{
  "scripts": {
    "dev": "electron-forge start",
    "build": "electron-forge package",
    "make": "electron-forge make",
    "publish": "electron-forge publish",
    "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}\""
  }
}

Building and Distribution

Development Build

bash
# Start development server with hot reload
pnpm dev

Production Build

bash
# Package the application
pnpm build

Create Installers

bash
# Create platform-specific installers
pnpm make

This creates installers for:

  • Windows: .exe installer
  • macOS: .zip archive
  • Linux: .deb and .rpm packages

Publish Application

bash
# Publish to distribution platforms
pnpm publish

Monorepo Considerations

Important: In monorepo projects, Electron binaries may not install correctly. If you encounter:

Error: Electron failed to install correctly, please delete node_modules/electron and try installing again

This is a common issue in monorepo projects - please follow these steps to resolve:

Solution

  1. Clean and reinstall:

    bash
    # Delete Electron related files
    rm -rf node_modules/electron
    
    # Reinstall
    pnpm install electron --force
  2. 🔥 Critical Step: Manually run Electron install script

    This step is very important! Electron binaries need to be downloaded manually:

    bash
    # Navigate to Electron directory
    cd node_modules/electron
    
    # Manually run install script (downloads binary files)
    node install.js

    Explanation: In monorepo environments, pnpm's symlink mechanism may cause Electron's install script to not execute properly, so you need to manually run install.js to download platform-specific binary files.

  3. Verify installation:

    bash
    # Check Electron version (should show version number, not error)
    npx electron --version
  4. Start the application:

    bash
    # Return to project root
    cd ../..
    
    # Start Electron application
    pnpm dev

5. If still failing

If the above steps still fail, try a complete cleanup:

bash
# Complete cleanup
rm -rf node_modules pnpm-lock.yaml

# Clear pnpm cache
pnpm store prune

# Reinstall
pnpm install

# Manually run Electron install script
cd node_modules/electron && node install.js

# Return to project root
cd ../..

# Start application
pnpm dev

Best Practices

Security

  • Use contextBridge for secure IPC communication
  • Disable Node.js integration in renderer process
  • Use preload scripts for safe API exposure
  • Validate all user inputs

Performance

  • Use Vite for fast development and optimized builds
  • Implement code splitting for large applications
  • Optimize images and assets
  • Use Electron's built-in performance tools

Cross-Platform Development

  • Test on all target platforms
  • Handle platform-specific features gracefully
  • Use Electron's platform detection APIs
  • Follow platform-specific UI guidelines