Building Desktop Applications with Electron Forge, Vite, and TypeScript

In the world of desktop application development, creating cross - platform applications efficiently is a top priority. Electron has emerged as a popular framework for building desktop applications using web technologies such as HTML, CSS, and JavaScript. Electron Forge simplifies the process of packaging, distributing, and automating the development of Electron applications. Vite, on the other hand, is a build tool that provides a faster and leaner development experience, especially for modern web applications. When combined with TypeScript, which adds static typing to JavaScript, we get a powerful stack for building robust and maintainable desktop applications. In this blog post, we will explore the fundamental concepts of Electron Forge, Vite, and TypeScript, learn how to use them together, and discover common and best practices.

Table of Contents

  1. Fundamental Concepts
    • Electron Forge
    • Vite
    • TypeScript
  2. Usage Methods
    • Setting up the Project
    • Configuration
    • Writing Code
  3. Common Practices
    • Directory Structure
    • Handling Main and Renderer Processes
  4. Best Practices
    • Error Handling
    • Performance Optimization
  5. Conclusion
  6. References

Fundamental Concepts

Electron Forge

Electron Forge is a tool that helps developers package and distribute Electron applications. It provides a unified interface for common tasks such as creating a new project, building the application for different platforms, and publishing it. Electron Forge comes with multiple templates and plugins, which can be used to customize the development process according to the requirements.

Vite

Vite is a modern build tool that focuses on speed and simplicity. It uses native ES modules in the browser during development, which eliminates the need for bundling upfront. This results in extremely fast cold starts and hot module replacement (HMR). Vite also provides a plugin system that allows developers to extend its functionality.

TypeScript

TypeScript is a superset of JavaScript that adds static typing to the language. It helps catch errors early in the development process by allowing developers to define types for variables, functions, and objects. TypeScript code is transpiled to plain JavaScript, which can be run in any JavaScript environment.

Usage Methods

Setting up the Project

First, make sure you have Node.js and npm (Node Package Manager) installed on your system. Then, create a new Electron Forge project using the following command:

npm init electron-forge@latest my - electron - vite - ts - app --template=webpack
cd my - electron - vite - ts - app

Next, install Vite and TypeScript:

npm install vite typescript @types/node --save - dev

Configuration

Create a vite.config.ts file in the root of your project. Here is a basic configuration for an Electron application:

import { defineConfig } from 'vite';

export default defineConfig({
    root: 'src/renderer',
    build: {
        outDir: '../dist/renderer',
        emptyOutDir: true
    }
});

For TypeScript, create a tsconfig.json file:

{
    "compilerOptions": {
        "target": "ESNext",
        "module": "ESNext",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true,
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "noEmit": true,
        "jsx": "react-jsx"
    },
    "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.jsx"]
}

Writing Code

In the src/main directory, create a main.ts file for the main process:

import { app, BrowserWindow } from 'electron';

function createWindow() {
    const win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            nodeIntegration: true,
            contextIsolation: false
        }
    });

    win.loadFile('dist/renderer/index.html');
}

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

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

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

In the src/renderer directory, create an index.tsx file:

import React from 'react';
import ReactDOM from 'react - dom/client';

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(<h1>Hello, Electron with Vite and TypeScript!</h1>);

Common Practices

Directory Structure

A good directory structure can make your project more organized and easier to maintain. Here is a suggested structure:

my - electron - vite - ts - app/
├── src/
│   ├── main/
│   │   └── main.ts
│   ├── renderer/
│   │   ├── index.tsx
│   │   └── index.html
├── dist/
│   ├── renderer/
│   └── main/
├── vite.config.ts
├── tsconfig.json
└── package.json

Handling Main and Renderer Processes

In Electron, the main process is responsible for creating and managing browser windows, while the renderer process runs the web pages inside those windows. Use the ipcMain and ipcRenderer modules to communicate between the main and renderer processes.

// main.ts
import { app, BrowserWindow, ipcMain } from 'electron';

ipcMain.on('message - from - renderer', (event, arg) => {
    console.log(arg);
    event.sender.send('message - from - main', 'Message received in main process');
});

// renderer/index.tsx
import { ipcRenderer } from 'electron';

ipcRenderer.send('message - from - renderer', 'Hello from renderer');
ipcRenderer.on('message - from - main', (event, arg) => {
    console.log(arg);
});

Best Practices

Error Handling

In both the main and renderer processes, implement proper error handling. For example, in the main process, you can handle uncaught exceptions:

// main.ts
process.on('uncaughtException', (error) => {
    console.error('Uncaught Exception:', error);
});

In the renderer process, use try - catch blocks around asynchronous operations:

// renderer/index.tsx
async function fetchData() {
    try {
        const response = await fetch('https://api.example.com/data');
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error('Error fetching data:', error);
    }
}

Performance Optimization

  • Minimize the use of global variables in the renderer process to avoid memory leaks.
  • Use lazy loading for large components or resources in the renderer process.
  • Optimize the build process by using code splitting and tree shaking features provided by Vite.

Conclusion

Combining Electron Forge, Vite, and TypeScript provides a powerful and efficient way to build cross - platform desktop applications. Electron Forge simplifies the packaging and distribution process, Vite offers a fast development experience, and TypeScript adds static typing for better code quality. By following the usage methods, common practices, and best practices outlined in this blog post, you can create robust and maintainable desktop applications.

References