Skip to content

First Steps

설치

모노레포 내에서 workspace:*로 참조한다:

json
{
  "dependencies": {
    "@repo/electron-ipc": "workspace:*"
  }
}

Peer dependency로 electron이 필요하다:

json
{
  "devDependencies": {
    "electron": ">=28"
  }
}

최소 예제

서비스 하나와 컨트롤러 하나로 구성된 최소 앱을 만든다.

1단계: Contract 정의

ts
// greeting/contract.ts
import { contract, handle } from '@repo/electron-ipc/contract';

export const greetingContract = contract({
  handles: {
    greet: handle<[string], string>('greet'),
  },
});

2단계: 서비스 정의

ts
// greeting/service.ts
import { Injectable } from '@repo/electron-ipc';

@Injectable()
class GreetingService {
  greet(name: string): string {
    return `Hello, ${name}!`;
  }
}

3단계: 컨트롤러 정의

메서드 이름을 contract 키와 동일하게 정의하면 @Controller(contract)가 자동으로 IPC 채널에 바인딩한다.

ts
// greeting/controller.ts
import { Controller, inject } from '@repo/electron-ipc';
import { greetingContract } from './contract';
import { GreetingService } from './service';

@Controller(greetingContract)
class GreetingController {
  private readonly greeting = inject(GreetingService);

  // contract.handles.greet 키 → 'greet' 채널 자동 바인딩
  greet(name: string): string {
    return this.greeting.greet(name);
  }
}

TS가 @Controller(greetingContract)를 통해 클래스 형태를 자동으로 검사한다. greet 메서드를 누락하거나 시그니처가 contract와 어긋나면 IDE에서 빨간 줄로 잡힌다.

4단계: 모듈 구성

ts
// greeting/module.ts
import { Module } from '@repo/electron-ipc';
import { GreetingService } from './service';
import { GreetingController } from './controller';

@Module({
  providers: [GreetingService],
  controllers: [GreetingController],
})
class GreetingModule {}

5단계: 메인 윈도우 Provider

ts
// window/service.ts
import { BrowserWindow } from 'electron';
import {
  Injectable,
  type OnMainWindowCreate,
} from '@repo/electron-ipc';

@Injectable()
export class WindowService implements OnMainWindowCreate {
  onMainWindowCreate(): BrowserWindow {
    return new BrowserWindow({
      width: 800,
      height: 600,
      webPreferences: { preload: '/path/to/preload.js' },
    });
  }
}

// window/module.ts
import { Module } from '@repo/electron-ipc';
import { WindowService } from './service';

@Module({ providers: [WindowService], controllers: [] })
export class WindowModule {}

6단계: 앱 부트스트랩

ts
// main.ts
import { createApp, SenderGuard } from '@repo/electron-ipc';
import { GreetingModule } from './greeting/module';
import { WindowModule } from './window/module';

const app = createApp({
  modules: [WindowModule, GreetingModule],
  guards: [new SenderGuard()],
});

app.start();

7단계: Preload Bridge

ts
// preload.ts
import { contextBridge } from 'electron';
import { createBridge } from '@repo/electron-ipc/bridge';
import { greetingContract } from './greeting/contract';

const contracts = { greeting: greetingContract } as const;
const api = createBridge(contracts);
contextBridge.exposeInMainWorld('api', api);

8단계: 렌더러에서 호출

ts
// renderer.ts
import { createService } from '@repo/electron-ipc/service';
import { greetingContract } from './greeting/contract';

const contracts = { greeting: greetingContract } as const;
const service = createService(window.api, contracts);

const result = await service.greeting.greet('World');
console.log(result); // "Hello, World!"

파일 구조

src/
├── greeting/
│   ├── contract.ts
│   ├── service.ts
│   ├── controller.ts
│   ├── module.ts
│   └── index.ts
├── main.ts
├── preload.ts
└── renderer.ts

다음 단계