Usage with Electron
Electron applications have a special architecture consisting of multiple processes with different responsibilities. Applying FSD in such a context requires adapting the structure to the Electron specifics.
βββ src
βββ app # Common app layer
β βββ main # Main process
β β βββ index.ts # Main process entry point
β βββ preload # Preload script and Context Bridge
β β βββ index.ts # Preload entry point
β βββ renderer # Renderer process
β βββ index.html # Renderer process entry point
βββ main
β βββ features
β β βββ user
β β βββ ipc
β β βββ get-user.ts
β β βββ send-user.ts
β βββ entities
β βββ shared
βββ renderer
β βββ pages
β β βββ settings
β β β βββ ipc
β β β β βββ get-user.ts
β β β β βββ save-user.ts
β β β βββ ui
β β β β βββ user.tsx
β β β βββ index.ts
β β βββ home
β β βββ ui
β β β βββ home.tsx
β β βββ index.ts
β βββ widgets
β βββ features
β βββ entities
β βββ shared
βββ shared # Common code between main and renderer
βββ ipc # IPC description (event names, contracts)
Public API rulesβ
Each process must have its own public API. For example, you can't import modules from main
to renderer
.
Only the src/shared
folder is public for both processes.
It's also necessary for describing contracts for process interaction.
Additional changes to the standard structureβ
It's suggested to use a new ipc
segment, where interaction between processes takes place.
The pages
and widgets
layers, based on their names, should not be present in src/main
. You can use features
, entities
and shared
.
The app
layer in src
contains entry points for main
and renderer
, as well as the IPC.
It's not desirable for segments in the app
layer to have intersection points
Interaction exampleβ
export const CHANNELS = {
GET_USER_DATA: 'GET_USER_DATA',
SAVE_USER: 'SAVE_USER',
} as const;
export type TChannelKeys = keyof typeof CHANNELS;
import { CHANNELS } from './channels';
export interface IEvents {
[CHANNELS.GET_USER_DATA]: {
args: void,
response?: { name: string; email: string; };
};
[CHANNELS.SAVE_USER]: {
args: { name: string; };
response: void;
};
}
import { CHANNELS } from './channels';
import type { IEvents } from './events';
type TOptionalArgs<T> = T extends void ? [] : [args: T];
export type TElectronAPI = {
[K in keyof typeof CHANNELS]: (...args: TOptionalArgs<IEvents[typeof CHANNELS[K]]['args']>) => IEvents[typeof CHANNELS[K]]['response'];
};
import { contextBridge, ipcRenderer } from 'electron';
import { CHANNELS, type TElectronAPI } from 'shared/ipc';
const API: TElectronAPI = {
[CHANNELS.GET_USER_DATA]: () => ipcRenderer.sendSync(CHANNELS.GET_USER_DATA),
[CHANNELS.SAVE_USER]: args => ipcRenderer.invoke(CHANNELS.SAVE_USER, args),
} as const;
contextBridge.exposeInMainWorld('electron', API);
import { ipcMain } from 'electron';
import { CHANNELS } from 'shared/ipc';
export const sendUser = () => {
ipcMain.on(CHANNELS.GET_USER_DATA, ev => {
ev.returnValue = {
name: 'John Doe',
email: 'john.doe@example.com',
};
});
};
import { CHANNELS } from 'shared/ipc';
export const getUser = () => {
const user = window.electron[CHANNELS.GET_USER_DATA]();
return user ?? { name: 'John Donte', email: 'john.donte@example.com' };
};