Electron integration for AssemblerJS with type-safe IPC communication between main process, renderer process, and preload scripts.
@assemblerjs/electron brings the power of AssemblerJS dependency injection to Electron applications, providing a unified architecture across all Electron processes with type-safe inter-process communication (IPC).
- 🔌 Main Process Integration - Use AssemblerJS DI in Electron's main process
- 🖥️ Renderer Process Support - DI for renderer processes
- 🔗 Preload Scripts - Bridge main and renderer with type-safety
- 🎯 Type-safe IPC - Full TypeScript support for IPC communication
- 🏗️ Unified Architecture - Same patterns across all processes
- ♻️ Lifecycle Management - Proper cleanup and resource management
npm install @assemblerjs/electron assemblerjs electron reflect-metadata
# or
yarn add @assemblerjs/electron assemblerjs electron reflect-metadataThe package provides three entry points for different Electron contexts:
// Main process
import { /* ... */ } from '@assemblerjs/electron';
// Renderer process
import { /* ... */ } from '@assemblerjs/electron/renderer';
// Preload script
import { /* ... */ } from '@assemblerjs/electron/preload';import 'reflect-metadata';
import { app, BrowserWindow } from 'electron';
import { Assemblage, Assembler, AbstractAssemblage } from 'assemblerjs';
@Assemblage()
class WindowManager implements AbstractAssemblage {
private mainWindow: BrowserWindow | null = null;
async onInit() {
await app.whenReady();
this.createWindow();
}
createWindow() {
this.mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});
this.mainWindow.loadFile('index.html');
}
async onDispose() {
this.mainWindow?.close();
}
}
@Assemblage({
inject: [[WindowManager]]
})
class ElectronApp implements AbstractAssemblage {
constructor(private windowManager: WindowManager) {}
async onInit() {
console.log('Electron app started');
}
}
// Bootstrap
const electronApp = Assembler.build(ElectronApp);import { contextBridge, ipcRenderer } from 'electron';
// Expose safe APIs to renderer
contextBridge.exposeInMainWorld('electronAPI', {
send: (channel: string, data: any) => {
ipcRenderer.send(channel, data);
},
on: (channel: string, callback: Function) => {
ipcRenderer.on(channel, (_, ...args) => callback(...args));
}
});import 'reflect-metadata';
import { Assemblage, Assembler, AbstractAssemblage } from 'assemblerjs';
declare global {
interface Window {
electronAPI: {
send: (channel: string, data: any) => void;
on: (channel: string, callback: Function) => void;
};
}
}
@Assemblage()
class AppService implements AbstractAssemblage {
sendMessage(message: string) {
window.electronAPI.send('message', message);
}
onInit() {
window.electronAPI.on('response', (data: any) => {
console.log('Received:', data);
});
}
}
@Assemblage({
inject: [[AppService]]
})
class RendererApp implements AbstractAssemblage {
constructor(private appService: AppService) {}
async onInit() {
this.appService.sendMessage('Hello from renderer');
}
}
const app = Assembler.build(RendererApp);import { ipcMain } from 'electron';
import { Assemblage, AbstractAssemblage } from 'assemblerjs';
@Assemblage()
class IpcService implements AbstractAssemblage {
onInit() {
ipcMain.on('message', (event, data) => {
console.log('Received from renderer:', data);
event.reply('response', { status: 'ok', data });
});
ipcMain.handle('get-data', async () => {
return { result: 'Some data' };
});
}
onDispose() {
ipcMain.removeAllListeners('message');
ipcMain.removeHandler('get-data');
}
}@Assemblage()
class DataService implements AbstractAssemblage {
async getData() {
const result = await window.electronAPI.invoke('get-data');
return result;
}
}// Main process
@Assemblage()
class DatabaseService implements AbstractAssemblage {
private db: any;
async onInit() {
this.db = await this.connect();
}
async query(sql: string) {
return this.db.query(sql);
}
async onDispose() {
await this.db.close();
}
}
@Assemblage({
inject: [[DatabaseService]]
})
class UserRepository implements AbstractAssemblage {
constructor(private db: DatabaseService) {}
async findById(id: string) {
return this.db.query(`SELECT * FROM users WHERE id = ${id}`);
}
}@Assemblage()
class MultiWindowManager implements AbstractAssemblage {
private windows = new Map<string, BrowserWindow>();
createWindow(id: string, options: BrowserWindowConstructorOptions) {
const window = new BrowserWindow(options);
this.windows.set(id, window);
return window;
}
getWindow(id: string) {
return this.windows.get(id);
}
closeWindow(id: string) {
const window = this.windows.get(id);
window?.close();
this.windows.delete(id);
}
async onDispose() {
for (const window of this.windows.values()) {
window.close();
}
this.windows.clear();
}
}Keep main and renderer logic separated. Use IPC for communication:
// ✅ Good - Separate concerns
// Main: File system, native APIs
// Renderer: UI logicAlways use contextIsolation and disable nodeIntegration:
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}Use lifecycle hooks for proper cleanup:
@Assemblage()
class ResourceManager implements AbstractAssemblage {
async onDispose() {
// Clean up resources
await this.cleanup();
}
}- Node.js: ≥ 18.12.0
- Electron: ≥ 28.0.0
- TypeScript: ≥ 5.0
- reflect-metadata: Required
# Build the package
npx nx build electron
# Run tests
npx nx test electron
# Lint
npx nx lint electronMIT
Part of the AssemblerJS monorepo