electron-incremental-update 3.0.0-beta.5 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,107 +1,100 @@
1
1
  # Electron Incremental Update
2
2
 
3
- This project is built on top of [vite-plugin-electron](https://github.com/electron-vite/vite-plugin-electron), offers a lightweight update solution for Electron applications without using native executables.
3
+ Lightweight incremental update tools for Electron applications built with Vite. The package provides a Vite plugin, an Electron startup wrapper, update providers, bytecode protection, and utilities for common Electron app paths.
4
+
5
+ ## Contents
4
6
 
5
7
  - [Electron Incremental Update](#electron-incremental-update)
6
- - [Key Features](#key-features)
7
- - [Dual Asar Architecture](#dual-asar-architecture)
8
- - [Update Process](#update-process)
9
- - [Additional Features](#additional-features)
10
- - [Getting Started](#getting-started)
11
- - [Install](#install)
12
- - [Project Structure](#project-structure)
13
- - [Setup Entry](#setup-entry)
14
- - [Setup `vite.config.ts`](#setup-viteconfigts)
15
- - [Modify package.json](#modify-packagejson)
16
- - [Config `electron-builder`](#config-electron-builder)
17
- - [Usage](#usage)
18
- - [Use In Main Process](#use-in-main-process)
19
- - [Alternative Provider Setup](#alternative-provider-setup)
20
- - [Custom logger](#custom-logger)
21
- - [Beta Channel Updates](#beta-channel-updates)
22
- - [Use Native Modules](#use-native-modules)
23
- - [Example](#example)
24
- - [Result in app.asar](#result-in-appasar)
25
- - [Bytecode Protection](#bytecode-protection)
26
- - [Benefits](#benefits)
27
- - [Limitation](#limitation)
28
- - [Utils](#utils)
29
- - [Electron Utilities](#electron-utilities)
30
- - [Crypto Utilities](#crypto-utilities)
31
- - [Zip Utilities](#zip-utilities)
8
+ - [Contents](#contents)
9
+ - [Why](#why)
10
+ - [How It Works](#how-it-works)
11
+ - [Installation](#installation)
12
+ - [Quick Start](#quick-start)
13
+ - [Entry Process](#entry-process)
14
+ - [Main Process](#main-process)
15
+ - [Vite Configuration](#vite-configuration)
16
+ - [Plugin Options](#plugin-options)
17
+ - [Electron Builder](#electron-builder)
18
+ - [Runtime Usage](#runtime-usage)
19
+ - [Providers](#providers)
20
+ - [Testing The Local Flow](#testing-the-local-flow)
21
+ - [Update Artifacts](#update-artifacts)
22
+ - [Native Modules](#native-modules)
23
+ - [Result in app.asar](#result-in-appasar)
24
+ - [Bytecode Protection](#bytecode-protection)
25
+ - [Development Bundling](#development-bundling)
26
+ - [Utilities](#utilities)
27
+ - [API Reference](#api-reference)
32
28
  - [Credits](#credits)
33
29
  - [License](#license)
34
30
 
35
- ## Key Features
36
-
37
- This solution provides a comprehensive update system for Electron applications, including:
38
-
39
- - **Vite Plugin** - Seamlessly integrates with your existing Vite build process
40
- - **Startup Entry Function** - Handles application initialization and update checking
41
- - **Updater Class** - Manages the complete update workflow with event-driven API
42
- - **Utility Functions** - Helper functions for file paths, version management, and more
31
+ ## Why
43
32
 
44
- ### Dual Asar Architecture
33
+ Electron applications are commonly shipped as full installers. This package keeps the installer stable and updates only the application asar file, which makes update packages smaller and keeps the runtime workflow explicit.
45
34
 
46
- The update system uses a two-file structure for efficient incremental updates:
35
+ Key features:
47
36
 
48
- - **`app.asar`** - The application entry point that loads and initializes the updater
49
- - **`${electron.app.name}.asar`** - Contains your application code (main process, preload scripts, and renderer) that gets replaced during updates
37
+ - Vite plugin based on `vite-plugin-electron/multi-env`
38
+ - Dual asar runtime layout
39
+ - Signed update metadata and asar verification
40
+ - GitHub and local development providers
41
+ - Optional V8 bytecode generation
42
+ - Utilities for app paths, native modules, renderer loading, and Electron startup
50
43
 
51
- ### Update Process
44
+ ## How It Works
52
45
 
53
- The update workflow follows these steps:
46
+ The packaged app uses two asar files:
54
47
 
55
- 1. **Check for Updates** - Query the remote server for available updates
56
- 2. **Download and Verify** - Download the update asar file and verify it using RSA signatures
57
- 3. **Prepare for Update** - The application quits to prepare for the update
58
- 4. **Apply Update** - On next launch, replace the old `${electron.app.name}.asar` with the new version and load it
48
+ - `app.asar`: the stable entry asar generated from `entry.files`
49
+ - `${app.name}.asar`: the replaceable application asar generated from the Electron main, preload, and renderer build outputs
59
50
 
60
- ### Additional Features
51
+ The update flow is:
61
52
 
62
- - **Smaller Update Packages** - Package all native modules into `app.asar` to minimize the update file size and download time [see usage](#use-native-modules)
63
- - **Source Code Protection** - Leverage V8 bytecode compilation to obfuscate and protect your JavaScript source code [see details](#bytecode-protection)
53
+ 1. `createElectronApp()` starts from `app.asar`.
54
+ 2. If `${app.name}.asar.tmp` exists, it is renamed to `${app.name}.asar`.
55
+ 3. The configured main file is loaded from `${app.name}.asar`.
56
+ 4. Your main process calls `updater.checkForUpdates()`.
57
+ 5. The provider downloads `version.json`.
58
+ 6. The updater compares versions and emits `update-available` when a newer update exists.
59
+ 7. `updater.downloadUpdate()` downloads, decompresses, verifies, and writes `${app.name}.asar.tmp`.
60
+ 8. `updater.quitAndInstall()` restarts the app so the new asar can be installed on next launch.
64
61
 
65
- ## Getting Started
66
-
67
- ### Install
62
+ ## Installation
68
63
 
69
64
  ```sh
70
- npm install -D electron-incremental-update
65
+ npm install -D electron-incremental-update @electron/asar @babel/core
71
66
  ```
72
67
 
73
68
  ```sh
74
- yarn add -D electron-incremental-update
69
+ pnpm add -D electron-incremental-update @electron/asar @babel/core
75
70
  ```
76
71
 
77
72
  ```sh
78
- pnpm add -D electron-incremental-update
73
+ yarn add -D electron-incremental-update @electron/asar @babel/core
79
74
  ```
80
75
 
81
- ### Project Structure
76
+ > Upgrading from `3.0.0-beta.x`? See [MIGRATION.md](./MIGRATION.md).
82
77
 
83
- Base on [electron-vite-vue](https://github.com/electron-vite/electron-vite-vue)
78
+ ## Quick Start
84
79
 
85
- ```
80
+ Recommended project layout:
81
+
82
+ ```txt
86
83
  electron
87
- ├── entry.ts // <- entry file
84
+ ├── entry.ts
88
85
  ├── main
89
86
  │ └── index.ts
90
87
  ├── preload
91
88
  │ └── index.ts
92
- └── native // <- possible native modules
93
- └── index.ts
89
+ └── native
90
+ └── db.ts
94
91
  src
95
92
  └── ...
96
93
  ```
97
94
 
98
- ### Setup Entry
99
-
100
- The entry is used to load the application and initialize the `Updater`
95
+ ### Entry Process
101
96
 
102
- `Updater` use the `provider` to check and download the update. The built-in `GithubProvider` is based on `BaseProvider`, which implements the `IProvider` interface (see [types](#provider)). And the `provider` is optional, you can setup later
103
-
104
- in `electron/entry.ts`
97
+ `electron/entry.ts` is the stable startup file. It creates the updater and loads the real main process from `${app.name}.asar`.
105
98
 
106
99
  ```ts
107
100
  import { createElectronApp } from 'electron-incremental-update'
@@ -109,123 +102,177 @@ import { GitHubProvider } from 'electron-incremental-update/provider'
109
102
 
110
103
  createElectronApp({
111
104
  updater: {
112
- // optional, you can setup later
113
105
  provider: new GitHubProvider({
114
- username: 'yourname',
115
- repo: 'electron',
106
+ user: 'your-github-user',
107
+ repo: 'your-repo',
116
108
  }),
117
109
  },
118
110
  beforeStart(mainFilePath, logger) {
119
- logger?.debug(mainFilePath)
111
+ logger?.debug(`Starting app from ${mainFilePath}`)
120
112
  },
121
113
  })
122
114
  ```
123
115
 
124
- - [some Github CDN resources](https://github.com/XIU2/UserScript/blob/master/GithubEnhanced-High-Speed-Download.user.js#L34)
116
+ ### Main Process
125
117
 
126
- ### Setup `vite.config.ts`
118
+ The main entry must default-export one function wrapped with `startupWithUpdater()`.
127
119
 
128
- The plugin config, `main` and `preload` parts are reference from [electron-vite-vue](https://github.com/electron-vite/electron-vite-vue)
120
+ ```ts
121
+ import { app, BrowserWindow, dialog } from 'electron'
122
+ import { startupWithUpdater } from 'electron-incremental-update'
123
+ import { getAppVersion, getPathFromPreload, loadPage } from 'electron-incremental-update/utils'
129
124
 
130
- - certificate will read from `process.env.UPDATER_CERT` first, if absent, read config
131
- - privatekey will read from `process.env.UPDATER_PK` first, if absent, read config
125
+ export default startupWithUpdater(async (updater) => {
126
+ await app.whenReady()
132
127
 
133
- See all config in [types](#plugin)
128
+ const win = new BrowserWindow({
129
+ webPreferences: {
130
+ preload: getPathFromPreload('index.js'),
131
+ },
132
+ })
134
133
 
135
- in `vite.config.mts`
134
+ loadPage(win)
136
135
 
137
- ```ts
138
- import { debugStartup, electronWithUpdater } from 'electron-incremental-update/vite'
139
- import { defineConfig } from 'vite'
136
+ updater.on('update-available', async ({ version }) => {
137
+ const { response } = await dialog.showMessageBox({
138
+ type: 'info',
139
+ message: `Version ${version} is available. Current version is ${getAppVersion()}.`,
140
+ buttons: ['Download', 'Later'],
141
+ })
140
142
 
141
- export default defineConfig(async ({ command }) => {
142
- const isBuild = command === 'build'
143
- return {
144
- plugins: [
145
- electronWithUpdater({
146
- isBuild,
147
- entry: {
148
- files: ['./electron/entry.ts', './electron/native/index.ts'],
149
- },
150
- main: {
151
- files: ['./electron/main/index.ts', './electron/main/worker.ts'],
152
- // see https://github.com/electron-vite/electron-vite-vue/blob/85ed267c4851bf59f32888d766c0071661d4b94c/vite.config.ts#L22-L28
153
- onstart: debugStartup,
154
- },
155
- preload: {
156
- files: './electron/preload/index.ts',
157
- },
158
- updater: {
159
- // options
160
- },
161
- }),
162
- ],
163
- server:
164
- process.env.VSCODE_DEBUG &&
165
- (() => {
166
- const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL)
167
- return {
168
- host: url.hostname,
169
- port: +url.port,
170
- }
171
- })(),
172
- }
143
+ if (response === 0) {
144
+ await updater.downloadUpdate()
145
+ }
146
+ })
147
+
148
+ updater.on('download-progress', (info) => {
149
+ win.webContents.send('update-progress', info)
150
+ })
151
+
152
+ updater.on('update-downloaded', async () => {
153
+ const { response } = await dialog.showMessageBox({
154
+ type: 'info',
155
+ message: 'Update downloaded.',
156
+ buttons: ['Restart Now', 'Later'],
157
+ })
158
+
159
+ if (response === 0) {
160
+ updater.quitAndInstall()
161
+ }
162
+ })
163
+
164
+ updater.on('update-not-available', (code, message) => {
165
+ console.log(`[${code}] ${message}`)
166
+ })
167
+
168
+ updater.on('error', (error) => {
169
+ console.error(error)
170
+ })
171
+
172
+ await updater.checkForUpdates()
173
173
  })
174
174
  ```
175
175
 
176
- Or use the helper function
176
+ ## Vite Configuration
177
+
178
+ Use `defineElectronConfig()` when the same Vite config owns the renderer and Electron processes.
177
179
 
178
180
  ```ts
179
181
  import { defineElectronConfig } from 'electron-incremental-update/vite'
180
182
 
181
183
  export default defineElectronConfig({
182
184
  entry: {
183
- files: ['./electron/entry.ts', './electron/native/index.ts'],
185
+ files: './electron/entry.ts',
184
186
  },
185
187
  main: {
186
- files: ['./electron/main/index.ts', './electron/main/worker.ts'],
187
- // see https://github.com/electron-vite/electron-vite-vue/blob/85ed267c4851bf59f32888d766c0071661d4b94c/vite.config.ts#L22-L28
188
- onstart: debugStartup,
188
+ files: './electron/main/index.ts',
189
189
  },
190
190
  preload: {
191
191
  files: './electron/preload/index.ts',
192
192
  },
193
193
  updater: {
194
- // options
194
+ minimumVersion: '0.0.0',
195
+ paths: {
196
+ asarOutputPath: 'release/my-app.asar',
197
+ compressedPath: 'release/my-app-1.0.0.asar.br',
198
+ versionPath: 'release/version.json',
199
+ },
195
200
  },
196
201
  renderer: {
197
- server:
198
- process.env.VSCODE_DEBUG &&
199
- (() => {
200
- const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL)
201
- return {
202
- host: url.hostname,
203
- port: +url.port,
202
+ server: process.env.VSCODE_DEBUG
203
+ ? {
204
+ host: '127.0.0.1',
205
+ port: 5173,
204
206
  }
205
- })(),
207
+ : undefined,
206
208
  },
207
209
  })
208
210
  ```
209
211
 
210
- ### Modify package.json
212
+ Use `electronWithUpdater()` directly when you want to manage the renderer config yourself.
213
+
214
+ ```ts
215
+ import { electronWithUpdater } from 'electron-incremental-update/vite'
216
+ import { defineConfig } from 'vite'
217
+
218
+ export default defineConfig({
219
+ plugins: [
220
+ electronWithUpdater({
221
+ entry: {
222
+ files: './electron/entry.ts',
223
+ },
224
+ main: {
225
+ files: './electron/main/index.ts',
226
+ },
227
+ preload: {
228
+ files: './electron/preload/index.ts',
229
+ },
230
+ }),
231
+ ],
232
+ })
233
+ ```
234
+
235
+ ### Plugin Options
236
+
237
+ Common options overview:
238
+
239
+ - `entry.files`: entry process input. Required.
240
+ - `main.files`: main process input. Required.
241
+ - `preload.files`: preload process input. Optional.
242
+ - `sourcemap`: defaults to development or `VSCODE_DEBUG`.
243
+ - `minify`: defaults to production builds.
244
+ - `bytecode`: `true` or bytecode options.
245
+ - `notBundle`: externalizes Node modules in development. Defaults to `true`.
246
+ - `external`: additional Vite/Rolldown externals. Use `true` to externalize `dependencies`.
247
+ - `buildVersionJson`: generates update JSON. Defaults to CI only.
248
+ - `localDevUpdate`: generates and serves a local update package during dev startup. Use `true`
249
+ for defaults, or pass `{ baseDir, packageJsonPath, chunkSize, chunkDelay }`. See [Testing The Local Flow](#testing-the-local-flow) for details.
250
+ - `updater.minimumVersion`: minimum supported entry asar version. Defaults to `0.0.0`.
251
+
252
+ > 📖 See [API.md → Plugin Options](./API.md#plugin-options) for the complete reference including all types, defaults, paths, keys, and generator overrides.
253
+
254
+ ## Electron Builder
255
+
256
+ Set `package.json#main` to the entry output file:
211
257
 
212
258
  ```json
213
259
  {
214
- "main": "dist-entry/entry.js" // <- entry file path
260
+ "main": "dist-entry/entry.js"
215
261
  }
216
262
  ```
217
263
 
218
- ### Config `electron-builder`
264
+ Minimal `electron-builder.config.cjs`:
219
265
 
220
266
  ```js
221
267
  const { name } = require('./package.json')
222
268
 
223
269
  const targetFile = `${name}.asar`
270
+
224
271
  /**
225
272
  * @type {import('electron-builder').Configuration}
226
273
  */
227
274
  module.exports = {
228
- appId: 'YourAppID',
275
+ appId: `org.${name}`,
229
276
  productName: name,
230
277
  files: [
231
278
  // entry files
@@ -243,168 +290,149 @@ module.exports = {
243
290
  }
244
291
  ```
245
292
 
246
- ## Usage
247
-
248
- ### Use In Main Process
293
+ ## Runtime Usage
249
294
 
250
- The updater should be initialized in your main process. The startup function will automatically handle update checks and installation.
251
-
252
- **NOTE: There should only be one function and should be the default export in the main index file**
295
+ ```ts
296
+ import { createElectronApp } from 'electron-incremental-update'
297
+ import { GitHubProvider } from 'electron-incremental-update/provider'
253
298
 
254
- in `electron/main/index.ts`
299
+ createElectronApp({
300
+ updater: {
301
+ provider: new GitHubProvider({
302
+ user: 'your-github-user',
303
+ repo: 'your-repo',
304
+ }),
305
+ },
306
+ beforeStart(mainFilePath, logger) {
307
+ logger?.debug(`Starting app from ${mainFilePath}`)
308
+ },
309
+ })
310
+ ```
255
311
 
256
- ```ts
257
- import { app, dialog } from 'electron'
258
- import { startupWithUpdater } from 'electron-incremental-update'
259
- import {
260
- getPathFromAppNameAsar,
261
- getAppVersion,
262
- getEntryVersion,
263
- } from 'electron-incremental-update/utils'
312
+ The full `Updater` API, `createElectronApp` options, `AppOption`, events, and error codes are documented in the API reference.
264
313
 
265
- export default startupWithUpdater(async (updater) => {
266
- await app.whenReady()
314
+ > 📖 See [API.md → Entry API](./API.md#entry-api) for the complete reference.
267
315
 
268
- // Display current app information
269
- console.table({
270
- [`${app.name}.asar path:`]: getPathFromAppNameAsar(),
271
- 'app version:': getAppVersion(),
272
- 'entry (installer) version:': getEntryVersion(),
273
- 'electron version:': process.versions.electron,
274
- })
316
+ ## Providers
275
317
 
276
- // Listen for download progress
277
- updater.onDownloading = ({ percent }) => {
278
- console.log(`Download progress: ${percent}%`)
279
- }
318
+ The package includes GitHub-based providers (file, atom, API) and a local development provider.
280
319
 
281
- // Listen for available updates
282
- updater.on('update-available', async ({ version }) => {
283
- const { response } = await dialog.showMessageBox({
284
- type: 'info',
285
- buttons: ['Download', 'Later'],
286
- message: `Version ${version} update available!`,
287
- })
288
- if (response === 0) {
289
- // 0 = Download button
290
- await updater.downloadUpdate()
291
- }
292
- })
320
+ - **GitHubProvider** reads `version.json` from a repository branch, downloads asar from Releases.
321
+ - **GitHubAtomProvider** reads the latest release from `releases.atom`.
322
+ - **GitHubApiProvider** uses the GitHub Releases API (supports tokens for private repos).
323
+ - **LocalDevProvider** — reads update artifacts from the local filesystem for dev testing.
293
324
 
294
- // Handle no updates available
295
- updater.on('update-not-available', (code, reason, info) => {
296
- console.log('No update available:', reason)
297
- })
325
+ All GitHub providers support a `urlHandler` for mirrors and custom gateways.
298
326
 
299
- // Handle download progress (alternative to onDownloading)
300
- updater.on('download-progress', (data) => {
301
- console.log('Download progress:', data)
302
- // Send progress to renderer if needed
303
- const [mainWindow] = BrowserWindow.getAllWindows()
304
- if (mainWindow) {
305
- mainWindow.webContents.send('update-progress', data)
306
- }
307
- })
327
+ See the [API reference](./API.md#providers) for the complete provider constructor options and method signatures.
308
328
 
309
- // Handle update completion
310
- updater.on('update-downloaded', () => {
311
- dialog
312
- .showMessageBox({
313
- type: 'info',
314
- message: 'Update downloaded successfully!',
315
- buttons: ['Restart Now', 'Later'],
316
- })
317
- .then(({ response }) => {
318
- if (response === 0) {
319
- updater.quitAndInstall()
320
- }
321
- })
322
- })
329
+ ### Testing The Local Flow
323
330
 
324
- // Handle errors
325
- updater.on('error', (error) => {
326
- console.error('Update error:', error)
327
- dialog.showErrorBox('Update Error', error.message || 'Failed to check for updates')
328
- })
331
+ Prefer `localDevUpdate: true` in the Vite plugin over constructing `LocalDevProvider` manually.
332
+ See [playground](./playground) as a complete local update test:
329
333
 
330
- // Start checking for updates
331
- updater.checkForUpdates()
332
- })
334
+ ```sh
335
+ bun run play
333
336
  ```
334
337
 
335
- #### Alternative Provider Setup
338
+ `bun run play` builds the package first, then starts the Vite dev server and Electron playground.
336
339
 
337
- You can also change the provider dynamically:
340
+ Expected flow:
338
341
 
339
- ```ts
340
- // In main.ts
341
- updater.provider = new GitHubProvider({
342
- owner: 'your-username',
343
- repo: 'your-repo',
344
- // Custom URL handling for mirrors or private repos
345
- urlHandler: (url) => {
346
- url.hostname = 'mirror.ghproxy.com'
347
- url.pathname = `https://github.com${url.pathname}`
348
- return url
349
- },
350
- })
351
- ```
342
+ 1. The plugin builds the Electron main, preload, and entry outputs.
343
+ 2. The plugin creates `DEV.asar` and a local update archive.
344
+ 3. `createElectronApp()` installs any existing `DEV.asar.tmp`.
345
+ 4. The app starts from `DEV.asar`.
346
+ 5. The updater checks the generated `version.json`.
347
+ 6. The playground shows an available local update.
348
+ 7. Clicking download emits simulated progress events.
349
+ 8. Clicking restart installs `DEV.asar.tmp` and restarts Electron.
352
350
 
353
- #### Custom logger
351
+ Explicit `updater.provider` values still take priority. If you set a provider manually, automatic
352
+ local provider setup is skipped.
354
353
 
355
- ```ts
356
- updater.logger = console
357
- ```
354
+ ## Update Artifacts
358
355
 
359
- #### Beta Channel Updates
356
+ The Vite plugin can generate:
360
357
 
361
- ```ts
362
- updater.receiveBeta = true
363
- ```
358
+ - `${app.name}.asar`
359
+ - `${app.name}-${version}.asar.br`
360
+ - `version.json`
364
361
 
365
- ### Use Native Modules
362
+ Default `version.json` shape:
366
363
 
367
- To reduce production size, it is recommended that all the **native modules** should be set as `dependency` in `package.json` and other packages should be set as `devDependencies`. Also, `electron-rebuild` only check dependencies inside `dependency` field.
364
+ ```json
365
+ {
366
+ "version": "1.0.0",
367
+ "minimumVersion": "0.0.0",
368
+ "signature": "...",
369
+ "beta": {
370
+ "version": "1.0.1-beta.1",
371
+ "minimumVersion": "0.0.0",
372
+ "signature": "..."
373
+ }
374
+ }
375
+ ```
368
376
 
369
- If you are using `electron-builder` to build distributions, all the native modules with its **large relevant `node_modules`** will be packaged into `app.asar` by default.
377
+ Stable releases update both the top-level fields and `beta`. Prerelease versions update only `beta`.
370
378
 
371
- Luckily, `vite` can bundle all the dependencies. Just follow the steps:
379
+ Set `buildVersionJson: true` if you need metadata during non-CI builds.
372
380
 
373
- 1. setup `entry.files` option
374
- 2. Manually copy the native binaries in `entry.postBuild` callback
375
- 3. Exclude all the dependencies in `electron-builder`'s config
376
- 4. call the native functions with `requireNative` / `importNative` in your code
381
+ > [!NOTE]
382
+ > The **default** version parser supports `major.minor.patch[-prerelease[.number]]` (e.g. `1.0.0-beta.1`).
383
+ > Build metadata (`+build`) and complex semver prerelease identifiers (e.g. `1.0.0-beta.1.2`)
384
+ > are not supported by default.
385
+ >
386
+ > To use full semver or a custom version scheme, override
387
+ > [`provider.isLowerVersion`](#providers) with your own comparator
388
+ > (e.g. `semver.lt` from the `semver` package).
377
389
 
378
- #### Example
390
+ ## Native Modules
379
391
 
380
- in `vite.config.ts`
392
+ To keep update packages small, put native modules and their native binaries in `app.asar`, then load them from the main process with `requireNative()` or `importNative()`.
381
393
 
382
394
  ```ts
383
- const plugin = electronWithUpdater({
384
- // options...
385
- entry: {
386
- files: ['./electron/native/entry.ts', './electron/native/db.ts', './electron/native/img.ts'],
387
- postBuild: ({ copyToEntryOutputDir, copyModules }) => {
388
- // for better-sqlite3
389
- copyToEntryOutputDir({
390
- from: './node_modules/better-sqlite3/build/Release/better_sqlite3.node',
391
- skipIfExist: false,
392
- })
393
- // for @napi-rs/image
394
- const startStr = '@napi-rs+image-'
395
- const fileName = readdirSync('./node_modules/.pnpm').find((p) => p.startsWith(startStr))!
396
- const archName = fileName.substring(startStr.length).split('@')[0]
397
- copyToEntryOutputDir({
398
- from: `./node_modules/.pnpm/${fileName}/node_modules/@napi-rs/image-${archName}/image.${archName}.node`,
399
- })
400
- // or just copy specific dependency
401
- copyModules({ modules: ['better-sqlite3'] })
402
- },
403
- },
404
- })
395
+ import { readdirSync } from 'node:fs'
396
+
397
+ import { electronWithUpdater } from 'electron-incremental-update/vite'
398
+
399
+ export default {
400
+ plugins: [
401
+ electronWithUpdater({
402
+ external: false,
403
+ entry: {
404
+ files: ['./electron/entry.ts', './electron/native/db.ts'],
405
+ postBuild({ isBuild, copyToEntryOutputDir }) {
406
+ if (!isBuild) {
407
+ return
408
+ }
409
+
410
+ copyToEntryOutputDir({
411
+ from: './node_modules/better-sqlite3/build/Release/better_sqlite3.node',
412
+ skipIfExist: false,
413
+ })
414
+
415
+ const packageName = readdirSync('./node_modules/.pnpm').find((name) =>
416
+ name.startsWith('@napi-rs+image-'),
417
+ )
418
+
419
+ if (packageName) {
420
+ const archName = packageName.substring('@napi-rs+image-'.length).split('@')[0]
421
+ copyToEntryOutputDir({
422
+ from: `./node_modules/.pnpm/${packageName}/node_modules/@napi-rs/image-${archName}/image.${archName}.node`,
423
+ })
424
+ }
425
+ },
426
+ },
427
+ main: {
428
+ files: './electron/main/index.ts',
429
+ },
430
+ }),
431
+ ],
432
+ }
405
433
  ```
406
434
 
407
- in `electron/native/db.ts`
435
+ Use the copied native binding from entry asar:
408
436
 
409
437
  ```ts
410
438
  import Database from 'better-sqlite3'
@@ -414,38 +442,27 @@ const db = new Database(':memory:', {
414
442
  nativeBinding: getPathFromEntryAsar('./better_sqlite3.node'),
415
443
  })
416
444
 
417
- export function test(): void {
418
- db.exec(
419
- 'DROP TABLE IF EXISTS employees; ' +
420
- 'CREATE TABLE IF NOT EXISTS employees (name TEXT, salary INTEGER)',
421
- )
422
-
423
- db.prepare('INSERT INTO employees VALUES (:n, :s)').run({
424
- n: 'James',
425
- s: 5000,
445
+ export function testDatabase(): void {
446
+ db.exec('CREATE TABLE IF NOT EXISTS employees (name TEXT, salary INTEGER)')
447
+ db.prepare('INSERT INTO employees VALUES (:name, :salary)').run({
448
+ name: 'James',
449
+ salary: 5000,
426
450
  })
427
-
428
- const r = db.prepare('SELECT * from employees').all()
429
- console.log(r)
430
- // [ { name: 'James', salary: 50000 } ]
431
-
432
- db.close()
433
451
  }
434
452
  ```
435
453
 
436
- in `electron/main/service.ts`
454
+ Load native helper modules from main:
437
455
 
438
456
  ```ts
439
457
  import { importNative, requireNative } from 'electron-incremental-update/utils'
440
458
 
441
- // commonjs
442
- requireNative<typeof import('../native/db')>('db').test()
459
+ requireNative<typeof import('../native/db')>('db').testDatabase()
443
460
 
444
- // esm
445
- importNative<typeof import('../native/db')>('db').test()
461
+ const nativeDb = await importNative<typeof import('../native/db')>('db')
462
+ nativeDb.testDatabase()
446
463
  ```
447
464
 
448
- in `electron-builder.config.js`
465
+ For `electron-builder`, exclude `node_modules` when you have bundled dependencies manually:
449
466
 
450
467
  ```js
451
468
  module.exports = {
@@ -457,7 +474,7 @@ module.exports = {
457
474
  }
458
475
  ```
459
476
 
460
- #### Result in app.asar
477
+ ### Result in app.asar
461
478
 
462
479
  Before: Redundant 🤮
463
480
 
@@ -525,114 +542,62 @@ After: Clean 😍
525
542
  └── package.json
526
543
  ```
527
544
 
528
- ### Bytecode Protection
545
+ ## Bytecode Protection
529
546
 
530
- Use V8 cache to protect the source code
547
+ Bytecode protection compiles JavaScript into V8 bytecode.
531
548
 
532
549
  ```ts
533
550
  electronWithUpdater({
534
- // ...
535
- bytecode: true, // or options
551
+ bytecode: true,
552
+ entry: {
553
+ files: './electron/entry.ts',
554
+ },
555
+ main: {
556
+ files: './electron/main/index.ts',
557
+ },
536
558
  })
537
559
  ```
538
560
 
539
- #### Benefits
561
+ Notes:
540
562
 
541
- https://electron-vite.org/guide/source-code-protection
563
+ - CommonJS only. Remove `"type": "module"` from `package.json` when enabling bytecode.
564
+ - Main process bytecode is enabled by default.
565
+ - To include preload scripts, use `bytecode: { enablePreload: true }`.
566
+ - If preload bytecode is enabled, create the `BrowserWindow` with `sandbox: false`.
542
567
 
543
- - Improve the string protection (see [original issue](https://github.com/alex8088/electron-vite/issues/552))
544
- - Protect all strings by default
545
- - Minification is allowed
568
+ ## Development Bundling
546
569
 
547
- #### Limitation
570
+ `notBundle` is enabled by default in development. It externalizes Node modules in entry and main builds to improve startup speed.
548
571
 
549
- - Only support commonjs
550
- - Only for main process by default, if you want to use in preload script, please use `electronWithUpdater({ bytecode: { enablePreload: true } })` and set `sandbox: false` when creating window
572
+ ```ts
573
+ electronWithUpdater({
574
+ notBundle: false,
575
+ entry: {
576
+ files: './electron/entry.ts',
577
+ },
578
+ main: {
579
+ files: './electron/main/index.ts',
580
+ },
581
+ })
582
+ ```
551
583
 
552
- ### Utils
584
+ ## Utilities
553
585
 
554
- Utility functions of Electron helper, crypto, and file compression.
586
+ Import from `electron-incremental-update/utils` for path helpers, platform checks, native module loading, crypto, compression, download, and version parsing utilities.
555
587
 
556
- ```ts
557
- import {
558
- // Electron utilities
559
- isDev,
560
- isWin,
561
- isMac,
562
- isLinux,
563
- getPathFromAppNameAsar,
564
- getPathFromEntryAsar,
565
- getPathFromMain,
566
- getPathFromPreload,
567
- getPathFromPublic,
568
- getAppVersion,
569
- getEntryVersion,
570
- requireNative,
571
- importNative,
572
- restartApp,
573
- setAppUserModelId,
574
- disableHWAccForWin7,
575
- singleInstance,
576
- setPortableDataPath,
577
- loadPage,
578
- beautifyDevTools,
579
- handleUnexpectedErrors,
580
-
581
- // Crypto utilities
582
- hashBuffer,
583
- aesEncrypt,
584
- aesDecrypt,
585
- defaultSignature,
586
- defaultVerifySignature,
587
-
588
- // Zip utilities
589
- defaultZipFile,
590
- defaultUnzipFile,
591
- } from 'electron-incremental-update/utils'
592
- ```
588
+ > 📖 See [API.md → Utilities API](./API.md#utilities-api) for the complete function list with signatures and descriptions.
589
+
590
+ ## API Reference
591
+
592
+ For the full API documentation including all types, options tables, provider constructors, and plugin configuration, see the standalone reference:
593
593
 
594
- #### Electron Utilities
595
-
596
- - **isDev** - Compile-time dev check
597
- - **isWin** - Check if running on Windows
598
- - **isMac** - Check if running on macOS
599
- - **isLinux** - Check if running on Linux
600
- - **getPathFromAppNameAsar(...paths)** - Get joined path of `${electron.app.name}.asar`
601
- - **getPathFromEntryAsar(...paths)** - Get joined path from entry asar
602
- - **getPathFromMain(...paths)** - Get joined path from main dir
603
- - **getPathFromPreload(...paths)** - Get joined path from preload dir
604
- - **getPathFromPublic(...paths)** - Get joined path from public dir
605
- - **getAppVersion()** - Get app version (returns entry version in dev)
606
- - **getEntryVersion()** - Get entry version
607
- - **requireNative(moduleName)** - Load native module using require from entry asar
608
- - **importNative(moduleName)** - Load native module using import from entry asar
609
- - **restartApp()** - Restart the Electron app
610
- - **setAppUserModelId(id)** - Fix app model ID (Windows only)
611
- - **disableHWAccForWin7()** - Disable hardware acceleration for Windows 7
612
- - **singleInstance(window)** - Keep single instance and restore window
613
- - **setPortableDataPath(dirName, create)** - Set userData dir to exe dir for portable apps
614
- - **loadPage(win, htmlFilePath)** - Load dev server URL in dev or HTML file otherwise
615
- - **beautifyDevTools(win, options)** - Beautify devtools font and scrollbar
616
- - **handleUnexpectedErrors(callback)** - Handle all unhandled errors
617
-
618
- #### Crypto Utilities
619
-
620
- - **hashBuffer(data, length)** - Hash data using SHA-256
621
- - **aesEncrypt(plainText, key, iv)** - Encrypt text using AES
622
- - **aesDecrypt(encryptedText, key, iv)** - Decrypt text using AES
623
- - **defaultSignature(buffer, privateKey, cert, version)** - Generate RSA signature for asar file
624
- - **defaultVerifySignature(buffer, version, signature, cert)** - Verify RSA signature of asar file
625
-
626
- #### Zip Utilities
627
-
628
- - **defaultZipFile(buffer)** - Compress file using brotli
629
- - **defaultUnzipFile(buffer)** - Decompress file using brotli
594
+ > 📖 **[API.md](./API.md)** — Auto-generated JSDoc extraction + manual reference sections.
630
595
 
631
596
  ## Credits
632
597
 
633
- - [Obsidian](https://obsidian.md/) for upgrade strategy
634
- - [vite-plugin-electron](https://github.com/electron-vite/vite-plugin-electron) for vite plugin
635
- - [electron-builder](https://github.com/electron-userland/electron-builder) for update api
598
+ - [Obsidian](https://obsidian.md/) for the dual asar update strategy
599
+ - [vite-plugin-electron](https://github.com/electron-vite/vite-plugin-electron) for the Vite Electron plugin foundation
600
+ - [electron-builder](https://github.com/electron-userland/electron-builder) for Electron packaging
636
601
  - [electron-vite](https://github.com/alex8088/electron-vite) for bytecode plugin inspiration
637
602
 
638
603
  ## License