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.
Directorysrc
Directoryapp Common app layer
Directorymain Main process
- index.ts Main process entry point
Directorypreload Preload script and Context Bridge
- index.ts Preload entry point
Directoryrenderer Renderer process
- index.html Renderer process entry point
Directorymain
Directoryfeatures
Directoryuser
Directoryipc
- get-user.ts
- send-user.ts
- entities
- shared
Directoryrenderer
Directorypages
Directorysettings
Directoryipc
- get-user.ts
- save-user.ts
Directoryui
- user.tsx
- index.ts
Directoryhome
Directoryui
- home.tsx
- index.ts
- widgets
- features
- entities
- shared
Directoryshared Common code between main and renderer
- ipc IPC description (event names, contracts)
Public API rules
Section titled “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
Section titled “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
Section titled “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' };};