electron-vite Documentation

Next generation Electron build tooling based on Vite 新一代 Electron 开发构建工具,支持源代码保护

RAW Doc

1. Project Overview & Quickstart (alex8088/electron-vite)

Features

  • ⚡️ Vite powered and use the same way.
  • 🛠 Pre-configure with sensible defaults optimized for Electron.
  • 💡 Optimize asset handling for Electron main process.
  • 🚀 Fast HMR & hot reloading.
  • 🔥 Isolated build for multi-entry application development.
  • ✨ Simplify multi-threading development.
  • 🔒 Compile code to v8 bytecode to protect source code.
  • 🔌 Easy to debug in IDEs such as VSCode or WebStorm.
  • 📦 Out-of-the-box support for TypeScript, Vue, React, Svelte, SolidJS and more.

Usage

Install

sh
npm i electron-vite -D

Development & Build

In a project where electron-vite is installed, you can use electron-vite binary directly with npx electron-vite or add the npm scripts to your package.json file like this:

json
{
  "scripts": {
    "start": "electron-vite preview",
    "dev": "electron-vite dev",
    "prebuild": "electron-vite build"
  }
}

Configuration

When running electron-vite from the command line, electron-vite will automatically try to resolve a config file named electron.vite.config.js inside project root. The most basic config file looks like this:

js
// electron.vite.config.js
export default {
  main: {
    // vite config options
  },
  preload: {
    // vite config options
  },
  renderer: {
    // vite config options
  }
}

Getting Started

Clone the electron-vite-boilerplate or use the create-electron tool to scaffold your project.

bash
npm create @quick-start/electron@latest

Currently supported template presets include:

JavaScript TypeScript
vanilla vanilla-ts
vue vue-ts
react react-ts
svelte svelte-ts
solid solid-ts

Contribution

See Contributing Guide.

License

MIT © alex.wei

2. Official Technical Reference & Guides (alex8088/electron-vite-docs)

File: packages/zh-CN/docs/guide/assets.md


outline: deep

资源处理

::: tip 提示
本章节中的资源处理功能仅适用于主进程。关于渲染进程的静态资源处理,请参阅 Vite 的 静态资源处理。
:::

Public 目录

公共目录默认为 <root>/resources,专用于主进程和预加载脚本。它可以通过 main.publicDir 和 preload.publicDir 选项进行配置。

如果你有 图标、可执行程序、wasm 文件 等资源,可以将它们放在这个目录中。

请注意:

  • 公共目录中的所有资源都不会复制到输出目录。所以在打包 app 的时候,公共目录应该一起打包。
  • 推荐使用 ?asset 后缀导入公共资源。

::: warning 提示
值得注意的是渲染进程中的公共资源处理不同于主进程和预加载脚本。

  • 默认情况下,渲染进程的工作目录位于 src/renderer,因此需要在该目录下创建公共资源目录。默认的公共目录名为 public,也可以通过 renderer.publicDir 指定。
  • 渲染进程的公共资源将被复制到输出目录。
    :::

将资源引入为文件路径

在主进程中,可以使用 ?asset 后缀将资源作为文件路径导入:

js {2}
import { Tray, nativeImage } from 'electron'
import appIcon from '../../build/icon.ico?asset'

let tray = new Tray(nativeImage.createFromPath(appIcon))

在此示例中,appIcon 将解析为:

js
const path = require("path");
const appIcon = path.join(__dirname, "./chunks/icon-4363016c.ico");

并且 build/icon.ico 将被复制到输出目录 (out/main/chunks/icon-4363016c.ico)。

如果 appIcon 放在 public 目录 中:

js {2}
import { Tray, nativeImage } from 'electron'
import appIcon from '../../resources/icon.ico?asset'

let tray = new Tray(nativeImage.createFromPath(appIcon))

此时 appIcon 将被解析为:

js
const path = require("path");
const appIcon = path.join(__dirname, "../../resources/icon.ico");

并且 resources/icon.ico 不会被复制到输出目录。

将 Json 文件引入为文件路径

当你导入 json 文件时,Vite 的 json 插件会将其解析为 json 对象。但有时我们想将其作为文件路径导入,这种情况下我们可以使用 ?commonjs-external&asset 后缀来导入。

js
import jsonFile from '../../resources/config.json?commonjs-external&asset'

`app.asar.unpacked` 文件路径问题

由于 ASAR 的限制,我们不能将所有文件打包到 ASAR 存档中。有时,这会产生一个问题,因为二进制文件的路径会发生变化,但你的 path.join(__dirname, 'binary') 并没有改变。要使其正常工作,需要修复路径。将路径中的 app.asar 转换为 app.asar.unpacked。

你可以使用 ?asset&asarUnpack 后缀来支持。例如:

js
import bin from '../../resources/hello.exe?asset&asarUnpack'

然后 bin 将解析为:

js
const path = require("path");
const bin = path.join(__dirname, "../../resources/hello.exe").replace("app.asar", "app.asar.unpacked");

导入原生 Node 模块

有两种使用 *.node 模块的方法。

  1. 作为 node 模块导入
js
import addon from '../../resources/hello.node'

console.log(addon?.hello())
  1. 作为文件路径导入
