npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

electron-incremental-update

v3.0.0-beta.5

Published

Electron incremental update tools with Vite plugin, support bytecode protection

Downloads

624

Readme

Electron Incremental Update

This project is built on top of vite-plugin-electron, offers a lightweight update solution for Electron applications without using native executables.

Key Features

This solution provides a comprehensive update system for Electron applications, including:

  • Vite Plugin - Seamlessly integrates with your existing Vite build process
  • Startup Entry Function - Handles application initialization and update checking
  • Updater Class - Manages the complete update workflow with event-driven API
  • Utility Functions - Helper functions for file paths, version management, and more

Dual Asar Architecture

The update system uses a two-file structure for efficient incremental updates:

  • app.asar - The application entry point that loads and initializes the updater
  • ${electron.app.name}.asar - Contains your application code (main process, preload scripts, and renderer) that gets replaced during updates

Update Process

The update workflow follows these steps:

  1. Check for Updates - Query the remote server for available updates
  2. Download and Verify - Download the update asar file and verify it using RSA signatures
  3. Prepare for Update - The application quits to prepare for the update
  4. Apply Update - On next launch, replace the old ${electron.app.name}.asar with the new version and load it

Additional Features

  • Smaller Update Packages - Package all native modules into app.asar to minimize the update file size and download time see usage
  • Source Code Protection - Leverage V8 bytecode compilation to obfuscate and protect your JavaScript source code see details

Getting Started

Install

npm install -D electron-incremental-update
yarn add -D electron-incremental-update
pnpm add -D electron-incremental-update

Project Structure

Base on electron-vite-vue

electron
├── entry.ts // <- entry file
├── main
│   └── index.ts
├── preload
│   └── index.ts
└── native // <- possible native modules
    └── index.ts
src
└── ...

Setup Entry

The entry is used to load the application and initialize the Updater

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). And the provider is optional, you can setup later

in electron/entry.ts

import { createElectronApp } from 'electron-incremental-update'
import { GitHubProvider } from 'electron-incremental-update/provider'

createElectronApp({
  updater: {
    // optional, you can setup later
    provider: new GitHubProvider({
      username: 'yourname',
      repo: 'electron',
    }),
  },
  beforeStart(mainFilePath, logger) {
    logger?.debug(mainFilePath)
  },
})

Setup vite.config.ts

The plugin config, main and preload parts are reference from electron-vite-vue

  • certificate will read from process.env.UPDATER_CERT first, if absent, read config
  • privatekey will read from process.env.UPDATER_PK first, if absent, read config

See all config in types

in vite.config.mts

import { debugStartup, electronWithUpdater } from 'electron-incremental-update/vite'
import { defineConfig } from 'vite'

export default defineConfig(async ({ command }) => {
  const isBuild = command === 'build'
  return {
    plugins: [
      electronWithUpdater({
        isBuild,
        entry: {
          files: ['./electron/entry.ts', './electron/native/index.ts'],
        },
        main: {
          files: ['./electron/main/index.ts', './electron/main/worker.ts'],
          // see https://github.com/electron-vite/electron-vite-vue/blob/85ed267c4851bf59f32888d766c0071661d4b94c/vite.config.ts#L22-L28
          onstart: debugStartup,
        },
        preload: {
          files: './electron/preload/index.ts',
        },
        updater: {
          // options
        },
      }),
    ],
    server:
      process.env.VSCODE_DEBUG &&
      (() => {
        const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL)
        return {
          host: url.hostname,
          port: +url.port,
        }
      })(),
  }
})

Or use the helper function

import { defineElectronConfig } from 'electron-incremental-update/vite'

export default defineElectronConfig({
  entry: {
    files: ['./electron/entry.ts', './electron/native/index.ts'],
  },
  main: {
    files: ['./electron/main/index.ts', './electron/main/worker.ts'],
    // see https://github.com/electron-vite/electron-vite-vue/blob/85ed267c4851bf59f32888d766c0071661d4b94c/vite.config.ts#L22-L28
    onstart: debugStartup,
  },
  preload: {
    files: './electron/preload/index.ts',
  },
  updater: {
    // options
  },
  renderer: {
    server:
      process.env.VSCODE_DEBUG &&
      (() => {
        const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL)
        return {
          host: url.hostname,
          port: +url.port,
        }
      })(),
  },
})

Modify package.json

{
  "main": "dist-entry/entry.js" // <- entry file path
}

Config electron-builder

const { name } = require('./package.json')

const targetFile = `${name}.asar`
/**
 * @type {import('electron-builder').Configuration}
 */
module.exports = {
  appId: 'YourAppID',
  productName: name,
  files: [
    // entry files
    'dist-entry',
  ],
  npmRebuild: false,
  asarUnpack: ['**/*.{node,dll,dylib,so}'],
  directories: {
    output: 'release',
  },
  extraResources: [
    { from: `release/${targetFile}`, to: targetFile }, // <- asar file
  ],
  publish: null, // <- disable publish
}

