02 · 창 & 라이프사이클
이 챕터가 답하는 질문:
BrowserWindow하나는 무엇이고, 언제 만들어지며, 언제·어떻게 사라지는가? 작성: 2026-05-19 / 스타일: Minto +/explain
한 줄 답 (Pyramid Top)
BrowserWindow는 OS 네이티브 창 +webContents+ 렌더러 프로세스를 한 묶음으로 추상화한 객체다. 웹은 브라우저가 라이프사이클을 관리해 주지만, Electron에서는 개발자가ready→window-all-closed→before-quit→quit흐름을 직접 처리해야 하고, 그 처리는 macOS / Windows / Linux에서 모두 다르게 생겼다.
챕터 지도 — app 이벤트 시퀀스
이 시퀀스가 모든 Electron 앱의 골격이다. 본문 6개는 이 시퀀스의 각 마디를 한 챕터씩 분해한다.
Why — 왜 창과 라이프사이클이 별도 챕터인가
웹 개발자가 Electron으로 옮길 때 가장 먼저 부딪치는 세 가지가 모두 이 챕터에 있다.
| 잘못된 직관 | 실제 | 어디서 |
|---|---|---|
”창은 그냥 window.open()처럼 띄우면 됨” | BrowserWindow는 OS 핸들 + webContents + 프로세스 셋이 묶인 무거운 객체. 잘못 만들면 메모리가 누수된다. | 01 |
| ”앱 종료는 자연스럽게 됨” | macOS는 창을 닫아도 종료되지 않고, Windows의 squirrel-installer는 첫 실행에서 바로 죽어야 한다. OS마다 다르다. | 02 |
| ”쿠키는 브라우저처럼 알아서 됨” | session 객체로 직접 관리. partition으로 멀티계정 격리도 직접. | 04 |
세 직관을 그대로 두면 — 닫아도 안 죽는 좀비 프로세스 / 다중 로그인 누수 / 딥링크 실패 가 운영 단계에서 한꺼번에 터진다.
본문 6개 인덱스
| # | 파일 | 한 줄 답 | 핵심 키워드 |
|---|---|---|---|
| 1 | 01-browserwindow-anatomy.md | BrowserWindow는 OS 창 + webContents + 렌더러 프로세스의 3-in-1 객체. webPreferences로 그 셋의 권한을 한 번에 정한다. | BrowserWindow · webPreferences · contextIsolation · sandbox · show:false |
| 2 | 02-app-lifecycle.md | app 모듈은 프로세스 전체의 라이프사이클을 들고 있고, OS마다 다른 종료 규약을 직접 처리해야 한다. | ready · window-all-closed · before-quit · activate · second-instance |
| 3 | 03-webcontents.md | webContents는 창의 콘텐츠 영역을 표현하는 객체 — 로드·네비게이션·인쇄·세션·devtools가 전부 여기 붙는다. | did-finish-load · will-navigate · openDevTools · setWindowOpenHandler |
| 4 | 04-session-and-cookies.md | session은 쿠키 + 캐시 + 스토리지 + webRequest의 묶음. partition으로 진짜 다른 origin처럼 격리할 수 있다. | session · partition · persist: · cookies · webRequest |
| 5 | 05-window-state-and-multi-window.md | 창 위치/크기 저장은 내가 한다. 다중 창은 Map으로 직접 관리. tray는 숨은 창에 대한 또 다른 통로. | electron-window-state · Tray · 다중 창 Map · ipcMain.emit |
| 6 | 06-deep-link-and-protocol.md | 커스텀 protocol(myapp://)은 OS 레지스트리에 등록되고, 그 결과는 macOS의 open-url 또는 Windows의 second-instance argv로 들어온다. | setAsDefaultProtocolClient · open-url · second-instance · argv |
읽는 순서
- 처음 Electron을 만든다 →
01 → 02 → 03(한 시간이면 기본 골격). - 창을 여러 개 띄워야 한다 →
01 → 05. - 세션·쿠키 격리가 필요 (멀티 계정 앱) →
04단독으로 충분. - OAuth 콜백·딥링크 처리 →
02 → 06. - 메모리 누수 디버깅 →
01 → 03(창 참조와 webContents 누수가 1순위).
What-if — 무엇이 잘못될 수 있는가
| 함정 | 증상 | 원인 챕터 |
|---|---|---|
| 창 객체를 전역에 들고 close 후 nullify 안 함 | 메모리 누수 / 렌더러 좀비 프로세스 | 01 |
macOS에서 window-all-closed에 무조건 app.quit() | dock 아이콘이 죽지만 앱은 dock에 남음 (또는 그 반대) | 02 |
webContents.loadURL 후 did-finish-load 안 기다리고 IPC | renderer가 아직 준비 안 됨 → IPC 메시지 유실 | 03 |
partition 없이 두 BrowserWindow에서 다른 계정 로그인 | 쿠키가 공유돼 같은 계정이 됨 | 04 |
| 창 위치를 저장 안 함 | 사용자가 매번 처음 위치에서 시작 → “왜 항상 가운데?” 불만 | 05 |
macOS protocol 등록만 하고 open-url 이벤트 핸들러 없음 | 딥링크 클릭 → 앱이 열리기만 하고 URL을 못 받음 | 06 |
| Windows에서 single-instance lock 없이 protocol 등록 | 딥링크 한 번에 앱이 두 개 뜸 | 06 |
| squirrel install/uninstall 훅 무시 | 설치 직후 바로 종료 안 해서 인스톨러가 멈춤 | 02 |
Insight — 한 단락 이야기
“macOS의 활성화 패턴은 NeXTSTEP에서 왔다”
macOS 앱은 창이 다 닫혀도 죽지 않는다 — 이건 NeXTSTEP 시절(1989) “앱은 도큐먼트가 아니라 서비스”라는 사고에서 시작했다. Cocoa의
NSApplicationDelegate에는applicationShouldTerminateAfterLastWindowClosed라는 메서드가 있고, 기본값이NO다. Electron의window-all-closed/activate이중 이벤트는 이 NeXT/Cocoa 규약을 웹 개발자가 직접 흉내내라고 노출한 것이다. 그래서 모든 Electron 튜토리얼 앞부분에 똑같은 12줄 코드가 박혀 있다 —if (process.platform !== 'darwin') app.quit();app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); });이 한 패턴이 35년 전 NeXT의 결정을 2026년의 Electron 앱이 매일 호출하게 만든다.
한 단락 요약
이 챕터는 “창 하나가 무엇이고 언제 살고 죽는가” 라는 한 질문을 6개의 각도로 분해한다. 해부(01) → 앱 이벤트(02) → 콘텐츠(03) → 세션(04) → 상태와 다중 창(05) → OS와의 연결고리(06) 의 순서로 읽으면, 새 창을 띄우는 한 줄 코드 뒤에 무엇이 일어나는지가 끝까지 보인다.