js
import addon from '../../resources/hello.node?asset'
import { createRequire } from 'module'
const require = createRequire(import.meta.url)

const native = require(addon)
console.log(native?.hello())

值得注意的是通常需要根据 platform 和 arch 来导入原生 node 模块。如果能保证 node 模块是根据 platform 和 arch 生成的,使用第一种导入方式是没有问题的。否则,你可以使用第二种方法根据 platform 和 arch 来导入正确的 node 模块。

导入 WebAssembly

::: warning 提示
在 Vite 中,可以通过 ?init 后缀处理 .wasm 文件,它支持浏览器,但不支持 Node.js(Electron 主进程)。
:::

在主进程中,预编译的 .wasm 文件可以通过 ?loader 来导入。默认导出一个初始化函数,返回值为所导出 wasm 实例对象的 Promise:

js
import loadWasm from '../../resources/add.wasm?loader'

loadWasm().then((instance) => {
  // Exported function live under instance.exports
  const add = instance.exports.add as (a: number, b: number) => number
  const sum = add(5, 6)
  console.log(sum); // Outputs: 11
})

初始化函数还可以将 imports 对象传递给 WebAssembly.instantiate 作为其第二个参数:

js
loadWasm({
  env: {
    memoryBase: 0,
    tableBase: 0,
    memory: new WebAssembly.Memory({
      initial: 256,
      maximum: 512
    }),
    table: new WebAssembly.Table({
      initial: 0,
      maximum: 0,
      element: 'anyfunc'
    })
  }
}).then(() => {
  /* ... */
})

在渲染进程中,请参阅 Vite 的 WebAssembly 功能,了解更多详细信息。


File: packages/zh-CN/docs/guide/build.md

构建生产版本

当需要打包你的 Electron 应用程序以进行分发时,通常需要先运行 electron-vite build 命令。

输出目录

默认情况下,构建输出将放置在 out(相对于项目根目录)。

text
├──out/
│  ├──main
│  ├──preload
│  └──renderer
└──...

你也可以通过命令行来重新指定它,例如 electron-vite dev/build/preview --outDir=dist。

此外,还可以使用 build.outDir 选项来分别指定主进程、渲染器和预加载脚本的输出目录。

js [electron.vite.config.js] {4,9,14}
export default defineConfig({
  main: {
    build: {
      outDir: 'dist/main'
    }
  },
  preload: {
    build: {
      outDir: 'dist/preload'
    }
  },
  renderer: {
    build: {
      outDir: 'dist/renderer'
    }
  }
})

::: tip 提示
建议将所有打包后的代码放在 同一个目录 中,因为这些代码都是 Electron 应用运行所必需的。这样做还可以在打包应用时更容易排除源代码,从而减小最终的包体积。
:::

自定义构建

构建过程可以通过多种 构建配置选项 来自定义。具体来说,你可以通过 build.rollupOptions 直接调整底层的 Rollup 选项:

js [electron.vite.config.js]
export default defineConfig({
  main: {
    rollupOptions: {
      // ...
    }
  },
  preload: {
    rollupOptions: {
      // ...
    }
  },
  renderer: {
    rollupOptions: {
      // ...
    }
  }
})

分块策略

有效的代码分块策略对于优化 Electron 应用的性能至关重要。

代码分块可以通过 build.rollupOptions.output.manualChunks 进行配置(请参阅 Rollup 文档)。

js [electron.vite.config.ts]
import { defineConfig, splitVendorChunkPlugin } from 'electron-vite'

export default defineConfig({
  main: {
    build: {
      rollupOptions: {
        output: {
          manualChunks(id): string | void {
            if (id.includes('foo')) {
              return 'foo'
            }
          }
        }
      }
    }
  },
  // ...
})

依赖处理

请参阅 依赖处理.

隔离构建

请参阅 隔离构建.

源代码保护

请参阅 源代码保护。


File: packages/zh-CN/docs/guide/cli.md

命令行界面

`electron-vite`

别名:electron-vite dev、 electron-vite serve。

该命令将构建主进程和预加载脚本源代码,并为渲染器启动一个开发服务器,最后启动 Electron 应用程序。

`electron-vite preview`

该命令将构建主进程、渲染器和预加载脚本源代码,并启动 Electron 应用程序进行预览。

`electron-vite build`

该命令将构建主进程、渲染器和预加载脚本源代码。通常在打包 Electron 应用程序之前,需要执行此命令。

选项

通用选项

选项 描述
-c, --config <file> 定义配置文件路径
-l, --logLevel <level> 设置日志级别 (optional: info, warn, error, silent)
-m, --mode <mode> 设置环境模式
-w, --watch 用于热重载的监视模式 (default: false)
--ignoreConfigWarning 忽略配置缺失警告 (default: false)
--sourcemap 输出 source maps 支持 debug (default: false)
--outDir <dir> 设置输出目录 (default: out)
--entry <file> 指定 Electron 入口文件
-v, --version 显示版本号
-h, --help 显示可用的 CLI 选项

::: tip 提示
--ignoreConfigWarning 选项允许你在配置缺失时忽略警告。例如,不需要使用预加载脚本。
:::

Dev 选项

