Skip to content

Configuration ​

electronup exports exactly one helper for type hints: defineConfig. It changes no runtime behavior; it only helps you write a correctly typed config.

Create the config file in the project root. A file in another directory can be passed through the CLI.

Config file ​

Supported file names (lookup order):

text
electronup.config.ts
electronup.config.mjs
electronup.config.cjs
electronup.config.js

Recommended form:

ts
// electronup.config.ts
import { defineConfig } from 'electronup'

export default defineConfig({
  // ...
})

defineConfig also accepts a function or an async function:

ts
import { defineConfig } from 'electronup'

export default defineConfig(({ command, mode, root }) => {
  return {
    // command is 'serve' (dev) or 'build'; mode is the -m value
  }
})

Paths ​

These top-level options decide the input and output directories of each stage:

OptionDefaultUsed by
renderDirrenderRenderer root, used as Vite root
mainDirmainMain process directory; the entry is its index.ts (.mts / .cts / .js / .mjs / .cjs also work)
publicDirpublicVite publicDir, copied as-is
libDirlibelectron-builder extraFiles, copied into the app directory
resourceDirdist/resourceRenderer, main process and preload output
outDirdist/outelectron-builder installer output
ts
export default defineConfig({
  renderDir: 'render',
  mainDir: 'main',
  publicDir: 'public',
  libDir: 'lib',
  resourceDir: 'dist/resource',
  outDir: 'dist/out',
})

Notes

  • Except for libDir, these paths are joined with the project root before use.
  • renderDir, publicDir and mainDir cannot be overridden inside Vite: setting viteConfig.root, viteConfig.publicDir or packConfig.entry has no effect (packConfig only exposes external / noExternal).
  • electronup dev clears resourceDir; electronup build clears both resourceDir and the packaging output directory (builderConfig.directories.output when set, otherwise outDir). Never point them at source directories. A safety check runs first: if a directory is the project root or one of its parents, the home directory, a filesystem root, inside node_modules / .git, or is itself a symlink / junction, electronup exits with an error and deletes nothing.

Vite ​

Renderer build, configured with viteConfig.

viteConfig is Vite's UserConfig (minus publicDir and ssr). Most Vite options can be passed directly. These are managed by electronup:

OptionPreset behavior
base./ by default, overridable
build.minifyControlled by --no-minify, not overridable
build.outDirAbsolute path of resourceDir, not overridable
build.modulePreload{ polyfill: false } by default; can be overridden in viteConfig.build.modulePreload
rootrenderDir, not overridable
publicDirAbsolute path of publicDir, not overridable

Everything else behaves like Vite:

ts
import Vue from '@vitejs/plugin-vue'
import { defineConfig } from 'electronup'

export default defineConfig({
  viteConfig: {
    plugins: [Vue()],
    build: {
      sourcemap: true,
    },
  },
})

Environment variables

electronup runs dotenv on the project root .env and .env.{mode} files before starting Vite. VITE_-prefixed variables are exposed to the renderer through import.meta.env; the main process and preload scripts can read all variables.

pack ​

Main process build, done by Vite in SSR (Node) mode and configured with packConfig.

For stability, packConfig exposes only two fields:

OptionTypeDescription
external(string | RegExp)[]Modules excluded from the bundle; electron and Node built-ins are always excluded
noExternal(string | RegExp)[]Modules forced into the bundle
ts
export default defineConfig({
  packConfig: {
    external: ['native-module'],
    noExternal: ['some-esm-only-package'],
  },
})

Presets:

ItemValue
Entry${mainDir}/index.{ts,mts,cts,js,mjs,cjs}, the first one that exists in this order
OutputresourceDir/electron.cjs
Formatcjs (fixed)
build.targetnode18 (the Node version bundled with Electron 28)
build.emptyOutDirfalse (resourceDir is cleaned by electronup)
build.watchEnabled in dev, disabled in build
minifyFollows the CLI in build (on by default); off in dev
sourcemapOn in dev, off in build
EnvInjects .env / .env.{mode}, NODE_ENV and RENDER_PORT, replacing process.env.X and import.meta.env.X via define
DependenciesPackages declared in dependencies / peerDependencies / optionalDependencies of package.json stay external (electron-builder installs them into the app); devDependencies and other modules are bundled. When the output is CJS, ESM-only runtime dependencies whose exports only offer an import condition are bundled too, since require would fail at runtime

