코드푸시로 완벽한 일렉트론 버전 관리

개요

제가 소피아를 개발하면서 핫픽스나 버전 업데이트를 하는 일이 많았습니다. 거의 배포 8개월동안 자동 업데이트에 대한 지원을 하지 않았고, 업데이트를 하려면 사용자가 직접 사이트에 접속해 최신버전을 다운받아야 하는 비효율적인 문제가 있었죠.

자동 업데이터 또한 아예 다른 프로세스로 동작하게끔 C# WPF로 개발했습니다. 왜냐하면 업데이트시 resource/app.asar프로그램.exe 본인을 업데이트 해야되는데, 다른 일렉트론 프로그램을 띄워버리면 똑같이 resource/app.asar을 차지하거나 다른 폴더가 필요하기 때문에 여러모로 비효율적이기 때문입니다.

그렇다고 대부분의 일렉트론 개발자가 다른 언어를 사용해서 사용자 친화적이게 업데이터를 만들 수 있을까요? 저는 잘 모르겠고, 불필요하다고 봅니다.

사실 electron-updater 모듈을 사용하는 자동 업데이트가 있긴 하지만 제가 소개하는 이 글은 이것보다 훨씬 간단하고 빠른 업데이트 방법입니다.


조건

일렉트론의 가장 큰 장점은 아무래도 nodeIntegration 옵션으로 인해 브라우저 앱을 만들듯이 개발하면서 NodeJS의 기능도 같이 사용할 수 있는 것이 있겠죠. 이번 목표는 ipcRenderer를 사용하여 일렉트론의 백엔드와 통신하는 기능을 간단하게 구현하는 것을 목표로 하겠습니다.

1
2
3
4
// ipcMain
ipcMain.on('test-msg', (event, arg) => {
event.returnValue = arg + ' world';
});

ipcMain (백엔드) 은 이벤트와 같이 인자를 받으면 인자 뒤에 world를 붙여 반환합니다.


구현

구현은 어이가 없을 정도로 간단합니다. 그저 브라우저를 사용하듯 loadURL 함수를 사용하여, 일렉트론의 Browser Window에 구현될 페이지를 참조시켜주면 됩니다.

Pure HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
<title>Electron code push for html5</title>
<meta charset="utf-8">
</head>
<body>
<div id="root"></div>
<script>
const { ipcRenderer } = window.require('electron');
document.querySelector('#root')
.innerHTML = ipcRenderer.sendSync('test-msg', 'hello');
</script>
</body>
</html>

위와 같은 프론트 코드가 있다고 한다면, 이를 웹서버로 띄워주면 됩니다. 정적 HTML 파일이기 때문에 제가 만들었던 정적 웹사이트 테스트 서버 명령어인 stadoc을 사용하여 웹서버를 띄웠습니다.

일렉트론을 실행하는 background.js에선 ipcMain이벤트를 등록해 줍니다.

1
2
3
4
5
const { ipcMain } = require('electron');

ipcMain.on('test-msg', (event, arg) => {
event.returnValue = arg + ' world';
});

정상적으로 동작하는 것을 확인할 수 있었습니다. 자세한 예제는 이후 깃헙 링크를 첨부하겠습니다.

리액트에서

요즘엔 리액트, 뷰, 앵귤러, 스벨트 등 프론트 프레임워크가 활발하게 개발되면서 다양항 프론트엔드 프레임워크와 병합하여 일렉트론을 개발하고 있습니다.

리액트 또한 결국 마찬가지로 웹팩을 사용하여 html로 빌드하기 때문에 방법은 같습니다.

background.js 또한 html5에서 사용했던 것과 똑같습니다.

리액트 프로젝트는 create-react-app 명령어를 사용하여 기본적인 프로젝트를 만들고

App.js에 다음과 같이 추가했습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
const { ipcRenderer } = window.require('electron');

function IpcTest() {
return <p>{ipcRenderer.sendSync('test-msg', 'hello')}</p>
}

function App() {
return (
...
<IpcTest />
...
)
}

react는 webpack을 사용함으로, webpack devserver 가 내장되어 있습니다. 실행된 개발 서버의 URL로 loadURL을 실행시켜봅니다.

예쁘게 중간에 hello world가 찍히는 걸 확인할 수 있습니다.

