본문으로 건너뛰기

프로세스 모델

일렉트론은 크로미움의 다중 프로세스 아키텍처를 계승하여 프레임워크를 현대의 웹 브라우저와 구조적으로 매우 유사합니다. 이 안내서에서는 자습서에 적용된 개념에 대해 자세히 알아봅니다.

단일 프로세스가 아닌 이유

웹 브라우저는 매우 복잡한 애플리케이션입니다. 웹 콘텐츠를 표시하는 기본 기능 외에도 여러 창(또는 탭)을 관리하고 타사 확장 프로그램을 로드하는 등 부차적인 책임이 많습니다.

이전에는 브라우저가 일반적으로 이 모든 기능에 대해 단일 프로세스를 사용했습니다. 이 패턴은 열려 있는 각 탭에 대한 오버헤드가 적다는 것을 의미하지만, 웹 사이트 하나가 충돌하거나 중단되면 전체 브라우저에 영향을 미친다는 것을 의미하기도 합니다.

다중 프로세스 모델

이 문제를 해결하기 위해, 크롬 팀은 각 탭이 자체 프로세스에서 렌더링되어 웹 페이지의 버그 또는 악성 코드가 앱 전체에 미칠 수 있는 피해를 제한하기로 결정했습니다. 그리고 단일 브라우저 프로세스가 이러한 프로세스들과 전체 앱의 수명 주기를 제어합니다. 이 모델을 시각화한 것이 다음 크롬 만화의 다이어그램입니다.

일렉트론 앱은 매우 유사한 구조를 가집니다. 앱 개발자는 주 프로세스렌더러 프로세스라는 두 개의 프로세스를 제어합니다. 렌더러 프로세스는 앞에서 설명한 크롬의 브라우저 자체 프로세스와 유사합니다.

주 프로세스

각 일렉트론 앱에는 앱의 진입점 역할을 하는 단일 주(主, main) 프로세스가 있습니다. 주 프로세스는 노드 환경에서 실행되므로, 모듈을 require하고 모든 노드 API를 사용할 수 있습니다.

창 관리

주 프로세스의 주요 목적은 BrowserWindow 모듈을 사용하여 앱 창을 만들고 관리하는 것입니다.

BrowserWindow 클래스의 각 인스턴스는 별도의 렌더러 프로세스에서 웹 페이지를 로드하는 앱 창을 만듭니다. 창의 webContents 객체를 사용하여 주 프로세스에서 이 웹 콘텐츠와 상호 작용할 수 있습니다.

main.js
js
const { BrowserWindow } = require('electron');
const win = new BrowserWindow({ width: 800, height: 1500 });
win.loadURL('https://github.com');
const contents = win.webContents;
console.log(contents);
main.js
js
const { BrowserWindow } = require('electron');
const win = new BrowserWindow({ width: 800, height: 1500 });
win.loadURL('https://github.com');
const contents = win.webContents;
console.log(contents);
참고

렌더러 프로세스는 BrowserView 모듈과 같은 웹 임베드에 대해서도 생성됩니다. 삽입된 웹 콘텐츠도 webContents 객체로 접근하는 것이 가능합니다.

BrowserWindow 모듈은 EventEmitter이므로, 다양한 사용자 이벤트(예: 창 최소화 또는 최대화)에 대한 처리기를 추가할 수도 있습니다.

BrowserWindow 인스턴스가 파괴되면 해당 렌더러 프로세스도 종료됩니다.

앱의 수명 주기

주 프로세스는 또한 일렉트론의 app 모듈을 통해 앱의 수명 주기를 제어합니다. 이 모듈은 사용자 지정 애플리케이션 동작(예: 프로그래밍 방식으로 앱 종료하기, 앱 도크 수정하기, 정보 패널 표시하기)을 추가하는 데 사용할 수 있는 대규모 이벤트 및 메서드 모음을 제공합니다.

실용적인 예로, 빠른 시작 안내서에 표시된 앱은 app API를 사용하여 보다 네이티브한 앱 창 경험을 만듭니다.

main.js
js
// 맥OS가 아닌 플랫폼에서는 열려 있는 창이 없을 때 앱을 종료합니다.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
main.js
js
// 맥OS가 아닌 플랫폼에서는 열려 있는 창이 없을 때 앱을 종료합니다.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});

네이티브 API

웹 콘텐츠용 크로미움 래퍼 이상으로 일렉트론의 기능을 확장하기 위해, 주 프로세스는 사용자의 운영 체제와 상호 작용하는 사용자 정의 API도 추가합니다. 일렉트론은 메뉴, 대화 상자, 트레이 아이콘과 같은 네이티브 데스크톱 기능을 제어하는 ​​다양한 모듈을 제공합니다.

일렉트론의 주 프로세스 모듈의 전체 목록은 API 문서를 확인하세요.

렌더러 프로세스

각 일렉트론 앱은 열려 있는 각 BrowserWindow(그리고 웹 임베드)에 대해 별도의 렌더러 프로세스를 생성합니다. 이름에서 알 수 있듯이, 렌더러는 웹 콘텐츠의 렌더링을 담당합니다. 모든 의도와 목적을 위해 렌더러 프로세스에서 실행되는 코드는 웹 표준에 따라 작동해야 합니다(적어도 크로미움이 수행하는 한).

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

