Перейти к содержимому

Использование с 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, как пример, нельзя импортировать модули из main в renderer. Общедоступным между процессами кодом является только папка src/shared. Она же необходима для описания контрактов по взаимодействию процессов.

Дополнительные изменения в стандартной структуре

Заголовок раздела «Дополнительные изменения в стандартной структуре»

Предлагается использовать новый сегмент ipc, в котором происходит взаимодействие между процессами. Слои pages и widgets, исходя из названия, не должны присутствовать в src/main, вы можете использовать features, entities и shared. Слой app в src содержит точки входа для main и renderer, а также IPC. Сегментам в слое app нежелательно иметь точек пересечения

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' };
};