Electron 🔗 Vite
English | 简体中文
In short, vite-plugin-electron makes developing Electron apps as easy as normal Vite projects.
Important
This project will drop support for vite@<8 in the upcoming v1 release.
For users needing Vite 7 compatibility, use v0.29.1. it is stable and production-ready.
- 🌱 Fully compatible with Vite and Vite's ecosystem (Based on Vite)
- 🔮 Full-featured JavaScript API, really easy to integrate with complex projects.
- 🐣 Few APIs, easy to use
- Add the following dependency to your project
npm i -D vite-plugin-electron- Add
vite-plugin-electronto thepluginssection ofvite.config.ts
import electron from 'vite-plugin-electron/simple'
export default {
plugins: [
electron({
main: {
// Shortcut of `build.lib.entry`
entry: 'electron/main.ts',
},
preload: {
// Shortcut of `build.rollupOptions.input`
input: 'electron/preload.ts',
},
// Optional: Use Node.js API in the Renderer process
renderer: {},
}),
],
}- Create the
electron/main.tsfile and type the following code
import { app, BrowserWindow } from 'electron'
app.whenReady().then(() => {
const win = new BrowserWindow({
title: 'Main window',
})
// You can use `process.env.VITE_DEV_SERVER_URL` when the vite command is called `serve`
if (process.env.VITE_DEV_SERVER_URL) {
win.loadURL(process.env.VITE_DEV_SERVER_URL)
} else {
// Load your file
win.loadFile('dist/index.html')
}
})- Add the
mainentry topackage.json
{
+ "main": "dist-electron/main.mjs"
}That's it! You can now use Electron in your Vite app ✨
In most cases, the vite-plugin-electron/simple API is recommended. If you know very well how this plugin works or you want to use vite-plugin-electron API as a secondary encapsulation of low-level API, then the flat API is more suitable for you. It is also simple but more flexible. :)
The difference compared to the simple API is that it does not identify which entry represents preload and the adaptation to preload.
import electron from 'vite-plugin-electron'
export default {
plugins: [
electron({
entry: 'electron/main.ts',
}),
],
}- Simple API is based on the Flat API
- Simple API includes some Preload scripts preset configs.
- Flat API provides some more general APIs, which you can use for secondary encapsulation, such as nuxt-electron.
electron(options: ElectronOptions | ElectronOptions[])
export interface ElectronOptions {
/**
* Shortcut of `build.lib.entry`
*/
entry?: import('vite').LibraryOptions['entry']
vite?: import('vite').InlineConfig
/**
* Triggered when Vite is built every time -- `vite serve` command only.
*
* If this `onstart` is passed, Electron App will not start automatically.
* However, you can start Electron App via `startup` function.
*/
onstart?: (args: {
/**
* Electron App startup function.
* It will mount the Electron App child-process to `process.electronApp`.
*
* You can also set environment variables to control the Electron CLI flags.
* Supported env vars:
* - `REMOTE_DEBUGGING_PORT`
* - `ELECTRON_IGNORE_CERTIFICATE_ERRORS`
* - `ELECTRON_DISABLE_WEB_SECURITY`
* - `ELECTRON_INSPECT`
* - `ELECTRON_INSPECT_BRK`
*
* `1` or `true` turns a flag on, `0` or `false` turns it off, and any other non-empty
* value is appended as `=<value>`.
*
* @param argv default value `['.', '--no-sandbox']`
* @param options options for `child_process.spawn`
* @param customElectronPkg custom electron package name (default: 'electron')
*/
startup: (
argv?: string[],
options?: import('node:child_process').SpawnOptions,
customElectronPkg?: string,
) => Promise<void>
/** Reload Electron-Renderer */
reload: () => void
}) => void | Promise<void>
}Important
vite-plugin-electron/multi-env is only available in vite-plugin-electron@>=1.0.0.
It does not exist in 0.x releases.
Using Vite's Environment API to build Electron targets instead of manually calling build(). It is the future-facing way to handle multi-target builds, and the configuration is more concise and easier to maintain: use rolldownOptions.input to specify the entry and overridable environment config for each target.
Flat API:
import electron from 'vite-plugin-electron/multi-env'
export default {
plugins: [
electron([
{
input: 'electron/main.ts',
},
{
input: 'electron/preload.ts',
},
]),
],
}Simple API:
import { electronSimple } from 'vite-plugin-electron/multi-env'
export default {
plugins: [
electronSimple({
main: {
input: 'electron/main.ts',
options: {
define: {
__ELECTRON_TARGET__: JSON.stringify('main'),
},
},
},
preload: {
input: 'electron/preload.ts',
options: {
define: {
__ELECTRON_TARGET__: JSON.stringify('preload'),
},
},
},
// You can also add custom targets, and they will be built in the same way as the main process, but with different environment variables.
custom: {
input: 'electron/custom.ts',
options: {
define: {
__ELECTRON_TARGET__: JSON.stringify('custom'),
},
},
},
}),
],
}electronSimple() accepts an object grouped by environment name. The main and preload keys reuse the same default presets as vite-plugin-electron/simple, while custom keys are built like main-process targets with their own environment options.
export interface MultiEnvElectronOptions {
/**
* Optional name for the Electron environment `electron_${name}`.
*
* By default, the plugin will generate environment names like `electron_0`,
* `electron_1`, etc. based on the order of the options provided.
*/
name?: string
/**
* Shortcut of `options.build.rolldownOptions.input`
*/
input?: import('vite').BuildEnvironmentOptions['rolldownOptions']['input']
/**
* Shortcut of `options.build.rolldownOptions.plugins`
*/
plugins?: import('vite').BuildEnvironmentOptions['rolldownOptions']['plugins']
/**
* Per-environment Vite options.
*/
options?: import('vite').EnvironmentOptions
onstart?: ElectronOptions['onstart']
}Let's use the official template-vanilla-ts created based on create vite as an example
+ ├─┬ electron
+ │ └── main.ts
├─┬ src
│ ├── main.ts
│ ├── style.css
│ └── vite-env.d.ts
├── .gitignore
├── favicon.svg
├── index.html
├── package.json
├── tsconfig.json
+ └── vite.config.tsThis is just the default behavior, and you can modify them at any time through custom config in the vite.config.js
{ "type": "module" }
┏————————————————————┳——————————┳———————————┓
│ built │ format │ suffix │
┠————————————————————╂——————————╂———————————┨
│ main process │ esm │ .js │
┠————————————————————╂——————————╂———————————┨
│ preload scripts │ cjs │ .mjs │ diff
┠————————————————————╂——————————╂———————————┨
│ renderer process │ - │ .js │
┗————————————————————┸——————————┸———————————┛
{ "type": "commonjs" } - default
┏————————————————————┳——————————┳———————————┓
│ built │ format │ suffix │
┠————————————————————╂——————————╂———————————┨
│ main process │ cjs │ .js │
┠————————————————————╂——————————╂———————————┨
│ preload scripts │ cjs │ .js │ diff
┠————————————————————╂——————————╂———————————┨
│ renderer process │ - │ .js │
┗————————————————————┸——————————┸———————————┛
There are many cases here 👉 electron-vite-samples
The local demo suite lives in playground/ and includes flat, simple, multi-env, and worker modes that import the plugin source directly from this repo.
vite-plugin-electron's JavaScript APIs are fully typed, and it's recommended to use TypeScript or enable JS type checking in VS Code to leverage the intellisense and validation.
ElectronOptions- typeresolveViteConfig- function, Resolve the default Vite'sInlineConfigfor build Electron-MainwithExternalBuiltins- functionbuild- functionstartup- function
Example
import { build, startup } from 'vite-plugin-electron'
const isDev = process.env.NODE_ENV === 'development'
const isProd = process.env.NODE_ENV === 'production'
build({
entry: 'electron/main.ts',
vite: {
mode: process.env.NODE_ENV,
build: {
minify: isProd,
watch: isDev ? {} : null,
},
plugins: [
{
name: 'plugin-start-electron',
closeBundle() {
if (isDev) {
// Startup Electron App
startup()
}
},
},
],
},
})startup() uses these env vars. 1 or true turns a flag on, 0 or false turns it off, and any other non-empty value is appended as =<value>.
REMOTE_DEBUGGING_PORTappends--remote-debugging-port=<value>ELECTRON_IGNORE_CERTIFICATE_ERRORSappends--ignore-certificate-errorsELECTRON_DISABLE_WEB_SECURITYappends--disable-web-securityELECTRON_INSPECTappends--inspector--inspect=<value>ELECTRON_INSPECT_BRKappends--inspect-brkor--inspect-brk=<value>
Use notBundle() in development to externalize dependencies in Electron entries.
This keeps startup fast by skipping dependency bundling while running vite serve.
For production builds, let bundling run as usual.
import { defineConfig } from 'vite'
import electron from 'vite-plugin-electron'
import { notBundle } from 'vite-plugin-electron/plugin'
export default defineConfig(({ command }) => ({
plugins: [
electron({
entry: 'electron/main.ts',
vite: {
plugins: [command === 'serve' && notBundle()],
},
}),
],
}))Under the Hood
Use build.rolldownOptions.external to externalize dependencies from package.json
API
notBundle(options?: NotBundleOptions)
export interface NotBundleOptions {
/**
* Override `build.rolldownOptions.external`.
*
* If omitted, dependencies from package.json
* (dependencies/devDependencies/peerDependencies/optionalDependencies)
* are externalized automatically.
*/
filter?: import('vite').RolldownOptions['external']
}Hot Reload
Since v0.29.0, when preload scripts are rebuilt, they will send an electron-vite&type=hot-reload event to the main process.
If your App doesn't need a renderer process, this will give you hot-reload.
// electron/main.ts
process.on('message', (msg) => {
if (msg === 'electron-vite&type=hot-reload') {
for (const win of BrowserWindow.getAllWindows()) {
// Hot reload preload scripts
win.webContents.reload()
}
}
})Use esmShim() to inject __dirname and __filename shims for ESM Electron entries that rely on these CJS globals.
Only files that actually reference __dirname or __filename are transformed, so there is no overhead for files that don't need it.
import { defineConfig } from 'vite'
import electron from 'vite-plugin-electron'
import { esmShim, notBundle } from 'vite-plugin-electron/plugin'
export default defineConfig(({ command }) => ({
plugins: [
electron({
entry: 'electron/main.ts',
vite: {
plugins: [command === 'serve' && notBundle(), esmShim()],
},
}),
],
}))Under the Hood
For each matching file, the following shim is prepended:
import { fileURLToPath } from 'node:url'
import { dirname } from 'node:path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)API
esmShim() — no options, just add it to plugins.
It just executes the electron . command in the Vite build completion hook and then starts or restarts the Electron App.
- 🚨 By default, the files in
electronfolder will be built into thedist-electron
We have two ways to use C/C++ native modules
First way
In general, Vite may not correctly build Node.js packages, especially C/C++ native modules, but Vite can load them as external packages
So, put your Node.js package in dependencies. Unless you know how to properly build them with Vite
export default {
plugins: [
electron({
entry: 'electron/main.ts',
vite: {
build: {
rolldownOptions: {
// Here are some C/C++ modules them can't be built properly
external: ['serialport', 'sqlite3'],
},
},
},
}),
],
}Second way
Use 👉 vite-plugin-native
import native from 'vite-plugin-native'
export default {
plugins: [
electron({
entry: 'electron/main.ts',
vite: {
plugins: [native(/* options */)],
},
}),
],
}