[Electron] Process Model

abi hong·2023년 12월 26일

Electron

목록 보기
5/5

Electron 공식문서를 정리한 글입니다.

Process Model

Electron은 다중 프로세스 아키텍처를 Chromium에서 상속받았다. 이로 인해, 이 프레임워크는 현대적인 웹 브라우저와 아키텍처적으로 매우 유사하다.

왜 단일 프로세스가 아닐까? (Why not a single process?)

웹 브라우저는 아주 복잡한 애플리케이션이다. 웹 컨텐츠를 표시하는 주요 기능 이외에도 여러 개의 창을 관리하고 확장 프로그램을 로드하는 등 부가적인 기능들이 있다.

과거에는 브라우저가 이 모든 기능을 위해 하나의 프로세스를 사용했다. 이러한 패턴은 사용하고 있는 각 탭에 대한 적은 overhead를 의미했지만, 한 웹사이트가 멈추거나 충돌이 발생하면 이는 브라우저 전체에 영향을 미칠 수 있다는 것을 의미했다.

멀티 프로세스 모델 (The multi-process model)

이 문제를 해결하기 위해, Chrome 팀은 각 탭이 자체 프로세스에서 렌더링하도록 결정했다. 이렇게 함으로써, 웹 페이지의 버그나 악성 코드가 애플리케이션 전체에 미칠 수 있는 피해를 제한할 수 있었다. 하나의 브라우저 프로세스가 이러한 프로세스들과 애플리케이션의 라이프사이클 전반을 제어한다.

Electron 애플리케이션은 매우 유사한 구조를 가지고 있다. 앱 개발자로서, mainrenderer, 2가지 유형의 프로세스를 제어할 수 있다. 위에서 설명한 Chrome의 브라우저와 renderer프로세스와 유사한 역할을 한다.

The main process

각 Electron 앱은 애플리케이션의 진입점으로 작동하는 single main 프로세스를 갖고 있다. main 프로세스는 Node.js 환경에서 실행되므로 Node.js API를 모두 사용할 수 있다.

Window management

main 프로세스의 주요 목적은 BrowserWindow 모듈을 사용해 애플리케이션 창들을 만들고 관리하는 것이다. BrowserWindow 클래스의 각 인스턴스는 별도의 renderer 프로세스에서 웹 페이지를 로드하는 애플리케이션 창을 생성한다. main 프로세스에서 window의 webContents 객체를 사용해 웹 컨텐츠와 상호작용 할 수 있다.

const { BrowserWindow } = require('electron')

const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://github.com')

const contents = win.webContents
console.log(contents)

BrowserWindow 모듈은 EventEmitter이기 때문에, 다양한 사용자 이벤트에 대한 핸들러도 추가할 수 있다. (ex. 창을 최소화 또는 최대화 하는 경우)

BrowserWindow 인스턴스가 삭제되면, renderer 프로세스 또한 종료된다.

Application lifecycle

main 프로세스는 Electron의 app 모듈을 통해 애플리케이션의 라이프사이클을 조절한다. 이 모듈은 사용자 정의 애플리케이션 동작을 추가할 수 있는 다양한 이벤트와 메서드를 제공한다.

예를 들어, app API를 사용해 더 네이티브한 애플리케이션 window 경험을 만들 수 있다.

// quitting the app when no windows are open on non-macOS platforms
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})

Native APIs

Electron은 웹 컨텐츠를 위한 Chromium wrapper를 넘어서기 위해, main 프로세스는 사용자 운영체제와 상호작용하기 위한 사용자 정의 API를 추가한다. Electron은 네이티브한 데스크탑 기능(ex. 메뉴, 대화상자, tray icon 등)을 제어하는 다양한 모듈을 노출시킨다.

The renderer process

각 Electron 앱은 열린 BrowserWindow 마다 별도의 renderer 프로세스를 생성한다. 이름에서 알 수 있듯이, renderer는 웹 컨텐츠를 렌더링하는 역할을 한다. (renderer 프로세스에서 실행되는 코드는 웹 표준에 따라 동작해야 한다.)

따라서 하나의 브라우저 창 내에서의 모든 사용자 인터페이스 및 앱 기능은 웹에서 사용하는 것과 동일한 도구와 패러다임을 사용해 작성되어야 한다.

  • HTML 파일이 renderer 프로세스의 진입점이다.
  • UI 스타일링은 CSS를 통해 추가된다.
  • 실행 가능한 JS 코드는 <script> 태그를 통해 추가된다.

또한, 이것은 rendererrequire나 다른 Node.js API에 직접적인 접근 권한이 없다는 것을 의미한다. renderer 에서 npm 모듈을 직접적으로 포함시키기 위해서는 웹에서 사용하는 것과 동일한 bundler 도구(ex. webpack 또는 parcel)를 사용해야 한다.

Preload scripts

Preload 스크립트는 웹 컨텐츠가 로드되기 전에 renderer 프로세스에서 실행되는 코드를 포함한다. 이 스크립트는 renderer 컨텍스트 내에서 실행되지만, Node.js API에 접근할 수 있도록 더 많은 권한을 부여받는다.

Preload 스크립트는 main 프로세스의 BrowserWindow 생성자의 webPreferences 옵션에서 첨부할 수 있다.

const { BrowserWindow } = require('electron')
// ...
const win = new BrowserWindow({
  webPreferences: {
    preload: 'path/to/preload.js'
  }
})
// ...

Preload 스크립트는 전역 window 인터페이스를 renderer 와 공유하며 Node.js API에 접근할 수 있기 때문에, 웹 컨텐츠가 사용할 수 있는 window 전역에 임의의 API를 노출함으로써 renderer를 강화하는데 도움이 된다.

Preload 스크립트는 연결된 renderer와 전역 window 객체를 공유하지만, contextIsolation 기본값 때문에 Preload 스크립트에서 window로 직접적으로 변수를 첨부할 수는 없다.

window.myAPI = {
  desktop: true
}
const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('myAPI', {
  desktop: true
})
console.log(window.myAPI)
// => { desktop: true }

이 특징은 2가지 주요한 목적으로 매우 유용하다.

  • ipcRendererrenderer에 노출시켜 IPC를 사용해 renderer에서 main 프로세스 작업을 트리거할 수 있다. (그 반대도 가능하다.)
  • 만약 기존의 웹 앱을 호스팅하는 원격 url의 Electron wrapper를 개발 중이라면, renderer의 전역 window 객체에 사용자 지정 속성을 추가해 웹 클라이언트 측에서 데스크탑 전용 로직을 사용할 수 있다.

The utility process

각 Electron 앱은 main 프로세스에서 UtilityProcess API를 사용해 여러 자식 프로세스를 생성할 수 있다.

Utility 프로세스는 Node.js 환경에서 실행되며, 모든 Node.js API를 사용할 수 있다. The utility process can be used to host for example: untrusted services, CPU intensive tasks or crash prone components which would have previously been hosted in the main process or process spawned with Node.js [child_process.fork](https://nodejs.org/dist/latest-v16.x/docs/api/child_process.html#child_processforkmodulepath-args-options) API.

Utility 프로세스와 Node.js child_process 모듈로 생성된 프로세스의 주요 차이점은 Utility 프로세스가 MessagePorts를 사용하여 renderer 프로세스와 통신 채널을 설정할 수 있다는 것이다. Electron 앱은 main 프로세스에서 자식 프로세스를 fork해야 할 때 항상 Node.js child_process.fork API 대신 UtilityProcess API를 선호한다.

0개의 댓글