Electron 간단 정리하기

박성룡 ( Andrew park )
12 min readJan 24, 2021

--

최근 Electron 에 흥미가 생겨 이를 정리해보고자 한다.

Electron 은 JavaScript, HTML, CSS 로 데스크톱 애플리케이션을 만들 수있는 프레임 워크이다.

Electron 은 Node.JS 로 로컬 시스템에 접근하고, Chromium 으로 화면을 구성한다.

HTML 에서 JS 로, Electron API 를 호출하면, Node 에서 제공하는 API 로 Local System 에 접근하여, 작업을 처리하는 등이 가능해 진다.

또한, Chromium 으로 화면을 구성하기 때문에, 내부에서 파일로 가지고 있는 HTML 뿐만 아니라, 외부에서 동작하는 HTML 사이트를 가져와서 그리는 것 또한 가능하다.

Electron 은 2021년 1월 를 기준으로 10 버전 이 최신이다.

10버전을 기준으로 Node.JS 12.16 TLS 버전을 지원하고, Chromium 은 85 버전을 지원하고 있다.

때문에, 만약 Electron 기준으로만, 화면을 구성한다면, Browser Polyfill 걱정 없이 개발이 가능하다.

Electorn 이모저모

Electron 의 최대의 장점은 Desktop App 을 Javascript 만으로도 개발이 가능다는 점이다.

비슷한 예로 Javascript 로 Mobile App 개발이 가능한 React Native 가 있다.

Electorn 은 Node 기반이기 때문에 npm 을 이용해서, 방대한 Javascript 오픈소스를 사용할 수 있다.

배포 시에는, Dependency 가 있는 Node Module 을 포함 시킬 수 있다.

또한 웹으로 개발한 소스를 약간의 수정과정을 거치면 그대로 사용이 가능하며, 필요하다면, Javascript 개발자가 직접 Desktop App 개발을 시작하는 것도 어렵지 않다.

한 코드 베이스로 Window, Mac, Linux 프로그램 작업 및 배포가 가능하다.

하지만 단점도 무시할 수 없는데, 가장 큰 단점은 “hello would” 를 출력하는 간단한 앱이라도 거대한 Chromium, 여러 Node Moudle 등을 포함 하므로 100 MB 이상의 저장 공간이 필요하다.

또한 다른 Native Code 로 만든 프로그램에 비해 속도와 메모리 측면에서도 가볍지는 않다.

하지만 단점에 비해 장점이 더 크기 때문에, Electorn 으로 App 개발을 시작 해보는건 나쁘지 않다고 본다.

간단 예제

// package.json
{
"name": "electron-app",
"main": "main.js",
"scripts": {
"start": "electron ."
}
}
// main.js
const { app, BrowserWindow } = require('electron')
app.whenReady().then(() => {
const win = new BrowserWindow()
win.loadFile('index.html')
// win.loadUrl('http://example.com')
})
app.on('window-all-closed', () => {
app.quit()
})
  1. package.json 의 main 에 있는 파일이 시작점이다.
  2. Electorn app 이 준비되면 (app.whenReady), BrowserWindow 로 새창을 생성한다.
  3. BrowserWindow 에 노출시킬 url 혹은 html 파일을 세팅한다.
  4. 만약 Electorn app 의 창이 모두 종료되었을때 (‘window-all-closed’) app.quit() 으로 App 을 종료 시킨다.

Electorn 을 구성하는 Main Process 와 Renderer Process

Electorn 에서 가장 중요한 개념은 Main ProcessRenderer Process 이다.

Electorn 은 마치 서버 와 브라우저로 구성되어 있다고 비유할 수 있다.

이때 Main Process 가 Node 서버 이며, Renderer Process 가 Chromium 브라우저 이다.

Main Process

Electron App 은 반드시 1개의 Main Process 를 가진다.

Main Process 에서 Node.js API 사용이 가능하며 File System 등에 자유롭게 접근이 가능하다.

Main Process 에서 BrowserWindowRenderer Process 를 생성하고, 웹페이지로 GUI 를 구성하게 된다.

만약 Main Process 가 종료되면, Renderer Process 도 함께 종료된다.

const { app, BrowserWindow } = require('electron')const win = new BrowserWindow({
nodeIntegration: true,
})
win.loadFile('index.html')

Renderer Process

Main Process 에서 Renderer Process 는 0개 이상일 수 있다.

각각의 Renderer Process 들은 독립적으로 작동하기 때문에, 다른 Renderer Process 에게 영향을 주지 않는다. 때문에 한페이지가 죽더라도, 다른 페이지에서는 여전히 동작이 가능하다.

만약 Renderer Process 에서 Node.js API 에 접근하려면 nodeIntegration 설정하면 되며, DevTool 을 확인해보고 싶다면, webContents.openDevTools 를 호출하면 된다.

// main process
const win = new BrowserWindow({
nodeIntegration: true,
})
win.webContents.openDevTools()
// renderer process
<p>
<script>
const os = require('os')
document.write(`
arch: ${os.arch()},
platform: ${os.platform()},
type: ${os.type()},
uptime: ${os.uptime()},
hostname: ${os.hostname()},
release: ${os.release()}
`)
</script>
</p>

