From c4bc79839b162b9a827bf9a814f95a170430f12e Mon Sep 17 00:00:00 2001 From: GeorgKrom Date: Fri, 23 May 2025 19:33:30 +0600 Subject: [PATCH 01/11] 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 000000000..e683e8590 --- /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 02/11] 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 e683e8590..362e6abc3 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 03/11] 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 362e6abc3..645940da9 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 04/11] 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 645940da9..7bfca2b33 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 067ae2d0d..6e1914756 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 05/11] 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 7bfca2b33..997672348 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 6e1914756..067ae2d0d 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 06/11] 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 000000000..cdf5bdc00 --- /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 07/11] 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 cdf5bdc00..ce61ce316 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 08/11] 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 ce61ce316..dc779d722 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 09/11] =?UTF-8?q?fix:=20replace=20'=D0=9E=D0=B1=D1=89?= =?UTF-8?q?=D0=B8=D0=B9=20=D1=81=D0=B5=D0=B3=D0=BC=D0=B5=D0=BD=D1=82=20app?= =?UTF-8?q?'=20text=20with=20'=D0=9E=D0=B1=D1=89=D0=B8=D0=B9=20=D1=81?= =?UTF-8?q?=D0=BB=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 997672348..4bf74c3ae 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 From 82514f538ed1d71bd1cd89a4b7469a8c47d8b023 Mon Sep 17 00:00:00 2001 From: GeorgKrom Date: Sat, 12 Jul 2025 01:34:52 +0600 Subject: [PATCH 10/11] docs(guides): update next.js tech guide to the relevant status --- .../current/guides/tech/with-nextjs.mdx | 128 +++++++----------- .../current/guides/tech/with-nextjs.mdx | 124 +++++++---------- 2 files changed, 97 insertions(+), 155 deletions(-) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx index a30b16912..4d66966ae 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx @@ -1,72 +1,73 @@ --- sidebar_position: 10 --- -# Usage with NextJS +# Usage with Next.js -It is possible to implement FSD in a NextJS project, but conflicts arise due to differences between the requirements for the NextJS project structure and the principles of FSD in two points:  +It's possible to implement FSD in a Next.js project, but conflicts arise due to differences between Next.js project structure requirements and FSD principles. -- Routing files in the `pages` layer -- Conflict or absence of the `app` layer in NextJS +## Conflict between FSD and Next.js in the `app` layer -## Conflict between FSD and NextJS on `pages` layer {#pages-conflict} +Next.js suggests using the `app` folder to define application routes. Next.js expects files in the `app` folder to correspond to URLs. +This routing mechanism **does not align** with the FSD concept, as it's not possible to maintain a flat slice structure with such a routing mechanism. +The approach is to move the Next.js `app` folder to the project root and import FSD pages into the Next.js `app` folder. +This preserves the FSD project structure inside the `src` folder. -NextJS suggests using the `pages` folder to define application routes. NextJS expects files in the `pages` folder to match URLs. -This routing mechanism **does not correspond** to the FSD concept, since it is not possible to maintain a flat slice structure in such a routing mechanism. +You should also add a `pages` folder to the project root because App Router is compatible with Pages Router. +The best way is to put an empty `.gitkeep` or `README.md` file inside describing the need to save this folder. -### Moving the `pages` folder of NextJS to the root folder of the project (recommended) +```sh +├── app # App folder (Next.js) +├── pages # Legacy pages folder (Next.js) +│ └── .gitkeep # To maintain compatibility with Pages Router +└── src + ├── app + ├── pages + ├── widgets + ├── features + ├── entities + └── shared +``` -The approach is to move the `pages` NextJS folder to the root folder of the project and import the FSD pages into the `pages` NextJS folder. This saves -the FSD project structure inside the `src` folder. +### Example of re-exporting a page from `src/pages/example` to `app/example/page.tsx` -```sh -├── pages # NextJS pages folder -├── src -│ ├── app -│ ├── entities -│ ├── features -│ ├── pages # FSD pages folder -│ ├── shared -│ ├── widgets +```tsx title="app/example/page.tsx" +export { Example as default, metadata } from '@/pages/example'; ``` -### Renaming the `pages` layer within the FSD structure +### Middleware +If you use middleware in your project, it must be located in the project root alongside the `app` and `pages` folders. -Another way to solve the problem is to rename the `pages` layer in the FSD structure to avoid conflicts with the NextJS `pages` folder. -You can rename the `pages` layer in FSD to `views`. -In that way, the structure of the project in the `src` folder is preserved without contradiction with the requirements of NextJS. +### Instrumentation +The `instrumentation.ts|js` file allows you to monitor the performance and behavior of your application. If you use it, it must be located in the project root, similar to `middleware.ts|js`. -```sh -├── app -├── entities -├── features -├── pages # NextJS pages folder -├── views # Renamed FSD pages folder -├── shared -├── widgets +### Route Handlers +It is suggested to use a new `routes` segment in the `app` or `pages` layer to work with Route Handlers. + +```tsx title="app/api/get-example/route.ts" +export { getExample as GET } from '@/pages/example'; ``` -Keep in mind that it's highly recommended to document this rename prominently in your project's README or internal documentation. This rename is a part of your ["project knowledge"][project-knowledge]. +### Additional recommendations +- Use the `queries` segment in the `shared` layer to describe database queries and their further use in higher layers. +- Caching and revalidating queries logic is better kept in the same place as the queries themselves, in a basic scenario this is the `api` segment in the `shared` layer. -## The absence of the `app` folder in NextJS {#app-absence} +## Legacy approach with Pages Router -In NextJS below version 13, there is no explicit `app` folder, instead NextJS provides the `_app.tsx` file, -which plays the role of a wrapping component for all project pages. +In Next.js below version 13, there is no explicit `app` folder. Next.js provides a `_app.tsx` file instead, which serves as a wrapper component for all project pages. -### Importing app functionality to `pages/_app.tsx` file +### Importing functionality into `pages/_app.tsx` -To solve the problem of missing the `app` folder in the NextJS structure, you can create an `App` component inside the `app` layer and import the `App` component into `pages/_app.tsx` so that NextJS can work with it. +To solve the problem of missing the `app` folder in the Next.js structure, you can create an `App` component inside the `app` layer and import it into `pages/_app.tsx` so that Next.js can work with it. For example: -```tsx -// app/providers/index.tsx - -const App = ({ Component, pageProps }: AppProps) => { +```tsx title="app/providers/index.tsx" +const App = ({ Component, pageProps }: IProps) => { return ( - + @@ -75,47 +76,16 @@ const App = ({ Component, pageProps }: AppProps) => { export default App; ``` -Then you can import the `App` component and global project styles into `pages/_app.tsx` as follows: -```tsx -// pages/_app.tsx +Then you can import the `App` component and global project styles into `pages/_app.tsx` as follows: -import 'app/styles/index.scss' +```tsx title="pages/_app.tsx" +import 'app/styles/globals.css' export { default } from 'app/providers'; ``` -## Dealing with App Router {#app-router} - -App Router has become stable in NextJS version 13.4. App Router allows you to use the `app` folder for routing instead of the `pages` folder. -To comply with the principles of FSD, you should treat the NextJS `app` folder in the same way as recommended -to resolve a conflict with the NextJS `pages` folder. - -The approach is to move the NextJS `app` folder to the root folder of the project and import the FSD pages into the NextJS `app` folder. This saves -the FSD project structure inside the `src` folder. You should still also add the `pages` folder to the root, because the App router is compatible with the Pages router. - -``` -├── app # NextJS app folder -├── pages # Stub NextJS pages folder -│ ├── README.md # Description of why this folder exists -├── src -│ ├── app # FSD app folder -│ ├── entities -│ ├── features -│ ├── pages # FSD pages folder -│ ├── shared -│ ├── widgets -``` - -[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)][ext-app-router-stackblitz] - -## Middleware - -If you are using middleware in a project, it also needs to be moved from `src` to the root of the project. Otherwise, NextJS simply won't see it — middleware must be located strictly at the root next to `app` or `pages`. - -## See also {#see-also} - -- [(Thread) About the pages directory in NextJS](https://t.me/feature_sliced/3623) +## See also -[project-knowledge]: /docs/about/understanding/knowledge-types -[ext-app-router-stackblitz]: https://stackblitz.com/edit/stackblitz-starters-aiez55?file=README.md +[Next.js Project Structure](https://Next.js.org/docs/app/getting-started/project-structure) +[Next.js Page Layouts](https://Next.js.org/docs/app/getting-started/layouts-and-pages) \ No newline at end of file diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx index b4778fa09..dc69cf1ee 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx @@ -1,71 +1,74 @@ --- sidebar_position: 10 --- -# Использование с NextJS +# Использование с Next.js -В NextJS проекте возможно реализовать FSD, но возникают конфликты из-за различий между требованиями к структуре проекта NextJS и принципами FSD в двух пунктах:  +В Next.js проекте возможно реализовать FSD, но возникают конфликты из-за различий между требованиями к структуре проекта Next.js и принципами FSD. -- Маршрутизация файлов в слое `pages` -- Конфликт или отсутствие слоя `app` в NextJS +## Конфликт между FSD и Next.js в слое `app` -## Конфликт между FSD и NextJS в слое `pages` {#pages-conflict} - -NextJS предлагает использовать папку `pages` для определения маршрутов приложения. NextJS ожидает, что файлы в папке `pages` будут соответствовать URL-адресам. +Next.js предлагает использовать папку `app` для определения маршрутов приложения. Next.js ожидает, что файлы в папке `app` будут соответствовать URL-адресам. Этот механизм маршрутизации **не соответствует** концепции FSD, поскольку в таком механизме маршрутизации не представляется возможным соблюсти плоскую структуру слайсов. -### Перемещение папки `pages` NextJS в корневую папку проекта (рекомендуется) +Подход заключается в перемещении папки `app` Next.js в корневую папку проекта и импорте страниц FSD в папку `app` Next.js. +Это сохраняет структуру проекта FSD внутри папки `src`. -Подход заключается в перемещении папки `pages` NextJS в корневую папку проекта и импорте страниц FSD в папку `pages` NextJS. Это сохраняет -структуру проекта FSD внутри папки `src`. +Вам также стоит добавить в корневую папку проекта папку `pages`, потому что App Router совместим с Pages Router. +Лучшим способом будет положить внутрь пустой файл `.gitkeep` или `README.md` с описанием необходимости сохранить данную папку. ```sh -├── pages # Папка pages (NextJS) -├── src -│ ├── app -│ ├── entities -│ ├── features -│ ├── pages # Папка pages (FSD) -│ ├── shared -│ ├── widgets +├── app # Папка app (Next.js) +├── pages # Устаревшая папка pages (Next.js) +│ └── .gitkeep # Для сохранения совместимости с Pages Router +└── src + ├── app + ├── pages + ├── widgets + ├── features + ├── entities + └── shared ``` -### Переименование папки `pages` в структуре FSD +### Пример ре-экспорта страницы из `src/pages/example` в `app/example/page.tsx` -Другой способ решить проблему - переименовать слой `pages` в структуре FSD, чтобы избежать конфликтов с папкой `pages` NextJS. -Вы можете переименовать слой `pages` в FSD в `views`.  -Таким образом, структура проекта в папке `src` сохраняется без противоречий с требованиями NextJS. +```tsx title="app/example/page.tsx" +export { Example as default, metadata } from '@/pages/example'; +``` -```sh -├── app -├── entities -├── features -├── pages # Папка pages (NextJS) -├── views # Переименованная папка страниц FSD -├── shared -├── widgets +### Middleware +Если вы используете middleware в проекте, он обязательно должен располагаться в корне проекта рядом с папками `app` и `pages`. + +### Instrumentation +Файл `instrumentation.ts|js` позволяет отслеживать производительность и поведение вашего приложения. Если вы его используете, то она обязательно должен находиться в корне проекта по аналогии с `middleware.ts|js` + +### Route Handlers +Предлагается использовать новый сегмент `routes` в слое `app` или `pages` для работы c Route Handlers. + +```tsx title="app/api/get-example/route.ts" +export { getExample as GET } from '@/pages/example'; ``` -Учтите, что в этом случае рекомендуется задокументировать это переименование в видном месте — в README проекта или внутренней документации. Это переименование — часть ["проектных знаний"][project-knowledge]. +### Дополнительные рекомендации +- Используйте сегмент `queries` в слое `shared` для описания запросов к БД и их дальнейшего использования в вышестоящих слоях. +- Логику кэширования и ревалидации запросов лучше держать там же, где и сами запросы, в базовом сценарии это сегмент `api` в слое `shared`. -## Отсутствие папки `app` в NextJS {#app-absence} +## Устаревший подход с Pages Router -В NextJS ниже версии 13 нет явной папки `app`, вместо этого NextJS предоставляет файл `_app.tsx`, +В Next.js ниже версии 13 нет явной папки `app`, вместо этого Next.js предоставляет файл `_app.tsx`, который играет роль компонента обертывания для всех страниц проекта. ### Импорт функциональности в `pages/_app.tsx` -Чтобы решить проблему отсутствия папки `app` в структуре NextJS, вы можете создать компонент `App` внутри слоя `app` и импортировать компонент `App` в `pages/_app.tsx`, чтобы NextJS мог с ним работать. +Чтобы решить проблему отсутствия папки `app` в структуре Next.js, вы можете создать компонент `App` внутри слоя `app` и импортировать компонент `App` в `pages/_app.tsx`, чтобы Next.js мог с ним работать. Например: -```tsx -// app/providers/index.tsx - -const App = ({ Component, pageProps }: AppProps) => { +```tsx title="app/providers/index.tsx" +const App = ({ Component, pageProps }: IProps) => { return ( - + @@ -74,47 +77,16 @@ const App = ({ Component, pageProps }: AppProps) => { export default App; ``` -Затем вы можете импортировать компонент `App` и глобальные стили проекта в `pages/_app.tsx` следующим образом: -```tsx -// pages/_app.tsx +Затем вы можете импортировать компонент `App` и глобальные стили проекта в `pages/_app.tsx` следующим образом: -import 'app/styles/index.scss' +```tsx title="pages/_app.tsx" +import 'app/styles/globals.css' export { default } from 'app/providers'; ``` -## Работа с App Router {#app-router} - -App Router стал стабильным в NextJS версии 13.4. App Router позволяет использовать папку `app` для маршрутизации вместо папки `pages`. -Для соответствия принципам FSD, вам следует обращаться с папкой `app` NextJS так же, как рекомендуется -для устранения конфликта с папкой `pages` NextJS. - -Подход заключается в перемещении папки `app` NextJS в корневую папку проекта и импорте страниц FSD в папку `app` NextJS. Это сохраняет -структуру проекта FSD внутри папки `src`. Вам также стоит добавить в корневую папку проекта папку `pages`, потому что App Router совместим с Pages Router. - -``` -├── app # Папка app (NextJS) -├── pages # Пустая папка pages (NextJS) -│ ├── README.md # Описание того, зачем нужна эта папка -├── src -│ ├── app # Папка app (FSD) -│ ├── entities -│ ├── features -│ ├── pages # Папка pages (FSD) -│ ├── shared -│ ├── widgets -``` - -[![Открыть в StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)][ext-app-router-stackblitz] - -## Middleware - -Если вы используете middleware в проекте, его также нужно переместить из `src` в корень проекта. Иначе NextJS просто не увидит его — middleware должен находиться строго в корне рядом с `app` или `pages`. - -## См. также {#see-also} - -- [(Тред) Про pages директорию в NextJS](https://t.me/feature_sliced/3623) +## См. также -[project-knowledge]: /docs/about/understanding/knowledge-types -[ext-app-router-stackblitz]: https://stackblitz.com/edit/stackblitz-starters-aiez55?file=README.md +[Структура проекта Next.js](https://Next.js.org/docs/app/getting-started/project-structure) +[Компоновка страниц Next.js](https://Next.js.org/docs/app/getting-started/layouts-and-pages) \ No newline at end of file From a20b4ea7bff1b6fac0fad30eedf497a0c594695b Mon Sep 17 00:00:00 2001 From: GeorgKrom Date: Thu, 17 Jul 2025 14:18:01 +0600 Subject: [PATCH 11/11] docs(guides): review issues fix and improvements --- .../current/guides/tech/with-nextjs.mdx | 89 ++++++++++++------- .../current/guides/tech/with-nextjs.mdx | 88 +++++++++++------- 2 files changed, 115 insertions(+), 62 deletions(-) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx index 4d66966ae..9ae37087f 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx @@ -7,22 +7,33 @@ It's possible to implement FSD in a Next.js project, but conflicts arise due to ## Conflict between FSD and Next.js in the `app` layer -Next.js suggests using the `app` folder to define application routes. Next.js expects files in the `app` folder to correspond to URLs. +Next.js suggests using the `app` folder to define application routes. It expects files in the `app` folder to correspond to pathnames. This routing mechanism **does not align** with the FSD concept, as it's not possible to maintain a flat slice structure with such a routing mechanism. The approach is to move the Next.js `app` folder to the project root and import FSD pages into the Next.js `app` folder. -This preserves the FSD project structure inside the `src` folder. +This preserves the FSD project structure inside the `src` folder, where layer folders should be placed. You should also add a `pages` folder to the project root because App Router is compatible with Pages Router. -The best way is to put an empty `.gitkeep` or `README.md` file inside describing the need to save this folder. +The best way is to put `README.md` file inside describing the need to save this folder. ```sh -├── app # App folder (Next.js) -├── pages # Legacy pages folder (Next.js) -│ └── .gitkeep # To maintain compatibility with Pages Router +├── app # App folder (Next.js) + ├── api + │ └── get-example + │ └── route.ts +│ └── example +│ └── page.tsx +├── pages # Legacy pages folder (Next.js) +│ └── README.md # To maintain compatibility with Pages Router └── src ├── app ├── pages + │ └── example + │ ├── index.ts + │ ├── ui + │ │ └── example.tsx + │ └── routes + │ └── get-example.ts # Route handler ├── widgets ├── features ├── entities @@ -44,48 +55,64 @@ The `instrumentation.ts|js` file allows you to monitor the performance and behav ### Route Handlers It is suggested to use a new `routes` segment in the `app` or `pages` layer to work with Route Handlers. -```tsx title="app/api/get-example/route.ts" +```tsx title="app/routes/get-example/route.ts" export { getExample as GET } from '@/pages/example'; ``` ### Additional recommendations -- Use the `queries` segment in the `shared` layer to describe database queries and their further use in higher layers. +- Use the `api` segment in the `shared` layer to describe database queries and their further use in higher layers. - Caching and revalidating queries logic is better kept in the same place as the queries themselves, in a basic scenario this is the `api` segment in the `shared` layer. ## Legacy approach with Pages Router +Page routers should be placed in the `pages` folder in the root of the project, similar to `app` for the App Router. +The structure inside `src` where the layer folders are located remains unchanged. -In Next.js below version 13, there is no explicit `app` folder. Next.js provides a `_app.tsx` file instead, which serves as a wrapper component for all project pages. +```sh +├── pages # Pages folder (Next.js) +│ ├── _app.tsx +│ ├── api +│ │ └── example.ts # API Route re-export +│ └── example +│ └── index.tsx +└── src + ├── app + ├── pages + │ └── example + │ ├── index.ts + │ ├── ui + │ │ └── example.tsx + │ └── routes + │ ├── config.ts # API Route config + │ └── handler.ts # API Route + ├── widgets + ├── features + ├── entities + └── shared +``` -### Importing functionality into `pages/_app.tsx` +### Functionality re-export from the public API `src/app/index.ts` to `pages/_app.tsx` -To solve the problem of missing the `app` folder in the Next.js structure, you can create an `App` component inside the `app` layer and import it into `pages/_app.tsx` so that Next.js can work with it. -For example: +```tsx title="pages/_app.tsx" +export { App as default } from '@/app'; +``` -```tsx title="app/providers/index.tsx" -const App = ({ Component, pageProps }: IProps) => { - return ( - - - - - - - - ); -}; +### Example of re-exporting a page from `src/pages/example` to `pages/example/index.tsx` -export default App; +```tsx title="pages/example/index.tsx" +export { Example as default } from '@/pages/example'; ``` -Then you can import the `App` component and global project styles into `pages/_app.tsx` as follows: +### API Routes +It is suggested to use a new `routes` segment in the `app` or `pages` layer to work with API Routes. -```tsx title="pages/_app.tsx" -import 'app/styles/globals.css' - -export { default } from 'app/providers'; +```tsx title="app/api/example.ts" +export { handler as default, config } from '@/pages/example'; ``` ## See also [Next.js Project Structure](https://Next.js.org/docs/app/getting-started/project-structure) -[Next.js Page Layouts](https://Next.js.org/docs/app/getting-started/layouts-and-pages) \ No newline at end of file +[Next.js Page Layouts](https://Next.js.org/docs/app/getting-started/layouts-and-pages) + +[project-knowledge]: /docs/about/understanding/knowledge-types +[ext-app-router-stackblitz]: https://stackblitz.com/edit/stackblitz-starters-aiez55?file=README.md \ No newline at end of file diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx index dc69cf1ee..82d222138 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx @@ -11,18 +11,29 @@ Next.js предлагает использовать папку `app` для о Этот механизм маршрутизации **не соответствует** концепции FSD, поскольку в таком механизме маршрутизации не представляется возможным соблюсти плоскую структуру слайсов. Подход заключается в перемещении папки `app` Next.js в корневую папку проекта и импорте страниц FSD в папку `app` Next.js. -Это сохраняет структуру проекта FSD внутри папки `src`. +Это сохраняет структуру проекта FSD внутри папки `src`, где должны размещаться слои. Вам также стоит добавить в корневую папку проекта папку `pages`, потому что App Router совместим с Pages Router. -Лучшим способом будет положить внутрь пустой файл `.gitkeep` или `README.md` с описанием необходимости сохранить данную папку. +Лучшим способом будет положить внутрь `README.md` с описанием необходимости сохранить данную папку. ```sh -├── app # Папка app (Next.js) -├── pages # Устаревшая папка pages (Next.js) -│ └── .gitkeep # Для сохранения совместимости с Pages Router +├── app # Папка app (Next.js) + ├── api + │ └── get-example + │ └── route.ts +│ └── example +│ └── page.tsx +├── pages # Устаревшая папка pages (Next.js) +│ └── README.md # Для сохранения совместимости с Pages Router └── src ├── app ├── pages + │ └── example + │ ├── index.ts + │ ├── ui + │ │ └── example.tsx + │ └── routes + │ └── get-example.ts # Route handler ├── widgets ├── features ├── entities @@ -39,7 +50,7 @@ export { Example as default, metadata } from '@/pages/example'; Если вы используете middleware в проекте, он обязательно должен располагаться в корне проекта рядом с папками `app` и `pages`. ### Instrumentation -Файл `instrumentation.ts|js` позволяет отслеживать производительность и поведение вашего приложения. Если вы его используете, то она обязательно должен находиться в корне проекта по аналогии с `middleware.ts|js` +Файл `instrumentation.ts|js` позволяет отслеживать производительность и поведение вашего приложения. Если вы его используете, то он обязательно должен находиться в корне проекта по аналогии с `middleware.ts|js` ### Route Handlers Предлагается использовать новый сегмент `routes` в слое `app` или `pages` для работы c Route Handlers. @@ -49,44 +60,59 @@ export { getExample as GET } from '@/pages/example'; ``` ### Дополнительные рекомендации -- Используйте сегмент `queries` в слое `shared` для описания запросов к БД и их дальнейшего использования в вышестоящих слоях. +- Используйте сегмент `api` в слое `shared` для описания запросов к БД и их дальнейшего использования в вышестоящих слоях. - Логику кэширования и ревалидации запросов лучше держать там же, где и сами запросы, в базовом сценарии это сегмент `api` в слое `shared`. ## Устаревший подход с Pages Router +Роуты страниц должны помещаться в папку `pages` в корне проекта по аналогии с `app` для App Router. +Структура внутри `src`, где располагаются папки слоёв остаётся без изменений. -В Next.js ниже версии 13 нет явной папки `app`, вместо этого Next.js предоставляет файл `_app.tsx`, -который играет роль компонента обертывания для всех страниц проекта. +```sh +├── pages # Папка pages (Next.js) +│ ├── _app.tsx +│ ├── api +│ │ └── example.ts # API Route ре-экспорт +│ └── example +│ └── index.tsx +└── src + ├── app + ├── pages + │ └── example + │ ├── index.ts + │ ├── ui + │ │ └── example.tsx + │ └── routes + │ ├── config.ts # Конфигурация API Rout'а + │ └── handler.ts # API Route + ├── widgets + ├── features + ├── entities + └── shared +``` -### Импорт функциональности в `pages/_app.tsx` +### Ре-экспорт функциональности в `pages/_app.tsx` из публичного API `src/app/index.ts` -Чтобы решить проблему отсутствия папки `app` в структуре Next.js, вы можете создать компонент `App` внутри слоя `app` и импортировать компонент `App` в `pages/_app.tsx`, чтобы Next.js мог с ним работать. -Например: +```tsx title="pages/_app.tsx" +export { App as default } from '@/app'; +``` -```tsx title="app/providers/index.tsx" -const App = ({ Component, pageProps }: IProps) => { - return ( - - - - - - - - ); -}; +### Пример ре-экспорта страницы из `src/pages/example` в `pages/example/index.tsx` -export default App; +```tsx title="pages/example/index.tsx" +export { Example as default } from '@/pages/example'; ``` -Затем вы можете импортировать компонент `App` и глобальные стили проекта в `pages/_app.tsx` следующим образом: +### API Routes +Предлагается использовать новый сегмент `routes` в слое `app` или `pages` для работы c API Routes. -```tsx title="pages/_app.tsx" -import 'app/styles/globals.css' - -export { default } from 'app/providers'; +```tsx title="app/api/example.ts" +export { handler as default, config } from '@/pages/example'; ``` ## См. также [Структура проекта Next.js](https://Next.js.org/docs/app/getting-started/project-structure) -[Компоновка страниц Next.js](https://Next.js.org/docs/app/getting-started/layouts-and-pages) \ No newline at end of file +[Компоновка страниц Next.js](https://Next.js.org/docs/app/getting-started/layouts-and-pages) + +[project-knowledge]: /docs/about/understanding/knowledge-types +[ext-app-router-stackblitz]: https://stackblitz.com/edit/stackblitz-starters-aiez55?file=README.md \ No newline at end of file