Usage

Use In Main Process

The updater should be initialized in your main process. The startup function will automatically handle update checks and installation.

NOTE: There should only be one function and should be the default export in the main index file

in electron/main/index.ts

import { app, dialog } from 'electron'
import { startupWithUpdater } from 'electron-incremental-update'
import {
  getPathFromAppNameAsar,
  getAppVersion,
  getEntryVersion,
} from 'electron-incremental-update/utils'

export default startupWithUpdater(async (updater) => {
  await app.whenReady()

  // Display current app information
  console.table({
    [`${app.name}.asar path:`]: getPathFromAppNameAsar(),
    'app version:': getAppVersion(),
    'entry (installer) version:': getEntryVersion(),
    'electron version:': process.versions.electron,
  })

  // Listen for download progress
  updater.onDownloading = ({ percent }) => {
    console.log(`Download progress: ${percent}%`)
  }

  // Listen for available updates
  updater.on('update-available', async ({ version }) => {
    const { response } = await dialog.showMessageBox({
      type: 'info',
      buttons: ['Download', 'Later'],
      message: `Version ${version} update available!`,
    })
    if (response === 0) {
      // 0 = Download button
      await updater.downloadUpdate()
    }
  })

  // Handle no updates available
  updater.on('update-not-available', (code, reason, info) => {
    console.log('No update available:', reason)
  })

  // Handle download progress (alternative to onDownloading)
  updater.on('download-progress', (data) => {
    console.log('Download progress:', data)
    // Send progress to renderer if needed
    const [mainWindow] = BrowserWindow.getAllWindows()
    if (mainWindow) {
      mainWindow.webContents.send('update-progress', data)
    }
  })

  // Handle update completion
  updater.on('update-downloaded', () => {
    dialog
      .showMessageBox({
        type: 'info',
        message: 'Update downloaded successfully!',
        buttons: ['Restart Now', 'Later'],
      })
      .then(({ response }) => {
        if (response === 0) {
          updater.quitAndInstall()
        }
      })
  })

  // Handle errors
  updater.on('error', (error) => {
    console.error('Update error:', error)
    dialog.showErrorBox('Update Error', error.message || 'Failed to check for updates')
  })

  // Start checking for updates
  updater.checkForUpdates()
})

Alternative Provider Setup

You can also change the provider dynamically:

// In main.ts
updater.provider = new GitHubProvider({
  owner: 'your-username',
  repo: 'your-repo',
  // Custom URL handling for mirrors or private repos
  urlHandler: (url) => {
    url.hostname = 'mirror.ghproxy.com'
    url.pathname = `https://github.com${url.pathname}`
    return url
  },
})

Custom logger

updater.logger = console

Beta Channel Updates

updater.receiveBeta = true

Use Native Modules

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.

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.

Luckily, vite can bundle all the dependencies. Just follow the steps:

  1. setup entry.files option
  2. Manually copy the native binaries in entry.postBuild callback
  3. Exclude all the dependencies in electron-builder's config
  4. call the native functions with requireNative / importNative in your code

Example

in vite.config.ts

const plugin = electronWithUpdater({
  // options...
  entry: {
    files: ['./electron/native/entry.ts', './electron/native/db.ts', './electron/native/img.ts'],
    postBuild: ({ copyToEntryOutputDir, copyModules }) => {
      // for better-sqlite3
      copyToEntryOutputDir({
        from: './node_modules/better-sqlite3/build/Release/better_sqlite3.node',
        skipIfExist: false,
      })
      // for @napi-rs/image
      const startStr = '@napi-rs+image-'
      const fileName = readdirSync('./node_modules/.pnpm').find((p) => p.startsWith(startStr))!
      const archName = fileName.substring(startStr.length).split('@')[0]
      copyToEntryOutputDir({
        from: `./node_modules/.pnpm/${fileName}/node_modules/@napi-rs/image-${archName}/image.${archName}.node`,
      })
      // or just copy specific dependency
      copyModules({ modules: ['better-sqlite3'] })
    },
  },
})

in electron/native/db.ts

import Database from 'better-sqlite3'
import { getPathFromEntryAsar } from 'electron-incremental-update/utils'

const db = new Database(':memory:', {
  nativeBinding: getPathFromEntryAsar('./better_sqlite3.node'),
})

export function test(): void {
  db.exec(
    'DROP TABLE IF EXISTS employees; ' +
      'CREATE TABLE IF NOT EXISTS employees (name TEXT, salary INTEGER)',
  )

  db.prepare('INSERT INTO employees VALUES (:n, :s)').run({
    n: 'James',
    s: 5000,
  })

  const r = db.prepare('SELECT * from employees').all()
  console.log(r)
  // [ { name: 'James', salary: 50000 } ]

  db.close()
}

in electron/main/service.ts

import { importNative, requireNative } from 'electron-incremental-update/utils'

// commonjs
requireNative<typeof import('../native/db')>('db').test()

