일렉트론을 사용해서 토이프로젝트를 진행한적이 있는데요, 메인 프로세스와 렌더러 프로세스간 IPC 통신을 할 때 중복되는 코드가 발생한다는 것과 타입 추론이 잘 되지 않는다는 문제를 발견했습니다.
이를 어떻게 개선할 수 있을까 고민하다가 trpc
에서 영감을 받아 타입 안정성(타입 추론, 런타임 validation)을 높일 수 있는 방법을 만들어 보았습니다.
실제 사용 코드는 https://github.com/cheatkey/smart-file-manager/blob/main/src/main/preload.ts 에서 보실 수 있습니다.
일렉트론에서 메인 프로세스 <-> 렌더러 프로세스가 통신하는 방법
일렉트론은 아래와 같은 구조를 띄고 있습니다.
-
메인 프로세스 (Main Process): Electron 애플리케이션의 백그라운드에서 실행되며, 애플리케이션의 라이프 사이클을 관리하고 시스템 리소스에 직접 접근합니다.
-
렌더러 프로세스 (Renderer Process): UI를 표시하며, 각각의 브라우저 창에 대응되는 독립적인 환경에서 실행되어 사용자 인터페이스와 사용자 상호작용을 처리합니다.
즉, 메인 프로세스가 서버의 역할을, 렌더러 프로세스가 프론트엔드의 역할을 수행하게 됩니다.
프로세스간 통신할 때 사용할 수 있는 메소드는 여러가지가 있습니다.
-
ipcRenderer.send(channel, ...args)
- Renderer Process에서 Main Process로 비동기 메시지를 보내는 데 사용됩니다.
-
ipcMain.on(channel, listener)
- Main Process에서 특정 채널로부터 메시지를 지속적으로 수신하도록 리스너를 설정합니다.
-
ipcRenderer.invoke(channel, ...args)
- Renderer Process에서 Main Process로 메시지를 비동기적으로 보내고, Promise를 반환받아 결과를 기다립니다.
이 외에도 많은 메소드가 있는데요, 자세한 내용은 Electron ipcRenderer docs에서 확인하실 수 있습니다.
단순히 데이터를 메인 프로세스에 전송만 하려면 send
메소드를, 응답을 받기 위해서는 invoke
를 사용합니다.
invoke 메소드 사용법
렌더러 프로세스에서 메인 프로세스로 데이터를 보낼 때, 첫번째 인자로 채널의 이름을 지정합니다. 추후 메인 프로세스에서도 동일한 채널 이름을 핸들링해야 정상적으로 통신할 수 있습니다.
두번째 인자에는 메인 프로세스로 보낼 데이터를 넣어줍니다. 참고로 데이터는 The structured clone algorithm으로 직렬화 되기 때문에 프로토타입은 포함되지 않으며, 직렬화가 불가능한 데이터인 함수, 심볼, weekMap 등은 전송하면 에러가 발생합니다.
메인 프로세스에서는 handle
을 사용합니다. 렌더러 프로세스에서 사용했던 채널 이름을 동일하게 입력하고, 두번째 인자에는 비지니스 로직을 작성하면 됩니다.
invoke 메소드의 불편함
하지만 코드베이스가 조금만 커져도 invoke
를 사용하는데 불편함이 느껴졌습니다.
- 타입이 지원되지 않음
invoke
사용시 어떤 데이터를 보내야 하는지 / 어떤 데이터가 반환되는지 타입 추론이 되지 않습니다.
- 채널을 통해 통신하는 구조
- 데이터를 보낼 때, 받을 때 문자열로 된 채널을 기준으로 통신하기 때문에 채널 이름을 변경하거나 리팩토링 시 실수가 발생하기 쉬운 구조를 가지고 있습니다.
invoke 메소드 개발자 경험 (DX) 개선하기
위에서 언급했던 2가지 문제를 해결하기 위해서 아래 기능을 구현해보려고 합니다.
-
invoke
사용 시 어떤 데이터를 넣어야 하며, 어떤 데이터가 반환되는지 타입 추론하기 -
런타임 안정성을 위해서 zod로 validation을 하기
-
함수의 이름을 채널로 사용하기
zod validation과 타입 추론이 되는 핸들러 래핑 함수 만들기
trpc
에서 영감을 얻었는데요, 먼저 메인 프로세스에서 데이터를 핸들링 할 때 사용하는 함수를 래핑하는 함수를 제작했습니다.
첫번째 인자로 zod schema
를 입력 받습니다. 이는 2가지 용도로 활용 되는데요,
- 함수 실행 시, 인자로 받은 데이터를 검증해서 잘못된 데이터가 들어온건 아닌지 판별합니다.
- 타입을 추출하여 함수의 인자 타입을 자동으로 추론합니다.
두번째 인자로는 함수의 내용을 입력 받습니다. 비지니스 로직을 입력하면 되고, zod schema
의 (2)번 용도로 인자의 타입을 별도로 지정해주지 않아도 됩니다.
실제 구현 코드는 아래와 같습니다.
ipcFunction
으로 래핑 시, 함수의 인자가 자동으로 zod
로 지정한 타입으로 추론됩니다.
이후 preload.ts
파일에서 invoke
의 타입을 지정해줍니다.
총 3개의 제네릭 타입을 사용합니다.
- 채널의 타입은
ipcFunction
으로 래핑된 함수의 이름 - 인자의 타입은
zod
스키마에서 타입을 파싱 - 리턴 타입은
ipcFunction
으로 래핑된 함수의 반환 타입
이를 코드로 구현하면 아래와 같습니다.
이제 렌더러 프로세스에서 invoke
사용 시 반환 결과가 타입 추론 되는 것을 볼 수 있습니다.
Conclusion
zod를 이용하여 타입을 검증하고, 인자의 타입을 추론하고, 이를 메인 프로세스 <-> 렌더러 프로세스간 통신에서 타입을 안전하게 다루도록 사용해봤습니다.
개인적으로는 자동으로 타입이 추론되는 구조를 만들면서 trpc
가 대략적으로 어떻게 구현되었을지 감을 잡을 수 있었습니다.