Before writing
Electron을 현업에서 사용한지가 2년이 다 되가는 것 같습니다. 처음 사용하였을 때는 크로스플랫폼을 지원해서 아주 강력하고 좋은 프레임워크라고 생각이 들었습니다.
(쓰면 쓸수록 제약도 많고 무거워서 역시 native가 짱이다라는 생각이 들기도 합니다.)
그래도 가벼운 프로그램을 만드는데 사용한다면 오직 js만 다루어서 응용프로그램을 만들 수 있게 해주는 꽤나 쓸만한 프레임워크입니다. 약 2년간 사용+공부를 하면서 뭘 공부하면 좋은지에 대해 써볼까합니다.
What is Electron?
Electron is a framework for building desktop applications using
JavaScript, HTML, and CSS
. By embedding Chromium and Node.js into its binary, Electron allows you to maintain one JavaScript codebase and create cross-platform apps that work on Windows, macOS, and Linux — no native development experience required.
공식 document에 가면 위와같이 설명되있습니다. 말그대로 Web/Front-End
기술들로 데스크톱 응용프로그램을 만들 수 있게 해주는 프레임워크입니다.(심지어 크로스 플랫폼으로요)
마지막에 native에 대한 개발 경험이 필요 없다고 적혀있는데, 사용하는 내내 정말 필요없다고 생각이 들었습니다. 구현에 사용하려는 OS의 기능들만 대강 알고 있으면,(ex: context_menu, key_shortcut, 등) 구현에는 크게 무리가 없었던 것 같습니다.
개인적인 생각이지만 공식 문서도 제법 정리가 잘 되있고, 은근히 사용하는 사람이 많아서 그런지 stack overflow에도 꽤나 많은 에러 및 오류 해결법들이 잘 정리되어 있어 개발하는데 아주 큰 어려움은 없었습니다.
1. Installation
설치에는 Node.js가 필요로합니다. 만약에 32bit window를 지원해줘야하는 응용 프로그램을 만드셔야한다면, Node.js 32bit를 설치 하셔야합니다. 이 점을 유의해주세요!
간단하게, electron 공식 quick start 부터 해볼까합니다.
거의 그럴일은 없겠지만 Node.js가 설치되어 있지 않다면, Node.js부터 설치를 해주셔야합니다.
npm 만세
- npm init위 명령어들까지는 Electron과 무관합니다. quick-start 를 위한 폴더를 만들고, node-package-manager(npm)을 이용해서 init을 해줍니다.
package.json
이 만들어지게 되는데 프로젝트와, 패키지들의 정보가 기록되어 있는 파일입니다. package.json내의scripts
value를 위와 같이 변경해주세요.
{
"scripts": {
"start": "electron ."
}
}
- ++ 혹시 script에 다른 명령어를 집어 넣으셨다면?
- 위 명령어를 실행하면 이것저것 입력을 하게하는데
entry point
를 입력하실 때에,main.js
를 기입해주셔야 합니다. 그리고author
,description
에는 반드시 어떤 값이라도 기입해주셔야합니다. 위와 같은 이유는 Electron 공식 문서에서 몇가지 따라야할 룰이 있다고 설명해주기 때문입니다. mkdir my-electron-app && cd my-electron-app npm init
npm install --save-dev electron
//- install
--save-dev
는 개발용으로 설치하고, package.json에 기록해서 저장하겠다는 의미를 가지고, 중요한건 뒤에 있는 electron을 설치하게 됩니다.
- install
- make electron app
- package.json생성 시에 entry point를 main.js로 만들었기 때문에 root폴더(my-electron-app 폴더) 안에 main.js라는 js를 생성하고 아래와 같은 코드를 입력합니다.
- 코드에 대한 설명은 주석으로 대체하겠습니다.
# require로 설치한 electron을 불러와서 electron 안에 있는 app, BrowserWindow 객체를 정의합니다.
const { app, BrowserWindow } = require('electron')
# 여기서 path는 현재 app이 사용할 절대 경로 입니다.
const path = require('path')
function createWindow () {
// 생성자를 통해 window를 생성할 수 있습니다.
const win = new BrowserWindow({
width: 800,
height: 600,
// 아래 옵션은 preload할 파일을 지정하는 것 입니다.
// preload에 대한 자세할 설명은 아래에서
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
preload한 스크립트에는 웹 콘텐츠(index.html)가 로드되기 전에 렌더러 프로세스에서 실행되는 코드가 포함되어 있습니다. 이러한 스크립트는 렌더러 컨텍스트 내에서 실행되지만 Node.js API에 대한 액세스 권한이 있으면 더 많은 권한이 부여됩니다. 이 말을 이해하기 위해선, Electron의 동작원리를 어느정도 이해해야하는데, 간단히 말하자면
Main process
(main.js)위에서Renderer process
가 도는데 이Renderer process
가Main process
에서 load한 index.html을 그려줍니다. preload한 스크립트는 이 index.html이 load되기 이전 시점에 load할 js를 설명합니다. 설명에 두서 없긴하지만 이 구조에 대해서는 다음 장에서 자세히 살펴보도록 하겠습니다.
// DOMContent가 로드가 끝난시점에 process(Node 전역 프로세스 객체)통해
// `Main process`로 접근하여 version 정보를 가져옴.
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const type of ['chrome', 'node', 'electron']) {
replaceText(`${type}-version`, process.versions[type])
}
})
preload를 활용하면, Main process
의 Node 전역 객체에 접근과 Dom에 접근이 가능하기 때문에 잘 활용하면 쓸만한 구석이 있어보이나, 초기 페이지 load시에나 활용가능한 여지가 있지, 프로그램이 커진다면, proeload.js만으로 Main process
의 접근을 모두 충당할 수는 없기 때문에, 나중에 공부할 Main process
- Renderer process
간의 통신을 통해 Main process
에서만 사용할 수 있는 객체의 property를 가져온다거나 method를 충분히 실행시킬 수 있습니다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
We are using Node.js <span id="node-version"></span>,
Chromium <span id="chrome-version"></span>,
and Electron <span id="electron-version"></span>.
<!-- You can also require other files to run in this process -->
</body>
</html>
main.js
에서 load
할 index.html
파일입니다.
2. Electron Architecture
아래 구조도는 개인적인 관점으로 바라본 Electron Desktop Application의 대략적인 구조입니다.
Electron을 잘 사용하려면 기본 구조를 이해하는 것이 제법 중요하다고 생각합니다. 전체 프로그램 설계에 아무래도 좀 더 좋은 영향을 주지 않을까 생각합니다.
특히 window간 통신이나 Main process
<=> Renderer Process
간의 통신이 많은 프로그램이라면, 통신을 위한 설계에 좋은 영향을 줄것이라고 생각합니다.
Electron Desktop Application을 크게 나눠본다면
- 1개의 Application
- 1개의
Main process
- n개의
Renderer process
- n개의 Preload Script
위와 같이 구성된다고 할 수 있습니다.
- 1개의
Main process
는 Node JS기반으로 만들어지며, Native UI
와 NodeJS내의 Node API
를 이용해 OS
와 통신을 하여 특정 기능을 수행합니다. 그리고 ipcMain
객체를 이용하여 Renderer process
와의 통신도 가능합니다.
Renderer process
는 html, css, js
를 이용하여 렌더링을 해줍니다. 또한 ipcRenderer
객체를 이용하여 Main process
와 통신을 수행합니다. Renderer process
는 Chromium
을 기반으로 합니다.
물론.. 개발상의 이유로 WINDOW2
처럼 Node.js
환경에서 생성하여 Node API
에 직접 접근하는 것도 가능합니다. 다만 이전에는 BroswerWindow
를 통해 Renderer process
생성 시에 default로 Node.js
환경에서 생성하였으나, 이제 보안상의 이유로 default는 WINDOW1
처럼 생성이 됩니다.Preload Script
는 Main Process에서 window를 생성하고, Renderer Process가 생성되기 전에, Main Process에서 실행하게 되는 script를 의미합니다. 그래서 Preload Script
에서는 Node js API
객체에 접근이 가능합니다.
위에서 설명했듯이 Node js API
객체에 접근하기 위해서는, window 생성시 webPreferences
내 nodeIntegration
를 true
로 변경하던가,Main Process
와의 통신을 통해 Main Process
에서 접근하는게 좀 더 바람직하다고 생각합니다. 왜냐하면 많은 통신이 이루어질 경우 preload.js 로는 유지보수에 용이한 코드를 작성하기 어렵기 때문입니다.