Electron React Boilerplate

repository·main·Indexed 12 days ago

https://github.com/electron-react-boilerplate/electron-react-boilerplate

A production-ready template for building scalable desktop applications using Electron and React. It comes pre-configured with React Router, Webpack, and React Fast Refresh, featuring a secure IPC communication layer via contextBridge and a structured main process entrypoint for lifecycle and window management.

Tokens
1.9K
Snippets
9
Records
10
Agent score
98%

What's inside Electron React Boilerplate

  1. Configure Asset Paths for Windows and Packaging

    main

    The boilerplate uses a dynamic path resolution strategy to ensure assets (like icons) are found whether the app is running in development mode or is packaged for production.

    • In Production: Assets are resolved from process.resourcesPath/assets.
    • In Development: Assets are resolved from the local ../../assets directory relative to the compiled main file.

    Use the internal getAssetPath helper to retrieve these paths reliably.

    const RESOURCES_PATH = app.isPackaged
        ? path.join(process.resourcesPath, 'assets')
        : path.join(__dirname, '../../assets');
    
    const getAssetPath = (...paths: string[]): string => {
        return path.join(RESOURCES_PATH, ...paths);
      };
    
    // Usage example:
    // icon: getAssetPath('icon.png')
  2. Understand the Main Process entrypoint

    main

    The src/main/main.ts file serves as the entrypoint for the Electron main process. It is responsible for:

    1. Lifecycle Management: Handling application startup via app.whenReady(), window creation, and application shutdown.
    2. Window Management: Creating the BrowserWindow instance, configuring its dimensions, icon, and preload scripts.
    3. Process Communication: Setting up IPC (Inter-Process Communication) listeners using ipcMain to communicate with renderer processes.
    4. Environment Configuration: Automatically enabling debugging tools (like electron-debug) and installing developer extensions (like REACT_DEVELOPER_TOOLS) when running in a development environment.
    5. Auto-Updates: Initializing the AppUpdater class to handle background updates via electron-updater.

    When running npm run build or npm run build:main, this file is compiled to ./src/main.js using webpack.

  3. Initialize the React application in the renderer process

    main

    The renderer process entrypoint (src/renderer/index.tsx) mounts the React application into the DOM element with the ID root. It also demonstrates how to interact with the Electron IPC (Inter-Process Communication) layer via the window.electron object, which is exposed by the preload script.

    import { createRoot } from 'react-dom/client';
    import App from './App';
    
    const container = document.getElementById('root') as HTMLElement;
    const root = createRoot(container);
    root.render(<App />);
  4. Communicate via IPC in the Main Process

    main

    The main process uses ipcMain to listen for messages sent from the renderer process. You can define custom listeners to handle specific events and reply back to the sender.

    In this boilerplate, an example listener is provided for the ipc-example channel. It receives an argument, logs it, and replies with a 'pong' message.

    import { ipcMain } from 'electron';
    
    ipcMain.on('ipc-example', async (event, arg) => {
      const msgTemplate = (pingPong: string) => `IPC test: ${pingPong}`;
      console.log(msgTemplate(arg));
      event.reply('ipc-example', msgTemplate('pong'));
    });
  5. Configure application routing in App.tsx

    main

    The application's UI structure and routing are defined in the App component located in src/renderer/App.tsx. It uses react-router-dom with a MemoryRouter to manage navigation within the Electron renderer process. To add new pages or views, you should define new components and register them as Route elements within the Routes component inside App.

    import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';
    
    export default function App() {
      return (
        <Router>
          <Routes>
            <Route path="/" element={<Hello />} />
            {/* Add new routes here */}
          </Routes>
        </Router>
      );
    }
  6. Use the exposed 'electron' API in the renderer process

    main

    The preload script exposes a global electron object to the renderer process via contextBridge. This object provides a secure way to communicate with the Main process using IPC (Inter-Process Communication) without exposing the full ipcRenderer module.

    Available methods on window.electron.ipcRenderer:

    • sendMessage(channel, ...args): Sends a message to the Main process on the specified channel.
    • on(channel, func): Registers a listener for a channel. Returns a cleanup function to remove the listener.
    • once(channel, func): Registers a listener that triggers only once.

    All channels must be defined in the Channels type (e.g., 'ipc-example').

    // Example: Sending a message
    window.electron.ipcRenderer.sendMessage('ipc-example', 'hello', 123);
    
    // Example: Listening for messages with cleanup
    const unsubscribe = window.electron.ipcRenderer.on('ipc-example', (data) => {
      console.log('Received:', data);
    });
    
    // Later, to stop listening:
    unsubscribe();
  7. Use IPC via the window.electron API

    main

    The boilerplate exposes Electron's ipcRenderer through a window.electron object (provided by the preload script). You can use this to listen for one-time events or send messages to the main process.

    Available methods shown in the entrypoint:

    • window.electron.ipcRenderer.once(channel, callback): Listens for a single occurrence of a specific IPC channel.
    • window.electron.ipcRenderer.sendMessage(channel, args): Sends data to a specific IPC channel.
    // Listening for a one-time event
    window.electron?.ipcRenderer.once('ipc-example', (arg) => {
      console.log(arg);
    });
    
    // Sending a message
    window.electron?.ipcRenderer.sendMessage('ipc-example', ['ping']);