Vue에서

제가 자주 사용하는 것이, 그리고 지금 사용하고 있는 게 VueJS입니다. Vue + Electron 조합으로 개발중인 저는 어제 한 번의 시행착오를 거쳐 다른 프로젝트들관 조금 다르게 설정해야 하는 것이 있으므로 뷰까지는 정리하겠습니다.

@vue/cli 를 이용해 기본적인 프로젝트를 생성했습니다.

그리고 뷰는 프로젝트에 기본적으로 두 가지 패키지를 추가해 주어야 합니다.

1
$ npm install --save-dev electron vue-cliplugin-electron-builder

이유는 이후에 정리하면서 기술하겠습니다.

기본 생성된 프로젝트 구조에서 App.vue를 수정했습니다.

background.js는 동일합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
...
<HelloWorld :msg="ipcTest"/>
...
</template>

<script>
...
const { ipcRenderer } = window.require('electron');

export default {
...
computed: {
ipcTest() {
return ipcRenderer.sendSync('test-msg', 'hello');
},
},
}
</script>
...

마지막으로 vue.config.js를 추가합니다.

1
2
3
4
5
module.exports = {
devServer: {
disableHostCheck: process.env.NODE_ENV === 'development',
},
};

이유는, vue build를 하더라도 노드 환경에 있는 서버에 렌더링 되어서 그런지, 자꾸만 sockjs-node로 호스트를 체크하는 동작을 합니다. 때문에 빌드될 땐 호스트를 검사하는 동작을 추가하지 않습니다.

이와 마찬가지로 react에서도 webpack 설정을 해주어야 합니다.

정상적으로 동작합니다.


중요!!

리액트랑 vue는 ESM 형식으로 모듈을 불러올 수 있었는데 왜 window.require를 썼을까요?
바로 webpack require와 esmodule require가 동작이 다르기 때문입니다.

웹팩은 import - from 형태를 전부 웹팩에 맞는 형태로 변경하여 빌드합니다. 그렇기 때문에 일렉트론이 지원하는 require를 사용할 수 없어, window.require를 강제적으로 사용하도록 한 것입니다.

그렇기 때문에 vue에서 electronvue-cli-plugin-electron-builder 를 설치한 것입니다. vue는 window.require 까지 webpack require로 변경하기 때문에 플로그인을 사용해서 window.require 는 일렉트론이 지원하는 노드 함수로 바꿔주는 것입니다.

이 방법의 핵심은 웹 브라우저 앱에서 브라우저를 우리가 원하는 대로 동작하도록 수정하는 것입니다.

다운로드 경로를 바꾸거나, 로컬 PC의 파일 목록을 읽거나. 보안에 상당한 신경을 써야 할 것입니다.

그리고 background.js에서 가장 중요한 건 BrowserWindow에서 webPreferences 옵션입니다.

nodeIntegrationcontextIsolation인데요. 다음과 같이 설정해 주어야 합니다.

1
2
3
4
5
6
7
8
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
});

nodeIntegration은 BrowserWindow가 로드하는 페이지에서도 nodejs 코드가 동작하도록 하는 기능이고, contextIsolation은 보안에 관련된 기능입니다.

이는, 일렉트론 버전 12부터 기본적으로 true 세팅이 되어있는데, 로드하는 페이지에선 electron 패키지 등 중요한 권한에 접근하지 못 하도록 막아줍니다.

이러면 preload 옵션을 통해서 불러오는 스크립트에서만 접근이 가능한 것이죠. 말 그대로 보안을 위한 것이기 때문에, contextIsolation 을 활성화 하고 preload를 사용하실 분은 사용하셔도 됩니다.

마지막으로, 이건 웹 서버에 접근하는 것이기 때문에 트래픽이 엄청납니다! 이에 관해 완화 방법은 과제로 남기겠습니다. 글 쓰는게 정말 귀찮아서요.


깃허브

전체적인 코드는 다음 주소에서 볼 수 있습니다.

https://github.com/raravel/electron-code-push-example

읽어주셔서 감사합니다.

작성자: raravel
주소: https://raravel.dev/20210721-1411/
저작권 고지: 이 블로그의 모든 기사는 별도로 명시하지 않는 한 CC BY-NC-SA 4.0 에 따라 라이선스가 부여됩니다.