Electron 에 React 적용해 보기

최근 Electron 에 흥미가 생겨 환경 세팅를 했던, 과정을 정리해보고자 한다.

어떠한 도구를 사용해도 상관없지만, 가장 쉽고, 개발하기 편한 React 를 Electorn 에서 사용해보고자 한다.

React 에 대한 고찰

현대의 웹은 복잡하고, 화려하다. 이는 단순 문서에서 Application 형태로 진화해가고 있기 때문이다.

순수 HTML, CSS, Javascript 으로 화면을 제어하는데, 공수가 많이 들기 시작하면서, 많은 개발자들이 화면과 DOM 을 제어할 수 있는 라이브러리와 프레임워크를 개발하였고, 이제 이런 도구들이 쓰는일이 어렵지 않게 되었다.

Electorn 은 내장된 Chromium 브라우저를 통해, Html, CSS, JS 만으로 GUI 를 구성할 수 있다. 때문에, 이를 순수 Html, CSS, JS로 개발해도 되지만, DOM 을 쉽게 조작할 수 있는 도구가 있다면, 개발 공수는 확연히 달라질 것이다.

React 는 DOM 을 매우 쉽게 조작할 수 있도록 도와준다. 완성된 React App 은 네트워크가 동작하지 않더라도 빌드된 html 과 js 만으로 완벽하게 단독으로 동작이 가능하다.

그렇다면 일렉트론에서는 React 를 어떻게 사용해야할까?

Electorn 에서 React 사용하기

Electorn 에서는 화면을 그리기 위해서는 html 파일 혹은 URL 이 필요하다.

React 는 Build 시 완성된 Html CSS, JS 를 생성해낸다.

Electorn 이 Production 상태 일때라면, Build 된 파일을 읽거나, 임의의 서버에 올린, React Page 을 노출시키면 된다.

하지만 개발 할때는 매번 수정사항이 생길때 마다 빌드해서 사용한다면, 빌드시간이 너무 오래 걸려 개발공수 시간이 오래 걸리게 된다.

React App 은 DevServer 를 통해 개발된 수정사항 만을 즉시 build 하고 화면에 적용시키는 Hot Reload 기능이 존재한다.

때문에, Develop Mode 에서는 DevServer 를 띄워, Election 과 URL 로 연결하여 개발을 진행하고, Production Mode 일때는 Build 한 파일을 Electorn 에 File 로 탑재한다면, 쉽게 개발하고, 배포할 수 있게 된다.

React 준비하기

React 를 준비하기 위해서는 보통 create-react-app 를 이용한다.

만약 해당방법이 싫거나, electron 을 먼저 세팅했다면,

을 설치하고, 를 준비한다.

중요한 점은 를 세팅해주어야 한다.

이유인즉, ReactScript 에서 기본 경로를 로 잡기때문에, 빌드후에 경로를 못찾을 수 있기 때문이다.

<!-- pubilc/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div id="root"></div>
</body>
</html>
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
const App = () => {
return <div>hello world</div>
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
// package.json
{
...
"homepage": "./",
"scripts": {
...
"react:start": "react-scripts start"
},
"devDependencies": {
...
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-scripts": "^4.0.1",
}
...
}

Electorn 준비하기

Election 을 준비하기 위해서는 파일과 파일이 필요하다.

main 에서 을 entry file 로 정의하는 것이 중요하며, react 에서 사용하는 과 함께 있는 것이 좋다.

// package.json
{
"name": "electron-app",
"main": "pubilc/electron.js",
"scripts": {
...
"electron:start": "electron ."
},
"devDependencies": {
...
"electron": "^11.1.1"
}
}
// main.js
const { app, BrowserWindow } = require('electron')
app.whenReady().then(() => {
const win = new BrowserWindow()
win.loadFile('http://localhost:3000')
})
app.on('window-all-closed', () => {
app.quit()
})

동시에 실행하기 concurrently 이해하기

Electorn 과 React 가 준비 되었다면 동시에 실행시킬 일만 남았다.

보통 스크립트를 동시에 실행시킬때는 && 를 많이 쓸 수 있다.

ex )

하지만 React 에서는 해당 방법이 통하지 않는데, ReactScript 실행 후 종료되지 않고, 계속 변화를 watch 하고 있어, electron 이 실행되지 않는다.

때문에 다른 방법을 써야하는데 각각실행시켜도 되지만, 동시 실행을 지원해주는 도구가 존재한다.

concurrently 는 동시에 여러 명령을 실행할 수 있도록 도와준다.
ex )

로 설치하고, 앞서 세팅한 React 와 Election 의 실행 Command 를 입력해 준다.
ex)

// package.json
{
...
"scripts": {
"start": "concurrently \"npm run react:start\" \"npm run electron:start\"",
//...
},
"devDependencies": {
...
"concurrently": "^5.3.0",
}
...
}

wait-on 이해하기

Electorn 과 React 가 동시에 실행이 됬으나 이슈는 남아 있다.

