본문 바로가기
IT/Electron JS

Electron JS

by 밤톨엽 2022. 12. 21.

개요

2021년 12월부터 회사 팀을 옮기면서 메인으로 담당하고 있는 메인 프로젝트 중 하나가 Electron JS로 개발되어 운영 중에 있다.

ElectronJS을 사용함에 있어 동작 방식과 구조를 파악할 필요성이 있어서 공식 문서를 읽고 중요 포인트를 정리해 보았다.

Electron?

Electron JS는 JavaScript, HTML, CSS를 사용해서 크로스 플랫폼(Mac, Window, Linux OS) 데스크톱 앱을 빌드하고 개발할 수 있는 프레임워크이다.

Electron은 자체적으로 Chromium과 Node.js를 내장하고 있다. 이는 순수 자바스크립트 코드 베이스로만 개발자가 네이티브 언어 개발 경험 없이 보다 쉽게 데스크톱 앱을 개발할 수 있으며 프레임워크에서 개발에 필요한 다양한 도구들을 제공해주고 있다.

Electron 구조

Electron은 크로미움의 multi-process 아키텍처를 상속받은 프로세스 모델을 가지고 있다. 문서를 살펴보다 보면 Electron 프레임워크가 모던한 웹 브라우저의 아키텍처와 비슷한 구조로 구성되어 있다고 강조해 주고 있다.

Electron은 App의 생명주기를 컨트롤할 수 있는 Main Process 영역과 Main Process를 기반으로 실행된 Browser의 Window 쪽 컨트롤을 담당하는 Renderer Process 영역으로 나누어져 있다.

Electron의 기본적인 구조는 아래 그림과 동일하다.

electron model
electron model

싱글 프로세스가 아닌 이유?

웹 브라우저들은 복잡성이 굉장히 많이 증가했다. 한편으로 웹 브라우저의 주 기능 중 하나인 어떤 웹 콘텐츠를 보여주는 주 기능 외에도 많은 부가적인 책임을 가지고 있다. 단편적인 예시로 여러 창(혹은 탭) 등을 컨트롤, 서드파티 플러그인들을 로드.. 등등이 있다.

이전 브라우저(IE 브라우저)는 보통 싱글 프로세스에서 모든 기능들을 담당했었다. 이 패턴의 장점은 탭을 열 때마다 오버헤드가 적다는 장점이 있지만, 만약 웹사이트가 오류가 발생한다면 브라우저 전체가 종료된다는 문제점이 있다.
(생각해 보면 정말로 인터넷 익스플로러에서 탭을 잔뜩 열어놨는데.. 오류 나서 아예 인터넷 창 전체가 꺼지는 현상이 종종 발생했었다.)

멀티 프로세스 모델

크롬 팀에서는 각 탭이 렌더링 될때마다 각 고유 프로세스로 구성되도록 하였고 이것은 버그나 악성 코드가 전체 애플리케이션에 영향을 끼지 않도록 제한을 둘 수 있도록 멀티 프로세스 모델을 가지게 되었다.

정리하자면 아래 그림처럼 하나의 크롬 브라우저 프로세스가 이런 여러 탭들의 프로세스들을 관리하고 전체 애플리케이션의 생명주기를 관리한다.

Chrome Multi Process
Chrome Multi Process

위에 그림은 크롬팀에서 크롬을 제작하는 과정을 표현한 Chrome Comic Book 4페이지에서 나온다.
(총 40페이지며 모두 영어로 되어있고 생각보다 내용이 어렵지만 크롬팀에서 어떤 고민을 하면서 브라우저를 설계했는지 알아볼 수 있다.)

Electron 애플리케이션의 구조도 위처럼 매우 유사한 형태를 가지고 있다. Electron으로 개발을 진행하게 되면 main process, renderer process라고 하는 두개의 프로세스들을 다루게 된다.

Main Process

각각의 Electron 애플리케이션은 단일 main process를 가지게 되며, 애플리케이션의 진입 지점에서 실행이 된다.

main processNode.js 환경에서 동작하기 때문에 main process 영역에서는 require 모듈 기능과 Node.js 에서 제공하는 API를 모두 사용할 수 있다.

Window management

메인 프로세스의 주목적 중 하나는 애플리케이션의 창을 BrowserWindow 모듈과 함께 관리하는 것이다.