选项 描述
--inspect [port] 指定端口启用 V8 inspector (default: 5858)
--inspectBrk [port] 和--inspect 一样,但会暂停运行
--remoteDebuggingPort 远程调试端口
--noSandbox 强制渲染器进程在非沙盒环境下运行
--rendererOnly 仅为渲染器启动开发服务

::: tip 提示

  • --inspect 选项允许你在指定的端口上启用 V8 Inspector。外部调试器可以连接到此端口。有关更多详细信息,请参阅 V8 Inspector。

  • --inspectBrk 选项与 --inspect 选项类似,但会在 JavaScript 的第一行暂停执行。

  • --remoteDebuggingPort 选项用于 IDE 调试。

  • --noSandbox 选项将强制 Electron 在非沙盒环境下运行。它通常用于使 Electron 在 Linux 上以 root 身份运行。

  • --rendererOnly 选项仅用于 dev 命令以跳过主进程和预加载脚本构建,并仅为渲染器启动开发服务。此选项将大大提高 dev 命令速度。
    :::

::: warning 警告
使用 --rendererOnly 选项时,electron-vite 命令必须至少运行过一次。此外,你需要在不更改主进程和预加载脚本源代码的情况下使用它。
:::

Preview 选项

选项 描述
--noSandbox 强制渲染器进程在非沙盒环境下运行
--skipBuild 跳过构建

::: tip 提示

  • --noSandbox 选项将强制 Electron 在非沙盒环境下运行。它通常用于使 Electron 在 Linux 上以 root 身份运行。

  • --skipBuild 选项仅用于 preview 命令跳过构建并启动 Electron 应用程序进行预览。
    :::


File: packages/zh-CN/docs/guide/debugging.md

调试

electron-vite 支持调试主进程和渲染进程代码。

::: tip 提示
当你通过 --outDir CLI 参数自定义构建输出目录时,调试器配置也需要添加此参数。但通过配置文件的 build.outDir 进行自定义时则不需要这样做。
:::

VSCode

添加文件 .vscode/launch.json,配置内容为:

json [.vscode/launch.json]
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Main Process",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceRoot}",
      "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
      "windows": {
        "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
      },
      "runtimeArgs": ["--sourcemap"],
      "env": {
        "REMOTE_DEBUGGING_PORT": "9222"
      }
    },
    {
      "name": "Debug Renderer Process",
      "port": 9222,
      "request": "attach",
      "type": "chrome",
      "webRoot": "${workspaceFolder}/src/renderer",
      "timeout": 60000,
      "presentation": {
        "hidden": true
      }
    }
  ],
  "compounds": [
    {
      "name": "Debug All",
      "configurations": ["Debug Main Process", "Debug Renderer Process"],
      "presentation": {
        "order": 1
      }
    }
  ]
}

然后,在(主进程或渲染进程)源代码中设置一些断点。转到“调试”选项卡并确保选择“Debug All”,然后你可以按 F5 开始调试。

::: tip 提示
你也可以选择 Debug Main Process 仅调试主进程。由于渲染进程只能通过附加方式调试,所以无法单独调试渲染进程。
:::

WebStorm

创建 Npm 运行配置。使用以下设置进行主进程调试:

Setting Value
Command run
Scripts dev
Arguments -- --sourcemap --remote-debugging-port=9222

同时,创建一个 Attach to Node.js/Chrome 运行配置。使用以下设置进行渲染进程调试:

Setting Value
Host localhost
Port 9222

然后在调试模式下运行这些配置。

V8 Inspector, e.g. Chrome DevTools

electron-vite 还支持在没有 IDE 的情况下调试。可以使用以下命令之一启动 electron-vite。

sh
# Electron will listen for V8 inspector.
electron-vite --inspect --sourcemap

# Like --inspect but pauses execution on the first line of JavaScript.
electron-vite --inspect-brk --sourcemap

一旦 electron-vite 启动,你可以通过在浏览器上打开 chrome://inspect 并连接到 V8 inspector 来使用 Chrome DevTools 调试。

::: tip 提示
如果你想调试源代码而不是打包后的代码,则应该附加 --sourcemap 选项。
:::


File: packages/zh-CN/docs/guide/dependency-handling.md


outline: deep

依赖处理

在使用 electron-vite 进行打包时,依赖会被智能处理,以保持最终构建的 轻量化。本指南介绍依赖处理的默认行为,以及如何对其进行自定义。

默认行为

默认情况下,electron-vite 会将 electron 模块和所有 Node.js 内置模块视为外部依赖。

`dependencies`

  • 主进程和预加载脚本 —— package.json 中 dependencies 下列出的依赖会被视为外部依赖,不会被打包。

    ::: tip 为什么
    这些依赖在应用打包时(例如使用 electron-builder)仍会被包含进来。
    :::

  • 渲染进程 —— package.json 中 dependencies 下列出的依赖会被打包。

    ::: tip 为什么
    打包依赖可以减少 chunk 数量,有助于保持渲染进程的高性能。
    :::

    ::: warning 注意
    渲染进程中使用的依赖 最好安装为 devDependencies,以帮助减小最终安装包的体积。
    :::

`devDependencies` 和幻影依赖