Render Process 파일 접근 하기

만약 Render Process 에서 LocalFile 에 접근해야 한다면, 어떻게 할 수 있을까?

일반적으로 브라우저 에서 <input type="file" /> 파일에 접근한다면, C:\fakepath\filename 형태로 경로가 노출 되며 브라우저에서 LocalFile 에 직접 접근하는건 보안상 막고 있다.
(추후 LocalFileSystemAPI 가 나오면 달라질수 있다)

Electorn 으로 노출되는 WEB 의 경우 DOM API 에 표준 API 이외에 Electorn 이 제공하는 API 내용이 추가되어 있다.

만약 실제 Local Full Path 를 확인해 보고 싶다면, Event.target.files[0].path 에서 실제경로를 확인할 수 있다.

/// jsx
<input type="file" onChange={(event) => {
console.log(event.target.value)
//C:\fakepath\filename
console.log(event?.target?.files[0].path)
// Users/000/dir/filename
}} />

혹은 Drag & Drop 을 이용한다면, 아래와 같이 작성해 볼 수 있다.

document.addEventListener('drop', (event) => {
event.preventDefault();
event.stopPropagation();
for (const file of event.dataTransfer.files) {
console.log(file.path)
// Users/000/dir/filename
}
});

이외에도 IPC 를 통해 브라우저를 거치지 않고, Main Process 에서 dialog.showOpenDialog() 를 통해 파일에 접근하고, 이를 Renderer Process 에게 알려줄 수 있다.

// main processipcMain.on("open-dialog-file", async event => {
const file = await dialog.showOpenDialog({
properties: ["openFile"]
});
if (file) {
event.sender.send('selected', file.filePaths[0]);
}
});// renderer processwindow.ipcRenderer.on('selected', (event, path) => {
console.log('selected', path)
})

Main Process 와 Render Process 의 IPC 통신

IPC 는 프로세스 간 통신 (Inter-Process Communication, IPC) 를 뜻한다.

Process 사이에 서로 데이터를 주고받는 행위 또는 그에 대한 방법이나 경로를 뜻한다.

Renderer Process 는 메모리 관리 및 보안상 로컬 시스템에 접근하는 것보다, IPC 를 통해 Main 프로세스와 통신 하며 시스템에 접근하는 것이 권장된다.

Electron IPC 에는 ipcMain, ipcRenderer 가 있다.

ipcMainMain Process 에서 Renderer Process 에게 데이터를 받거나 전달해주는 모듈이다.

ipcRendererRenderer Process 에서 Main Process 에게 데이터를 받거나 전달해주는 모듈이다.

(ipcMain, ipcRenderer).on 에서 구독중인 채널로 부터 EventEmitter 객체와 전달되는 값을 받는다.

A Process 에서 넘어온 EventEmitter 객체 로 다시 A Process 의 적절한 채널에게 event.sender.send 를 이용하여, 값을 전달할 수 있다.

// main processipcMain.on('message', async (event, ...args) => {
event.sender.send('reply', 'pong');
});
// renderer processipcRenderer.on('reply', async (event, ...args) => {
console.log(args)
});

만약 Render Process 직접 전달 해야 한다면, webContents.send 로 전달할 수 있다.

// main processconst win = new BrowserWindow()
win.webContents.send('reply', 'send')
// renderer processipcRenderer.on('reply', async (event, ...args) => {
console.log(args)
});

onsend 로 나눠진 형태 이외에 처리된 결과를 바로받는 형태도 존재한다.

// main processipcMain.handle('invokable-ipc', async (event, ...args) => {
const result = await //...
return result
})
// renderer process(async function() {
const result = await ipcRenderer.invoke('invokable-ipc')
console.log(result)
})()

Remote 모듈

Render Process 에서 Electron 에서 제공하는 dialog, menu 등의 모듈을 사용하려면, IPC 통신이 이루어져야 하는데, Main ProcessRenderer Process 에서 데이터를 주고받는 형태 IPC 를 간략하게 만든 remote module 이 존재한다.

remote 모듈은 IPC 와는 조금은 다르게 동작하는데, Main Process 에서만 사용 했던 API 들을 Renderer Process 에서 직접 사용하도록 설정이 가능하게 한다.

// main processnew BrowserWindow({
webPreferences: {
enableRemoteModule: true
}
});
// renderer processconst {dialog} = require('electron').remote
(async () => {
const file = await dialog.showOpenDialog({
properties: ["openFile"]
});
console.log(file?.filePaths[0])
})()

반대로 Main Process 에서 Renderer Process 에 접근 하려면, webContents.executeJavascript 메서드를 사용할 수 있다.

Electorn 와 React

Electorn 을 Javascript 로 구현하는것은 어렵지 않다.

하지만 화면을 순수 HTML 과 JS로 구현 하는건, 공수가 많이 드는 일이다.

React 나 Vue 같이 Javascript 로 화면을 제어할 수 있는 도구를 활용한다면, 쉽게 작업할 수 있을 것이다.

다음 글에서 해당 내용을 작성해보고자 한다.

--

--

박성룡 ( Andrew park )
박성룡 ( Andrew park )

Written by 박성룡 ( Andrew park )

Javascript is great We may not be great

No responses yet