Skip to content

docs(guides): add electron tech guide #824

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
---
sidebar_position: 10
---
# Использование с Electron

Electron-приложения имеют особую архитектуру, состоящую из нескольких процессов с разными ответственностями. Применение FSD в таком контексте требует адаптации структуры под специфику Electron.

```sh
└── src
├── app # Общий сегмент app
│ ├── main # Main процесс
│ │ └── index.ts # Точка входа main процесса
│ ├── preload # Preload скрипт и Context Bridge
│ │ └── index.ts # Точка входа preload
│ └── renderer # Renderer процесс
│ └── index.html # Точка входа renderer процесса
├── main
│ ├── services
│ │ └── user
│ │ └── ipc
│ │ ├── get-user.ts
│ │ └── send-user.ts
│ └── shared
├── renderer
│ └── pages
│ │ ├── settings
│ │ │ ├── ipc
│ │ │ │ ├── get-user.ts
│ │ │ │ └── save-user.ts
│ │ │ ├── ui
│ │ │ │ └── user.tsx
│ │ │ └── index.ts
│ │ └── home
│ │ ├── ui
│ │ │ └── home.tsx
│ │ └── index.ts
│ └── shared
└── shared # Общий код между main и renderer
└── ipc # Описание IPC (наименование event'ов, контракты)
```

## Правила для публичного API
Каждый процесс должен иметь свой публичный API, как пример, нельзя импортировать модули из `main` в `renderer`.
Общедоступным между процессами кодом является только папка `src/shared`.
Она же необходима для описания контрактов по взаимодействию процессов.

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

## Пример взаимодействия

```typescript title="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;
```

```typescript title="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;
};
}
```

```typescript title="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'];
};
```

```typescript title="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);
```

```typescript title="src/main/services/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: '[email protected]',
};
});
};
```

```typescript title="src/renderer/page/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 Dont e', email: '[email protected]' };
};
```

## См. также
- [Документация по моделям процессов](https://www.electronjs.org/docs/latest/tutorial/process-model)
- [Документация по изоляции контекстов](https://www.electronjs.org/docs/latest/tutorial/context-isolation)
- [Документация по IPC](https://www.electronjs.org/docs/latest/tutorial/ipc)
- [Пример](https://github.com/georgkrom/electron-fsd.git)
10 changes: 10 additions & 0 deletions src/pages/examples/_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,5 +426,15 @@ export const examples: Example[] = [
updatedAt: "2024-08-10",
tech: ["react", "redux-toolkit", "typescript"],
},
{
title: "Electron FSD",
description:
'Electron application template using Feature-Sliced Design',
source: "https://github.com/georgkrom/electron-fsd",
preview: require("./img/electron-fsd.png"),
version: VERSIONS.V2,
updatedAt: "2025-06-09",
tech: ["react", "electron", "typescript"],
},
// Reverse the list (last examples should be at the top)
].reverse();
Binary file added src/pages/examples/img/electron-fsd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.