只有在项目中被实际 import 或 require 的依赖才会被包含进构建中。无论这些依赖是否列在 package.json 的 devDependencies下,还是所谓的“幻影依赖”(即存在于 node_modules 中,但未在 package.json 中显式声明),只要它们在代码中被引用,就会被打包。

::: tip 注意
打包工具(如 electron-builder)默认会排除 devDependencies。
:::

自定义

你可以通过配置 build.externalizeDeps.exclude 和 build.externalizeDeps.include 来自定义依赖处理方式,从而更精确地控制哪些依赖被打包或外部化。

js [electron.vite.config.js] {7}
import { defineConfig } from 'electron-vite'

export default defineConfig({
  main: {
    build: {
      externalizeDeps: {
        exclude: ['foo']
      }
    }
  }
  // ...
})

在此示例中,foo 包会被打包。你也可以将其安装到 devDependencies 中,也能达到相同的效果。

::: warning 注意
在 electron-vite 5 之前,应使用 externalizeDepsPlugin 进行配置。
:::

全量打包

如果你想全量打包所有依赖,可以禁用 build.externalizeDeps 以关闭 electron-vite 的自动依赖处理功能。

js [electron.vite.config.js] {6}
import { defineConfig } from 'electron-vite'

export default defineConfig({
  main: {
    build: {
      externalizeDeps: false
    }
  }
  // ...
})

请注意,全量打包会显著 增加构建时间,你应合理规划模块的分块策略,以 避免影响应用程序的启动性能。

::: tip 注意
在预加载脚本中,需要禁用依赖外部化以支持 Electron 的沙盒环境。更多详情,请参阅 沙盒的限制。
:::

在全量打包的场景下,有些模块无法被完全打包,例如 Node.js addon。这些模块必须保持外部化,可以通过 build.rollupOptions.external 进行配置。

js [electron.vite.config.js] {5}
export default defineConfig({
  main: {
    build: {
      rollupOptions: {
        external: ['sqlite3']
      }
    }
  }
  // ...
})

File: packages/zh-CN/docs/guide/dev.md


outline: deep

开发

项目结构

约定

推荐使用如下项目结构:

text
.
├──src
│  ├──main
│  │  ├──index.ts
│  │  └──...
│  ├──preload
│  │  ├──index.ts
│  │  └──...
│  └──renderer    # with vue, react, etc.
│     ├──src
│     ├──index.html
│     └──...
├──electron.vite.config.ts
├──package.json
└──...

遵循此约定,electron-vite 可以用最少的配置进行工作。

当运行 electron-vite 时,它会自动寻找主进程、渲染器和预加载脚本的入口文件。默认的入口配置:

  • 主进程: <root>/src/main/{index|main}.{js|ts|mjs|cjs}
  • 预加载脚本: <root>/src/preload/{index|preload}.{js|ts|mjs|cjs}
  • 渲染器: <root>/src/renderer/index.html

如果找不到入口点,它将抛出一个错误。你可以通过设置 build.rollupOptions.input 选项来修复它。

请在 下一小节 中查看示例。

自定义

尽管我们强烈推荐上面的项目结构,但这不是必需的。你可以对其进行配置以满足你的使用场景。

假设你有下面这样的项目文件结构:

text
.
├──electron
│  ├──main
│  │  ├──index.ts
│  │  └──...
│  └──preload
│     ├──index.ts
│     └──...
├──src   # with vue, react, etc.
├──index.html
├──electron.vite.config.ts
├──package.json
└──...

你的 electron.vite.config.ts 文件应该是这样:

js [electron.vite.config.ts]
import { defineConfig } from 'electron-vite'
import { resolve } from 'path'

export default defineConfig({
  main: {
    build: {
      rollupOptions: {
        input: {
          index: resolve(__dirname, 'electron/main/index.ts')
        }
      }
    }
  },
  preload: {
    build: {
      rollupOptions: {
        input: {
          index: resolve(__dirname, 'electron/preload/index.ts')
        }
      }
    }
  },
  renderer: {
    root: '.',
    build: {
      rollupOptions: {
        input: {
          index: resolve(__dirname, 'index.html')
        }
      }
    }
  }
})

::: warning 提示
默认情况下,渲染器的工作目录位于 src/renderer 中。在此示例中,渲染器的 root 选项应设置为 “.”。
:::

使用预加载脚本

预加载脚本会在渲染器的网页加载之前注入。 如果你想向渲染器加入需要特殊权限的功能,你可以通过 contextBridge 接口定义 全局对象。

预加载脚本的作用:

  • 增强渲染器:预加载脚本运行在具有 HTML DOM APIs 和 Node.js、Electron APIs 的有限子集访问权限的环境中。
  • 在主进程和渲染进程之间通信:使用 Electron 的 ipcMain 和 ipcRenderer 模块进行进程间通信(IPC)。

了解更多有关 预加载脚本。

例子

  1. 创建一个预加载脚本并通过 contextBridge.exposeInMainWorld 将 方法 或 变量 暴露给渲染器。

  2. 将脚本附在渲染进程上,在 BrowserWindow 构造器中使用 webPreferences.preload 传入脚本的路径。

  3. 在渲染器进程中使用暴露的函数和变量:

::: code-group

js [preload.js]
import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('electron', {
  ping: () => ipcRenderer.invoke('ping')
})
js [main.js] {7,11}
import { app, BrowserWindow } from 'electron'
import path from 'path'

const createWindow = () => {
  const win = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  ipcMain.handle('ping', () => 'pong')

  win.loadFile('index.html')
}

app.whenReady().then(() => {
  createWindow()
})
js [renderer.js] {2}
const func = async () => {
  const response = await window.electron.ping()
  console.log(response) // prints out 'pong'
}

func()

:::

沙盒的限制

从 Electron 20 开始,预加载脚本默认沙盒化,不再拥有完整 Node.js 环境的访问权。实际上,这意味着你只拥有一个 polyfilled 的 require 函数(类似于 Node 的 require 模块),它只能访问一组有限的 API。

可用的 API 详细信息
Electron 模块 仅 渲染进程模块
Node.js 模块 events, timers, url
Polyfilled 的全局模块 Buffer, process, clearImmediate, setImmediate

在沙盒模式下,如果你在预加载脚本中使用 require 或 import 引入其他模块,可能会遇到以下错误:

Unable to load preload scripts -> Error: module not found: 'foo'

要正确处理依赖,你可以采用以下两种方案之一:

  1. 禁用沙盒

  2. 使用 electron-vite 全量打包依赖

    将所有需要的模块都包含在构建输出中,使得预加载脚本能够在运行时访问它们。更多信息,请参阅 依赖处理 和 隔离构建 章节。

::: details 如何在 Electron 中禁用沙盒
在 Electron 中,可以使用 BrowserWindow 构造函数中的 sandbox: false 选项在每个进程的基础上禁用渲染器沙盒。

js
const win = new BrowserWindow({
  webPreferences: {
    sandbox: false
  }
})

:::

了解有关 Electron 进程沙盒 的更多信息。

工具包

为了提高开发效率,推荐使用 @electron-toolkit/preload。
它提供了一种简单的方式,将 Electron 的 API(ipcRenderer、webFrame、process)暴露给渲染进程。

首先,在启用上下文隔离的情况下,使用 contextBridge 将 Electron APIs 暴露给渲染器,否则将其添加到全局 DOM。然后,你就可以在渲染进程中直接使用这些 APIs。

::: code-group

js [preload.js]
import { contextBridge } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'

if (process.contextIsolated) {
  try {
    contextBridge.exposeInMainWorld('electron', electronAPI)
  } catch (error) {
    console.error(error)
  }
} else {
  window.electron = electronAPI
}
js [renderer.js]
// Send a message to the main process with no response
window.electron.ipcRenderer.send('electron:say', 'hello')

// Send a message to the main process with the response asynchronously
window.electron.ipcRenderer.invoke('electron:doAThing', '').then((re) => {
  console.log(re)
})

// Receive messages from the main process
window.electron.ipcRenderer.on('electron:reply', (_, args) => {
  console.log(args)
})

:::

了解更多有关 @electron-toolkit/preload。

::: warning IPC 安全问题
最安全的方法是使用辅助函数来包装 ipcRenderer 调用,而不是直接通过 context bridge 暴露整个 ipcRenderer 模块。
:::

Webview

将预加载脚本附加到 webview 的最简单方法是通过 webContents 的 will-attach-webview 事件处理。

js
mainWindow.webContents.on('will-attach-webview', (e, webPreferences) => {
  webPreferences.preload = join(import.meta.dirname, '../preload/index.js')
})

`nodeIntegration`

electron-vite 不支持 nodeIntegration。我们建议使用预加载脚本,并避免在渲染进程中使用 Node.js 模块,这是大多数流行 Electron 应用所采用的最佳实践。如果你确实需要使用该功能,则必须手动添加 polyfill 来实现支持(例如使用 vite-plugin-commonjs-externals 之类的插件)。

多窗口应用程序

当 Electron 应用程序具有多窗口时,这意味着可能有多个 html 页面和预加载脚本,你可以像下面一样修改你的配置文件:

js [electron.vite.config.js] {7,8,17,18}
export default {
  main: {},
  preload: {
    build: {
      rollupOptions: {
        input: {
          browser: resolve(__dirname, 'src/preload/browser.js'),
          webview: resolve(__dirname, 'src/preload/webview.js')
        }
      }
    }
  },
  renderer: {
    build: {
      rollupOptions: {
        input: {
          browser: resolve(__dirname, 'src/renderer/browser.html'),
          webview: resolve(__dirname, 'src/renderer/webview.html')
        }
      }
    }
  }
}

::: tip 如何加载多页
查阅 使用 HMR 章节,了解更多详细信息。
:::

传递参数给 Electron 应用程序

可以在 electron-vite CLI 后面附加一个 -- 以及要传递的参数。

  • 传递 Electron CLI 命令:
json
"scripts": {
  "dev": "electron-vite dev -- --trace-warnings"
}

::: tip 提示
electron-vite 已经支持 --inspect、--inspect-brk、--remote-debugging-port 和 --no-sandbox 命令,所以你不需要为这些命令做这样的处理。有关更多详细信息,请参阅 命令行界面。
:::

  • 传递程序参数:
json
"scripts": {
  "dev": "electron-vite dev -- p1 p2"
}

双破折号之后的所有参数都将传递给 Electron 应用程序,你可以使用 process.argv 来处理它们。

多进程

Worker Threads

你可以在导入请求上添加 ?modulePath 或 ?nodeWorker 查询参数来直接导入一个 node worker。

  • 带 ?modulePath 后缀默认导出会是一个 worker 的模块路径。这种语法更接近于标准,是创建 worker 的 推荐 方式。
js
import { resolve } from 'node:path'
import { Worker } from 'node:worker_threads'
import workerPath from './worker?modulePath'

new Worker(workerPath, {})
  • 带 ?nodeWorker 后缀默认导出会是一个自定义 worker 的构造函数。
js
import createWorker from './worker?nodeWorker'

createWorker({ workerData: 'worker' })
  .on('message', (message) => {
    console.log(`Message from worker: ${message}`)
  })
  .postMessage('')

Utility Process and Child Process

electron-vite 支持使用 Electron UtilityProcess API 或 Node.js child_process 来 fork 子进程。子进程可以使用 ?modulePath 后缀导入。

::: code-group

js [main.js]
import { utilityProcess, MessageChannelMain } from 'electron'
import forkPath from './fork?modulePath'

const { port1, port2 } = new MessageChannelMain()
const child = utilityProcess.fork(forkPath)
child.postMessage({ message: 'hello' }, [port1])

port2.on('message', (e) => {
  console.log(`Message from child: ${e.data}`)
})
port2.start()
port2.postMessage('hello')
js [fork.js]
process.parentPort.on('message', (e) => {
  const [port] = e.ports
  port.on('message', (e) => {
    console.log(`Message from parent: ${e.data}`)
  })
  port.start()
  port.postMessage('hello')
})

:::

Electron 的 ESM 支持

Electron 从 Electron 28 开始支持 ES 模块。 electron-vite 2.0 同样支持使用 ESM 来开发和构建你的 Electron 应用程序。

Electron ESM 的局限性

我们首先应该了解 Electron ESM 的局限性:

  1. Electron 的主进程和预加载脚本都支持 ESM 并且都使用 Node.js 的 ESM 加载器。
  2. Electron 的预加载脚本必须 非沙盒化 并且文件以 .mjs 扩展名结尾。

了解更多有关 Electron ESM。

启用 ESM

electron-vite 启用 ESM 有两种方法:

  1. 将 "type": "module" 添加到最近的 package.json 中。

    使用这种方式时,主进程和预加载脚本将被打包成 ES 模块文件。请注意,预加载脚本文件以 .mjs 扩展名结尾。你需要修复代码中引用这些脚本的文件名。

  2. 在配置文件中将 build.rollupOptions.output.format 设置为 es。

    js [electron.vite.config.js]
    export default defineConfig({
      main: {
        build: {
          rollupOptions: {
            output: {
              format: 'es'
            }
          }
        }
      },
      preload: {
        build: {
          rollupOptions: {
            output: {
              format: 'es'
            }
          }
        }
      }
      // ...
    })

    使用这种方式时,主进程和预加载脚本将被打包成 ES 模块文件,并以 .mjs 扩展名结尾。你需要修复 package.json 中的 main 字段(Electron 入口点)以及代码中引用这些预加载脚本的文件名。

另外,从 electron-vite 2.0 开始,即使 Electron 版本低于 28,也可以在 package.json 中使用 "type": "module"。这种情况下,主进程和预加载脚本将被打包成 CommonJS 文件并以 .cjs 扩展名结尾。

迁移至 ESM

在此之前,我们一直使用 CommonJS 作为构建格式。我们可能会遇到一些不支持 CommonJS 的 NPM 包,但是我们可以通过 electron-vite 重新打包它们来支持 Electron。同样,当我们迁移到 ESM 时,我们也会遇到一些问题。

  • 在 ES 模块中导入 CommonJS 模块

    js
    import { autoUpdater } from 'electron-updater' // ❌ SyntaxError: Named export 'autoUpdater' not found.

    在上面的例子中,我们将得到一个 SyntaxError。但是,我们仍然可以使用标准导入语法在 ES 模块中导入 CommonJS 模块。如果模块提供 module.exports 对象作为默认导出或使用 Object.defineProperty 定义导出,则具名导入将不起作用,但默认导入将起作用。

    js
    import updater from 'electron-updater' // ✅
  • ES 模块与 CommonJS 的区别

    js
    import { BrowserWindow } from 'electron'
    import { join } from 'path'
    ​
    const mainWindow = new BrowserWindow({
      webPreferences: {
        preload: join(__dirname,'../preload/index.mjs') // ❌ ReferenceError: __dirname is not defined
      }
    })

    在 ESM 中,一些重要的 CommonJS 引用没有定义。其中包括 require、require.resolve、__filename、__dirname 等。查阅 Node.js 的 ES 模块与 CommonJS 的区别,了解更多详细信息。

    js
    import { BrowserWindow } from 'electron'
    import { fileURLToPath } from 'url'
    ​
    const mainWindow = new BrowserWindow({
      webPreferences: {
        preload: fileURLToPath(new URL('../preload/index.mjs', import.meta.url)) // ✅
      }
    })

