Main Process Architecture Guide. Dependency Injection Pattern (DIP)...
Use this skill when you want to:
The main process architecture is built on a Dependency Injection Container pattern, utilizing TypeScript decorators and metadata reflection. This architecture enables modular, testable, and maintainable Electron main process code.
All dependencies are injected through constructors, never instantiated directly within classes.
Anti-Pattern (Don't do this):
@Injectable()
export class BadService {
private authService = new AuthService(); // ā Direct instantiation
}
Correct Pattern:
@Injectable()
export class GoodService {
constructor(private authService: AuthService) {} // ā
Injected dependency
}
Each service, IPC handler, and window manager should have one clear responsibility.
When one module needs functionality from another module, use the Provider Pattern to expose only the necessary interface.
Modules export only what other modules need via the exports array in @RgModule.
app.ts (Bootstrap) initializes every module through bootstrapModules so that the DI container and metadata decorators can hydrate the application graph.Module Layer (@RgModule) defines metadata such as imports, providers, ipc, windows, and exports, giving structure to each feature module.Service Layer, IPC Handlers, and Window Managers represent the main entry points for business logic, renderer communication, and BrowserWindow lifecycle hooks within a module.Shared Utilities ($shared, config) provide cross-cutting helpers that can be used by services, IPC handlers, and windows to share configuration, storage, and utility functions.Every feature module follows this structure:
module-name/
āāā module.ts # Module definition with @RgModule decorator
āāā service.ts # Business logic (optional, can be multiple services)
āāā ipc.ts # IPC communication handlers (optional)
āāā window.ts # BrowserWindow management (optional)
āāā tokens.ts # Dependency injection tokens (optional)
āāā types.ts # TypeScript type definitions (optional)
āāā services/ # Additional services (optional)
āāā service-a.ts
āāā service-b.ts
import { RgModule } from "../@core/decorators/rg-module.js";
import { SomeService } from "./service.js";
import { SomeIpc } from "./ipc.js";
import { SomeWindow } from "./window.js";
@RgModule({
imports: [], // Other modules this module depends on
providers: [], // Services, factories, and values to register
ipc: [], // IPC handlers
windows: [], // Window managers
exports: [], // Services to expose to other modules
})
export class SomeModule {}
Class[]): Modules to import so their exported providers become available in this module.Provider[]): Services, factories, or values registered in the DI container for this module.Class[]): IPC handler classes decorated with @IpcHandler() to manage renderer/main communication.Class[]): Window manager classes decorated with @WindowManager() that control BrowserWindow lifecycles.Class[]): Providers that this module exposes to any module that imports it.Defines a module and its dependencies.
Usage:
@RgModule({
imports: [RestApiModule, CryptoModule],
providers: [UserService],
ipc: [UserIpc],
windows: [],
exports: [UserService],
})
export class UserModule {}
Marks a class as injectable into the DI container.
Usage:
@Injectable()
export class UserService {
constructor(
@Inject(USER_REST_API_PROVIDER)
private restApiProvider: TUserRestApiProvider
) {}
async fetchUser(id: string): Promise<TUser | undefined> {
// Implementation
}
}
Injects a dependency using a token (Symbol).
Usage:
@Injectable()
export class SomeService {
constructor(@Inject(SOME_TOKEN) private dependency: TSomeType) {}
}
Marks a class as an IPC communication handler.
Usage:
@IpcHandler()
export class UserIpc implements TIpcHandlerInterface {
constructor(private userService: UserService) {}
async onInit({ getWindow }: TParamOnInit<TWindows["main"]>) {
const mainWindow = getWindow("window:main");
ipcMainOn("fetchUser", async (event, userId: string) => {
const user = await this.userService.fetchUser(userId);
event.reply("fetchUser", user);
});
}
}
Lifecycle:
onInit() is called when the module is bootstrappedDefines a window manager with configuration.
Usage:
@WindowManager<TWindows["main"]>({
hash: "window:main",
isCache: true,
options: {
width: 800,
height: 600,
show: false,
resizable: true,
},
})
export class MainWindow implements TWindowManager {
constructor(@Inject(SOME_PROVIDER) private someProvider: TSomeProvider) {}
onWebContentsDidFinishLoad(window: BrowserWindow): void {
// Called after window content finishes loading
}
}
Lifecycle Methods:
onWebContentsDidFinishLoad(window) - Called when web contents finish loadingPurpose: Declare module metadata and wire dependencies.
Template:
import { RgModule } from "../@core/decorators/rg-module.js";
import { DependencyModule } from "../dependency/module.js";
import { DependencyService } from "../dependency/service.js";
import { FeatureService } from "./service.js";
import { FeatureIpc } from "./ipc.js";
import { FEATURE_DEPENDENCY_PROVIDER } from "./tokens.js";
import type { TFeatureDependencyProvider } from "./types.js";
@RgModule({
imports: [DependencyModule],
ipc: [FeatureIpc],
providers: [
FeatureService,
{
provide: FEATURE_DEPENDENCY_PROVIDER,
useFactory: (
depService: DependencyService
): TFeatureDependencyProvider => ({
someMethod: (args) => depService.someMethod(args),
}),
inject: [DependencyService],
},
],
exports: [FeatureService],
})
export class FeatureModule {}
Purpose: Implement core business logic and data operations.
Template:
import { Injectable } from "../@core/decorators/injectable.js";
import { Inject } from "../@core/decorators/inject.js";
import { FEATURE_DEPENDENCY_PROVIDER } from "./tokens.js";
import type { TFeatureDependencyProvider } from "./types.js";
@Injectable()
export class FeatureService {
constructor(
@Inject(FEATURE_DEPENDENCY_PROVIDER)
private dependencyProvider: TFeatureDependencyProvider
) {}
async performAction(input: string): Promise<TResult | undefined> {
const result = await this.dependencyProvider.someMethod(input);
if (result.error !== undefined) {
console.error("Error:", result.error);
return undefined;
}
return result.data;
}
}
Purpose: Handle IPC messages between main and renderer processes.
Template:
import { ipcMainOn } from "../$shared/utils.js";
import { IpcHandler } from "../@core/decorators/ipc-handler.js";
import type {
TIpcHandlerInterface,
TParamOnInit,
} from "../@core/types/ipc-handler.js";
import { FeatureService } from "./service.js";
@IpcHandler()
export class FeatureIpc implements TIpcHandlerInterface {
constructor(private featureService: FeatureService) {}
async onInit({ getWindow }: TParamOnInit<TWindows["main"]>) {
const mainWindow = getWindow("window:main");
ipcMainOn("featureAction", async (event, payload: TPayload) => {
const result = await this.featureService.performAction(payload.input);
event.reply("featureAction", result);
});
}
}
Purpose: Manage BrowserWindow lifecycle and interactions.
Template:
import { BrowserWindow } from "electron";
import { WindowManager } from "../@core/decorators/window-manager.js";
import { Inject } from "../@core/decorators/inject.js";
import { isDev } from "../$shared/utils.js";
import type { TWindowManager } from "../types.js";
import { FEATURE_PROVIDER } from "./tokens.js";
import type { TFeatureProvider } from "./types.js";
@WindowManager<TWindows["feature"]>({
hash: "window:feature",
isCache: true,
options: {
width: 500,
height: 400,
show: false,
resizable: isDev(),
},
})
export class FeatureWindow implements TWindowManager {
constructor(@Inject(FEATURE_PROVIDER) private provider: TFeatureProvider) {}
onWebContentsDidFinishLoad(window: BrowserWindow): void {
// Initialize window after content loads
this.provider.initialize(window);
}
}
Purpose: Define unique symbols for dependency injection.
Template:
export const FEATURE_PROVIDER = Symbol("FEATURE_PROVIDER");
export const FEATURE_DEPENDENCY_PROVIDER = Symbol(
"FEATURE_DEPENDENCY_PROVIDER"
);
Purpose: Define TypeScript types and interfaces.
Template:
import type { BrowserWindow } from "electron";
export type TFeatureProvider = {
initialize: (window: BrowserWindow) => void;
cleanup: () => void;
};
export type TFeatureDependencyProvider = {
someMethod: (input: string) => Promise<TResult>;
};
export type TResult = {
data?: any;
error?: {
message: string;
code?: string;
};
};
Use Case: Utility services with no IPC or windows.
Example: CryptoModule, MenuModule, TrayModule
Structure:
// module.ts
@RgModule({
providers: [CryptoService],
exports: [CryptoService],
})
export class CryptoModule {}
// service.ts
@Injectable()
export class CryptoService {
async encrypt(key: string, data: string): Promise<string> {
// Implementation
}
async decrypt(key: string, encrypted: string): Promise<string> {
// Implementation
}
}
Use Case: Service that depends on other modules.
Example: AuthModule, UserModule
Structure:
// module.ts
@RgModule({
imports: [RestApiModule],
providers: [
AuthService,
{
provide: AUTH_REST_API_PROVIDER,
useFactory: (restApiService: RestApiService): TAuthRestApiProvider => ({
get: (endpoint, options) => restApiService.get(endpoint, options),
}),
inject: [RestApiService],
},
],
exports: [AuthService],
})
export class AuthModule {}
// tokens.ts
export const AUTH_REST_API_PROVIDER = Symbol("AUTH_REST_API_PROVIDER");
// types.ts
export type TAuthRestApiProvider = {
get: <T>(endpoint: string, options?: any) => Promise<TApiResponse<T>>;
};
// service.ts
@Injectable()
export class AuthService {
constructor(
@Inject(AUTH_REST_API_PROVIDER)
private restApiProvider: TAuthRestApiProvider
) {}
async login(credentials: TCredentials): Promise<TUser | undefined> {
const response = await this.restApiProvider.get("/auth/login");
return response.data;
}
}
Use Case: Feature that communicates with renderer process.
Example: NotificationModule, UserModule
Structure:
// module.ts
@RgModule({
ipc: [NotificationIpc],
providers: [NotificationService],
exports: [NotificationService],
})
export class NotificationModule {}
// ipc.ts
@IpcHandler()
export class NotificationIpc implements TIpcHandlerInterface {
constructor(private notificationService: NotificationService) {}
async onInit({ getWindow }: TParamOnInit<TWindows["main"]>) {
ipcMainOn(
"showNotification",
async (event, options: TNotificationOptions) => {
this.notificationService.setNotification(options);
event.reply("showNotification", { success: true });
}
);
}
}
Use Case: Feature that creates and manages BrowserWindows.
Example: AppModule, ResourcesModule
Structure:
// module.ts
@RgModule({
imports: [MenuModule, TrayModule],
ipc: [AppIpc],
windows: [AppWindow],
providers: [
AppService,
{
provide: MENU_PROVIDER,
useFactory: (menuService: MenuService): TMenuProvider => ({
buildMenu: (items) => menuService.buildMenu(items),
}),
inject: [MenuService],
},
],
})
export class AppModule {}
// window.ts
@WindowManager<TWindows["main"]>({
hash: "window:main",
isCache: true,
options: {
width: 800,
height: 600,
show: false,
},
})
export class AppWindow implements TWindowManager {
constructor(@Inject(MENU_PROVIDER) private menuProvider: TMenuProvider) {}
onWebContentsDidFinishLoad(window: BrowserWindow): void {
this.menuProvider.buildMenu();
window.show();
}
}
// ipc.ts
@IpcHandler()
export class AppIpc implements TIpcHandlerInterface {
constructor(private appService: AppService) {}
async onInit({ getWindow }: TParamOnInit<TWindows["main"]>) {
const mainWindow = getWindow("window:main");
const window = await mainWindow.create();
ipcMainOn("closeApp", () => {
window?.close();
});
}
}
Use Case: Large modules with multiple services.
Example: ResourcesModule, UpdaterModule
Structure:
resources/
āāā module.ts
āāā ipc/
ā āāā actions.ts
ā āāā open.ts
āāā services/
ā āāā resources.ts
ā āāā cacheWindows.ts
ā āāā types.ts
āāā windows/
ā āāā add.ts
ā āāā update.ts
ā āāā delete.ts
āāā tokens.ts
āāā types.ts
// module.ts
@RgModule({
imports: [RestApiModule, CryptoModule, TrayModule],
ipc: [ResourcesActionsIpc, ResourcesOpenIpc],
windows: [UpdateWindow, AddWindow, DeleteWindow],
providers: [
ResourcesService,
CacheWindowsService,
{
provide: RESOURCES_REST_API_PROVIDER,
useFactory: (
restApiService: RestApiService
): TResourcesRestApiProvider => ({
get: (endpoint, options) => restApiService.get(endpoint, options),
post: (endpoint, data, options) =>
restApiService.post(endpoint, data, options),
put: (endpoint, data, options) =>
restApiService.put(endpoint, data, options),
delete: (endpoint, options) => restApiService.delete(endpoint, options),
}),
inject: [RestApiService],
},
{
provide: RESOURCES_CRYPTO_PROVIDER,
useFactory: (cryptoService: CryptoService): TResourcesCryptoProvider => ({
encrypt: (key, value) => cryptoService.encrypt(key, value),
decrypt: (key, vault) => cryptoService.decrypt(key, vault),
}),
inject: [CryptoService],
},
],
exports: [ResourcesService],
})
export class ResourcesModule {}
// services/resources.ts
@Injectable()
export class ResourcesService {
constructor(
@Inject(RESOURCES_REST_API_PROVIDER)
private restApiProvider: TResourcesRestApiProvider
) {}
async byId(id: string): Promise<TResource | undefined> {
const response = await this.restApiProvider.get(`/resources/${id}`);
return response.data;
}
async create(data: TResourceData): Promise<TResource | undefined> {
const response = await this.restApiProvider.post("/resources", data);
return response.data;
}
}
Providers create abstraction layers between modules, exposing only necessary functionality while hiding implementation details.
1. Create Token:
// tokens.ts
export const SERVICE_PROVIDER = Symbol("SERVICE_PROVIDER");
2. Define Provider Type:
// types.ts
export type TServiceProvider = {
methodA: (arg: string) => Promise<TResult>;
methodB: () => void;
};
3. Register in Module:
// module.ts
@RgModule({
imports: [DependencyModule],
providers: [
{
provide: SERVICE_PROVIDER,
useFactory: (depService: DependencyService): TServiceProvider => ({
methodA: (arg) => depService.methodA(arg),
methodB: () => depService.methodB(),
}),
inject: [DependencyService],
},
],
})
export class FeatureModule {}
4. Inject in Consumer:
// service.ts
@Injectable()
export class ConsumerService {
constructor(@Inject(SERVICE_PROVIDER) private provider: TServiceProvider) {}
async useProvider(): Promise<void> {
await this.provider.methodA("input");
this.provider.methodB();
}
}
providers: [MyService];
// Equivalent to:
providers: [
{
provide: MyService,
useClass: MyService,
},
];
providers: [
{
provide: MY_TOKEN,
useFactory: (dep: Dependency): TMyType => ({
method: () => dep.doSomething(),
}),
inject: [Dependency],
},
];
providers: [
{
provide: CONFIG_TOKEN,
useValue: {
apiUrl: "https://api.example.com",
timeout: 5000,
},
},
];
Goal: Create a module that manages application settings with IPC communication.
Step 1: Create Directory Structure
src/main/settings/
āāā module.ts
āāā service.ts
āāā ipc.ts
āāā tokens.ts
āāā types.ts
Step 2: Define Types
// types.ts
export type TSettings = {
theme: "light" | "dark";
language: string;
autoUpdate: boolean;
};
export type TUpdateSettingsPayload = Partial<TSettings>;
Step 3: Create Service
// service.ts
import { Injectable } from "../@core/decorators/injectable.js";
import { getElectronStorage, setElectronStorage } from "../$shared/store.js";
@Injectable()
export class SettingsService {
constructor() {}
getSettings(): TSettings {
const settings = getElectronStorage("settings");
return (
settings ?? {
theme: "light",
language: "en",
autoUpdate: true,
}
);
}
updateSettings(updates: TUpdateSettingsPayload): TSettings {
const currentSettings = this.getSettings();
const newSettings = { ...currentSettings, ...updates };
setElectronStorage("settings", newSettings);
return newSettings;
}
}
Step 4: Create IPC Handler
// ipc.ts
import { ipcMainOn } from "../$shared/utils.js";
import { IpcHandler } from "../@core/decorators/ipc-handler.js";
import type {
TIpcHandlerInterface,
TParamOnInit,
} from "../@core/types/ipc-handler.js";
import { SettingsService } from "./service.js";
import type { TUpdateSettingsPayload } from "./types.js";
@IpcHandler()
export class SettingsIpc implements TIpcHandlerInterface {
constructor(private settingsService: SettingsService) {}
async onInit({ getWindow }: TParamOnInit<TWindows["main"]>) {
ipcMainOn("getSettings", (event) => {
const settings = this.settingsService.getSettings();
event.reply("getSettings", settings);
});
ipcMainOn("updateSettings", (event, payload: TUpdateSettingsPayload) => {
const settings = this.settingsService.updateSettings(payload);
event.reply("updateSettings", settings);
});
}
}
Step 5: Create Module
// module.ts
import { RgModule } from "../@core/decorators/rg-module.js";
import { SettingsService } from "./service.js";
import { SettingsIpc } from "./ipc.js";
@RgModule({
ipc: [SettingsIpc],
providers: [SettingsService],
exports: [SettingsService],
})
export class SettingsModule {}
Step 6: Register in app.ts
// app.ts
import { SettingsModule } from "./settings/module.js";
app.on("ready", async () => {
await bootstrapModules([
AppPreloadModule,
AppModule,
SettingsModule, // Add here
// ... other modules
]);
});
Goal: Create a Weather module that fetches weather data from an external API.
Step 1: Create Structure
src/main/weather/
āāā module.ts
āāā service.ts
āāā ipc.ts
āāā tokens.ts
āāā types.ts
Step 2: Define Types
// types.ts
export type TWeatherData = {
temperature: number;
condition: string;
humidity: number;
location: string;
};
export type TWeatherRestApiProvider = {
get: <T>(endpoint: string, options?: any) => Promise<TApiResponse<T>>;
};
type TApiResponse<T> = {
data?: T;
error?: {
message: string;
code?: string;
};
status: number;
};
Step 3: Create Tokens
// tokens.ts
export const WEATHER_REST_API_PROVIDER = Symbol("WEATHER_REST_API_PROVIDER");
Step 4: Create Service
// service.ts
import { Injectable } from "../@core/decorators/injectable.js";
import { Inject } from "../@core/decorators/inject.js";
import { WEATHER_REST_API_PROVIDER } from "./tokens.js";
import type { TWeatherRestApiProvider, TWeatherData } from "./types.js";
@Injectable()
export class WeatherService {
constructor(
@Inject(WEATHER_REST_API_PROVIDER)
private restApiProvider: TWeatherRestApiProvider
) {}
async getWeather(city: string): Promise<TWeatherData | undefined> {
const response = await this.restApiProvider.get<TWeatherData>(
`https://api.weather.com/v1/weather?city=${city}`,
{
headers: {
"Content-Type": "application/json",
},
}
);
if (response.error !== undefined) {
console.error("Weather API Error:", response.error);
return undefined;
}
return response.data;
}
}
Step 5: Create IPC Handler
// ipc.ts
import { ipcMainOn } from "../$shared/utils.js";
import { IpcHandler } from "../@core/decorators/ipc-handler.js";
import type {
TIpcHandlerInterface,
TParamOnInit,
} from "../@core/types/ipc-handler.js";
import { WeatherService } from "./service.js";
@IpcHandler()
export class WeatherIpc implements TIpcHandlerInterface {
constructor(private weatherService: WeatherService) {}
async onInit({ getWindow }: TParamOnInit<TWindows["main"]>) {
ipcMainOn("getWeather", async (event, city: string) => {
const weather = await this.weatherService.getWeather(city);
event.reply("getWeather", weather);
});
}
}
Step 6: Create Module with Provider
// module.ts
import { RgModule } from "../@core/decorators/rg-module.js";
import { RestApiModule } from "../rest-api/module.js";
import { RestApiService } from "../rest-api/service.js";
import { WeatherService } from "./service.js";
import { WeatherIpc } from "./ipc.js";
import { WEATHER_REST_API_PROVIDER } from "./tokens.js";
import type { TWeatherRestApiProvider } from "./types.js";
@RgModule({
imports: [RestApiModule],
ipc: [WeatherIpc],
providers: [
WeatherService,
{
provide: WEATHER_REST_API_PROVIDER,
useFactory: (
restApiService: RestApiService
): TWeatherRestApiProvider => ({
get: (endpoint, options) => restApiService.get(endpoint, options),
}),
inject: [RestApiService],
},
],
exports: [WeatherService],
})
export class WeatherModule {}
Goal: Create a Preferences window module.
Step 1: Create Structure
src/main/preferences/
āāā module.ts
āāā service.ts
āāā ipc.ts
āāā window.ts
āāā tokens.ts
āāā types.ts
Step 2: Update Window Types
// src/main/config.ts (add to windows object)
export const windows: TWindows = {
// ... existing windows
preferences: "window:preferences",
};
Step 3: Define Types
// types.ts
import type { BrowserWindow } from "electron";
export type TPreferencesProvider = {
openPreferences: () => void;
closePreferences: () => void;
};
Step 4: Create Tokens
// tokens.ts
export const PREFERENCES_PROVIDER = Symbol("PREFERENCES_PROVIDER");
Step 5: Create Service
// service.ts
import { Injectable } from "../@core/decorators/injectable.js";
import { getWindow } from "../@core/control-window/receive.js";
@Injectable()
export class PreferencesService {
constructor() {}
async openPreferences(): Promise<void> {
const preferencesWindow =
getWindow<TWindows["preferences"]>("window:preferences");
if (preferencesWindow !== undefined) {
const window = await preferencesWindow.create();
window?.show();
}
}
closePreferences(): void {
const preferencesWindow =
getWindow<TWindows["preferences"]>("window:preferences");
if (preferencesWindow !== undefined) {
preferencesWindow.hide();
}
}
}
Step 6: Create Window Manager
// window.ts
import { BrowserWindow } from "electron";
import { WindowManager } from "../@core/decorators/window-manager.js";
import { isDev } from "../$shared/utils.js";
import type { TWindowManager } from "../types.js";
@WindowManager<TWindows["preferences"]>({
hash: "window:preferences",
isCache: true,
options: {
width: 600,
height: 500,
show: false,
resizable: isDev(),
title: "Preferences",
},
})
export class PreferencesWindow implements TWindowManager {
constructor() {}
onWebContentsDidFinishLoad(window: BrowserWindow): void {
// Initialize preferences window
console.log("Preferences window loaded");
}
}
Step 7: Create IPC Handler
// ipc.ts
import { ipcMainOn } from "../$shared/utils.js";
import { IpcHandler } from "../@core/decorators/ipc-handler.js";
import type {
TIpcHandlerInterface,
TParamOnInit,
} from "../@core/types/ipc-handler.js";
import { PreferencesService } from "./service.js";
@IpcHandler()
export class PreferencesIpc implements TIpcHandlerInterface {
constructor(private preferencesService: PreferencesService) {}
async onInit({ getWindow }: TParamOnInit<TWindows["main"]>) {
ipcMainOn("openPreferences", async () => {
await this.preferencesService.openPreferences();
});
ipcMainOn("closePreferences", () => {
this.preferencesService.closePreferences();
});
}
}
Step 8: Create Module
// module.ts
import { RgModule } from "../@core/decorators/rg-module.js";
import { PreferencesService } from "./service.js";
import { PreferencesIpc } from "./ipc.js";
import { PreferencesWindow } from "./window.js";
@RgModule({
ipc: [PreferencesIpc],
windows: [PreferencesWindow],
providers: [PreferencesService],
exports: [PreferencesService],
})
export class PreferencesModule {}
ā Do:
ā Don't:
ā Do:
@Injectable() decorator on all servicesundefined on errors with loggingā Don't:
new MyService())any typeā Do:
ā Don't:
ā Do:
ipcMainOn helper for type safetyā Don't:
ā Do:
onWebContentsDidFinishLoad for initializationisCache: true for singleton windowsā Don't:
ā Do:
undefined on errorsdialog.showMessageBox for critical errorsā Don't:
ā Do:
types.tsā Don't:
any typeā Do:
PascalCase for class namescamelCase for method and variable namesUPPER_SNAKE_CASE for constants and tokensT (e.g., TUser, TSettings)ModuleServiceIpcWindowā Don't:
ā Do:
services/ subfolder for multiple servicesipc/ subfolder for multiple IPC handlerswindows/ subfolder for multiple windowsā Don't:
ā Do:
importsā Don't:
@RgModule({
providers: [UtilityService],
exports: [UtilityService],
})
export class UtilityModule {}
@RgModule({
imports: [DependencyModule],
providers: [
FeatureService,
{
provide: FEATURE_PROVIDER,
useFactory: (dep: DependencyService) => ({
method: () => dep.method(),
}),
inject: [DependencyService],
},
],
exports: [FeatureService],
})
export class FeatureModule {}
@RgModule({
ipc: [FeatureIpc],
providers: [FeatureService],
exports: [FeatureService],
})
export class FeatureModule {}
@RgModule({
imports: [DependencyModule],
ipc: [FeatureIpc],
windows: [FeatureWindow],
providers: [FeatureService],
})
export class FeatureModule {}
@RgModule({
imports: [Dep1Module, Dep2Module, Dep3Module],
ipc: [Feature1Ipc, Feature2Ipc],
windows: [Window1, Window2],
providers: [
Service1,
Service2,
Service3,
{ provide: PROVIDER_1, useFactory: ..., inject: [...] },
{ provide: PROVIDER_2, useFactory: ..., inject: [...] },
],
exports: [Service1],
})
export class ComplexModule {}
Cause: Service is not registered in module providers or dependency module is not imported.
Solution:
providers arrayimports arrayCause: Two modules import each other.
Solution:
Cause: Window not registered in module or hash mismatch.
Solution:
windows array in @RgModulehash in @WindowManager matches usageCause: IPC handler not registered or channel name mismatch.
Solution:
ipc array in @RgModuleonInit is implementedThis architecture provides a robust, scalable foundation for Electron main process development using Dependency Injection and modular design. By following these patterns and best practices, you can create maintainable, testable, and well-organized code.
Key Takeaways:
@RgModule, @Injectable, @IpcHandler, @WindowManager) for metadata-driven architectureWhen generating new code, always follow these patterns to maintain architectural consistency.