Usage with Electron
Electron 애플리케이션은 역할이 다른 여러 프로세스(Main, Renderer, Preload)로 구성됩니다.
따라서 FSD를 적용하려면 Electron 특성에 맞게 구조를 조정해야 합니다.
디렉터리src
디렉터리app Common app layer
디렉터리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 규칙
섹션 제목: “Public API 규칙”-
각 프로세스는 자신만의 Public API를 가져야 합니다.
- 예)
renderer코드가main폴더 모듈을 직접 import 하면 안 됩니다.
- 예)
-
단,
src/shared폴더는 두 프로세스 모두에게 공개됩니다.- (프로세스 간 통신 계약과 타입 정의를 위해 필요합니다)
표준 구조의 추가 변경 사항
섹션 제목: “표준 구조의 추가 변경 사항”ipcsegment를 새로 만들어, 프로세스 간 통신(채널, 핸들러)을 한곳에 모읍니다.src/main에는 이름 그대로pages,widgetslayer를 두지 않습니다.
대신features,entities,shared만 사용합니다.src/applayer는 Main, Renderer entry와 IPC initialization code만 담는 전용 영역입니다.applayer 내부의 각 segment는 서로 교차 의존이 발생하지 않도록 구성하는 것이 좋습니다.
Interaction example
섹션 제목: “Interaction example”export const CHANNELS = { GET_USER_DATA: 'GET_USER_DATA', SAVE_USER: 'SAVE_USER',} as const;
export type TChannelKeys = keyof typeof CHANNELS;import { CHANNELS } from './channels';
export interface IEvents { [CHANNELS.GET_USER_DATA]: { args: void, response?: { name: string; email: string; }; }; [CHANNELS.SAVE_USER]: { args: { name: string; }; response: void; };}import { CHANNELS } from './channels';import type { IEvents } from './events';
type TOptionalArgs<T> = T extends void ? [] : [args: T];
export type TElectronAPI = { [K in keyof typeof CHANNELS]: (...args: TOptionalArgs<IEvents[typeof CHANNELS[K]]['args']>) => IEvents[typeof CHANNELS[K]]['response'];};import { contextBridge, ipcRenderer } from 'electron';import { CHANNELS, type TElectronAPI } from 'shared/ipc';
const API: TElectronAPI = { [CHANNELS.GET_USER_DATA]: () => ipcRenderer.sendSync(CHANNELS.GET_USER_DATA), [CHANNELS.SAVE_USER]: args => ipcRenderer.invoke(CHANNELS.SAVE_USER, args),} as const;
contextBridge.exposeInMainWorld('electron', API);import { ipcMain } from 'electron';import { CHANNELS } from 'shared/ipc';
export const sendUser = () => { ipcMain.on(CHANNELS.GET_USER_DATA, ev => { ev.returnValue = { name: 'John Doe', email: 'john.doe@example.com', }; });};import { CHANNELS } from 'shared/ipc';
export const getUser = () => { const user = window.electron[CHANNELS.GET_USER_DATA]();
return user ?? { name: 'John Donte', email: 'john.donte@example.com' };};