Electron 프로세스 모델 — 메인과 렌더러의 관계
"Electron은 왜 프로세스를 나누는가?" — Chromium이 멀티 프로세스 아키텍처를 쓰는 이유와 같습니다. 하나의 탭이 죽어도 다른 탭은 살아있어야 하니까요.
면접에서 Electron 아키텍처를 물어보면, 단순히 "메인과 렌더러가 있다"보다는 왜 나뉘었는지, 각각 어떤 API에 접근할 수 있는지를 설명할 수 있어야 합니다.
Chromium의 멀티 프로세스 모델
Electron은 Chromium의 멀티 프로세스 아키텍처를 그대로 사용합니다.
┌─────────────────────────────────┐
│ 메인 프로세스 │ ← Node.js 런타임
│ (Browser Process) │ ← 앱 라이프사이클 관리
│ │ ← 시스템 API 접근
├─────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 렌더러 1 │ │ 렌더러 2 │ │ ← 각 BrowserWindow
│ │(Renderer)│ │(Renderer)│ │ ← 웹 페이지 렌더링
│ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────┘
왜 프로세스를 나누는가
- **안정성 **: 하나의 렌더러가 크래시해도 메인 프로세스와 다른 렌더러에 영향 없음
- ** 보안 **: 렌더러에서 직접 시스템 API에 접근하지 못하게 격리
- ** 성능 **: 각 렌더러가 독립적으로 동작하므로 한 창의 무거운 작업이 다른 창에 영향을 주지 않음
메인 프로세스 (Main Process)
메인 프로세스는 Electron 앱에서 ** 단 하나만 존재 **합니다. package.json의 main 필드가 가리키는 파일에서 시작됩니다.
메인 프로세스가 하는 일
const { app, BrowserWindow, Menu, dialog } = require('electron');
// 1. 앱 라이프사이클 관리
app.whenReady().then(() => {
// 2. BrowserWindow 생성/관리
const win = new BrowserWindow({ width: 800, height: 600 });
// 3. 네이티브 메뉴 설정
const menu = Menu.buildFromTemplate([
{ label: '파일', submenu: [{ role: 'quit' }] }
]);
Menu.setApplicationMenu(menu);
// 4. 시스템 다이얼로그
// dialog.showOpenDialog(win, { properties: ['openFile'] });
});
메인 프로세스에서만 접근 가능한 모듈
| 모듈 | 역할 |
|---|---|
app | 앱 라이프사이클 |
BrowserWindow | 윈도우 생성/관리 |
Menu / Tray | 네이티브 메뉴, 트레이 아이콘 |
dialog | 파일 열기/저장 다이얼로그 |
globalShortcut | 전역 키보드 단축키 |
autoUpdater | 자동 업데이트 |
렌더러 프로세스 (Renderer Process)
각 BrowserWindow는 자체 렌더러 프로세스를 가집니다. 렌더러 프로세스는 기본적으로 ** 브라우저 환경 **입니다.
// 렌더러에서 실행되는 코드 — 일반 웹 코드와 동일
document.getElementById('btn').addEventListener('click', () => {
console.log('버튼이 클릭되었습니다');
// Node.js API 직접 사용 불가 (보안상)
// require('fs') ← 이런 코드는 동작하지 않음
});
렌더러 프로세스의 특징
- 일반 웹 페이지와 동일한 환경 (DOM, Web API 사용 가능)
- ** 기본적으로 Node.js API에 접근 불가** (보안을 위해)
- 메인 프로세스와 통신하려면 IPC(Inter-Process Communication) 사용
- 각 렌더러는 독립된 프로세스로 동작
Preload 스크립트
Preload 스크립트는 메인과 렌더러 사이의 ** 안전한 브릿지** 역할을 합니다.
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
// contextBridge로 렌더러에 안전하게 API를 노출합니다
contextBridge.exposeInMainWorld('electronAPI', {
// 파일 열기 요청을 메인 프로세스로 전달
openFile: () => ipcRenderer.invoke('dialog:openFile'),
// 앱 버전 가져오기
getVersion: () => ipcRenderer.invoke('app:getVersion'),
});
// 렌더러에서 사용 — window.electronAPI로 접근 가능
const filePath = await window.electronAPI.openFile();
Preload 스크립트의 특성
- Node.js API 접근 가능 (require, process 등)
- DOM 접근 가능 (window, document 등)
- 두 세계를 모두 접근할 수 있지만,
contextBridge로 필요한 것만 노출하는 것이 보안 원칙 - BrowserWindow 생성 시
webPreferences.preload로 지정
프로세스 간 통신 흐름
렌더러 Preload 메인 프로세스
│ │ │
│ window.electronAPI │ │
│ .openFile() │ │
│ ─────────────────────► │ │
│ │ ipcRenderer │
│ │ .invoke('open') │
│ │ ───────────────────► │
│ │ │ dialog
│ │ │ .showOpenDialog()
│ │ 결과 반환 │
│ │ ◄─────────────────── │
│ Promise 해결 │ │
│ ◄───────────────────── │ │
nodeIntegration은 왜 끄는가
Electron 초기에는 nodeIntegration: true로 설정하면 렌더러에서 직접 require('fs') 같은 Node.js API를 쓸 수 있었습니다.
// ⚠️ 위험한 설정 — 절대 프로덕션에서 사용하지 마세요
const win = new BrowserWindow({
webPreferences: {
nodeIntegration: true, // 렌더러에서 Node.js 사용 가능
contextIsolation: false, // 컨텍스트 격리 해제
},
});
이게 왜 위험한지:
- 렌더러에서 외부 스크립트를 로드하면 그 스크립트가 파일 시스템에 접근 가능
- XSS 공격이 시스템 전체를 장악할 수 있음
- ** 반드시
contextIsolation: true(기본값)을 유지하고 preload를 사용해야 합니다**
유틸리티 프로세스
Electron 22부터는 UtilityProcess를 사용해 무거운 작업을 별도 프로세스에서 처리할 수 있습니다.
// main.js에서 유틸리티 프로세스 생성
const { utilityProcess } = require('electron');
const child = utilityProcess.fork(path.join(__dirname, 'heavy-task.js'));
child.postMessage({ type: 'start', data: largeData });
child.on('message', (result) => {
console.log('작업 완료:', result);
});
// heavy-task.js — 별도 프로세스에서 실행
process.parentPort.on('message', (e) => {
const { type, data } = e.data;
// CPU 집약적인 작업 수행
const result = processLargeData(data);
process.parentPort.postMessage(result);
});
면접 포인트 정리
- 메인 프로세스는 1개, 렌더러 프로세스는 BrowserWindow당 1개
- 메인: 앱 라이프사이클, 시스템 API / 렌더러: UI 렌더링
- preload 스크립트는 두 프로세스를 안전하게 연결하는 브릿지
contextIsolation: true+contextBridge가 현재 권장 보안 패턴nodeIntegration: true는 보안 취약점이므로 사용 금지- 무거운 작업은 UtilityProcess로 분리 가능
프로세스 모델을 이해했으면, 다음은 BrowserWindow의 다양한 옵션을 알아볼 차례입니다.
댓글 로딩 중...