非常重要的是,electron-vite 兼容一些 CommonJS 语法(包括 require、require.resolve、__filename、__dirname),在迁移过程中不需要修复这些语法问题。不过,我们仍然建议在新项目中使用 ESM 语法。

ESM 和 CJS 语法兼容性

electron-vite 对 ES 模块 和 CommonJS 语法做了一些兼容性处理,允许开发者以最少的迁移工作在两种语法之间自由切换。但需要注意的是,源代码保护 目前仅支持 CommonJS。


File: packages/zh-CN/docs/guide/distribution.md

分发

ASAR 存档局限性

Electron 应用程序的源代码通常会打包到 ASAR 存档中,这是一种为 Electron 应用程序而设计的简易存档格式。

但 ASAR 存档有局限性:

  • 某些 Node API 需要额外解压缩到一个临时文件中,并将临时文件的路径传递给 API 以使其工作。这会为这些 API 增加一些开销。比如 child_process.execFile、fs.open、process.dlopen 等。
  • 有一些 Node API 不支持在 ASAR 存档中执行二进制文件,例如 child_process.exec、child_process.spawn。

如上所述,最好不要将这些资源打包到 ASAR 存档中。

有那些不应该打包的二进制文件:

  • 原生 Node 模块,如 sqlite、fluent-ffmpeg 等。
  • 引用的二进制文件,如 *.node、*.app、*.exe 等。

例如,在 electron-builder 中你可以这样配置:

