Использование с Electron
Electron-приложения имеют особую архитектуру, состоящую из нескольких процессов с разными ответственностями. Применение FSD в таком контексте требует адаптации структуры под специфику Electron.
Директорияsrc
Директорияapp
Директорияmain
- index.ts
Директорияpreload
- index.ts
Директорияrenderer
- index.html
Директория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/
Директорияipc/
- …
Правила для публичного API
Заголовок раздела «Правила для публичного API»Каждый процесс должен иметь свой публичный API, как пример, нельзя импортировать модули из main в renderer.
Общедоступным между процессами кодом является только папка src/shared.
Она же необходима для описания контрактов по взаимодействию процессов.
Дополнительные изменения в стандартной структуре
Заголовок раздела «Дополнительные изменения в стандартной структуре»Предлагается использовать новый сегмент ipc, в котором происходит взаимодействие между процессами.
Слои pages и widgets, исходя из названия, не должны присутствовать в src/main, вы можете использовать features, entities и shared.
Слой app в src содержит точки входа для main и renderer, а также IPC.
Сегментам в слое app нежелательно иметь точек пересечения
Пример взаимодействия
Заголовок раздела «Пример взаимодействия»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' };};