The main process build does not read the project's vite.config.* and does not copy publicDir. In dev, Electron is launched (or restarted) after every main process build is written; changes to main process files trigger a rebuild.

Bundled dependencies

When using the update helpers exported by electronup, you need both of the following (see Auto update):

  1. Install electron-updater as an app dependency. It is loaded lazily at runtime and cannot be bundled into the main process output, even via noExternal.
  2. Put electronup into noExternal so the helper code is bundled into the main process output:
ts
export default defineConfig({
  packConfig: {
    noExternal: ['electronup'],
  },
})

preload ​

Preload build, configured with preload. Accepts one config object or an array (multiple entries).

Each item is built by Vite in SSR (Node) mode and must provide entry (glob patterns are not supported):

ts
import { resolve } from 'node:path'
import { defineConfig } from 'electronup'

export default defineConfig({
  preload: {
    entry: { preload: resolve(__dirname, 'preload/index.ts') },
  },
})

Per-item defaults are listed below; fields in the item config override them:

ItemDefault
Formatcjs; the output file name follows the entry key (preload → preload.cjs); format: 'esm' outputs .mjs
Output dirresourceDir; can be set per item with outDir. An outDir outside resourceDir is added to the default builder files automatically (include it yourself if you override files) and must stay inside the project root
EnvSame as the main process; per-item env wins
external / noExternalSame dependency rules as the main process; electron is never bundled; noExternal: true bundles everything else
minify / sourcemapBy default dev does not minify and generates sourcemaps; build minifies per the CLI without sourcemaps
pluginsVite plugins, appended after electronup's built-in plugins
viteConfigAny other Vite options, merged last with mergeConfig and able to override the presets above

Both dev and build build preload scripts before the main process. In dev they are also built in watch mode: a change rebuilds the preload and restarts Electron. A failed first build does not block dev; fixing it triggers a restart.

If your Electron version supports ESM preload, pass format: 'esm' in the item to override the default.

Upgrading from 0.3.x

Since 0.4.0 preload scripts are built with Vite. preload only accepts the PreloadConfig fields above and no longer takes vite-plus pack (tsdown) options. entry, outDir, format, minify, sourcemap, external, noExternal, env and plugins keep their names (format must be a single value); remove other pack options or rewrite them as the matching Vite options under viteConfig.

electron-builder ​

Installer packaging, configured with builderConfig. Only used by build.

builderConfig is electron-builder's Configuration. Presets:

