electron-incremental-update 3.0.0-beta.4 → 3.0.0-beta.6

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,68 +1,108 @@
1
- ## Electron Incremental Update
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.
4
-
5
- ### Key Features
6
-
7
- The solution includes a Vite plugin, a startup entry function, an `Updater` class, and a set of utilities for Electron.
8
-
9
- It use 2 asar file structure for updates:
10
-
11
- - `app.asar`: The application entry, loads the `${electron.app.name}.asar` and initializes the updater on startup
12
- - `${electron.app.name}.asar`: The package that contains main / preload / renderer process code
13
-
14
- ### Update Steps
15
-
16
- 1. Check update from remote server
17
- 2. If update available, download the update asar, verify by presigned RSA + Signature and write to disk
18
- 3. Quit and restart the app
19
- 4. Replace the old `${electron.app.name}.asar` on startup and load the new one
20
-
21
- ### Other Features
22
-
23
- - Update size reduction: All **native modules** should be packaged into `app.asar` to reduce `${electron.app.name}.asar` file size, [see usage](#use-native-modules)
24
- - Bytecode protection: Use V8 cache to protect source code, [see details](#bytecode-protection)
25
-
26
- ## Getting Started
27
-
28
- ### Install
1
+ # Electron Incremental Update
2
+
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
6
+
7
+ - [Electron Incremental Update](#electron-incremental-update)
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
+ - [Create The App](#create-the-app)
20
+ - [Updater API](#updater-api)
21
+ - [Providers](#providers)
22
+ - [GitHub](#github)
23
+ - [Default](#default)
24
+ - [Atom](#atom)
25
+ - [Api](#api)
26
+ - [URL Handling](#url-handling)
27
+ - [Local](#local)
28
+ - [Recommended Setup](#recommended-setup)
29
+ - [Custom Options](#custom-options)
30
+ - [Manual Provider](#manual-provider)
31
+ - [Testing The Local Flow](#testing-the-local-flow)
32
+ - [Update Artifacts](#update-artifacts)
33
+ - [Native Modules](#native-modules)
34
+ - [Result in app.asar](#result-in-appasar)
35
+ - [Bytecode Protection](#bytecode-protection)
36
+ - [Development Bundling](#development-bundling)
37
+ - [Utilities](#utilities)
38
+ - [Credits](#credits)
39
+ - [License](#license)
40
+
41
+ ## Why
42
+
43
+ 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.
44
+
45
+ Key features:
46
+
47
+ - Vite plugin based on `vite-plugin-electron/multi-env`
48
+ - Dual asar runtime layout
49
+ - Signed update metadata and asar verification
50
+ - GitHub and local development providers
51
+ - Optional V8 bytecode generation
52
+ - Utilities for app paths, native modules, renderer loading, and Electron startup
53
+
54
+ ## How It Works
55
+
56
+ The packaged app uses two asar files:
57
+
58
+ - `app.asar`: the stable entry asar generated from `entry.files`
59
+ - `${app.name}.asar`: the replaceable application asar generated from the Electron main, preload, and renderer build outputs
60
+
61
+ The update flow is:
62
+
63
+ 1. `createElectronApp()` starts from `app.asar`.
64
+ 2. If `${app.name}.asar.tmp` exists, it is renamed to `${app.name}.asar`.
65
+ 3. The configured main file is loaded from `${app.name}.asar`.
66
+ 4. Your main process calls `updater.checkForUpdates()`.
67
+ 5. The provider downloads `version.json`.
68
+ 6. The updater compares versions and emits `update-available` when a newer update exists.
69
+ 7. `updater.downloadUpdate()` downloads, decompresses, verifies, and writes `${app.name}.asar.tmp`.
70
+ 8. `updater.quitAndInstall()` restarts the app so the new asar can be installed on next launch.
71
+
72
+ ## Installation
29
73
 
30
74
  ```sh
31
- npm install -D electron-incremental-update
75
+ npm install -D electron-incremental-update @electron/asar @babel/core
32
76
  ```
33
77
 
34
78
  ```sh
35
- yarn add -D electron-incremental-update
79
+ pnpm add -D electron-incremental-update @electron/asar @babel/core
36
80
  ```
37
81
 
38
82
  ```sh
39
- pnpm add -D electron-incremental-update
83
+ yarn add -D electron-incremental-update @electron/asar @babel/core
40
84
  ```
41
85
 
42
- ### Project Structure
86
+ ## Quick Start
43
87
 
44
- Base on [electron-vite-vue](https://github.com/electron-vite/electron-vite-vue)
88
+ Recommended project layout:
45
89
 
46
- ```
90
+ ```txt
47
91
  electron
48
- ├── entry.ts // <- entry file
92
+ ├── entry.ts
49
93
  ├── main
50
94
  │ └── index.ts
51
95
  ├── preload
52
96
  │ └── index.ts
53
- └── native // <- possible native modules
54
- └── index.ts
97
+ └── native
98
+ └── db.ts
55
99
  src
56
100
  └── ...
57
101
  ```
58
102
 
59
- ### Setup Entry
60
-
61
- The entry is used to load the application and initialize the `Updater`
103
+ ### Entry Process
62
104
 
63
- `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
64
-
65
- in `electron/entry.ts`
105
+ `electron/entry.ts` is the stable startup file. It creates the updater and loads the real main process from `${app.name}.asar`.
66
106
 
67
107
  ```ts
68
108
  import { createElectronApp } from 'electron-incremental-update'
@@ -70,117 +110,193 @@ import { GitHubProvider } from 'electron-incremental-update/provider'
70
110
 
71
111
  createElectronApp({
72
112
  updater: {
73
- // optinal, you can setup later
74
113
  provider: new GitHubProvider({
75
- username: 'yourname',
76
- repo: 'electron',
114
+ user: 'your-github-user',
115
+ repo: 'your-repo',
77
116
  }),
78
117
  },
79
118
  beforeStart(mainFilePath, logger) {
80
- logger?.debug(mainFilePath)
119
+ logger?.debug(`Starting app from ${mainFilePath}`)
81
120
  },
82
121
  })
83
122
  ```
84
123
 
85
- - [some Github CDN resources](https://github.com/XIU2/UserScript/blob/master/GithubEnhanced-High-Speed-Download.user.js#L34)
124
+ ### Main Process
125
+
126
+ The main entry must default-export one function wrapped with `startupWithUpdater()`.
86
127
 
87
- ### Setup `vite.config.ts`
128
+ ```ts
129
+ import { app, BrowserWindow, dialog } from 'electron'
130
+ import { startupWithUpdater } from 'electron-incremental-update'
131
+ import { getAppVersion, getPathFromPreload, loadPage } from 'electron-incremental-update/utils'
88
132
 
89
- The plugin config, `main` and `preload` parts are reference from [electron-vite-vue](https://github.com/electron-vite/electron-vite-vue)
133
+ export default startupWithUpdater(async (updater) => {
134
+ await app.whenReady()
90
135
 
91
- - certificate will read from `process.env.UPDATER_CERT` first, if absend, read config
92
- - privatekey will read from `process.env.UPDATER_PK` first, if absend, read config
136
+ const win = new BrowserWindow({
137
+ webPreferences: {
138
+ preload: getPathFromPreload('index.js'),
139
+ },
140
+ })
93
141
 
94
- See all config in [types](#plugin)
142
+ loadPage(win)
95
143
 
96
- in `vite.config.mts`
144
+ updater.on('update-available', async ({ version }) => {
145
+ const { response } = await dialog.showMessageBox({
146
+ type: 'info',
147
+ message: `Version ${version} is available. Current version is ${getAppVersion()}.`,
148
+ buttons: ['Download', 'Later'],
149
+ })
97
150
 
98
- ```ts
99
- import { debugStartup, electronWithUpdater } from 'electron-incremental-update/vite'
100
- import { defineConfig } from 'vite'
151
+ if (response === 0) {
152
+ await updater.downloadUpdate()
153
+ }
154
+ })
101
155
 
102
- export default defineConfig(async ({ command }) => {
103
- const isBuild = command === 'build'
104
- return {
105
- plugins: [
106
- electronWithUpdater({
107
- isBuild,
108
- main: {
109
- files: ['./electron/main/index.ts', './electron/main/worker.ts'],
110
- // see https://github.com/electron-vite/electron-vite-vue/blob/85ed267c4851bf59f32888d766c0071661d4b94c/vite.config.ts#L22-L28
111
- onstart: debugStartup,
112
- },
113
- preload: {
114
- files: './electron/preload/index.ts',
115
- },
116
- updater: {
117
- // options
118
- },
119
- }),
120
- ],
121
- server:
122
- process.env.VSCODE_DEBUG &&
123
- (() => {
124
- const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL)
125
- return {
126
- host: url.hostname,
127
- port: +url.port,
128
- }
129
- })(),
130
- }
156
+ updater.on('download-progress', (info) => {
157
+ win.webContents.send('update-progress', info)
158
+ })
159
+
160
+ updater.on('update-downloaded', async () => {
161
+ const { response } = await dialog.showMessageBox({
162
+ type: 'info',
163
+ message: 'Update downloaded.',
164
+ buttons: ['Restart Now', 'Later'],
165
+ })
166
+
167
+ if (response === 0) {
168
+ updater.quitAndInstall()
169
+ }
170
+ })
171
+
172
+ updater.on('update-not-available', (code, message) => {
173
+ console.log(`[${code}] ${message}`)
174
+ })
175
+
176
+ updater.on('error', (error) => {
177
+ console.error(error)
178
+ })
179
+
180
+ await updater.checkForUpdates()
131
181
  })
132
182
  ```
133
183
 
134
- Or use the helper function
184
+ ## Vite Configuration
185
+
186
+ Use `defineElectronConfig()` when the same Vite config owns the renderer and Electron processes.
135
187
 
136
188
  ```ts
137
189
  import { defineElectronConfig } from 'electron-incremental-update/vite'
138
190
 
139
191
  export default defineElectronConfig({
192
+ entry: {
193
+ files: './electron/entry.ts',
194
+ },
140
195
  main: {
141
- files: ['./electron/main/index.ts', './electron/main/worker.ts'],
142
- // see https://github.com/electron-vite/electron-vite-vue/blob/85ed267c4851bf59f32888d766c0071661d4b94c/vite.config.ts#L22-L28
143
- onstart: debugStartup,
196
+ files: './electron/main/index.ts',
144
197
  },
145
198
  preload: {
146
199
  files: './electron/preload/index.ts',
147
200
  },
148
201
  updater: {
149
- // options
202
+ minimumVersion: '0.0.0',
203
+ paths: {
204
+ asarOutputPath: 'release/my-app.asar',
205
+ gzipPath: 'release/my-app-1.0.0.asar.gz',
206
+ versionPath: 'release/version.json',
207
+ },
150
208
  },
151
209
  renderer: {
152
- server:
153
- process.env.VSCODE_DEBUG &&
154
- (() => {
155
- const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL)
156
- return {
157
- host: url.hostname,
158
- port: +url.port,
210
+ server: process.env.VSCODE_DEBUG
211
+ ? {
212
+ host: '127.0.0.1',
213
+ port: 5173,
159
214
  }
160
- })(),
215
+ : undefined,
161
216
  },
162
217
  })
163
218
  ```
164
219
 
165
- ### Modify package.json
220
+ Use `electronWithUpdater()` directly when you want to manage the renderer config yourself.
221
+
222
+ ```ts
223
+ import { electronWithUpdater } from 'electron-incremental-update/vite'
224
+ import { defineConfig } from 'vite'
225
+
226
+ export default defineConfig({
227
+ plugins: [
228
+ electronWithUpdater({
229
+ entry: {
230
+ files: './electron/entry.ts',
231
+ },
232
+ main: {
233
+ files: './electron/main/index.ts',
234
+ },
235
+ preload: {
236
+ files: './electron/preload/index.ts',
237
+ },
238
+ }),
239
+ ],
240
+ })
241
+ ```
242
+
243
+ ### Plugin Options
244
+
245
+ Common options:
246
+
247
+ - `entry.files`: entry process input. Required.
248
+ - `main.files`: main process input. Required.
249
+ - `preload.files`: preload process input. Optional.
250
+ - `sourcemap`: defaults to development or `VSCODE_DEBUG`.
251
+ - `minify`: defaults to production builds.
252
+ - `bytecode`: `true` or bytecode options.
253
+ - `notBundle`: externalizes Node modules in development. Defaults to `true`.
254
+ - `external`: additional Vite/Rolldown externals. Use `true` to externalize `dependencies`.
255
+ - `buildVersionJson`: generates update JSON. Defaults to CI only.
256
+ - `localDevUpdate`: generates and serves a local update package during dev startup. Use `true`
257
+ for defaults, or pass `{ baseDir, packageJsonPath, chunkSize, chunkDelay }`. See [Local Development](#local-development) for details.
258
+ - `updater.minimumVersion`: minimum supported entry asar version. Defaults to `0.0.0`.
259
+
260
+ Default output paths:
261
+
262
+ - `updater.paths.asarOutputPath`: `release/${app.name}.asar`
263
+ - `updater.paths.gzipPath`: `release/${app.name}-${version}.asar.gz`
264
+ - `updater.paths.versionPath`: `release/version.json`
265
+ - `updater.paths.entryOutDir`: `dist-entry`
266
+ - `updater.paths.electronDistPath`: `dist-electron`
267
+ - `updater.paths.rendererDistPath`: `dist`
268
+
269
+ Signing keys:
270
+
271
+ - `updater.keys.privateKeyPath`: defaults to `keys/private.pem`
272
+ - `updater.keys.certPath`: defaults to `keys/cert.pem`
273
+ - `UPDATER_PK`: overrides `privateKeyPath`
274
+ - `UPDATER_CERT`: overrides `certPath`
275
+
276
+ The plugin creates a simple self-signed certificate when the key files are missing.
277
+
278
+ ## Electron Builder
279
+
280
+ Set `package.json#main` to the entry output file:
166
281
 
167
282
  ```json
168
283
  {
169
- "main": "dist-entry/entry.js" // <- entry file path
284
+ "main": "dist-entry/entry.js"
170
285
  }
171
286
  ```
172
287
 
173
- ### Config `electron-builder`
288
+ Minimal `electron-builder.config.cjs`:
174
289
 
175
290
  ```js
176
291
  const { name } = require('./package.json')
177
292
 
178
293
  const targetFile = `${name}.asar`
294
+
179
295
  /**
180
296
  * @type {import('electron-builder').Configuration}
181
297
  */
182
298
  module.exports = {
183
- appId: 'YourAppID',
299
+ appId: `org.${name}`,
184
300
  productName: name,
185
301
  files: [
186
302
  // entry files
@@ -194,144 +310,331 @@ module.exports = {
194
310
  extraResources: [
195
311
  { from: `release/${targetFile}`, to: targetFile }, // <- asar file
196
312
  ],
197
- // disable publish
198
- publish: null,
313
+ publish: null, // <- disable publish
199
314
  }
200
315
  ```
201
316
 
202
- ## Usage
317
+ ## Runtime Usage
203
318
 
204
- ### Use In Main Process
319
+ ### Create The App
205
320
 
206
- In most cases, you should also setup the `UpdateProvider` before updating, unless you setup params when calling `checkUpdate` or `downloadUpdate`.
321
+ ```ts
322
+ import { createElectronApp } from 'electron-incremental-update'
207
323
 
208
- The update steps are similar to [electron-updater](https://github.com/electron-userland/electron-updater) and have same methods and events on `Updater`
324
+ createElectronApp({
325
+ updater: {
326
+ provider,
327
+ receiveBeta: false,
328
+ logger: console,
329
+ },
330
+ onInstall(install, tempAsarPath, appNameAsarPath, logger) {
331
+ logger?.info(`Installing ${tempAsarPath} to ${appNameAsarPath}`)
332
+ install()
333
+ },
334
+ onStartError(error, logger) {
335
+ logger?.error('Failed to start app', error)
336
+ },
337
+ })
338
+ ```
339
+
340
+ `updater` can also be an async factory:
209
341
 
210
- **NOTE: There should only one function and should be default export in the main index file**
342
+ ```ts
343
+ createElectronApp({
344
+ updater: async () => {
345
+ const { Updater } = await import('electron-incremental-update')
346
+ return new Updater({ provider })
347
+ },
348
+ })
349
+ ```
211
350
 
212
- in `electron/main/index.ts`
351
+ ### Updater API
213
352
 
214
353
  ```ts
215
- import { app } from 'electron'
216
- import { startupWithUpdater, UpdaterError } from 'electron-incremental-update'
217
- import { getPathFromAppNameAsar, getVersions } from 'electron-incremental-update/utils'
354
+ updater.provider = provider
355
+ updater.receiveBeta = true
356
+ updater.logger = console
218
357
 
219
- export default startupWithUpdater((updater) => {
220
- await app.whenReady()
358
+ await updater.checkForUpdates()
359
+ await updater.downloadUpdate()
360
+ updater.cancel()
361
+ updater.quitAndInstall()
362
+ ```
221
363
 
222
- console.table({
223
- [`${app.name}.asar path:`]: getPathFromAppNameAsar(),
224
- 'app version:': getAppVersion(),
225
- 'entry (installer) version:': getEntryVersion(),
226
- 'electron version:': process.versions.electron,
227
- })
364
+ Events:
228
365
 
229
- updater.onDownloading = ({ percent }) => {
230
- console.log(percent)
231
- }
366
+ - `update-available`: emitted with update info and current app/entry versions.
367
+ - `update-not-available`: emitted with a code, message, and optional update info.
368
+ - `download-progress`: emitted while downloading.
369
+ - `update-downloaded`: emitted after the temporary asar is ready.
370
+ - `update-cancelled`: emitted after cancellation.
371
+ - `error`: emitted with `UpdaterError`.
232
372
 
233
- updater.on('update-available', async ({ version }) => {
234
- const { response } = await dialog.showMessageBox({
235
- type: 'info',
236
- buttons: ['Download', 'Later'],
237
- message: `v${version} update available!`,
238
- })
239
- if (response !== 0) {
240
- return
241
- }
242
- await updater.downloadUpdate()
243
- })
373
+ ## Providers
244
374
 
245
- updater.on('update-not-available', (code, reason, info) => console.log(code, reason, info))
375
+ ### GitHub
246
376
 
247
- updater.on('download-progress', (data) => {
248
- console.log(data)
249
- main.send(BrowserWindow.getAllWindows()[0], 'msg', data)
250
- })
377
+ GitHub providers read update metadata from a repository or release and download the generated
378
+ `{name}-{version}.asar.gz` artifact from GitHub Releases.
251
379
 
252
- updater.on('update-downloaded', () => {
253
- updater.quitAndInstall()
254
- })
380
+ #### Default
381
+
382
+ Reads `version.json` from the repository branch and downloads the asar from GitHub Releases.
255
383
 
256
- updater.checkForUpdates()
384
+ ```ts
385
+ import { GitHubProvider } from 'electron-incremental-update/provider'
386
+
387
+ const provider = new GitHubProvider({
388
+ user: 'your-github-user',
389
+ repo: 'your-repo',
390
+ branch: 'HEAD',
257
391
  })
258
392
  ```
259
393
 
260
- #### Dynamicly setup `UpdateProvider`
394
+ Default URLs:
395
+
396
+ - update JSON: `https://github.com/{user}/{repo}/raw/{branch}/{versionPath}`
397
+ - asar: `https://github.com/{user}/{repo}/releases/download/v{version}/{name}-{version}.asar.gz`
398
+
399
+ #### Atom
400
+
401
+ Reads the latest release tag from `releases.atom`, then downloads the update JSON and asar from that release.
261
402
 
262
403
  ```ts
263
- updater.provider = new GitHubProvider({
264
- user: 'yourname',
265
- repo: 'electron',
266
- // setup url handler
267
- urlHandler: (url) => {
268
- url.hostname = 'mirror.ghproxy.com'
404
+ import { GitHubAtomProvider } from 'electron-incremental-update/provider'
405
+
406
+ const provider = new GitHubAtomProvider({
407
+ user: 'your-github-user',
408
+ repo: 'your-repo',
409
+ })
410
+ ```
411
+
412
+ #### Api
413
+
414
+ Uses the GitHub Releases API and can use a token for private repositories or higher rate limits.
415
+
416
+ ```ts
417
+ import { GitHubApiProvider } from 'electron-incremental-update/provider'
418
+
419
+ const provider = new GitHubApiProvider({
420
+ user: 'your-github-user',
421
+ repo: 'your-repo',
422
+ token: process.env.GITHUB_TOKEN,
423
+ })
424
+ ```
425
+
426
+ #### URL Handling
427
+
428
+ GitHub providers support `urlHandler` for mirrors, custom gateways, or request rewriting.
429
+
430
+ ```ts
431
+ const provider = new GitHubProvider({
432
+ user: 'your-github-user',
433
+ repo: 'your-repo',
434
+ urlHandler(url) {
435
+ url.hostname = 'mirror.example.com'
269
436
  url.pathname = `https://github.com${url.pathname}`
270
437
  return url
271
438
  },
272
439
  })
273
440
  ```
274
441
 
275
- #### Custom logger
442
+ ### Local
443
+
444
+ Local updates are for development and manual update-flow testing. They let you test
445
+ `checkForUpdates()`, download progress, `update-downloaded`, and restart/install behavior without
446
+ publishing a GitHub release.
447
+
448
+ Most projects should use the Vite `localDevUpdate` option instead of manually creating a
449
+ `LocalDevProvider`.
450
+
451
+ #### Recommended Setup
452
+
453
+ Enable local update generation in `vite.config.ts`:
276
454
 
277
455
  ```ts
278
- updater.logger = console
456
+ import { electronWithUpdater } from 'electron-incremental-update/vite'
457
+ import { defineConfig } from 'vite'
458
+
459
+ export default defineConfig({
460
+ plugins: [
461
+ electronWithUpdater({
462
+ entry: {
463
+ files: './electron/entry.ts',
464
+ },
465
+ main: {
466
+ files: './electron/main/index.ts',
467
+ },
468
+ preload: {
469
+ files: './electron/preload/index.ts',
470
+ },
471
+ localDevUpdate: true,
472
+ }),
473
+ ],
474
+ })
279
475
  ```
280
476
 
281
- #### Setup Beta Channel
477
+ When `localDevUpdate` is enabled in development:
478
+
479
+ - the Vite plugin creates a local update package before Electron starts
480
+ - `createElectronApp()` auto-configures `LocalDevProvider` when no explicit `updater.provider` is set
481
+ - dev-only `forceUpdate` is enabled so update checks are not skipped in development
482
+ - production builds keep the normal provider and signature behavior
483
+
484
+ The default generated files are:
485
+
486
+ - `release/local-update/release/version.json`
487
+ - `release/local-update/{name}-{version}.asar.gz`
488
+ - `DEV.asar`
489
+ - `DEV.asar.tmp` after `downloadUpdate()`
490
+
491
+ The generated update version is the next patch version from the installed local dev asar. For
492
+ example, if `DEV.asar` contains version `1.2.3`, the generated update is `1.2.4`.
493
+
494
+ #### Custom Options
282
495
 
283
496
  ```ts
284
- updater.receiveBeta = true
497
+ export default defineConfig({
498
+ plugins: [
499
+ electronWithUpdater({
500
+ // ...
501
+ localDevUpdate: {
502
+ baseDir: 'release/local-update',
503
+ packageJsonPath: 'playground/package.json',
504
+ chunkSize: 32 * 1024,
505
+ chunkDelay: 50,
506
+ },
507
+ }),
508
+ ],
509
+ })
510
+ ```
511
+
512
+ Options:
513
+
514
+ - `baseDir`: directory for generated local update resources. Defaults to `release/local-update`.
515
+ - `packageJsonPath`: package metadata used for update name/version. Defaults to the project
516
+ `package.json`.
517
+ - `chunkSize`: bytes per simulated download progress chunk. Defaults to `64 * 1024`.
518
+ - `chunkDelay`: delay between progress chunks in milliseconds. Defaults to `30`.
519
+
520
+ `chunkSize` must be greater than `0`, and `chunkDelay` must be greater than or equal to `0`.
521
+
522
+ #### Manual Provider
523
+
524
+ Use `LocalDevProvider` directly only when you need to wire local artifacts yourself.
525
+
526
+ ```ts
527
+ import { LocalDevProvider } from 'electron-incremental-update/provider'
528
+
529
+ const provider = new LocalDevProvider({
530
+ baseDir: 'release/local-update',
531
+ chunkSize: 32 * 1024,
532
+ chunkDelay: 50,
533
+ })
285
534
  ```
286
535
 
287
- ### Use Native Modules
536
+ It reads:
537
+
538
+ - `{baseDir}/{versionPath}`
539
+ - `{baseDir}/{name}-{version}.asar.gz`
540
+
541
+ #### Testing The Local Flow
542
+
543
+ Use the [playground](./playground) as a complete local update test:
544
+
545
+ ```sh
546
+ bun run play
547
+ ```
288
548
 
289
- 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.
549
+ `bun run play` builds the package first, then starts the Vite dev server and Electron playground.
290
550
 
291
- If you are using `electron-builder` to build distributions, all the native modules with its **large relavent `node_modiles`** will be packaged into `app.asar` by default.
551
+ Expected flow:
552
+
553
+ 1. The plugin builds the Electron main, preload, and entry outputs.
554
+ 2. The plugin creates `DEV.asar` and a local update archive.
555
+ 3. `createElectronApp()` installs any existing `DEV.asar.tmp`.
556
+ 4. The app starts from `DEV.asar`.
557
+ 5. The updater checks the generated `version.json`.
558
+ 6. The playground shows an available local update.
559
+ 7. Clicking download emits simulated progress events.
560
+ 8. Clicking restart installs `DEV.asar.tmp` and restarts Electron.
561
+
562
+ Explicit `updater.provider` values still take priority. If you set a provider manually, automatic
563
+ local provider setup is skipped.
564
+
565
+ ## Update Artifacts
566
+
567
+ The Vite plugin can generate:
568
+
569
+ - `${app.name}.asar`
570
+ - `${app.name}-${version}.asar.gz`
571
+ - `version.json`
572
+
573
+ Default `version.json` shape:
574
+
575
+ ```json
576
+ {
577
+ "version": "1.0.0",
578
+ "minimumVersion": "0.0.0",
579
+ "signature": "...",
580
+ "beta": {
581
+ "version": "1.0.1-beta.1",
582
+ "minimumVersion": "0.0.0",
583
+ "signature": "..."
584
+ }
585
+ }
586
+ ```
292
587
 
293
- Luckily, `vite` can bundle all the dependencies. Just follow the steps:
588
+ Stable releases update both the top-level fields and `beta`. Prerelease versions update only `beta`.
294
589
 
295
- 1. setup `nativeModuleEntryMap` option
296
- 2. Manually copy the native binaries in `postBuild` callback
297
- 3. Exclude all the dependencies in `electron-builder`'s config
298
- 4. call the native functions with `requireNative` / `importNative` in your code
590
+ Set `buildVersionJson: true` if you need metadata during non-CI builds.
299
591
 
300
- #### Example
592
+ ## Native Modules
301
593
 
302
- in `vite.config.ts`
594
+ 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()`.
303
595
 
304
596
  ```ts
305
- const plugin = electronWithUpdater({
306
- // options...
307
- updater: {
308
- entry: {
309
- nativeModuleEntryMap: {
310
- db: './electron/native/db.ts',
311
- img: './electron/native/img.ts',
597
+ import { readdirSync } from 'node:fs'
598
+
599
+ import { electronWithUpdater } from 'electron-incremental-update/vite'
600
+
601
+ export default {
602
+ plugins: [
603
+ electronWithUpdater({
604
+ external: false,
605
+ entry: {
606
+ files: ['./electron/entry.ts', './electron/native/db.ts'],
607
+ postBuild({ isBuild, copyToEntryOutputDir }) {
608
+ if (!isBuild) {
609
+ return
610
+ }
611
+
612
+ copyToEntryOutputDir({
613
+ from: './node_modules/better-sqlite3/build/Release/better_sqlite3.node',
614
+ skipIfExist: false,
615
+ })
616
+
617
+ const packageName = readdirSync('./node_modules/.pnpm').find((name) =>
618
+ name.startsWith('@napi-rs+image-'),
619
+ )
620
+
621
+ if (packageName) {
622
+ const archName = packageName.substring('@napi-rs+image-'.length).split('@')[0]
623
+ copyToEntryOutputDir({
624
+ from: `./node_modules/.pnpm/${packageName}/node_modules/@napi-rs/image-${archName}/image.${archName}.node`,
625
+ })
626
+ }
627
+ },
312
628
  },
313
- postBuild: ({ copyToEntryOutputDir, copyModules }) => {
314
- // for better-sqlite3
315
- copyToEntryOutputDir({
316
- from: './node_modules/better-sqlite3/build/Release/better_sqlite3.node',
317
- skipIfExist: false,
318
- })
319
- // for @napi-rs/image
320
- const startStr = '@napi-rs+image-'
321
- const fileName = readdirSync('./node_modules/.pnpm').find((p) => p.startsWith(startStr))!
322
- const archName = fileName.substring(startStr.length).split('@')[0]
323
- copyToEntryOutputDir({
324
- from: `./node_modules/.pnpm/${fileName}/node_modules/@napi-rs/image-${archName}/image.${archName}.node`,
325
- })
326
- // or just copy specific dependency
327
- copyModules({ modules: ['better-sqlite3'] })
629
+ main: {
630
+ files: './electron/main/index.ts',
328
631
  },
329
- },
330
- },
331
- })
632
+ }),
633
+ ],
634
+ }
332
635
  ```
333
636
 
334
- in `electron/native/db.ts`
637
+ Use the copied native binding from entry asar:
335
638
 
336
639
  ```ts
337
640
  import Database from 'better-sqlite3'
@@ -341,38 +644,27 @@ const db = new Database(':memory:', {
341
644
  nativeBinding: getPathFromEntryAsar('./better_sqlite3.node'),
342
645
  })
343
646
 
344
- export function test(): void {
345
- db.exec(
346
- 'DROP TABLE IF EXISTS employees; ' +
347
- 'CREATE TABLE IF NOT EXISTS employees (name TEXT, salary INTEGER)',
348
- )
349
-
350
- db.prepare('INSERT INTO employees VALUES (:n, :s)').run({
351
- n: 'James',
352
- s: 5000,
647
+ export function testDatabase(): void {
648
+ db.exec('CREATE TABLE IF NOT EXISTS employees (name TEXT, salary INTEGER)')
649
+ db.prepare('INSERT INTO employees VALUES (:name, :salary)').run({
650
+ name: 'James',
651
+ salary: 5000,
353
652
  })
354
-
355
- const r = db.prepare('SELECT * from employees').all()
356
- console.log(r)
357
- // [ { name: 'James', salary: 50000 } ]
358
-
359
- db.close()
360
653
  }
361
654
  ```
362
655
 
363
- in `electron/main/service.ts`
656
+ Load native helper modules from main:
364
657
 
365
658
  ```ts
366
659
  import { importNative, requireNative } from 'electron-incremental-update/utils'
367
660
 
368
- // commonjs
369
- requireNative<typeof import('../native/db')>('db').test()
661
+ requireNative<typeof import('../native/db')>('db').testDatabase()
370
662
 
371
- // esm
372
- importNative<typeof import('../native/db')>('db').test()
663
+ const nativeDb = await importNative<typeof import('../native/db')>('db')
664
+ nativeDb.testDatabase()
373
665
  ```
374
666
 
375
- in `electron-builder.config.js`
667
+ For `electron-builder`, exclude `node_modules` when you have bundled dependencies manually:
376
668
 
377
669
  ```js
378
670
  module.exports = {
@@ -452,702 +744,103 @@ After: Clean 😍
452
744
  └── package.json
453
745
  ```
454
746
 
455
- ### Bytecode Protection
747
+ ## Bytecode Protection
456
748
 
457
- Use V8 cache to protect the source code
749
+ Bytecode protection compiles JavaScript into V8 bytecode.
458
750
 
459
751
  ```ts
460
752
  electronWithUpdater({
461
- // ...
462
- bytecode: true, // or options
753
+ bytecode: true,
754
+ entry: {
755
+ files: './electron/entry.ts',
756
+ },
757
+ main: {
758
+ files: './electron/main/index.ts',
759
+ },
463
760
  })
464
761
  ```
465
762
 
466
- #### Benifits
467
-
468
- https://electron-vite.org/guide/source-code-protection
469
-
470
- - Improve the string protection (see [original issue](https://github.com/alex8088/electron-vite/issues/552))
471
- - Protect all strings by default
472
- - Minification is allowed
763
+ Notes:
473
764
 
474
- #### Limitation
765
+ - CommonJS only. Remove `"type": "module"` from `package.json` when enabling bytecode.
766
+ - Main process bytecode is enabled by default.
767
+ - To include preload scripts, use `bytecode: { enablePreload: true }`.
768
+ - If preload bytecode is enabled, create the `BrowserWindow` with `sandbox: false`.
475
769
 
476
- - Only support commonjs
477
- - 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
770
+ ## Development Bundling
478
771
 
479
- ### Utils
772
+ `notBundle` is enabled by default in development. It externalizes Node modules in entry and main builds to improve startup speed.
480
773
 
481
774
  ```ts
482
- /**
483
- * Compile time dev check
484
- */
485
- const isDev: boolean
486
- const isWin: boolean
487
- const isMac: boolean
488
- const isLinux: boolean
489
- /**
490
- * Get joined path of `${electron.app.name}.asar` (not `app.asar`)
491
- *
492
- * If is in dev, **always** return `'DEV.asar'`
493
- */
494
- function getPathFromAppNameAsar(...paths: string[]): string
495
- /**
496
- * Get app version, if is in dev, return `getEntryVersion()`
497
- */
498
- function getAppVersion(): string
499
- /**
500
- * Get entry version
501
- */
502
- function getEntryVersion(): string
503
- /**
504
- * Use `require` to load native module from entry asar
505
- * @param moduleName file name in entry
506
- * @example
507
- * requireNative<typeof import('../native/db')>('db')
508
- */
509
- function requireNative<T = any>(moduleName: string): T
510
- /**
511
- * Use `import` to load native module from entry asar
512
- * @param moduleName file name in entry
513
- * @example
514
- * await importNative<typeof import('../native/db')>('db')
515
- */
516
- function importNative<T = any>(moduleName: string): Promise<T>
517
- /**
518
- * Restarts the Electron app.
519
- */
520
- function restartApp(): void
521
- /**
522
- * Fix app use model id, only for Windows
523
- * @param id app id, default is `org.${electron.app.name}`
524
- */
525
- function setAppUserModelId(id?: string): void
526
- /**
527
- * Disable hardware acceleration for Windows 7
528
- *
529
- * Only support CommonJS
530
- */
531
- function disableHWAccForWin7(): void
532
- /**
533
- * Keep single electron instance and auto restore window on `second-instance` event
534
- * @param window brwoser window to show
535
- */
536
- function singleInstance(window?: BrowserWindow): void
537
- /**
538
- * Set `userData` dir to the dir of .exe file
539
- *
540
- * Useful for portable Windows app
541
- * @param dirName dir name, default to `data`
542
- * @param create whether to create dir, default to `true`
543
- */
544
- function setPortableDataPath(dirName?: string, create?: boolean): void
545
- /**
546
- * Load `process.env.VITE_DEV_SERVER_URL` when dev, else load html file
547
- * @param win window
548
- * @param htmlFilePath html file path, default is `index.html`
549
- */
550
- function loadPage(win: BrowserWindow, htmlFilePath?: string): void
551
- interface BeautifyDevToolsOptions {
552
- /**
553
- * Sans-serif font family
554
- */
555
- sans: string
556
- /**
557
- * Monospace font family
558
- */
559
- mono: string
560
- /**
561
- * Whether to round scrollbar
562
- */
563
- scrollbar?: boolean
564
- }
565
- /**
566
- * Beautify devtools' font and scrollbar
567
- * @param win target window
568
- * @param options sans font family, mono font family and scrollbar
569
- */
570
- function beautifyDevTools(win: BrowserWindow, options: BeautifyDevToolsOptions): void
571
- /**
572
- * Get joined path from main dir
573
- * @param paths rest paths
574
- */
575
- function getPathFromMain(...paths: string[]): string
576
- /**
577
- * Get joined path from preload dir
578
- * @param paths rest paths
579
- */
580
- function getPathFromPreload(...paths: string[]): string
581
- /**
582
- * Get joined path from publich dir
583
- * @param paths rest paths
584
- */
585
- function getPathFromPublic(...paths: string[]): string
586
- /**
587
- * Get joined path from entry asar
588
- * @param paths rest paths
589
- */
590
- function getPathFromEntryAsar(...paths: string[]): string
591
- /**
592
- * Handle all unhandled error
593
- * @param callback callback function
594
- */
595
- function handleUnexpectedErrors(callback: (err: unknown) => void): void
596
- /**
597
- * Safe get value from header
598
- * @param headers response header
599
- * @param key target header key
600
- */
601
- function getHeader(headers: Record<string, Arrayable<string>>, key: any): any
602
- function downloadUtil<T>(
603
- url: string,
604
- headers: Record<string, any>,
605
- signal: AbortSignal,
606
- onResponse: (resp: IncomingMessage, resolve: (data: T) => void, reject: (e: any) => void) => void,
607
- ): Promise<T>
608
- /**
609
- * Default function to download json and parse to UpdateJson
610
- * @param url target url
611
- * @param headers extra headers
612
- * @param signal abort signal
613
- * @param resolveData on resolve
614
- */
615
- function defaultDownloadJSON<T>(
616
- url: string,
617
- headers: Record<string, any>,
618
- signal: AbortSignal,
619
- resolveData?: ResolveDataFn,
620
- ): Promise<T>
621
- /**
622
- * Default function to download json and parse to UpdateJson
623
- * @param url target url
624
- * @param headers extra headers
625
- * @param signal abort signal
626
- */
627
- function defaultDownloadUpdateJSON(
628
- url: string,
629
- headers: Record<string, any>,
630
- signal: AbortSignal,
631
- ): Promise<UpdateJSON>
632
- /**
633
- * Default function to download asar buffer,
634
- * get total size from `Content-Length` header
635
- * @param url target url
636
- * @param headers extra headers
637
- * @param signal abort signal
638
- * @param onDownloading on downloading callback
639
- */
640
- function defaultDownloadAsar(
641
- url: string,
642
- headers: Record<string, any>,
643
- signal: AbortSignal,
644
- onDownloading?: (progress: DownloadingInfo) => void,
645
- ): Promise<Buffer>
646
- ```
647
-
648
- ### Types
649
-
650
- #### Entry
651
-
652
- ```ts
653
- export interface AppOption {
654
- /**
655
- * Path to index file that make {@link startupWithUpdater} as default export
656
- *
657
- * Generate from plugin configuration by default
658
- */
659
- mainPath?: string
660
- /**
661
- * Updater options
662
- */
663
- updater?: (() => Promisable<Updater>) | UpdaterOption
664
- /**
665
- * Hooks on rename temp asar path to `${app.name}.asar`
666
- */
667
- onInstall?: OnInstallFunction
668
- /**
669
- * Hooks before app startup
670
- * @param mainFilePath main file path of `${app.name}.asar`
671
- * @param logger logger
672
- */
673
- beforeStart?: (mainFilePath: string, logger?: Logger) => Promisable<void>
674
- /**
675
- * Hooks on app startup error
676
- * @param err installing or startup error
677
- * @param logger logger
678
- */
679
- onStartError?: (err: unknown, logger?: Logger) => void
680
- }
681
- /**
682
- * Hooks on rename temp asar path to `${app.name}.asar`
683
- * @param install `() => renameSync(tempAsarPath, appNameAsarPath)`
684
- * @param tempAsarPath temp(updated) asar path
685
- * @param appNameAsarPath `${app.name}.asar` path
686
- * @param logger logger
687
- * @default install(); logger.info('update success!')
688
- */
689
- type OnInstallFunction = (
690
- install: VoidFunction,
691
- tempAsarPath: string,
692
- appNameAsarPath: string,
693
- logger?: Logger,
694
- ) => Promisable<void>
775
+ electronWithUpdater({
776
+ notBundle: false,
777
+ entry: {
778
+ files: './electron/entry.ts',
779
+ },
780
+ main: {
781
+ files: './electron/main/index.ts',
782
+ },
783
+ })
695
784
  ```
696
785
 
697
- #### Updater
698
-
699
- ```ts
700
- export interface UpdaterOption {
701
- /**
702
- * Update provider
703
- *
704
- * If you will not setup `UpdateJSON` or `Buffer` in params when checking update or download, this option is **required**
705
- */
706
- provider?: IProvider
707
- /**
708
- * Certifaction key of signature, which will be auto generated by plugin,
709
- * generate by `selfsigned` if not set
710
- */
711
- SIGNATURE_CERT?: string
712
- /**
713
- * Whether to receive beta update
714
- */
715
- receiveBeta?: boolean
716
- /**
717
- * Updater logger
718
- */
719
- logger?: Logger
720
- }
721
-
722
- export type Logger = {
723
- info: (msg: string) => void
724
- debug: (msg: string) => void
725
- warn: (msg: string) => void
726
- error: (msg: string, e?: Error) => void
727
- }
728
- ```
786
+ ## Utilities
729
787
 
730
- #### Provider
788
+ Import from `electron-incremental-update/utils`:
731
789
 
732
790
  ```ts
733
- export type OnDownloading = (progress: DownloadingInfo) => void
734
-
735
- export interface DownloadingInfo {
736
- /**
737
- * Download buffer delta
738
- */
739
- delta: number
740
- /**
741
- * Downloaded percent, 0 ~ 100
742
- *
743
- * If no `Content-Length` header, will be -1
744
- */
745
- percent: number
746
- /**
747
- * Total size
748
- *
749
- * If not `Content-Length` header, will be -1
750
- */
751
- total: number
752
- /**
753
- * Downloaded size
754
- */
755
- transferred: number
756
- /**
757
- * Download speed, bytes per second
758
- */
759
- bps: number
760
- }
761
-
762
- export interface IProvider {
763
- /**
764
- * Provider name
765
- */
766
- name: string
767
- /**
768
- * Download update json
769
- * @param versionPath parsed version path in project
770
- * @param signal abort signal
771
- */
772
- downloadJSON: (versionPath: string, signal: AbortSignal) => Promise<UpdateJSON>
773
- /**
774
- * Download update asar
775
- * @param name app name
776
- * @param updateInfo existing update info
777
- * @param signal abort signal
778
- * @param onDownloading hook for on downloading
779
- */
780
- downloadAsar: (
781
- name: string,
782
- updateInfo: UpdateInfo,
783
- signal: AbortSignal,
784
- onDownloading?: (info: DownloadingInfo) => void,
785
- ) => Promise<Buffer>
786
- /**
787
- * Check the old version is less than new version
788
- * @param oldVer old version string
789
- * @param newVer new version string
790
- */
791
- isLowerVersion: (oldVer: string, newVer: string) => boolean
792
- /**
793
- * Function to decompress file using brotli
794
- * @param buffer compressed file buffer
795
- */
796
- unzipFile: (buffer: Buffer) => Promise<Buffer>
797
- /**
798
- * Verify asar signature,
799
- * if signature is valid, returns the version, otherwise returns `undefined`
800
- * @param buffer file buffer
801
- * @param version target version
802
- * @param signature signature
803
- * @param cert certificate
804
- */
805
- verifySignaure: (
806
- buffer: Buffer,
807
- version: string,
808
- signature: string,
809
- cert: string,
810
- ) => Promisable<boolean>
811
- }
791
+ import {
792
+ aesDecrypt,
793
+ aesEncrypt,
794
+ beautifyDevTools,
795
+ defaultIsLowerVersion,
796
+ defaultSignature,
797
+ defaultUnzipFile,
798
+ defaultVerifySignature,
799
+ defaultZipFile,
800
+ getAppVersion,
801
+ getEntryVersion,
802
+ getPathFromAppNameAsar,
803
+ getPathFromEntryAsar,
804
+ getPathFromMain,
805
+ getPathFromPreload,
806
+ getPathFromPublic,
807
+ handleUnexpectedErrors,
808
+ hashBuffer,
809
+ importNative,
810
+ isDev,
811
+ isLinux,
812
+ isMac,
813
+ isWin,
814
+ loadPage,
815
+ parseVersion,
816
+ requireNative,
817
+ restartApp,
818
+ setAppUserModelId,
819
+ setPortableDataPath,
820
+ singleInstance,
821
+ } from 'electron-incremental-update/utils'
812
822
  ```
813
823
 
814
- #### Plugin
815
-
816
- ````ts
817
- export interface ElectronWithUpdaterOptions {
818
- /**
819
- * Whether is in build mode
820
- * ```ts
821
- * export default defineConfig(({ command }) => {
822
- * const isBuild = command === 'build'
823
- * })
824
- * ```
825
- */
826
- isBuild: boolean
827
- /**
828
- * Manually setup package.json, read name, version and main,
829
- * use `local-pkg` of `loadPackageJSON()` to load package.json by default
830
- * ```ts
831
- * import pkg from './package.json'
832
- * ```
833
- */
834
- pkg?: PKG
835
- /**
836
- * Whether to generate sourcemap
837
- * @default !isBuild
838
- */
839
- sourcemap?: boolean
840
- /**
841
- * Whether to minify the code
842
- * @default isBuild
843
- */
844
- minify?: boolean
845
- /**
846
- * Whether to generate bytecode
847
- *
848
- * **Only support CommonJS**
849
- *
850
- * Only 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
851
- */
852
- bytecode?: boolean | BytecodeOptions
853
- /**
854
- * Use `NotBundle()` plugin in main
855
- * @default true
856
- */
857
- useNotBundle?: boolean
858
- /**
859
- * Whether to generate version json
860
- * @default isCI
861
- */
862
- buildVersionJson?: boolean
863
- /**
864
- * Main process options
865
- *
866
- * To change output directories, use `options.updater.paths.electronDistPath` instead
867
- */
868
- main: {
869
- /**
870
- * Shortcut of `build.rollupOptions.input`
871
- */
872
- files: NonNullable<ElectronOptions['entry']>
873
- /**
874
- * Electron App startup function.
875
- *
876
- * It will mount the Electron App child-process to `process.electronApp`.
877
- * @param argv default value `['.', '--no-sandbox']`
878
- * @param options options for `child_process.spawn`
879
- * @param customElectronPkg custom electron package name (default: 'electron')
880
- */
881
- onstart?: ElectronOptions['onstart']
882
- } & ViteOverride
883
- /**
884
- * Preload process options
885
- *
886
- * To change output directories, use `options.updater.paths.electronDistPath` instead
887
- */
888
- preload: {
889
- /**
890
- * Shortcut of `build.rollupOptions.input`.
891
- *
892
- * Preload scripts may contain Web assets, so use the `build.rollupOptions.input` instead `build.lib.entry`.
893
- */
894
- files: NonNullable<ElectronOptions['entry']>
895
- } & ViteOverride
896
- /**
897
- * Updater options
898
- */
899
- updater?: UpdaterOptions
900
- }
901
-
902
- export interface UpdaterOptions {
903
- /**
904
- * Minimum version of entry
905
- * @default '0.0.0'
906
- */
907
- minimumVersion?: string
908
- /**
909
- * Options for entry (app.asar)
910
- */
911
- entry?: BuildEntryOption
912
- /**
913
- * Options for paths
914
- */
915
- paths?: {
916
- /**
917
- * Path to asar file
918
- * @default `release/${app.name}.asar`
919
- */
920
- asarOutputPath?: string
921
- /**
922
- * Path to version info output, content is {@link UpdateJSON}
923
- * @default `version.json`
924
- */
925
- versionPath?: string
926
- /**
927
- * Path to gzipped asar file
928
- * @default `release/${app.name}-${version}.asar.gz`
929
- */
930
- gzipPath?: string
931
- /**
932
- * Path to electron build output
933
- * @default `dist-electron`
934
- */
935
- electronDistPath?: string
936
- /**
937
- * Path to renderer build output
938
- * @default `dist`
939
- */
940
- rendererDistPath?: string
941
- }
942
- /**
943
- * signature config
944
- */
945
- keys?: {
946
- /**
947
- * Path to the pem file that contains private key
948
- * If not ended with .pem, it will be appended
949
- *
950
- * **If `UPDATER_PK` is set, will read it instead of read from `privateKeyPath`**
951
- * @default 'keys/private.pem'
952
- */
953
- privateKeyPath?: string
954
- /**
955
- * Path to the pem file that contains public key
956
- * If not ended with .pem, it will be appended
957
- *
958
- * **If `UPDATER_CERT` is set, will read it instead of read from `certPath`**
959
- * @default 'keys/cert.pem'
960
- */
961
- certPath?: string
962
- /**
963
- * Length of the key
964
- * @default 2048
965
- */
966
- keyLength?: number
967
- /**
968
- * X509 certificate info
969
- *
970
- * only generate simple **self-signed** certificate **without extensions**
971
- */
972
- certInfo?: {
973
- /**
974
- * The subject of the certificate
975
- *
976
- * @default { commonName: `${app.name}`, organizationName: `org.${app.name}` }
977
- */
978
- subject?: DistinguishedName
979
- /**
980
- * Expire days of the certificate
981
- *
982
- * @default 3650
983
- */
984
- days?: number
985
- }
986
- }
987
- overrideGenerator?: GeneratorOverrideFunctions
988
- }
989
-
990
- export interface BytecodeOptions {
991
- enable: boolean
992
- /**
993
- * Enable in preload script. Remember to set `sandbox: false` when creating window
994
- */
995
- preload?: boolean
996
- /**
997
- * Custom electron binary path
998
- */
999
- electronPath?: string
1000
- /**
1001
- * Before transformed code compile function. If return `Falsy` value, it will be ignored
1002
- * @param code transformed code
1003
- * @param id file path
1004
- */
1005
- beforeCompile?: (code: string, id: string) => Promisable<string | null | undefined | void>
1006
- }
1007
-
1008
- export interface BuildEntryOption {
1009
- /**
1010
- * Override to minify on entry
1011
- * @default isBuild
1012
- */
1013
- minify?: boolean
1014
- /**
1015
- * Override to generate sourcemap on entry
1016
- */
1017
- sourcemap?: boolean
1018
- /**
1019
- * Path to app entry output file
1020
- * @default 'dist-entry'
1021
- */
1022
- entryOutputDirPath?: string
1023
- /**
1024
- * Path to app entry file
1025
- * @default 'electron/entry.ts'
1026
- */
1027
- appEntryPath?: string
1028
- /**
1029
- * Vite input options of native modules in entry directory
1030
- *
1031
- * @default {}
1032
- * @example
1033
- * { db: './electron/native/db.ts' }
1034
- */
1035
- nativeModuleEntryMap?: Record<string, string>
1036
- /**
1037
- * Skip process dynamic require
1038
- *
1039
- * Useful for `better-sqlite3` and other old packages
1040
- */
1041
- ignoreDynamicRequires?: boolean
1042
- /**
1043
- * `external` option in `build.rollupOptions`,
1044
- * default is node built-in modules or native modules.
1045
- *
1046
- * If is in dev and {@link postBuild} is not setup, will also
1047
- * external `dependencies` in `package.json`
1048
- */
1049
- external?: NonNullable<NonNullable<InlineConfig['build']>['rollupOptions']>['external']
1050
- /**
1051
- * Custom options for `vite` build
1052
- * ```ts
1053
- * const options = {
1054
- * plugins: [esm(), bytecodePlugin()], // load on needed
1055
- * build: {
1056
- * sourcemap,
1057
- * minify,
1058
- * outDir: entryOutputDirPath,
1059
- * commonjsOptions: { ignoreDynamicRequires },
1060
- * rollupOptions: { external },
1061
- * },
1062
- * define,
1063
- * }
1064
- * ```
1065
- */
1066
- overrideViteOptions?: InlineConfig
1067
- /**
1068
- * By default, all the unbundled modules will be packaged by packager like `electron-builder`.
1069
- * If setup, all the `dependencies` in `package.json` will be bundled by default, and you need
1070
- * to manually handle the native module files.
1071
- */
1072
- postBuild?: (args: {
1073
- /**
1074
- * Get path from `entryOutputDirPath`
1075
- */
1076
- getPathFromEntryOutputDir: (...paths: string[]) => string
1077
- /**
1078
- * Check exist and copy file to `entryOutputDirPath`
1079
- *
1080
- * If `to` absent, set to `basename(from)`
1081
- *
1082
- * If `skipIfExist` absent, skip copy if `to` exist
1083
- */
1084
- copyToEntryOutputDir: (options: {
1085
- from: string
1086
- to?: string
1087
- /**
1088
- * Skip copy if `to` exist
1089
- * @default true
1090
- */
1091
- skipIfExist?: boolean
1092
- }) => void
1093
- /**
1094
- * Copy specified modules to entry output dir, just like `external` option in rollup
1095
- */
1096
- copyModules: (options: {
1097
- /**
1098
- * External Modules
1099
- */
1100
- modules: string[]
1101
- /**
1102
- * Skip copy if `to` exist
1103
- * @default true
1104
- */
1105
- skipIfExist?: boolean
1106
- }) => void
1107
- }) => Promisable<void>
1108
- }
1109
-
1110
- export interface GeneratorOverrideFunctions {
1111
- /**
1112
- * Custom signature generate function
1113
- * @param buffer file buffer
1114
- * @param privateKey private key
1115
- * @param cert certificate string, **EOL must be '\n'**
1116
- * @param version current version
1117
- */
1118
- generateSignature?: (
1119
- buffer: Buffer,
1120
- privateKey: string,
1121
- cert: string,
1122
- version: string,
1123
- ) => Promisable<string>
1124
- /**
1125
- * Custom generate update json function
1126
- * @param existingJson The existing JSON object.
1127
- * @param buffer file buffer
1128
- * @param signature generated signature
1129
- * @param version current version
1130
- * @param minVersion The minimum version
1131
- */
1132
- generateUpdateJson?: (
1133
- existingJson: UpdateJSON,
1134
- signature: string,
1135
- version: string,
1136
- minVersion: string,
1137
- ) => Promisable<UpdateJSON>
1138
- /**
1139
- * Custom generate zip file buffer
1140
- * @param buffer source buffer
1141
- */
1142
- generateGzipFile?: (buffer: Buffer) => Promisable<Buffer>
1143
- }
1144
- ````
824
+ Common helpers:
825
+
826
+ - `getPathFromAppNameAsar(...paths)`: path inside `${app.name}.asar`.
827
+ - `getPathFromEntryAsar(...paths)`: path inside `app.asar`.
828
+ - `getPathFromMain(...paths)`: path inside the built main directory.
829
+ - `getPathFromPreload(...paths)`: path inside the built preload directory.
830
+ - `getPathFromPublic(...paths)`: path inside `public` in dev or renderer output in production.
831
+ - `getAppVersion()`: current app version. In dev, returns the entry version.
832
+ - `getEntryVersion()`: version from Electron app metadata.
833
+ - `loadPage(win, htmlFilePath)`: loads `VITE_DEV_SERVER_URL` in dev or renderer HTML in production.
834
+ - `singleInstance(window)`: restores and focuses the window on `second-instance`.
835
+ - `setPortableDataPath(dirName, create)`: stores user data beside the executable for portable apps.
836
+ - `requireNative(moduleName)`: loads a CommonJS native helper from entry asar.
837
+ - `importNative(moduleName)`: imports an ES module native helper from entry asar.
1145
838
 
1146
839
  ## Credits
1147
840
 
1148
- - [Obsidian](https://obsidian.md/) for upgrade strategy
1149
- - [vite-plugin-electron](https://github.com/electron-vite/vite-plugin-electron) for vite plugin
1150
- - [electron-builder](https://github.com/electron-userland/electron-builder) for update api
841
+ - [Obsidian](https://obsidian.md/) for the dual asar update strategy
842
+ - [vite-plugin-electron](https://github.com/electron-vite/vite-plugin-electron) for the Vite Electron plugin foundation
843
+ - [electron-builder](https://github.com/electron-userland/electron-builder) for Electron packaging
1151
844
  - [electron-vite](https://github.com/alex8088/electron-vite) for bytecode plugin inspiration
1152
845
 
1153
846
  ## License