각각의 BrowserWindow 에서 생성한 인스턴스는 애플리케이션 창을 만들고 그것은 분리된 renderer process 영역에서 웹 페이지를 로드하게 된다. 우리는 main process 영역에서 BrowserWindow에서 제공하는 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 모듈은 Node.js EventEmitter 기반이기 때문에 우리는 사용자 쪽에서 발생하는 다양한 이벤트들에 대한 핸들러들을 추가할 수 있다. (예시: 창의 최소화 혹은 최대화)

만약 BrowserWindow 인스턴스를 삭제한다면, 그에 속하는 renderer process들 또한 같이 제거되게 된다.

Application lifecycle

main process는 애플리케이션의 생명주기를 Electron app 모듈을 통해 관리하게 된다. app 모듈은 굉장히 광범위한 이벤트와 메서드를 제공하며 우리는 이것을 통해 애플리케이션에 다양한 동작들을 구현할 수 있다.
(문서를 참고해 보면 이벤트만 30개 넘게 제공하고 있고 메서드는 더 많다. 😲)

문서에서 제공하는 단편적인 예시로 MacOS 에서는 창을 닫아도 프로그램이 완전히 종료되지 않는데 아래 코드 예시를 통해 열려있는 창이 없다면 애플리케이션을 완전히 종료하는 것도 가능하다.
(MacOS 환경은 창을 닫아도 프로그램이 종료되지 않기 때문에 원래는 Dock에서 프로그램을 직접 종료를 직접 해줘야 한다.)

// MacOS 운영체제에서 열려있는 창이 없다면 프로그램 완전 종료하기
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})

Renderer Process

각 Electron 애플리케이션은 BrowserWindow를 생성할 때 각각의 renderer process를 생성하게 되는데 여기서 renderer 라는 이름 처럼 웹 리소스를 렌더링하는데 책임을 가지고 있으며 renderer process 에서 동작하는 코드는 웹 표준기술에 따라야 된다.
(크로미움이 여태까지 그래왔다.)

문서에서 보면 모든 사용자의 인터페이스와 애플리케이션의 기능들은 하나의 브라우저 창에서 같은 도구와 패러다임으로 작성되어야 한다고 권장하고 있는데, 이것은 단일 창에서 모든 기능을 작성하는 접근방식은 개발과 유지보수가 단순화 되고 통합된 사용자 경험을 제공할 수 있기 때문에 이렇게 권장하는것으로 확인된다.

물론, 여러 개의 창을 생성해서 다른 프로세스끼리 상호작용 하는것도 가능하다.

renderer process는 직접적으로 Node.js API에 접근하거나 require 구문을 통해 모듈을 가져와서 사용할 순 없다.

왜냐? renderer process 쪽에서는 HTML, CSS, Javascript로 애플리케이션 UI와 기능을 구현하기 때문이다.

물론 화면 영역에서 SPA(Single Page Application)를 구현하기 위해 React, Vue, Angular 프론트엔드 라이브러리&프레임워크를 사용하거나 Webpack, Parcel 등의 번들러를 사용해서 npm modules 들을 포함해서 화면단에 포함시켜서 사용하는 방법도 있다.

그렇다면 renderer process 쪽에서 사용자가 Node.js 혹은 Electron 에서 구현되어있는 네이티브 데스크톱 기능들을 상호작용 하고싶을 수가 있는데 이것은 main process 영역에서만 접근이 가능하다. 사실상 직접적으로 renderer process 영역에서 직접적으로 Electron의 기능 영역에 접근할 수 있는 방법은 없다.

사실 Node.js 환경을 renderer 영역에서도 사용할 수 있게 개발시에 굉장히 쉽게 설정할 수 있다.
이것은 이전 버전에서 기본적으로 설정을 하고 있지만, 이후에는 보안상의 이유로 비활성화 될 수 있으니 지양하는 것이 좋다.

이후에 IPC(Inter-Process Communication) 라는 방법을 통해서 main processrenderer process 간에 메시지를  주고받아서 서로 상호작용 하는 방법을 제공한다.

Utility Process

Electron 애플리케이션은 Utility Process API를 사용해서 Child Process(백그라운드 프로세스)를 생성할 수 있다.

Utility Process는 Node.js 환경에서 동작하며 주로 CPU관련된 작업이나 신뢰하지 못하는 서비스등의 작업등을 포함해서 동작시킬 수 있다.

node.js 에서 제공하는 Child Process 기반으로 동작하는것 과 동일하니 한번 참고해보는게 좋다.