yaml
asarUnpack:
  - node_modules/sqlite3
  - out/main/chunks/*.node
  - resources/*

了解有关 ASAR 存档 的更多信息。

::: tip 提示
建议将这些资源放在 public 目录中,这样可以更容易配置排除项,不将它们打包到 asar 存档中。
:::

使用 Electron Builder 分发应用程序

electron-builder 是一个用于打包适用于 macOS、Windows 和 Linux 可分发 Electron 应用程序的完整解决方案。

  1. 你可以为 electron-builder 创建一个配置文件 electron-builder.yml,内容如下:
yaml
appId: com.electron.app
productName: vue-ts
directories:
  buildResources: build
files:
  - '!**/.vscode/*'
  - '!src/*'
  - '!electron.vite.config.{js,ts,mjs,cjs}'
  - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
  - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
  - '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
asarUnpack:
  - resources/**
afterSign: build/notarize.js
win:
  executableName: vue-ts
nsis:
  artifactName: ${name}-${version}-setup.${ext}
  shortcutName: ${productName}
  uninstallDisplayName: ${productName}
  createDesktopShortcut: always
mac:
  entitlementsInherit: build/entitlements.mac.plist
  extendInfo:
    - NSCameraUsageDescription: Application requests access to the device's camera.
    - NSMicrophoneUsageDescription: Application requests access to the device's microphone.
    - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
    - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
dmg:
  artifactName: ${name}-${version}.${ext}
linux:
  target:
    - AppImage
    - snap
    - deb
  maintainer: electronjs.org
  category: Utility
appImage:
  artifactName: ${name}-${version}.${ext}
npmRebuild: false
publish:
  provider: generic
  url: https://example.com/auto-updates
  1. 将执行脚本添加到 package.json:
json
"scripts": {
  "build:win": "npm run build && electron-builder --win --config",
  "build:mac": "npm run build && electron-builder --mac --config",
  "build:linux": "npm run build && electron-builder --linux --config"
}

使用 Electron Forge 分发应用程序

Electron Forge 是一个用于打包和发布 Electron 应用程序的工具。

  1. 你可以为 Electron Forge 创建一个配置文件 forge.config.cjs,内容如下:
js
module.exports = {
  packagerConfig: {
    ignore: [
      /^\/src/,
      /(.eslintrc.json)|(.gitignore)|(electron.vite.config.ts)|(forge.config.cjs)|(tsconfig.*)/,
    ],
  },
  rebuildConfig: {},
  makers: [
    {
      name: '@electron-forge/maker-squirrel',
      config: {},
    },
    {
      name: '@electron-forge/maker-zip',
      platforms: ['darwin'],
    },
    {
      name: '@electron-forge/maker-deb',
      config: {},
    },
    {
      name: '@electron-forge/maker-rpm',
      config: {},
    },
  ],
};
  1. 将执行脚本和依赖添加到 package.json:
json
"main": "./dist/main/index.js",
"scripts": {
  "start": "electron-vite preview --outDir=dist",
  "dev": "electron-vite dev --outDir=dist",
  "package": "electron-vite build --outDir=dist && electron-forge package",
  "make ": "electron-vite build --outDir=dist && electron-forge make"
},
"devDependencies": {
  "@electron-forge/cli": "^6.2.1",
  "@electron-forge/maker-deb": "^6.2.1",
  "@electron-forge/maker-rpm": "^6.2.1",
  "@electron-forge/maker-squirrel": "^6.2.1",
  "@electron-forge/maker-zip": "^6.2.1",
}

::: warning 警告
Electron Forge 的默认输出目录为 out,且禁止改写,这与 electron-vite 冲突。所以我们可以将 outDir 设置为 dist。
:::

Github Action CI/CD

你可以使用以下内容在项目的根目录中创建工作流文件 .github/workflows/release.yml。该工作流将帮助你在 Windows、MacOS 和 Linux 中构建和打包 Electron 应用程序。

yaml
name: Build/release Electron app

on:
  push:
    tags:
      - v*.*.*

jobs:
  release:
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]

    steps:
      - name: Check out Git repository
        uses: actions/checkout@v3

      - name: Install Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 16

      - name: Install Dependencies
        run: npm install

      - name: build-linux
        if: matrix.os == 'ubuntu-latest'
        run: npm run build:linux

      - name: build-mac
        if: matrix.os == 'macos-latest'
        run: npm run build:mac

      - name: build-win
        if: matrix.os == 'windows-latest'
        run: npm run build:win

      - name: release
        uses: softprops/action-gh-release@v1
        with:
          draft: true
          files: |
            dist/*.exe
            dist/*.zip
            dist/*.dmg
            dist/*.AppImage
            dist/*.snap
            dist/*.deb
            dist/*.rpm
            dist/*.tar.gz
            dist/*.yml
            dist/*.blockmap
        env:
          GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}

File: packages/zh-CN/docs/guide/env-and-mode.md

环境变量和模式

::: tip 提示
建议先阅读 Vite 的环境变量和模式 文档,然后再阅读以下部分。
:::

全局环境变量

electron-vite 会像 V​​ite 一样从项目根目录加载环境变量,并使用不同的前缀来限制可用范围。

默认情况下,以 MAIN_VITE_ 为前缀的变量暴露给主进程,PRELOAD_VITE_ 用于预加载脚本,RENDERER_VITE_ 则用于渲染器,VITE_ 则所有进程共用。

[.env]
KEY=123                # 无效变量
MAIN_VITE_KEY=123      # 仅主进程可用
PRELOAD_VITE_KEY=123   # 仅预加载脚本可用
RENDERER_VITE_KEY=123  # 仅渲染器可用
VITE_KEY=123           # 所有进程共用

如果你想自定义 env 变量的前缀,可以使用 envPrefix 选项。

js [electron.vite.config.js]
export default defineConfig({
  main: {
    envPrefix: 'M_VITE_'
  }
  // ...
})

如果你是 TypeScript 用户,请确保在 env.d.ts 中添加 import.meta.env 的类型定义,以获取用户自定义环境变量的类型检查和智能感知。

js
/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly MAIN_VITE_SOME_KEY: string
  // more env variables...
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

模式

默认情况下,dev 命令运行在 development (开发) 模式,而 build 和 preview 命令则运行在 production (生产) 模式。

你可以通过传递 --mode 选项标志来覆盖命令使用的默认模式。请参阅 Vite 模式。


File: packages/zh-CN/docs/guide/hmr-and-hot-reloading.md

HMR 和热重载

使用 HMR

为了在渲染进程中使用模块热替换(HMR)功能,需要使用 环境变量 来确定窗口浏览器是加载本地 html 文件还是本地 URL。

js
function createWindow() {
  // 创建窗口
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, '../preload/index.js')
    }
  })

  // 开发环境加载本地 URL,生产环境加载本地 HTML 文件
  if (!app.isPackaged && process.env['ELECTRON_RENDERER_URL']) {
    mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
  } else {
    mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
  }
}

变量 ELECTRON_RENDERER_URL 即为 Vite 运行时的本地 URL。

如果是多页应用,可以这样使用:

js
if (!app.isPackaged && process.env['ELECTRON_RENDERER_URL']) {
  mainWindow.loadURL(`${process.env['ELECTRON_RENDERER_URL']}/view.html`)
} else {
  mainWindow.loadFile(path.join(__dirname, '../renderer/view.html'))
}

::: tip 提示

  • 在开发环境,渲染进程 index.html 文件需要通过 <script type="module"> 引用脚本。

  • 在生产环境,BrowserWindow 加载的 index.html 文件应该是输出目录下的 html 文件,而不是源代码目录下的 html 文件。
    :::

启用热重载

热重载 指的是在主进程或预加载脚本文件发生变化时,自动重新构建并重启 Electron 应用。虽然这不是真正意义上的热更新(真正的热重载可以在不重启的情况下更新代码),但它能提供类似的开发体验。

electron-vite 的实现方式:

  • 启用 Rollup watcher 以监听主进程和预加载脚本文件
  • 当主进程文件被修改时,重新构建并重启 Electron 应用
  • 当预加载脚本文件被修改时,重新构建并触发渲染进程重载

有两种启用方式:

  1. 使用 CLI 选项 -w 或 --watch,例如 electron-vite dev --watch。这是首选方式,它更加灵活。
  2. 使用配置项 build.watch 并设为 {}。此外,还可以配置更多的 watcher 选项,见 WatcherOptions。

::: tip 注意
热重载只能在开发阶段中使用。
:::

::: warning 何时使用热重载
由于重载的时机不可控,热重载无法完美实现。在实践中,热重载并不总是有益的,因此建议使用 CLI 选项来灵活决定是否启用。
:::