Introduction
Electron is a great framework to build desktop applications. Using vite electron which is a new framework for electron helps in building the applications even faster and also comes with all the benefits that vite has.
Sometimes it can be confusing working with vite electron but lets break it down for easier understanding.
The Complete Setup
I am using react as my main renderer framework to create the front-end.
First, let's lay out all the code for a minimal, working Vite + Electron application. This will be our reference point.
Project Structure
Imagine we've created a project. The file structure would look like this:
my-electron-app/
├── electron/
│ ├── main.js # Main process entry point
│ └── preload.js # Preload script (the bridge)
├── src/
│ ├── App.jsx # A simple React component
│ └── main.jsx # Renderer process entry point
├── index.html
├── package.json
└── vite.config.js
package.json
This file defines our project, its dependencies, and the scripts to run and build it.
JSON
{
"name": "my-electron-app",
"version": "0.1.0",
"main": "electron/main.js", // <-- Important: Points to Electron's entry file
"scripts": {
"dev": "vite",
"build": "vite build && electron-builder"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.2.1",
"electron": "^28.2.1",
"electron-builder": "^24.9.1",
"vite": "^5.0.12",
"vite-plugin-electron-renderer": "^0.14.5"
},
"build": { // <-- Configuration for electron-builder
"appId": "com.my-app.id",
"productName": "My Awesome App",
"files": [
"dist/**/*",
"electron/**/*"
],
"directories": {
"buildResources": "assets",
"output": "release"
}
}
}
vite.config.js
This configures Vite to work nicely with Electron.
JavaScript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import electron from 'vite-plugin-electron-renderer';
export default defineConfig({
plugins: [
react(),
electron(), // <-- The key plugin for Electron
],
});
**
electron/main.js (The Main Process)**
This script is the heart of your Electron app. It runs in a Node.js environment and is responsible for creating windows and handling system events.
JavaScript
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) {
app.quit();
}
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'), // <-- Link to the preload script
contextIsolation: true, // <-- Security feature
nodeIntegration: false, // <-- Security feature
},
});
// Load the app.
if (process.env.VITE_DEV_SERVER_URL) {
// In development, load from the Vite dev server.
mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL);
// Open the DevTools.
mainWindow.webContents.openDevTools();
} else {
// In production, load the built index.html file.
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
}
};
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.js (The Bridge)
This special script acts as a secure bridge between your web-based frontend (Renderer) and the Node.js backend (Main).
JavaScript
const { contextBridge, ipcRenderer } = require('electron');
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld('api', {
send: (channel, data) => {
// whitelist channels
let validChannels = ['toMain'];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
let validChannels = ['fromMain'];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
}
});
src/main.jsx and src/App.jsx (The Renderer Process)
This is your standard Vite/React frontend code. It runs in a browser-like environment.
src/main.jsx
JavaScript
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
src/App.jsx
JavaScript
import React from 'react';
function App() {
const handleClick = () => {
// Use the exposed 'api' from the preload script
window.api.send('toMain', 'Hello from the Renderer Process!');
};
React.useEffect(() => {
window.api.receive('fromMain', (data) => {
console.log(`Received from main: ${data}`);
});
}, []);
return (
<div>
<h1>Hello from Vite + React + Electron!</h1>
<button onClick={handleClick}>Send Message to Main</button>
</div>
);
}
export default App;
Lets go to Q/A for better understanding.
How would you describe the fundamental difference between the "Main Process"? What can one do that the other cannot?
Answer:
- The Main Process is the stage manager🎭. it sets up the stage(the window) and manages the backstage opertaions.
- The Renderer Process is the actor 🧑🎤. It performs on the stage, displaying the content and interacting with the user.
Top comments (0)