Skip to content

Electron

ZNTC ships no Electron-specific integration. Like webpack / rspack, you bundle main · preload · renderer as three separate entries with ZNTC and wire them together via npm scripts. The renderer is served by ZNTC’s own dev server, so no extra plugin is needed.

my-electron-app/
├── src/
│ ├── main.ts # Electron main process
│ ├── preload.ts # contextBridge preload
│ └── renderer/
│ ├── index.html
│ └── app.tsx
├── dist/ # build output
└── package.json

Keep electron and Node built-ins external; emit as CommonJS.

{
"scripts": {
"build:main": "zntc --bundle src/main.ts --platform=node --format=cjs --packages=external -o dist/main.cjs",
"build:preload": "zntc --bundle src/preload.ts --platform=node --format=cjs --packages=external -o dist/preload.cjs"
}
}
  • --packages=external: keeps every bare import (electron, Node built-ins, electron-store, …) external; Electron requires them at runtime.
  • --format=cjs: CJS is the most compatible format for the Electron main process. (Use --format=esm if you target Electron 28+ ESM main.)

The renderer is a plain SPA. Serve it with ZNTC’s dev server and have the main process point BrowserWindow at it.

{
"scripts": {
"dev:renderer": "zntc dev src/renderer",
"build:renderer": "zntc build src/renderer"
}
}
src/main.ts
import { app, BrowserWindow } from "electron";
import { join } from "node:path";
const isDev = process.env.NODE_ENV === "development";
app.whenReady().then(() => {
const win = new BrowserWindow({
webPreferences: { preload: join(__dirname, "preload.cjs") },
});
if (isDev) {
win.loadURL("http://localhost:12300");
} else {
win.loadFile(join(__dirname, "renderer/index.html"));
}
});

Watch · restart · concurrent execution is wired with standard tooling (npm-run-all, wait-on, nodemon) — the same pattern as webpack/rspack’s Electron guides.

{
"scripts": {
"dev:renderer": "zntc dev src/renderer",
"dev:main": "zntc --bundle src/main.ts --platform=node --format=cjs --packages=external --watch -o dist/main.cjs",
"dev:preload": "zntc --bundle src/preload.ts --platform=node --format=cjs --packages=external --watch -o dist/preload.cjs",
"dev:electron": "wait-on tcp:12300 && nodemon --watch dist/main.cjs --watch dist/preload.cjs --exec electron dist/main.cjs",
"dev": "npm-run-all -p dev:renderer dev:main dev:preload dev:electron",
"build:main": "zntc --bundle src/main.ts --platform=node --format=cjs --packages=external -o dist/main.cjs",
"build:preload": "zntc --bundle src/preload.ts --platform=node --format=cjs --packages=external -o dist/preload.cjs",
"build:renderer": "zntc build src/renderer",
"build": "npm-run-all build:main build:preload build:renderer",
"package": "electron-builder"
}
}

Renderer HMR is handled by ZNTC’s dev server (see Dev Server). When the main / preload bundles rebuild, nodemon restarts the Electron process.

Use electron-builder or electron-forge’s generic mode as-is — ZNTC’s output is plain Node CJS / browser bundles, with no extra transform step required.

// package.json — electron-builder example
{
"build": {
"appId": "com.example.app",
"files": ["dist/**/*", "package.json"]
}
}
  • ZNTC itself does not auto-restart Electron when the main process rebuilds — the nodemon script above plays that role.
  • IPC type sync, autoUpdater, and other Electron ecosystem tools work independently of ZNTC.