From c4bc79839b162b9a827bf9a814f95a170430f12e Mon Sep 17 00:00:00 2001 From: GeorgKrom Date: Fri, 23 May 2025 19:33:30 +0600 Subject: [PATCH 1/9] docs(guides): add electron tech guide --- .../current/guides/tech/with-electron.mdx | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx new file mode 100644 index 0000000000..e683e8590f --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx @@ -0,0 +1,103 @@ +--- +sidebar_position: 10 +--- +# Использование с Electron + +Electron-приложения имеют особую архитектуру, состоящую из нескольких процессов с разными ответственностями. Применение FSD в таком контексте требует адаптации структуры под специфику Electron. + +```sh +└── src + ├── preload # preload скрипт и context bridge + ├── main # main процесс + │ ├── app + │ ├── processes # используется вместо слоёв pages и widgets + │ ├── features + │ ├── entities + │ └── shared + ├── renderer # renderer процесс + │ ├── app + │ ├── pages + │ ├── widgets + │ ├── features + │ ├── entities + │ └── shared + └── shared # общий код между main и renderer, описание IPC (наименование event'ов, контракты) +``` + +## Правила для публичного API +Каждый процесс должен иметь свой публиный API, нельзя импортировать модули из `main` в `renderer`. Общедоступным кодом является только папка `src/shared`. Она же служит для описания контрактов по взаимодействию процессов. + +## Дополнительные изменения в стандартной структуре +Предлагается использовать новый сегмент `ipc`, в котором происходит взаимодействие между процессами +Чтобы решить проблему отсутствия `pages` и `widgets` как таковых в `main` процессе, предлагается использовать вместо них слой `processes`. + +## Пример взаимодействия + +```typescript title="src/shared/constants/ipc-channels.ts" +export const IPC_CHANNELS = { + GET_USER_DATA: 'GET_USER_DATA', + SAVE_SETTINGS: 'SAVE_SETTINGS', +} as const; + +export type TChannelKeys = keyof typeof IPC_CHANNELS; +``` + +```typescript title="src/shared/types/ipc-events.ts" +import { IPC_CHANNELS } from '../constants/ipc-channels'; + +export interface IPCEvents { + [IPC_CHANNELS.GET_USER_DATA]: { + response: { name: string; email: string; }; + }; + [IPC_CHANNELS.SAVE_SETTINGS]: { + args: { theme: string; }; + response: void; + }; +} +``` + +```typescript title="src/shared/types/preload.ts" +import { IPC_CHANNELS } from '../constants/ipc-channels'; +import type { IPCEvents } from './ipc-events'; + +export type TElectronAPI = { + [K in keyof typeof IPC_CHANNELS]: IPCEvents[typeof IPC_CHANNELS[K]]['response'] extends void + ? (args: IPCEvents[typeof IPC_CHANNELS[K]]['args']) => void + : (args: IPCEvents[typeof IPC_CHANNELS[K]]['args']) => Promise; +}; +``` + +```typescript title="src/preload/context-bridge.ts" +import { contextBridge, ipcRenderer } from 'electron'; +import { IPC_CHANNELS } from 'shared/constants'; +import type { TElectronAPI } from 'shared/types'; + +const API: TElectronAPI = { + [IPC_CHANNELS.GET_USER_DATA]: () => ipcRenderer.invoke(IPC_CHANNELS.GET_USER_DATA), + [IPC_CHANNELS.SAVE_SETTINGS]: (args) => ipcRenderer.send(IPC_CHANNELS.SAVE_SETTINGS, args), +} as const; + +contextBridge.exposeInMainWorld('electron', API); +``` + +```typescript title="src/main/processes/user-manager/ipc/send-user-data.ts" +import { ipcMain } from 'electron'; +import { IPC_CHANNELS } from 'shared/constants'; + +ipcMain.handle(IPC_CHANNELS.GET_USER_DATA, () => { + return { name: 'John Doe', email: 'john.doe@example.com' }; +}); +``` + +```typescript title="src/renderer/page/user-settings/ipc/get-user-data.ts" +import { IPC_CHANNELS } from 'shared/constants'; + +const getUserData = async () => { + const userData = await window.electron[IPC_CHANNELS.GET_USER_DATA](); +}; +``` + +## См. также +- [Документация по моделям процессов](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) \ No newline at end of file From 7772df5cb01929cc5fd306fa04afbde13c75d4bb Mon Sep 17 00:00:00 2001 From: GeorgKrom Date: Fri, 23 May 2025 19:57:59 +0600 Subject: [PATCH 2/9] fix: spelling error --- .../current/guides/tech/with-electron.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx index e683e8590f..362e6abc3e 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx @@ -25,7 +25,7 @@ Electron-приложения имеют особую архитектуру, с ``` ## Правила для публичного API -Каждый процесс должен иметь свой публиный API, нельзя импортировать модули из `main` в `renderer`. Общедоступным кодом является только папка `src/shared`. Она же служит для описания контрактов по взаимодействию процессов. +Каждый процесс должен иметь свой публичный API, нельзя импортировать модули из `main` в `renderer`. Общедоступным между процессами кодом является только папка `src/shared`. Она же служит для описания контрактов по взаимодействию процессов. ## Дополнительные изменения в стандартной структуре Предлагается использовать новый сегмент `ipc`, в котором происходит взаимодействие между процессами From 7ff2cd5126b6ae9a40b3a610d9b47ca025c10105 Mon Sep 17 00:00:00 2001 From: GeorgKrom Date: Sun, 25 May 2025 17:08:50 +0600 Subject: [PATCH 3/9] docs(guides): update with-electron.mdx --- .../current/guides/tech/with-electron.mdx | 136 +++++++++++++----- 1 file changed, 104 insertions(+), 32 deletions(-) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx index 362e6abc3e..645940da99 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx @@ -7,21 +7,20 @@ Electron-приложения имеют особую архитектуру, с ```sh └── src - ├── preload # preload скрипт и context bridge - ├── main # main процесс - │ ├── app - │ ├── processes # используется вместо слоёв pages и widgets + ├── app + ├── main # Main процесс + │ ├── widgets │ ├── features │ ├── entities │ └── shared - ├── renderer # renderer процесс - │ ├── app + ├── renderer # Renderer процесс │ ├── pages │ ├── widgets │ ├── features │ ├── entities │ └── shared - └── shared # общий код между main и renderer, описание IPC (наименование event'ов, контракты) + └── shared # Общий код между main и renderer + └── ipc # Описание IPC (наименование event'ов, контракты) ``` ## Правила для публичного API @@ -29,74 +28,147 @@ Electron-приложения имеют особую архитектуру, с ## Дополнительные изменения в стандартной структуре Предлагается использовать новый сегмент `ipc`, в котором происходит взаимодействие между процессами -Чтобы решить проблему отсутствия `pages` и `widgets` как таковых в `main` процессе, предлагается использовать вместо них слой `processes`. +Слой `pages`, исходя из названия, не должен присутствовать в `src/main`. Слой `app` в `src` содержит точки входа для `main` и `renderer`, а также IPC. ## Пример взаимодействия -```typescript title="src/shared/constants/ipc-channels.ts" -export const IPC_CHANNELS = { +```typescript title="src/shared/ipc/channels.ts" +export const CHANNELS = { GET_USER_DATA: 'GET_USER_DATA', SAVE_SETTINGS: 'SAVE_SETTINGS', } as const; -export type TChannelKeys = keyof typeof IPC_CHANNELS; +export type TChannelKeys = keyof typeof CHANNELS; ``` -```typescript title="src/shared/types/ipc-events.ts" -import { IPC_CHANNELS } from '../constants/ipc-channels'; +```typescript title="src/shared/ipc/events.ts" +import { CHANNELS } from './channels'; -export interface IPCEvents { - [IPC_CHANNELS.GET_USER_DATA]: { +export interface IEvents { + [CHANNELS.GET_USER_DATA]: { response: { name: string; email: string; }; }; - [IPC_CHANNELS.SAVE_SETTINGS]: { + [CHANNELS.SAVE_SETTINGS]: { args: { theme: string; }; response: void; }; } ``` -```typescript title="src/shared/types/preload.ts" -import { IPC_CHANNELS } from '../constants/ipc-channels'; -import type { IPCEvents } from './ipc-events'; +```typescript title="src/shared/ipc/preload.ts" +import { CHANNELS } from './channels'; +import type { IEvents } from './events'; export type TElectronAPI = { - [K in keyof typeof IPC_CHANNELS]: IPCEvents[typeof IPC_CHANNELS[K]]['response'] extends void - ? (args: IPCEvents[typeof IPC_CHANNELS[K]]['args']) => void - : (args: IPCEvents[typeof IPC_CHANNELS[K]]['args']) => Promise; + [K in keyof typeof CHANNELS]: IEvents[typeof CHANNELS[K]]['response'] extends void + ? (args: IEvents[typeof CHANNELS[K]]['args']) => void + : (args: IEvents[typeof CHANNELS[K]]['args']) => Promise; }; ``` -```typescript title="src/preload/context-bridge.ts" +```typescript title="src/app/preload/context-bridge.ts" import { contextBridge, ipcRenderer } from 'electron'; -import { IPC_CHANNELS } from 'shared/constants'; -import type { TElectronAPI } from 'shared/types'; +import { CHANNELS, type TElectronAPI } from 'shared/ipc'; const API: TElectronAPI = { - [IPC_CHANNELS.GET_USER_DATA]: () => ipcRenderer.invoke(IPC_CHANNELS.GET_USER_DATA), - [IPC_CHANNELS.SAVE_SETTINGS]: (args) => ipcRenderer.send(IPC_CHANNELS.SAVE_SETTINGS, args), + [CHANNELS.GET_USER_DATA]: () => ipcRenderer.invoke(CHANNELS.GET_USER_DATA), + [CHANNELS.SAVE_SETTINGS]: (args) => ipcRenderer.send(CHANNELS.SAVE_SETTINGS, args), } as const; contextBridge.exposeInMainWorld('electron', API); ``` -```typescript title="src/main/processes/user-manager/ipc/send-user-data.ts" +```typescript title="src/main/widgets/user-manager/ipc/send-user-data.ts" import { ipcMain } from 'electron'; -import { IPC_CHANNELS } from 'shared/constants'; +import { CHANNELS } from 'shared/ipc'; -ipcMain.handle(IPC_CHANNELS.GET_USER_DATA, () => { +ipcMain.handle(CHANNELS.GET_USER_DATA, () => { return { name: 'John Doe', email: 'john.doe@example.com' }; }); ``` ```typescript title="src/renderer/page/user-settings/ipc/get-user-data.ts" -import { IPC_CHANNELS } from 'shared/constants'; +import { CHANNELS } from 'shared/ipc'; const getUserData = async () => { - const userData = await window.electron[IPC_CHANNELS.GET_USER_DATA](); + const userData = await window.electron[CHANNELS.GET_USER_DATA](); }; ``` +## Структура вышеописанного примера с дополнениями + +Теперь распишем подробнее структуру нашего проекта для большей наглядности. Предположим, что используется React в качестве UI библиотеки + +```sh +└── src + ├── app # Общий сегмент app + │ ├── main # Main процесс + │ │ ├── index.ts # Точка входа main процесса + │ │ └── create-window.ts + │ ├── preload # Preload скрипт и Context Bridge + │ │ ├── index.ts # Точка входа preload + │ │ └── context-bridge.ts + │ └── renderer # Renderer процесс + │ ├── index.html # Точка входа renderer процесса + │ ├── app.tsx + │ └── globals.css + ├── main + │ ├── widgets + │ │ ├── user-manager + │ │ │ ├── ipc + │ │ │ │ ├── handle-user-data.ts + │ │ │ │ └── handle-user-settings.ts + │ │ │ └── model + │ │ │ └── save-user.ts + │ │ ├── notifications + │ │ │ ├── model + │ │ │ └── notification-manager.ts + │ │ │ └── ipc + │ │ │ └── send-notification.ts + │ ├── features + │ │ ├── user-events-logger + │ │ │ └── model + │ │ │ └── log.ts + │ ├── entities + │ │ └── users + │ └── shared + ├── renderer + │ ├── pages + │ │ ├── settings + │ │ │ ├── ipc + │ │ │ │ ├── get-settings.ts + │ │ │ │ └── save-settings.ts + │ │ │ ├── ui + │ │ │ │ └── settings.tsx + │ │ │ └── index.ts + │ │ ├── dashboard + │ │ │ ├── ui + │ │ │ │ └── dashboard.tsx + │ │ │ └── index.ts + │ ├── widgets + │ │ ├── user-manager + │ │ │ ├── ipc + │ │ │ │ ├── get-users.ts + │ │ │ │ └── save-user.ts + │ │ │ ├── ui + │ │ │ │ └── user-widget.tsx + │ │ │ ├── model + │ │ │ │ └── user-state.ts + │ │ │ └── index.ts + │ └── features + │ ├── notifications + │ │ ├── ipc + │ │ │ └── get-notification.ts + │ │ ├── ui + │ │ │ └── notification.tsx + │ │ ├── model + │ │ │ └── state.ts + │ │ └── index.ts + │ └── shared + └── shared # Общий код между main и renderer + └── ipc # Описание IPC (наименование event'ов, контракты) +``` + ## См. также - [Документация по моделям процессов](https://www.electronjs.org/docs/latest/tutorial/process-model) - [Документация по изоляции контекстов](https://www.electronjs.org/docs/latest/tutorial/context-isolation) From bd2be13f7e5ab7fe71fb2a7f315c38bc712aeb1b Mon Sep 17 00:00:00 2001 From: GeorgKrom Date: Mon, 9 Jun 2025 00:57:58 +0600 Subject: [PATCH 4/9] docs: update with-electron.mdx, add an example --- .../current/guides/tech/with-electron.mdx | 169 +++++++----------- src/pages/examples/_config.ts | 10 ++ src/pages/examples/img/electron-fsd.png | Bin 0 -> 8265 bytes 3 files changed, 72 insertions(+), 107 deletions(-) create mode 100644 src/pages/examples/img/electron-fsd.png diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx index 645940da99..7bfca2b333 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx @@ -7,35 +7,55 @@ Electron-приложения имеют особую архитектуру, с ```sh └── src - ├── app - ├── main # Main процесс - │ ├── widgets - │ ├── features - │ ├── entities + ├── 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 # Renderer процесс - │ ├── pages - │ ├── widgets - │ ├── features - │ ├── entities + ├── 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'ов, контракты) + └── shared # Общий код между main и renderer + └── ipc # Описание IPC (наименование event'ов, контракты) ``` ## Правила для публичного API -Каждый процесс должен иметь свой публичный API, нельзя импортировать модули из `main` в `renderer`. Общедоступным между процессами кодом является только папка `src/shared`. Она же служит для описания контрактов по взаимодействию процессов. +Каждый процесс должен иметь свой публичный API, как пример, нельзя импортировать модули из `main` в `renderer`. +Общедоступным между процессами кодом является только папка `src/shared`. +Она же необходима для описания контрактов по взаимодействию процессов. ## Дополнительные изменения в стандартной структуре -Предлагается использовать новый сегмент `ipc`, в котором происходит взаимодействие между процессами -Слой `pages`, исходя из названия, не должен присутствовать в `src/main`. Слой `app` в `src` содержит точки входа для `main` и `renderer`, а также IPC. +Предлагается использовать новый сегмент `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_SETTINGS: 'SAVE_SETTINGS', + SAVE_USER: 'SAVE_USER', } as const; export type TChannelKeys = keyof typeof CHANNELS; @@ -46,10 +66,11 @@ import { CHANNELS } from './channels'; export interface IEvents { [CHANNELS.GET_USER_DATA]: { - response: { name: string; email: string; }; + args: void, + response?: { name: string; email: string; }; }; - [CHANNELS.SAVE_SETTINGS]: { - args: { theme: string; }; + [CHANNELS.SAVE_USER]: { + args: { name: string; }; response: void; }; } @@ -59,117 +80,51 @@ export interface IEvents { import { CHANNELS } from './channels'; import type { IEvents } from './events'; +type TOptionalArgs = T extends void ? [] : [args: T]; + export type TElectronAPI = { - [K in keyof typeof CHANNELS]: IEvents[typeof CHANNELS[K]]['response'] extends void - ? (args: IEvents[typeof CHANNELS[K]]['args']) => void - : (args: IEvents[typeof CHANNELS[K]]['args']) => Promise; + [K in keyof typeof CHANNELS]: (...args: TOptionalArgs) => IEvents[typeof CHANNELS[K]]['response']; }; ``` -```typescript title="src/app/preload/context-bridge.ts" +```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.invoke(CHANNELS.GET_USER_DATA), - [CHANNELS.SAVE_SETTINGS]: (args) => ipcRenderer.send(CHANNELS.SAVE_SETTINGS, args), + [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/widgets/user-manager/ipc/send-user-data.ts" +```typescript title="src/main/services/user/ipc/send-user.ts" import { ipcMain } from 'electron'; import { CHANNELS } from 'shared/ipc'; -ipcMain.handle(CHANNELS.GET_USER_DATA, () => { - return { name: 'John Doe', email: 'john.doe@example.com' }; -}); -``` - -```typescript title="src/renderer/page/user-settings/ipc/get-user-data.ts" -import { CHANNELS } from 'shared/ipc'; - -const getUserData = async () => { - const userData = await window.electron[CHANNELS.GET_USER_DATA](); +export const sendUser = () => { + ipcMain.on(CHANNELS.GET_USER_DATA, ev => { + ev.returnValue = { + name: 'John Doe', + email: 'john.doe@example.com', + }; + }); }; ``` -## Структура вышеописанного примера с дополнениями +```typescript title="src/renderer/page/user-settings/ipc/get-user.ts" +import { CHANNELS } from 'shared/ipc'; -Теперь распишем подробнее структуру нашего проекта для большей наглядности. Предположим, что используется React в качестве UI библиотеки +export const getUser = () => { + const user = window.electron[CHANNELS.GET_USER_DATA](); -```sh -└── src - ├── app # Общий сегмент app - │ ├── main # Main процесс - │ │ ├── index.ts # Точка входа main процесса - │ │ └── create-window.ts - │ ├── preload # Preload скрипт и Context Bridge - │ │ ├── index.ts # Точка входа preload - │ │ └── context-bridge.ts - │ └── renderer # Renderer процесс - │ ├── index.html # Точка входа renderer процесса - │ ├── app.tsx - │ └── globals.css - ├── main - │ ├── widgets - │ │ ├── user-manager - │ │ │ ├── ipc - │ │ │ │ ├── handle-user-data.ts - │ │ │ │ └── handle-user-settings.ts - │ │ │ └── model - │ │ │ └── save-user.ts - │ │ ├── notifications - │ │ │ ├── model - │ │ │ └── notification-manager.ts - │ │ │ └── ipc - │ │ │ └── send-notification.ts - │ ├── features - │ │ ├── user-events-logger - │ │ │ └── model - │ │ │ └── log.ts - │ ├── entities - │ │ └── users - │ └── shared - ├── renderer - │ ├── pages - │ │ ├── settings - │ │ │ ├── ipc - │ │ │ │ ├── get-settings.ts - │ │ │ │ └── save-settings.ts - │ │ │ ├── ui - │ │ │ │ └── settings.tsx - │ │ │ └── index.ts - │ │ ├── dashboard - │ │ │ ├── ui - │ │ │ │ └── dashboard.tsx - │ │ │ └── index.ts - │ ├── widgets - │ │ ├── user-manager - │ │ │ ├── ipc - │ │ │ │ ├── get-users.ts - │ │ │ │ └── save-user.ts - │ │ │ ├── ui - │ │ │ │ └── user-widget.tsx - │ │ │ ├── model - │ │ │ │ └── user-state.ts - │ │ │ └── index.ts - │ └── features - │ ├── notifications - │ │ ├── ipc - │ │ │ └── get-notification.ts - │ │ ├── ui - │ │ │ └── notification.tsx - │ │ ├── model - │ │ │ └── state.ts - │ │ └── index.ts - │ └── shared - └── shared # Общий код между main и renderer - └── ipc # Описание IPC (наименование event'ов, контракты) + return user ?? { name: 'John Dont e', email: 'john.donte@example.com' }; +}; ``` ## См. также - [Документация по моделям процессов](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) \ No newline at end of file +- [Документация по IPC](https://www.electronjs.org/docs/latest/tutorial/ipc) +- [Пример](https://github.com/georgkrom/electron-fsd.git) \ No newline at end of file diff --git a/src/pages/examples/_config.ts b/src/pages/examples/_config.ts index 067ae2d0d8..6e1914756a 100644 --- a/src/pages/examples/_config.ts +++ b/src/pages/examples/_config.ts @@ -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(); diff --git a/src/pages/examples/img/electron-fsd.png b/src/pages/examples/img/electron-fsd.png new file mode 100644 index 0000000000000000000000000000000000000000..e9c3c86528c3564aa02ee72b34545b1257b75cb7 GIT binary patch literal 8265 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 P08S^}PLv;m{{FuJ;B6rm literal 0 HcmV?d00001 From dd696f50dfe498417f1531fb37693143d22eaa2b Mon Sep 17 00:00:00 2001 From: GeorgKrom Date: Sat, 28 Jun 2025 17:55:08 +0600 Subject: [PATCH 5/9] fix: remove an example from a deprecated section, replace custom 'services' layer with default 'features', add all default layers to a structure tree --- .../current/guides/tech/with-electron.mdx | 18 +++++++++++------- src/pages/examples/_config.ts | 10 ---------- src/pages/examples/img/electron-fsd.png | Bin 8265 -> 0 bytes 3 files changed, 11 insertions(+), 17 deletions(-) delete mode 100644 src/pages/examples/img/electron-fsd.png diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx index 7bfca2b333..9976723484 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx @@ -15,14 +15,15 @@ Electron-приложения имеют особую архитектуру, с │ └── renderer # Renderer процесс │ └── index.html # Точка входа renderer процесса ├── main - │ ├── services + │ ├── features │ │ └── user │ │ └── ipc │ │ ├── get-user.ts │ │ └── send-user.ts + │ ├── entities │ └── shared ├── renderer - │ └── pages + │ ├── pages │ │ ├── settings │ │ │ ├── ipc │ │ │ │ ├── get-user.ts @@ -34,6 +35,9 @@ Electron-приложения имеют особую архитектуру, с │ │ ├── ui │ │ │ └── home.tsx │ │ └── index.ts + │ ├── widgets + │ ├── features + │ ├── entities │ └── shared └── shared # Общий код между main и renderer └── ipc # Описание IPC (наименование event'ов, контракты) @@ -46,7 +50,7 @@ Electron-приложения имеют особую архитектуру, с ## Дополнительные изменения в стандартной структуре Предлагается использовать новый сегмент `ipc`, в котором происходит взаимодействие между процессами. -Слои `pages` и `widgets`, исходя из названия, не должен присутствовать в `src/main`, вместо них предлагается в качестве самого верхнего уровня использовать слой `services`. +Слои `pages` и `widgets`, исходя из названия, не должны присутствовать в `src/main`, вы можете использовать `features`, `entities` и `shared`. Слой `app` в `src` содержит точки входа для `main` и `renderer`, а также IPC. Сегментам в слое `app` нежелательно иметь точек пересечения @@ -99,7 +103,7 @@ const API: TElectronAPI = { contextBridge.exposeInMainWorld('electron', API); ``` -```typescript title="src/main/services/user/ipc/send-user.ts" +```typescript title="src/main/features/user/ipc/send-user.ts" import { ipcMain } from 'electron'; import { CHANNELS } from 'shared/ipc'; @@ -113,13 +117,13 @@ export const sendUser = () => { }; ``` -```typescript title="src/renderer/page/user-settings/ipc/get-user.ts" +```typescript title="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 Dont e', email: 'john.donte@example.com' }; + return user ?? { name: 'John Donte', email: 'john.donte@example.com' }; }; ``` @@ -127,4 +131,4 @@ export const getUser = () => { - [Документация по моделям процессов](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) \ No newline at end of file +- [Пример](https://github.com/feature-sliced/examples/tree/master/examples/electron) \ No newline at end of file diff --git a/src/pages/examples/_config.ts b/src/pages/examples/_config.ts index 6e1914756a..067ae2d0d8 100644 --- a/src/pages/examples/_config.ts +++ b/src/pages/examples/_config.ts @@ -426,15 +426,5 @@ 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(); diff --git a/src/pages/examples/img/electron-fsd.png b/src/pages/examples/img/electron-fsd.png deleted file mode 100644 index e9c3c86528c3564aa02ee72b34545b1257b75cb7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8265 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 P08S^}PLv;m{{FuJ;B6rm From 5d31344712e5f800f70c3479e3f8b2fb58c0a053 Mon Sep 17 00:00:00 2001 From: GeorgKrom Date: Mon, 30 Jun 2025 15:38:27 +0600 Subject: [PATCH 6/9] docs(guides): translate the Electron tech guide to English --- .../current/guides/tech/with-electron.mdx | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx new file mode 100644 index 0000000000..cdf5bdc000 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx @@ -0,0 +1,134 @@ +--- +sidebar_position: 10 +--- +# 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. + +```sh +└── src + ├── app # Common app segment + │ ├── 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. +Segments in the `app` layer are not desirable to have intersection points + +## Interaction example + +```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 extends void ? [] : [args: T]; + +export type TElectronAPI = { + [K in keyof typeof CHANNELS]: (...args: TOptionalArgs) => 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/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', + }; + }); +}; +``` + +```typescript title="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 +- [Process Model Documentation](https://www.electronjs.org/docs/latest/tutorial/process-model) +- [Context Isolation Documentation](https://www.electronjs.org/docs/latest/tutorial/context-isolation) +- [Inter-Process Communication Documentation](https://www.electronjs.org/docs/latest/tutorial/ipc) +- [Example](https://github.com/feature-sliced/examples/tree/master/examples/electron) \ No newline at end of file From 05f701001a72e0cbc798195baa7780245609ee2a Mon Sep 17 00:00:00 2001 From: Georg Krom <48806948+GeorgKrom@users.noreply.github.com> Date: Thu, 3 Jul 2025 19:20:55 +0600 Subject: [PATCH 7/9] fix: electron tect guide translation text Co-authored-by: Lev Chelyadinov --- .../current/guides/tech/with-electron.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx index cdf5bdc000..ce61ce316f 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx @@ -52,7 +52,7 @@ It's also necessary for describing contracts for process interaction. 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. -Segments in the `app` layer are not desirable to have intersection points +It's not desirable for segments in the `app` layer to have intersection points ## Interaction example From 3a361abd01c5553eccbeee6d1e1779769b468017 Mon Sep 17 00:00:00 2001 From: GeorgKrom Date: Thu, 3 Jul 2025 19:24:39 +0600 Subject: [PATCH 8/9] fix: replace 'Common app segment' text with 'Common app layer' --- .../current/guides/tech/with-electron.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx index ce61ce316f..dc779d7229 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx @@ -7,7 +7,7 @@ Electron applications have a special architecture consisting of multiple process ```sh └── src - ├── app # Common app segment + ├── app # Common app layer │ ├── main # Main process │ │ └── index.ts # Main process entry point │ ├── preload # Preload script and Context Bridge From 60a9dbd62e9bf566e967564843aa791632c591f8 Mon Sep 17 00:00:00 2001 From: GeorgKrom Date: Thu, 3 Jul 2025 19:27:02 +0600 Subject: [PATCH 9/9] =?UTF-8?q?fix:=20replace=20'=D0=9E=D0=B1=D1=89=D0=B8?= =?UTF-8?q?=D0=B9=20=D1=81=D0=B5=D0=B3=D0=BC=D0=B5=D0=BD=D1=82=20app'=20te?= =?UTF-8?q?xt=20with=20'=D0=9E=D0=B1=D1=89=D0=B8=D0=B9=20=D1=81=D0=BB?= =?UTF-8?q?=D0=BE=D0=B9=20app'=20in=20a=20Russian=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../current/guides/tech/with-electron.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx index 9976723484..4bf74c3ae4 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-electron.mdx @@ -7,7 +7,7 @@ Electron-приложения имеют особую архитектуру, с ```sh └── src - ├── app # Общий сегмент app + ├── app # Общий слой app │ ├── main # Main процесс │ │ └── index.ts # Точка входа main процесса │ ├── preload # Preload скрипт и Context Bridge