electron-shadcn

repository·main·Indexed 21 days ago

https://github.com/luanroger/electron-shadcn

A production-ready boilerplate for desktop applications using Electron, React 19.2, and Shadcn UI. It features a modern tech stack including Vite 8, TypeScript 6, TanStack Router, and Tailwind 4.3. The template includes built-in auto-update workflows via GitHub Releases, security hardening through Electron Fuses, and a pre-configured Electron Forge setup for multi-process bundling and distribution.

Tokens
5.9K
Snippets
26
Records
35
Agent score
74%

What's inside electron-shadcn

  1. Overview of electron-shadcn tech stack

    main

    electron-shadcn is a pre-configured template for building desktop applications with Electron. It integrates several modern libraries for development, UI, and testing:

    Core & DX

    • Runtime: Electron 43, Vite 8
    • Language & Logic: TypeScript 6, Zod 4, oRPC
    • Data Fetching: React Query (TanStack)
    • Formatting: Prettier, Ultracite with Biome

    UI & Styling

    • Framework: React 19.2 (with React Compiler enabled by default)
    • Styling: Tailwind 4.3, Shadcn UI, Lucide icons
    • Routing: TanStack Router (file-based)
    • Internationalization: i18next
    • Typography: Geist (default font)

    Testing & Distribution

    • Testing: Vitest, Playwright, React Testing Library
    • Distribution: Electron Forge
    • CI/CD: GitHub Actions (configured for Playwright tests)
  2. Install and run electron-shadcn

    main

    To get started with the electron-shadcn template, clone the repository, install the dependencies, and run the development server. The main application entry point for the UI is located at /src/routes/index.tsx.

    # 1. Clone the repository
    git clone https://github.com/LuanRoger/electron-shadcn.git
    
    # 2. Install dependencies
    npm install
    
    # 3. Run the app
    npm run start
  3. Configure auto-updates via GitHub Releases

    main

    The project includes built-in auto-update functionality using update-electron-app. It is designed to work with open-source GitHub repositories by using GitHub Releases as the update source.

    Publishing Updates

    • Manual Publishing: Run the publish script locally. You must set the GITHUB_TOKEN environment variable with a GitHub Personal Access Token that has permissions to create releases.
    • Automated Publishing: Use the pre-configured GitHub Actions workflow (found in .github/workflows/publish.yml). This can be triggered manually to create a new release (created as a draft by default).
    • Mechanism: The project uses Electron Forge's GitHub publisher to handle the release creation.

    Client Behavior

    When the application starts, it automatically checks for updates. If an update is found, it downloads and installs it, then restarts the application to apply the changes.

    WARNING

    For private repositories, you must set up a custom update server as described in the Electron documentation.

    # Example of publishing a release locally
    # Ensure GITHUB_TOKEN is set in your environment
    npm run publish
  4. Configure Electron Forge with Vite and Fuses

    main

    The forge.config.ts file defines the build and packaging configuration for the Electron application. It utilizes @electron-forge/plugin-vite to manage the build process for the main, preload, and renderer processes, and @electron-forge/plugin-fuses to apply security hardening via Electron Fuses.

    Key configuration areas include:

    • Makers: Defines output formats like Squirrel (Windows), ZIP (macOS), RPM, and DEB (Linux).
    • PackagerConfig: Configures the underlying Electron packager, such as enabling asar.
    • Plugins: Integrates Vite for bundling and Fuses for security.
    • Publishers: Configures automated release workflows, such as publishing drafts to GitHub.
    import { ForgeConfig } from "@electron-forge/shared-types";
    import { VitePlugin } from "@electron-forge/plugin-vite";
    import { FusesPlugin } from "@electron-forge/plugin-fuses";
    
    const config: ForgeConfig = {
      makers: [
        new MakerSquirrel({}),
        new MakerZIP({}, ["darwin"]),
        new MakerRpm({}),
        new MakerDeb({}),
      ],
      packagerConfig: {
        asar: true,
      },
      plugins: [
        new VitePlugin({
          build: [
            { config: "vite.main.config.mts", entry: "src/main.ts", target: "main" },
            { config: "vite.preload.config.mts", entry: "src/preload.ts", target: "preload" },
          ],
          renderer: [
            { config: "vite.renderer.config.mts", name: "main_window" },
          ],
        }),
        new FusesPlugin({
          version: FuseVersion.V1,
          [FuseV1Options.RunAsNode]: false,
          [FuseV1Options.EnableCookieEncryption]: true,
          // ... other options
        }),
      ],
      publishers: [
        {
          name: "@electron-forge/publisher-github",
          config: {
            draft: true,
            repository: { name: "electron-shadcn", owner: "LuanRoger" },
          },
        },
      ],
    };
    
    export default config;
  5. Configure the '@' path alias in Vite

    main

    The project uses a Vite configuration to define a path alias @ which points to the ./src directory. This allows for cleaner absolute imports within the source code instead of using relative paths (e.g., import { ... } from '@/components/...').

    import path from "node:path";
    import { defineConfig } from "vite";
    
    export default defineConfig({
      resolve: {
        alias: {
          "@": path.resolve(import.meta.dirname, "./src"),
        },
      },
    });
  6. Configure the Vite renderer environment

    main

    The renderer process uses a Vite configuration that integrates TanStack Router, Tailwind CSS, React, and the React Compiler via Babel. It also defines a path alias @ pointing to the ./src directory to simplify imports within the renderer source code.

    import path from "node:path";
    import babel from "@rolldown/plugin-babel";
    import tailwindcss from "@tailwindcss/vite";
    import { tanstackRouter } from "@tanstack/router-plugin/vite";
    import react, { reactCompilerPreset } from "@vitejs/plugin-react";
    import { defineConfig } from "vite";
    
    export default defineConfig({
      plugins: [
        tanstackRouter({
          target: "react",
        }),
        tailwindcss(),
        react(),
        babel({ presets: [reactCompilerPreset()] }),
      ],
      resolve: {
        alias: {
          "@": path.resolve(import.meta.dirname, "./src"),
        },
        preserveSymlinks: true,
      },
    });
  7. Use the theme IPC API

    main

    The theme object provides a set of IPC (Inter-Process Communication) methods to manage the application's theme mode (e.g., light, dark) across the Electron main and renderer processes. You can use these methods to retrieve the current mode, explicitly set a mode, or toggle between modes.

    import { theme } from './src/ipc/theme/index';
    
    // Get the current theme mode
    const currentMode = await theme.getCurrentThemeMode();
    
    // Set a specific theme mode
    await theme.setThemeMode('dark');
    
    // Toggle between light and dark modes
    await theme.toggleThemeMode();
  8. Restore the application language with updateAppLanguage()

    main

    Use updateAppLanguage to synchronize the application state with the language previously saved in localStorage.

    This function:

    1. Retrieves the language string from localStorage using LOCAL_STORAGE_KEYS.LANGUAGE.
    2. If a language is found, it calls i18.changeLanguage(localLang) on the provided i18n instance.
    3. Updates the lang attribute on document.documentElement to match the retrieved language.

    If no language is stored in localStorage, the function returns early without making any changes.

    import { updateAppLanguage } from "@/actions/language";
    
    // Call this during app initialization to restore user preference
    updateAppLanguage(i18n);
  9. Use the shell API to open external links

    main

    The shell object provides methods for interacting with the operating system's shell. Currently, it exposes openExternalLink to allow the application to open URLs or files in the user's default web browser or file explorer via IPC.

    import { shell } from './ipc/shell';
    
    // Example usage (conceptually via IPC call):
    // shell.openExternalLink('https://example.com');
  10. Use the window IPC API

    main

    The window object provides a set of IPC (Inter-Process Communication) methods to control the Electron window from the renderer process. You can use these methods to perform standard window management tasks like closing, maximizing, or minimizing the current window.

    import { window } from './path/to/ipc/window';
    
    // Minimize the window
    window.minimizeWindow();
    
    // Maximize the window
    window.maximizeWindow();
    
    // Close the window
    window.closeWindow();
  11. Use the `app` IPC object for application metadata

    main

    The app object is an IPC (Inter-Process Communication) interface used to retrieve application-level metadata from the main process. It provides access to the current application version and the platform the application is running on.

    // Example of how the app IPC object is structured
    // Note: Actual usage involves calling these via Electron's ipcRenderer
    import { app } from './ipc/app';
    
    console.log(app.appVersion);
    console.log(app.currentPlatfom);
  12. Set a specific theme mode

    main

    Use setTheme(newTheme: ThemeMode) to explicitly set the application theme. This function updates the system theme via IPC, persists the choice in localStorage using the LOCAL_STORAGE_KEYS.THEME key, and updates the document's CSS class (adding/removing the .dark class on document.documentElement).

    Supported ThemeMode values typically include 'light', 'dark', or 'system'.

    import { setTheme } from '@/actions/theme';
    
    await setTheme('dark');