일렉트론(Electron) - 메인 프로세스와 렌더러 프로세스(main process, renderer process)
(일렉트론(Electron) - 자바스크립트로 데스크톱 앱 만들기에서 이어집니다.)
일렉트론은 내부적으로 메인 프로세스와 렌더러 프로세스(main process, renderer process) 구조를 가짐을 알게 되었다. 그러면 실제 코드 레벨에서 구현할 때는 이 구조가 어떻게 된다는 것일까? 공식 문서에서 제공하는 간략한 예제 코드들로 개념을 먼저 살펴보고자 한다.
메인 프로세스(main process)
단일 메인 프로세스
가 여러 개의 렌더러 프로세스를 관리한다고 했다. 일렉트론을 사용하면, 웹 브라우저에서가 아니라 데스크톱 응용 프로그램 창에서 화면을 표시할 수 있게 해 준다.
이 때, 그 응용프로그램 창(window) 의 생성 및 삭제 등을 관리하는 역할을 메인 프로세스
에서 하는 것이며, 메인 프로세스는 node 기반으로 동작하므로 파일 시스템 접근 등 Node.js API(https://nodejs.org/dist/latest-v16.x/docs/api/)를 모두 사용할 수 있다.
메인 프로세스 코드 안에서 electron의 BrowserWindow 모듈을 사용하면 윈도우를 생성할 수 있다. 여기서부터 시작되는 것이다!
1
2
3
4
5
// main.js - 메인 프로세스 로직
const { BrowserWindow } = require("electron");
const win = new BrowserWindow({ width: 800, height: 1500 });
win.loadURL("https://github.com");
위 코드는 정말 단순하게 메인 프로세스에서 800x1500 크기의 윈도우를 생성하고, 거기에 github.com 웹페이지를 띄운 예제이다.
알고 있는 웹페이지 주소를 입력해 띄우려면 위의 loadURL
메서드를 사용하면 되지만, 우리가 단순히 기존 웹사이트를 띄우려고 일렉트론을 사용하는 것은 아니다.
보통 리액트+웹팩+일렉트론 조합으로 구현한 결과물을 창에 띄울 때에는, loadFile
메서드(docs)를 사용하며, 번들 결과물을 주입하는 index.html
을 파라미터에 작성하는 형식이 될 것이다.
1
2
// main.js - 메인 프로세스 로직
win.loadFile("src/index.html");
창을 관리하는 것 뿐만 아니라, 메인 프로세스에서는 응용 프로그램 생명주기(lifecycle) 도 관리한다. electron의 app 모듈을 사용하여 생명주기를 관리하게 된다. 아주 중요한 역할이라고 할 수 있다.
1
2
3
4
5
// main.js - 메인 프로세스 로직
const { app } = require("electron");
app.on("window-all-closed", () => {
app.quit();
});
예를 들어 위의 코드는 window-all-closed 이벤트를 감지하면, 애플리케이션을 종료하겠다 라는 의미이다.
app 모듈에서 감지하는 lifecycle 관련 event는 위의 window-all-closed 말고도 정말 다양하다. (https://www.electronjs.org/docs/latest/api/app#events 에서 확인할 수 있다) 상황에 맞는 적절한 이벤트를 사용하여 개발자가 직접 애플리케이션의 수명주기를 관리할 수 있다.
지금까지는 메인 프로세스에서 사용 가능한 모듈 중 BrowserWindow
, app
두 가지만 살펴본 것이다. 일렉트론 구현 시 메인 프로세스에서 필수적으로 사용되는 모듈이라고 할 수 있다. 하지만 이 두가지 뿐만 아니라 Main Process용 Module도 그 종류가 정말 많다. 응용 프로그램에서 사용하기 위한 Menu, 알림을 위한 Notification 등 다양한 모듈의 종류는 https://www.electronjs.org/docs/latest/api/app#events에서 확인할 수 있다.
렌더러 프로세스(renderer process)
렌더러 프로세스는 응용프로그램 창 안에서 띄워지게 될 화면에 대해 관여한다. 즉, 단순하게 생각하면 기존에 익숙하게 개발하던 react, vue 등의 코드들이 렌더러 프로세스 쪽이라고 보면 된다.
렌더러 프로세스는 기존 react 등 웹 개발 코드 그대로 사용하면 된다. 그래서 electron이 제공하는 렌더러 프로세스용 모듈은, 메인 프로세스용 모듈보다는 가짓수가 확실히 적다. (electron 공식 사이트의 API 탭에서 확인할 수 있다.)
렌더러 프로세스에 제공하는 ipcRenderer
모듈을 예시로 들어볼 것인데, 이것은 렌더러 프로세스와 메인 프로세스 간 통신을 위해 제공하는 모듈이다. 이것이 왜 필요할까?
쉽게 말해서 우리가 짜고 있는 react 코드에서 무언가 메인 프로세스에서만 할 수 있는 일(메뉴 표시, 다이얼로그, 윈도우 관련, Node.js API 기능 등)이 필요할 경우, react 등의 기존 자바스크립트 코드(렌더러 프로세스)에서는 접근할 수 없는 일이기 때문에, 프로세스 간 통신(IPC, Inter-Process Communication)으로 메인 프로세스에게 요청을 해야 하기 때문이다.
아래 코드는 렌더러 프로세스와 메인 프로세스 간에 IPC 통신을 하는 간략한 예제이다.
1
2
3
4
5
6
7
8
9
10
// Renderer process
ipcRenderer.invoke("some-name", someArgument).then((result) => {
// ...
});
// Main process
ipcMain.handle("some-name", async (event, someArgument) => {
const result = await doSomeWork(someArgument);
return result;
});
렌더러 프로세스에서 사용할 수 있는 ipcRenderer
모듈이다. 메인 프로세스에서는 ipcMain
모듈을 사용하고 있다.
렌더러 프로세스에서, some-name이라는 이름을 가진 채널을 통해 someArgument와 함께 ipcRenderer.invoke
를 호출한다. 그러면 메인 프로세스에서는 ipcMain.handle
을 통해 렌더러 프로세스로부터 받은 요청을 처리하고, 결과값을 반환한다. (ipcRenderer.invoke
<-> ipcMain.handle
쌍이 된다.)
반환된 결과값은, 다시 비동기적으로 렌더러 프로세스가 result 값으로 받게 된다(then 이후 코드).
위의 ipcRenderer.invoke
<-> ipcMain.handle
쌍 외에도, ipcRenderer.send
<-> ipcMain.on
도 많이 사용하게 된다. 물론, 메인 프로세스에서 send 하고 렌더러 프로세스에서 receive 하는 구조도 얼마든지 만들 수 있다. 이들은 앞으로 이어지는 포스팅들에서 직접 사용되는 상황을 예시로 들어서 비교할 예정이다.
간단한 예제만으로 electron에서 사용하는 모듈에 대해 가볍게 훑어 보았으니, 다음 포스팅에서는 실제 구현을 해 보면서 확인해 보도록 하겠다.
레퍼런스