OptionPreset
electronVersionRead from the installed electron in the project; can be overridden by providing this field in builderConfig (it is not part of electron-builder's types, so a type assertion may be needed)
appIdcom. + package.json name (a leading @ is dropped, invalid characters become . with consecutive dots collapsed; a non-ASCII-only name falls back to com.electronup)
productNamepackage.json name
nsisNon-one-click installer (oneClick: false), per-user install (perMachine: false), Simplified Chinese, install directory selectable, desktop and start menu shortcuts, etc.
files${resourceDir}/**/*
extraFiles[libDir]
directories.outputDefaults to outDir; when builderConfig.directories.output is set it wins, and cleanup and electronup-effective-config.yaml follow it
asarSet to false when --no-asar is passed, overriding builderConfig.asar; otherwise builderConfig.asar is used, and electron-builder decides when it is unset
targetsPlatform and architecture resolved from CLI flags or the interactive wizard
mac.target['dmg', 'zip']: dmg for distributing the installer, zip is required by electron-updater on macOS

Fields in builderConfig override the presets, so appId, productName, nsis, files and friends can be customized:

ts
export default defineConfig({
  builderConfig: {
    appId: 'com.example.app',
    productName: 'Example',
    nsis: {
      oneClick: true,
    },
    publish: [
      { provider: 'github', owner: 'your-org', repo: 'your-app' },
    ],
  },
})

Platform targets: CLI flags (--win, --mac, --linux, --dir) generate electron-builder targets and take precedence over the matching target config in builderConfig. If your targets are stable, configure win / mac / linux in builderConfig and avoid the matching CLI flags.

Code signing and notarization ​

electronup presets nothing for signing, so artifacts are unsigned by default; whether they are signed depends on electron-builder configuration and your certificate setup (see "What it does not do" in Project).

Because builderConfig passes electron-builder's Configuration through untouched and your fields override the presets, every signing option is available via builderConfig.

Prerequisite: macOS signing and notarization only run on macOS (electronup already restricts mac builds to macOS); notarization also requires Xcode / command line tools.

Windows ​

Prefer environment variables so the certificate never lands in the repo:

  • CSC_LINK: path or base64 of the .pfx.
  • CSC_KEY_PASSWORD: certificate password.

Or configure it directly:

ts
export default defineConfig({
  builderConfig: {
    win: {
      certificateFile: 'certs/cert.pfx',
      certificatePassword: process.env.CSC_KEY_PASSWORD,
      signingHashAlgorithms: ['sha256'],
    },
  },
})

electron-builder 26 also supports two mutually exclusive options (Azure Trusted Signing wins automatically when set):

  • win.signtoolOptions: custom signtool.exe options.
  • win.azureSignOptions: Azure Trusted Signing.

macOS ​

ts
export default defineConfig({
  builderConfig: {
    mac: {
      identity: 'Developer ID Application: xxx (TEAMID)',
      hardenedRuntime: true,
      entitlements: 'build/entitlements.mac.plist',
      entitlementsInherit: 'build/entitlements.mac.plist',
    },
  },
})

Notarization is handled by electron-builder's built-in @electron/notarize. Provide any one credential group below and it runs automatically (disable with mac.notarize: false):

  • APPLE_API_KEY + APPLE_API_KEY_ID + APPLE_API_ISSUER (recommended)
  • APPLE_ID + APPLE_APP_SPECIFIC_PASSWORD + APPLE_TEAM_ID
  • APPLE_KEYCHAIN + APPLE_KEYCHAIN_PROFILE

Relation to auto update ​

Signing directly affects auto update:

  • macOS auto update requires a signed and notarized app, and the update artifact must be zip (mac.target already includes zip by default), otherwise the system rejects the update.
  • On Windows win.verifyUpdateCodeSignature defaults to true and verifies the update against publisherName, so signing is recommended for releases.

Environment variables ​

electronup reads two layers from the project root:

text
.env
.env.{mode}
ModeFiles
development (dev default).env + .env.development
production (build default).env + .env.production
Custom.env + .env.{mode}

Rules:

  • .env.{mode} is loaded after .env and overrides duplicate keys.
  • A missing .env or .env.{mode} is not an error; that layer is simply skipped.
  • NODE_ENV is written by electronup: development in dev, production in build.
  • In dev, RENDER_PORT holds the actual Vite port.
  • The renderer only sees VITE_-prefixed variables (import.meta.env); the main process and preload scripts see all variables (process.env). The renderer also reads .env files from the project root, and import.meta.env.MODE follows -m.
  • For the main process and preload, variables are inlined at build time as process.env.X. Both the global process and import process from 'node:process' (or 'process') work; import { env } from 'node:process' or other local names (such as import proc from 'node:process') are not inlined.
  • The main process and preload do not read finer-grained files such as .env.local; renderer variables are loaded by Vite and follow its rules (including .env.local).

Effective config ​

After every electronup build, the resolved config is written to the packaging output directory (builderConfig.directories.output, or outDir when not set):

text
${outDir}/electronup-effective-config.yaml

The file is only for troubleshooting; if writing it fails, electronup prints a warning and the build result and exit code are unaffected.

electronup build --web only builds the renderer, so it does not produce this file, and it removes the one left by a previous packaging build so you do not read a config that no longer matches the output.

It contains the merged vite, pack, preload and builder config (snapshotted before electron-builder runs) and is handy for checking whether an option took effect. The header records the electronup version, generation time and command; plugins only keep plugin names, functions show as [Function name], regular expressions as literals, and builder.targets as platform / architecture names. It is not the complete runtime config and may contain absolute paths, so treat it as a debugging artifact.

Full example ​

ts
import { resolve } from 'node:path'
import Vue from '@vitejs/plugin-vue'
import { defineConfig } from 'electronup'

export default defineConfig(({ command }) => ({
  renderDir: 'render',
  mainDir: 'main',
  publicDir: 'public',
  resourceDir: 'dist/resource',
  outDir: 'dist/out',

  viteConfig: {
    plugins: [Vue()],
    build: {
      sourcemap: command === 'serve',
    },
  },

  packConfig: {
    external: ['better-sqlite3'],
    noExternal: ['electronup'],
  },

  preload: {
    entry: { preload: resolve(__dirname, 'preload/index.ts') },
  },

  builderConfig: {
    appId: 'com.example.app',
    productName: 'Example',
    publish: [
      { provider: 'github', owner: 'your-org', repo: 'your-app' },
    ],
  },
}))

Released under the MIT License.