Starter template for fast and efficient Electron development using Vite and TypeScript
  • TypeScript 96.7%
  • CSS 2.6%
  • HTML 0.4%
  • JavaScript 0.3%
Find a file
Martin Burchard d207111c6f
All checks were successful
CI / ci (pull_request) Successful in 1m52s
CI / ci (push) Successful in 1m51s
fix(dialog): merge partial placement with defaults instead of replacing
- Spread caller placement into default placement so individual properties like screen can be
  set without losing horizontal/top defaults
- Add test verifying partial placement merge preserves defaults
2026-03-11 18:58:23 +01:00
.forgejo/workflows Modernise tooling config and harden CI workflows 2026-03-09 16:40:14 +01:00
.github/workflows Modernise tooling config and harden CI workflows 2026-03-09 16:40:14 +01:00
documentation Update README and flatten common module structure 2026-03-01 21:59:19 +01:00
modules fix(dialog): merge partial placement with defaults instead of replacing 2026-03-11 18:58:23 +01:00
types Modernise tooling config and harden CI workflows 2026-03-09 16:40:14 +01:00
.editorconfig A first Vite based Electron Project 2025-01-14 00:04:32 +01:00
.gitignore Add .pnpm-store to .gitignore 2026-03-01 21:04:01 +01:00
.node-version Major tooling upgrade and logging pipeline overhaul 2026-02-28 00:15:26 +01:00
.npmrc Major tooling upgrade and logging pipeline overhaul 2026-02-28 00:15:26 +01:00
electron-builder.config.js Disable electron-builder publish to fix dist builds 2026-03-01 22:29:30 +01:00
eslint.config.js Update tooling config and fix e18e lint rules 2026-03-09 20:57:32 +01:00
License A first Vite based Electron Project 2025-01-14 00:04:32 +01:00
package.json chore(deps): upgrade Electron from 40 to 41 2026-03-11 08:20:48 +01:00
pnpm-lock.yaml chore(deps): upgrade Electron from 40 to 41 2026-03-11 08:20:48 +01:00
pnpm-workspace.yaml Major tooling upgrade and logging pipeline overhaul 2026-02-28 00:15:26 +01:00
project.config.ts Window management rewrite, dialog system, core/demo separation 2026-02-28 00:15:26 +01:00
README.md Update README with window bounds persistence 2026-03-09 23:48:39 +01:00
tsconfig.app.json Update tooling config and fix e18e lint rules 2026-03-09 20:57:32 +01:00
tsconfig.base.json Update tooling config and fix e18e lint rules 2026-03-09 20:57:32 +01:00
tsconfig.json fix(tsconfig): remove composite + references from test config 2026-03-11 13:01:36 +01:00
tsconfig.node.json Fix macOS click-through and add vite/client types for electron module 2026-03-09 22:17:59 +01:00
tsconfig.test.json fix(tsconfig): remove composite + references from test config 2026-03-11 13:01:36 +01:00
tsconfig.typecheck.json Modernise tooling config and harden CI workflows 2026-03-09 16:40:14 +01:00
vite.config.ts Fix nested Electron build context and chunk resolution 2026-03-11 08:16:03 +01:00
vitest.config.ts Complete IPC layer: consistent naming, window registry, preload tests 2026-02-28 00:15:26 +01:00

vite-electron-starter

lang: Typescript CI License

the app running

Introduction

This starter template provides a quick and easy way to build Electron apps with TypeScript. It supports Hot Module Replacement (HMR) for the Electron main process, the preload script, and the app itself. The entire setup uses Vite in a clear, transparent manner, no hidden "black magic" in third-party plugins.

Tech Stack

  • Runtime: Electron 40 (bundled Node 24), Node 24+
  • Build: Vite 7, TypeScript 5.9
  • Package Manager: pnpm 10.30+
  • Linting: ESLint 10 + @antfu/eslint-config 7
  • Testing: Vitest 4
  • Settings: smol-toml (TOML persistence)
  • Distribution: electron-builder

Features

Multi-Page Support

Multiple windows with independent entry points, configured via project.config.ts. A custom Vite plugin resolves virtual modules and injects template variables. Currently, it includes: the main window, a dialogue window, and the display demo.

Window Management

The WindowController manages per-window state including pack mode (auto-sizing to content), placement within the display work area, and display-awareness for multi-monitor setups. Windows in pack mode use a ResizeObserver in the preload script to automatically report content size changes back to the main process, debounced at 50 ms.

