electron-expose
v0.1.4
Published
Generate type-safe Electron IPC bridges from TypeScript decorators.
Downloads
598
Maintainers
Readme
electron-expose
Generate type-safe Electron IPC bridges from decorated TypeScript functions.
Electron IPC usually means keeping channel names, main handlers, preload
bridges, shared types, and renderer calls in sync. electron-expose generates
that bridge from the functions you expose in code, so the renderer gets a typed
window.api without the repeated wiring.
One goal: make Electron IPC boring.
Why
- No manual
ipcMain.handle(...)andipcRenderer.invoke(...)pairing - No hand-maintained renderer API types
- No repeating the same method shape across main, preload, and renderer
- Type-safe
window.api.*calls generated from exposed functions
Mark class methods with decorators:
import { expose } from "electron-expose"
export class CalculatorRoutes {
@expose("math.calculate")
calculate(a: number, b: number): number {
return a + b
}
}Or expose standalone functions:
import { exposed } from "electron-expose"
export const getVersion = exposed(
"system.getVersion",
async (): Promise<string> => {
return app.getVersion()
},
)Then call the generated API from the renderer:
const answer = await window.api.math.calculate(2, 3)
const version = await window.api.system.getVersion()Install
pnpm add electron-exposeQuick Start
Initialize the project:
pnpm electron-expose initThen expose functions in the main process:
import { expose } from "electron-expose"
export class CalculatorRoutes {
@expose("math.calculate")
calculate(a: number, b: number): number {
return a + b
}
}Generate the Electron bridge:
pnpm electron-expose generateTo inspect discovered functions without writing generated files:
pnpm electron-expose listinit is interactive. It can create config, patch detected main/preload files,
and enable experimentalDecorators in tsconfig.json.
For CI or setup scripts:
pnpm electron-expose init --yesPlumb It In
Main process:
import { registerElectronExposeRoutes } from "./generated/electron-expose/main"
registerElectronExposeRoutes()Preload:
import { exposeElectronApi } from "./generated/electron-expose/preload"
exposeElectronApi()Renderer:
await window.api.math.calculate(2, 3)Config
Most projects can start with an empty config:
import { defineConfig } from "electron-expose"
export default defineConfig()Common options:
import { defineConfig } from "electron-expose"
export default defineConfig({
root: "src",
outDir: "src/generated/electron-expose",
globalApiName: "api",
routePrefix: "electron-expose",
rendererGlobal: "src/renderer/global.d.ts",
})By default, electron-expose scans root for *.ts and *.tsx, then only
generates bridge entries for @expose() or exposed(...).
Generated/build folders, declaration files, and node_modules are ignored
automatically.
For custom layouts:
export default defineConfig({
include: ["packages/main/src/**/*.ts"],
exclude: ["**/*.spec.ts"],
})Exposing Functions
Class methods use decorators:
export class UserRoutes {
@expose("users.get")
getUser(id: string): Promise<User> {
return userService.getUser(id)
}
}Classes must be exported and currently need a zero-argument constructor.
Decorators are used as build-time markers for electron-expose generate; they
are not runtime registration logic.
TypeScript does not allow decorators on top-level functions, so standalone
functions use exposed(...):
import { exposed } from "electron-expose"
export const ping = exposed("system.ping", (): string => "pong")Contributing
Issues, bug reports, and focused pull requests are welcome.
Before opening a pull request, run:
pnpm run check
pnpm run build