프로세스 모델
일렉트론은 크로미움의 다중 프로세스 아키텍처를 계승하여 프레임워크를 현대의 웹 브라우저와 구조적으로 매우 유사합니다. 이 안내서에서는 자습서에 적용된 개념에 대해 자세히 알아봅니다.
단일 프로세스가 아닌 이유
웹 브라우저는 매우 복잡한 애플리케이션입니다. 웹 콘텐츠를 표시하는 기본 기능 외에도 여러 창(또는 탭)을 관리하고 타사 확장 프로그램을 로드하는 등 부차적인 책임이 많습니다.
이전에는 브라우저가 일반적으로 이 모든 기능에 대해 단일 프로세스를 사용했습니다. 이 패턴은 열려 있는 각 탭에 대한 오버헤드가 적다는 것을 의미하지만, 웹 사이트 하나가 충돌하거나 중단되면 전체 브라우저에 영향을 미친다는 것을 의미하기도 합니다.
다중 프로세스 모델
이 문제를 해결하기 위해, 크롬 팀은 각 탭이 자체 프로세스에서 렌더링되어 웹 페이지의 버그 또는 악성 코드가 앱 전체에 미칠 수 있는 피해를 제한하기로 결정했습니다. 그리고 단일 브라우저 프로세스가 이러한 프로세스들과 전체 앱의 수명 주기를 제어합니다. 이 모델을 시각화한 것이 다음 크롬 만화의 다이어그램입니다.
일렉트론 앱은 매우 유사한 구조를 가집니다. 앱 개발자는 주 프로세스과 렌더러 프로세스라는 두 개의 프로세스를 제어합니다. 렌더러 프로세스는 앞에서 설명한 크롬의 브라우저 자체 프로세스와 유사합니다.
주 프로세스
각 일렉트론 앱에는 앱의 진입점 역할을 하는 단일 주(主, main) 프로세스가 있습니다. 주 프로세스는 노드 환경에서 실행되므로, 모듈을 require
하고 모든 노드 API를 사용할 수 있습니다.
창 관리
주 프로세스의 주요 목적은 BrowserWindow
모듈을 사용하여 앱 창을 만들고 관리하는 것입니다.
BrowserWindow
클래스의 각 인스턴스는 별도의 렌더러 프로세스에서 웹 페이지를 로드하는 앱 창을 만듭니다. 창의 webContents
객체를 사용하여 주 프로세스에서 이 웹 콘텐츠와 상호 작용할 수 있습니다.
main.jsjs
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.jsjs
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.jsjs
// 맥OS가 아닌 플랫폼에서는 열려 있는 창이 없을 때 앱을 종료합니다.app.on('window-all-closed', () => {if (process.platform !== 'darwin') app.quit();});
main.jsjs
// 맥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.jsjs
const { BrowserWindow } = require('electron');// ...const win = new BrowserWindow({webPreferences: {preload: 'path/to/preload.js',},});// ...
main.jsjs
const { BrowserWindow } = require('electron');// ...const win = new BrowserWindow({webPreferences: {preload: 'path/to/preload.js',},});// ...
사전 로드 스크립트는 렌더러와 전역 Window
인터페이스를 공유하고 노드 API에 접근할 수 있습니다. 따라서 웹 콘텐츠가 사용할 수 있는 window
에서 임의의 API를 노출하여, 렌더러를 향상시키는 역할을 합니다.
사전 로드 스크립트는 첨부된 렌더러와 window
전역을 공유합니다. 하지만 contextIsolation
기본값 때문에 사전 로드 스크립트의 변수를 window
에 직접 연결할 수는 없습니다.
preload.jsjs
window.myAPI = {desktop: true,};
preload.jsjs
window.myAPI = {desktop: true,};
renderer.jsjs
console.log(window.myAPI);// => undefined
renderer.jsjs
console.log(window.myAPI);// => undefined
컨텍스트 격리는 사전 로드 스크립트가 렌더러의 주 세계에서 격리되어 권한 있는 API가 웹 콘텐츠의 코드로 유출되는 것을 방지하는 것을 의미합니다.
대신 contextBridge
모듈을 사용하여 이를 안전하게 수행하세요.
preload.jsjs
const { contextBridge } = require('electron');contextBridge.exposeInMainWorld('myAPI', {desktop: true,});
preload.jsjs
const { contextBridge } = require('electron');contextBridge.exposeInMainWorld('myAPI', {desktop: true,});
renderer.jsjs
console.log(window.myAPI);// => { desktop: true }
renderer.jsjs
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를 선호할 수 있습니다.