The main window remembers its position, size, and maximized state across sessions, persisted as a TOML file in the user data directory. On startup, saved bounds are validated against the current display configuration: if the window ends up off-screen (e.g. after disconnecting an external monitor), it is automatically relocated to the centre of the primary display. The settings module is configurable (defaults, file name, directory), so customer projects can adapt it without changing the core code.

Target screen selection lets you choose which display a window or dialogue opens on: 'primary' (the OS primary display), 'app' (the display of the main application window), or 'active' (the display under the mouse cursor). Simply set placement.screen in any WindowConfiguration or DialogConfig.

A deferred whenWindowReady promise provides a clean lifecycle hook for post-load operations like pushing dialogue configuration.

Dialog System

A built-in dialogue system replaces native Electron dialogues with fully styled, configurable windows. Dialogues support five visual types (confirm, error, info, success, warning), configurable buttons with variants (primary, secondary, danger), and flexible placement. The backend owns all close decisions; the renderer only sends intents.

Lifecycle hooks (onOpened, onShown, onAction, onClosed) allow callers to react to dialogue state transitions.

See the Dialog System API Reference for detailed usage and configuration options.

Type-Safe IPC

Four communication patterns over typed IpcChannels:

  • Request-Response (invoke/handleFromRenderer): Frontend asks, the main process answers
  • Fire-and-Forget (send/onFromRenderer): Frontend sends it, the main process reacts
  • Broadcast (broadcast): The main process sends it to all renderer windows
  • Targeted Send (sendToRenderer): The main process sends it to a specific renderer window

The preload script exposes a minimal window.backend API via contextBridge, keeping the renderer fully sandboxed. All IPC arguments are automatically sanitized via safeClone in the preload and main process layers, transparently handling non-cloneable types (DOM nodes, Symbols, Errors, circular references, RegExp) so developers never have to worry about serialization boundaries.

Security

Context Isolation, Sandbox, and strict CSP are enabled by default. Node Integration and Webview are disabled.

Logging

Production-ready logging powered by bit-log, with source map support for both the main process and renderer. The preload script has its own logging pipeline that forwards events to the backend via a shared BackendForwardingAppender.

Coloured console output with timestamps, log levels, logger names, and resolved TypeScript source locations:

Console logging

The same information is written to a single log file per day, with backend and frontend events merged chronologically:

File logging

A custom PipelineAppender merges backend and frontend log events into a single, chronologically sorted log file. Events are buffered and reordered by timestamp to account for IPC latency, then flushed to Console and File delegates with origin prefixes (Backend : / Frontend:).

Inline source maps are deliberately included in production builds. The small size overhead is well worth it: log files show the original TypeScript file names and line numbers, making it much easier to locate and fix issues in deployed applications.

Distribution

Platform-specific builds are automated via CI workflows:

  • Linux: AppImage, built on GitHub Actions
  • macOS: DMG, built on GitHub Actions
  • Windows: Squirrel installer, built on GitHub Actions

Workflows trigger on published releases (assets attached automatically) and on manual dispatch (artefacts retained for 30 days). All builds use electron-builder.

Debugging

The dev server (pnpm dev) launches Electron with --inspect, so the main process debug port (9229) is always available.

Main process (backend): Start pnpm dev, then attach your debugger to port 9229. Breakpoints in the TypeScript sources under modules/electron/ and modules/common/ work via source maps. If you need to pause at startup before any application code runs, change --inspect to --inspect-brk in vite.config.ts (the spawn call around line 272). Electron will then wait for a debugger to attach before continuing.

  • IntelliJ / WebStorm: Run > Edit Configurations > Add New (+) > "Attach to Node.js/Chrome".
  • Set port to 9229 and confirm.
  • Start pnpm dev, then run the attach configuration.
  • Breakpoints in .ts files are resolved automatically.
  • VS Code: Add an attach configuration to .vscode/launch.json (not committed):
    {
      "type": "node",
      "request": "attach",
      "name": "Attach Electron Main",
      "port": 9229,
      "sourceMaps": true,
      "outFiles": ["${workspaceFolder}/dist-electron/**/*.js"]
    }
    
    Start pnpm dev, then launch the attach configuration (F5).

Renderer (frontend): The built-in Chromium DevTools open automatically in dev mode. You can set breakpoints directly in the Sources tab; Vite serves the original TypeScript files via source maps. For IDE-based renderer debugging, add --remote-debugging-port=9222 to the spawn arguments in vite.config.ts and create a "Chrome Remote" attach configuration pointing to port 9222.

Core/Demo Separation

All demo code lives in demo/ subdirectories within each module and can be deleted for a clean starter. Core functionality (window management, dialogue system, IPC, logging) is fully independent of the demo code.