// esm
importNative<typeof import('../native/db')>('db').test()

in electron-builder.config.js

module.exports = {
  files: [
    'dist-entry',
    // exclude all dependencies in electron-builder config
    '!node_modules/**',
  ],
}

Result in app.asar

Before: Redundant 🤮

.
├── dist-entry
│   ├── chunk-IVHNGRZY-BPUeB0jT.js
│   ├── db.js
│   ├── entry.js
│   └── image.js
├── node_modules
│   ├── @napi-rs
│   ├── base64-js
│   ├── better-sqlite3
│   ├── bindings
│   ├── bl
│   ├── buffer
│   ├── chownr
│   ├── decompress-response
│   ├── deep-extend
│   ├── detect-libc
│   ├── end-of-stream
│   ├── expand-template
│   ├── file-uri-to-path
│   ├── fs-constants
│   ├── github-from-package
│   ├── ieee754
│   ├── inherits
│   ├── ini
│   ├── mimic-response
│   ├── minimist
│   ├── mkdirp-classic
│   ├── napi-build-utils
│   ├── node-abi
│   ├── once
│   ├── prebuild-install
│   ├── pump
│   ├── rc
│   ├── readable-stream
│   ├── safe-buffer
│   ├── semver
│   ├── simple-concat
│   ├── simple-get
│   ├── string_decoder
│   ├── strip-json-comments
│   ├── tar-fs
│   ├── tar-stream
│   ├── tunnel-agent
│   ├── util-deprecate
│   └── wrappy
└── package.json

After: Clean 😍

.
├── dist-entry
│   ├── better_sqlite3.node
│   ├── chunk-IVHNGRZY-BPUeB0jT.js
│   ├── db.js
│   ├── entry.js
│   ├── image.js
│   └── image.win32-x64-msvc.node
└── package.json

Bytecode Protection

Use V8 cache to protect the source code

electronWithUpdater({
  // ...
  bytecode: true, // or options
})

Benefits

https://electron-vite.org/guide/source-code-protection

  • Improve the string protection (see original issue)
  • Protect all strings by default
  • Minification is allowed

Limitation

  • Only support commonjs
  • 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

Utils

Utility functions of Electron helper, crypto, and file compression.

import {
  // Electron utilities
  isDev,
  isWin,
  isMac,
  isLinux,
  getPathFromAppNameAsar,
  getPathFromEntryAsar,
  getPathFromMain,
  getPathFromPreload,
  getPathFromPublic,
  getAppVersion,
  getEntryVersion,
  requireNative,
  importNative,
  restartApp,
  setAppUserModelId,
  disableHWAccForWin7,
  singleInstance,
  setPortableDataPath,
  loadPage,
  beautifyDevTools,
  handleUnexpectedErrors,

  // Crypto utilities
  hashBuffer,
  aesEncrypt,
  aesDecrypt,
  defaultSignature,
  defaultVerifySignature,

  // Zip utilities
  defaultZipFile,
  defaultUnzipFile,
} from 'electron-incremental-update/utils'

Electron Utilities

  • isDev - Compile-time dev check
  • isWin - Check if running on Windows
  • isMac - Check if running on macOS
  • isLinux - Check if running on Linux
  • getPathFromAppNameAsar(...paths) - Get joined path of ${electron.app.name}.asar
  • getPathFromEntryAsar(...paths) - Get joined path from entry asar
  • getPathFromMain(...paths) - Get joined path from main dir
  • getPathFromPreload(...paths) - Get joined path from preload dir
  • getPathFromPublic(...paths) - Get joined path from public dir
  • getAppVersion() - Get app version (returns entry version in dev)
  • getEntryVersion() - Get entry version
  • requireNative(moduleName) - Load native module using require from entry asar
  • importNative(moduleName) - Load native module using import from entry asar
  • restartApp() - Restart the Electron app
  • setAppUserModelId(id) - Fix app model ID (Windows only)
  • disableHWAccForWin7() - Disable hardware acceleration for Windows 7
  • singleInstance(window) - Keep single instance and restore window
  • setPortableDataPath(dirName, create) - Set userData dir to exe dir for portable apps
  • loadPage(win, htmlFilePath) - Load dev server URL in dev or HTML file otherwise
  • beautifyDevTools(win, options) - Beautify devtools font and scrollbar
  • handleUnexpectedErrors(callback) - Handle all unhandled errors

Crypto Utilities

  • hashBuffer(data, length) - Hash data using SHA-256
  • aesEncrypt(plainText, key, iv) - Encrypt text using AES
  • aesDecrypt(encryptedText, key, iv) - Decrypt text using AES
  • defaultSignature(buffer, privateKey, cert, version) - Generate RSA signature for asar file
  • defaultVerifySignature(buffer, version, signature, cert) - Verify RSA signature of asar file

Zip Utilities

  • defaultZipFile(buffer) - Compress file using brotli
  • defaultUnzipFile(buffer) - Decompress file using brotli

Credits

License

MIT