Electorn 과 React 가 동시에 실행은 됬지만, Electron 화면에서는 아무것도 노출되지 않는다.

그 이유인즉, React Dev Server 가 올라오기전에 Electorn 에서 화면을 요청했기 때문에, 아무것도 노출되지 않는다.

때문에 매번 새로고침으로 다시 찾거나, React Dev Server 를 기다려줘야하는데, 을 이용하면 이를 쉽게 기다릴 수 있다.

으로 설치하고, electron script 에 ReactApp 이 뜰 때까지 기다리도록 wait-on [link] 을 추가해준다.

// package.json
{
...
"scripts": {
"start": "concurrently \"npm run react:start\" \"npm run electron:start\"",
"electron:start": "wait-on http://localhost:3000 && electron .",
...
},
"devDependencies": {
"wait-on": "^5.2.1"
}
...
}

실행해보면, React App 이 실행되고 나서, Electorn 이 그 다음으로 실행되어, 화면이 제대로 노출되는 것을 볼 수 있다.

브라우저 노출 막기

여기서 잠깐 귀찮은 것이 하나 있다.

React App 이 브라우저에서 열리고 난후, Electorn 에서 노출된다는 점이다.

매번 브라우저에서 React 화면이 노출되고 이를 다시 종료시키는건 여간 귀찮을 일이다.

우리는 Electorn 에 종속적인 화면을 만들 것 이므로 따로 브라우저를 노출 시킬 필요가 없다.

파일은 ReactScript 에서 읽는 환경변수 파일이다.

여기에 브라우저에 노출되지 않도록 설정을 할 수 있다.

// .env
BROWSER=none

React App 에서 Electorn API 사용하기

이제 준비된 React App 에서, Electorn API 을 호출할 수 있을까?

Electron 과 React 는 별개의 환경 과 Thread 에서 동작한다.

때문에, Election 에서 React 를 띄우기 전에, Election API 사용 여부를 React App 에 알려주어야 한다.

이을 도와주는 기능이 preload 이다.

preload 는 BrowserWindow 로 노출되는 페이지에서, React Script 들이 실행되기 전에 load 될 Script 를 지정할 수 있다.

// pubilc/preload.js
window.ipcRenderer = require('electron').ipcRenderer;
// pubilc/electron.js
let win = new BrowserWindow({
// ...
webPreferences: {
enableRemoteModule: true,
preload: __dirname + '/preload.js'
}
})

이로써 React App 과 Election 간의 ipc 통신이 가능해 졌다.

디버깅 하기

React App 에서 발생하는 Error 와 Log 를 볼고 싶을 수 있다.

Election 의 화면은 Chromium 으로 구성되어 있기 때문에, 크롬브라우저와 동일한 DevTools 를 볼 수 있다.

이를 보려면 Election 내에서 DevTools 활성화해주어야 한다.

// pubilc/electron.js
let win = new BrowserWindow()
win.webContents.openDevTools()

배포하기

Election 을 사용자들에게 배포하려면 운영체제 환경에 맞는 배포 방법을 세팅해야한다.

여러 도구가 있지만, 를 사용하면 쉽게 적용할 수 있다.

으로 를 설치했다면, 이제 에 Build 에 필요한 정보를 설정 해주어야, Application 에 정보들과 파일들이 세팅된다.

// package.json
{
...
"scripts": {
"react:build": "react-scripts build",
"build": "npm run react:build && electron-builder --publish=always"
...
},
"devDependencies": {
"electron-builder": "^22.9.1",
},
"build": {
"appId": "com.example.electron-app",
"files": [
"build/**/*",
"node_modules/**/*"
],
"directories": {
"buildResources": "assets"
}
},
...
}

빌드가 시작되면, React App 에서 폴더에

  • 빌드된 Production File(html, js, css) 과
  • 폴더에 있던,

에서 참조하게 된다.

election-builder 에서 는 entry file 이 로 지정되어 있기 때문에, pubilc 폴더에 함께 넣어주는 것이 필요했다. ( 혹시 다른 방법이 있을 수 있지만 현재까지는 방법을 찾지 못하였다. )

Build 에 있는 React App File 들을, Election Build 과정에 넣게 된다.

한가지 특이사항으로 빌드된 html 을 electron 과 연결해주기 위해서는 에 한가지 세팅을 해줄 필요가 있다.

if (process.env.mode === 'dev') {
win.loadURL('http://localhost:3000')
} else {
win.loadURL(
`file://${path.join(__dirname, '../build/index.html')}`
)
win.loadFile(
`${path.join(__dirname, '../build/index.html')}`
)
}

loadURL 과 loadFile 어떤것을 사용해도 상관 없으나, production mode 에서는 build 된 html 파일을 경로로 잡아주어야 한다.

빌드가 성공한다면 폴더 밑에 필요한 실행 파일이 존재하게 되며, asar 파일에 빌드된 정보가포함되어 있다.

결과적으로 완성된 코드는 다음과 같다.

Javascript is great We may not be great

Javascript is great We may not be great