μ£Όμš” μ½˜ν…μΈ λ‘œ κ±΄λ„ˆλ›°κΈ°

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​

src/shared/ipc/channels.ts
export const CHANNELS = {
GET_USER_DATA: 'GET_USER_DATA',
SAVE_USER: 'SAVE_USER',
} as const;

export type TChannelKeys = keyof typeof CHANNELS;
src/shared/ipc/events.ts
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;
};
}
src/shared/ipc/preload.ts
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'];
};
src/app/preload/index.ts
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);
src/main/features/user/ipc/send-user.ts
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',
};
});
};
src/renderer/pages/user-settings/ipc/get-user.ts
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' };
};

See also​