모든 웹 사양을 설명하는 것은 이 안내서의 범위를 벗어나지만, 이해해야 할 최소한의 사항은 다음과 같습니다.

  • HTML 파일은 렌더러 프로세스의 진입점입니다.
  • UI 스타일은 CSS(Cascading Style Sheets)를 통해 추가됩니다.
  • 실행 가능한 자바스크립트 코드는 <script> 요소를 통해 추가할 수 있습니다.

또한 이는 렌더러가 require이나 다른 노드 API에 직접 접근할 수 없다는 것을 의미합니다. 렌더러에 NPM 모듈을 직접 포함하려면 웹에서 사용하는 것과 동일한 번들러 도구 모음(예: webpack, parcel)을 사용해야 합니다.

위험

렌더러 프로세스는 개발 용이성을 위해 전체 노드 환경에서 생성될 수 있습니다. 이전에는 이 기능이 기본값이었지만 보안상의 이유로 비활성화되었습니다.

주 프로세스에서만 접근할 수 있다면, 렌더러 프로세스 사용자 인터페이스가 노드와 일렉트론의 기본 데스크톱 기능과 어떻게 상호 작용할 수 있는지 궁금할 것입니다. 실제로 일렉트론의 콘텐츠 스크립트를 가져올 수 있는 직접적인 방법은 없습니다.

사전 로드 스크립트

사전 로드 스크립트에는 웹 콘텐츠가 로드되기 전에 렌더러 프로세스에서 실행되는 코드가 포함되어 있습니다. 이 스크립트는 렌더러 컨텍스트 내에서 실행되지만 노드 API에 접근할 수 있으므로 더 많은 권한이 부여됩니다.

사전 로드 스크립트는 BrowserWindow 생성자의 webPreferences 옵션에서 주 프로세스에 첨부할 수 있습니다.

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

사전 로드 스크립트는 렌더러와 전역 Window 인터페이스를 공유하고 노드 API에 접근할 수 있습니다. 따라서 웹 콘텐츠가 사용할 수 있는 window에서 임의의 API를 노출하여, 렌더러를 향상시키는 역할을 합니다.

사전 로드 스크립트는 첨부된 렌더러와 window 전역을 공유합니다. 하지만 contextIsolation 기본값 때문에 사전 로드 스크립트의 변수를 window에 직접 연결할 수는 없습니다.

preload.js
js
window.myAPI = {
desktop: true,
};
preload.js
js
window.myAPI = {
desktop: true,
};
renderer.js
js
console.log(window.myAPI);
// => undefined
renderer.js
js
console.log(window.myAPI);
// => undefined

컨텍스트 격리는 사전 로드 스크립트가 렌더러의 주 세계에서 격리되어 권한 있는 API가 웹 콘텐츠의 코드로 유출되는 것을 방지하는 것을 의미합니다.

대신 contextBridge 모듈을 사용하여 이를 안전하게 수행하세요.

preload.js
js
const { contextBridge } = require('electron');
contextBridge.exposeInMainWorld('myAPI', {
desktop: true,
});
preload.js
js
const { contextBridge } = require('electron');
contextBridge.exposeInMainWorld('myAPI', {
desktop: true,
});
renderer.js
js
console.log(window.myAPI);
// => { desktop: true }
renderer.js
js
console.log(window.myAPI);
// => { desktop: true }

이 기능은 크게 두 가지 용도로 사용됩니다.

  • ipcRenderer 도우미를 렌더러에 노출하면 IPC(프로세스 간 통신)를 사용하여 렌더러에서 주 프로세스의 작업을 트리거할 수 있습니다. (그 반대도 가능)
  • 원격 URL에서 호스팅되는 기존 웹 앱의 일렉트론 래퍼를 개발하는 경우, 웹 클라이언트 측에서 데스크톱 전용 논리에 사용할 수 있는 렌더러의 window 전역에 커스텀 프로퍼티를 추가할 수 있습니다.

유틸리티 프로세스

각 일렉트론 앱은 유틸리티 프로세스 API를 사용하여 주 프로세스에서 여러 하위 프로세스를 생성할 수 있습니다. 유틸리티 프로세스는 노드 환경에서 실행되므로, 모듈을 require하고 모든 노드 API를 사용할 수 있습니다.

유틸리티 프로세스는 호스팅에 사용할 수 있습니다. 그 예로는 신뢰할 수 없는 서비스, CPU 집약적인 작업, 충돌하기 쉬운 컴포넌트가 있습니다. 예전에는 주 프로세스나 노드의 child_process.fork API로 생성된 프로세스에서 호스팅되었을 것입니다. 유틸리티 프로세스와 노드의 child_process 모듈에 의해 생성된 프로세스의 주요 차이점은, 유틸리티 프로세스가 MessagePort를 사용하여 렌더러 프로세스와 통신 채널을 설정할 수 있다는 점입니다. 일렉트론 앱은 주 프로세스에서 하위 프로세스를 분기해야 하는 경우, 항상 노드의 child_process.fork API보다 유틸리티 프로세스 API를 선호할 수 있습니다.