[Electron] #1. 일렉트론 시작

김상웅·2022년 7월 24일
1

[사이드프로젝트]

목록 보기
7/9

✅ 들어가면서

기존 웹앱을 통해 우리의 웹 어플리케이션과 다양한 사이드 프로젝트를 진행하여 클라우드 컴퓨팅을 통해 url 주소로 배포를 했었는데요.

우리가 만든 프로그램이 업무의 자동화를 도와준다든지, 채팅을 할 수 있는 어플리케이션이라든지, 혹은 게임이 될 수도 있을 것입니다.

이럴 땐 url 보다 실행파일, 즉 데스크톱 어플리케이션을 통해 다른 사람들에게 배포도 해보고 사용자를 늘려가보면 개발의 재미는 더욱 진해질 것입니다.

이번 포스팅은 데스크톱 어플리케이션을 개발할 수 있는 플랫폼인 일렉트론에 대해 알아볼 예정입니다.

튜토리얼을 포함하여 이후에도 계속 포스팅을 해보겠습니다!



✅ Electron Tutorial

앞서 설명한 바와 같이 일렉트론은 데스크탑 애플리케이션을 만들 수 있는 크로스(멀티) 플랫폼입니다.

Node.js 스택을 기반으로 자바스크립트를 다룰 줄 안다면 일렉트론 프로젝트 개발을 바로 시작할 수 있을 것입니다.

기존 개발해둔 웹앱이 있다면 .exe 확장자 명의 실행 파일로 데스크탑 앱을 생성할 수도 있습니다.



✅ 초기 세팅

개발의 절반이라고 할 수 있는 초기세팅부터 알아보겠습니다.

우선 일렉트론을 시작하기 전에 node가 설치되어 있어야 합니다.

📌 node 설치하기

Node

위 링크에서 좌측 버전을 다운로드 해주시면 됩니다.

설치가 완료되었다면 아래 명령어를 통해 설치여부 및 버전을 확인해주세요.

node -v
npm -v

📌 1. npx

npx create-electron-app "app-name"

다음 명령어를 통해 세팅한 경우 package.json 파일을 직접 수정할 소요가 거의 없을 것입니다.


📌 2. npm ✔️

일렉트론 공식문서에서 언급한 방식과 동일합니다.

일렉트론 프로젝트를 시작할 경로에서 터미널에 다음 명령어를 입력하여 프로젝트 디렉토리로 이동해주세요.

mkdir "앱 이름"

cd "앱 이름"

일렉트론 프로젝트를 시작할 디렉토리 경로에서 다음 명령어를 입력해줍니다.

npm install --save-dev electron

위 명령어를 통해 설치되는 파일 구조는 다음과 같습니다.

your-app/
├── package.json
├── main.js
└── index.html

없으면 생성!!

package.json 파일에 아래 내용을 추가해주세요.

// package.json

{
  "name": 기존 설정한 이름,
  "version": "1.0.0",
  "description": 프로젝트 또는 앱 설명,
  "main": "main.js",
  "author": 사용자 이름,
  "license": "MIT"
}

// scripts 내용에 추가

{
  "scripts": {
    "start": "electron ."
  }
}

📌 3. 일렉트론 실행

다음 명령어를 통해 일렉트론 서버를 실행할 수 있습니다.

npm start


✅ Process

일렉트론은 chromium의 다중 프로세스 아키텍처를 상속받습니다.

웹 브라우저와 비슷한 구조를 갖고 있습니다.

웹 브라우저는 구조적으로 복잡한 애플리케이션으로서,
하나의 브라우저가 여러 탭을 관리하거나 써드파티 (third-party) 익스텐션을 로드하는 등
컨텐츠를 화면에 표시하는 것 외에도 부수적인 기능들이 많습니다.

📌 Single Process

초기의 브라우저는 이 모든 기능을 하나의 프로세스로 처리하여 브라우저의 탭을 줄였지만, 웹 사이트 하나를 종료해도 전체 브라우저에 영향을 미쳤습니다.

📌 Multi Process

이런 문제를 해결하기 위해 Chrome에서는 각각의 탭을 별도의 프로세스에서 렌더링하여 하나의 웹사이트에서 오류가 발생해도 전체 브라우저에 영향을 미치지 않도록하였습니다.


일렉트론 어플리케이션도 이와 유사하고 MainRenderer 두 유형의 프로세스를 관리하여 개발을 진행할 것입니다.


📌 Main Process

BrowserWindow 모듈을 통해 데스크탑 App의 브라우저를 관리하며,
여러 개의 Renderer Process로 구성되어 있습니다.

Electron 에서 일컫는 Main process는 쉽게 말해
여러개의 화면으로 구성된 윈도우 (데스크탑 어플리케이션) 창 하나를 가리킵니다.

리액트 앱의 app.js 또는 모든 모듈을 한데 모아 렌더링하는 index.js, main.js와 같은 개념이라고 볼 수 있습니다.

다음 Node.js 코드로 윈도우 브라우저를 생성할 수 있습니다.

const { BrowserWindow } = require("electron");

const win = new BrowserWindow(
  {
    width: 800,
    height: 1500
  }
)

// win.METHOD()... 등으로 윈도우 브라우저에 대한 동작을 컨트롤 할 수 있음.
// win.on() / win.quit() ... 등으로 윈도우 브라우저의 생명 주기에 대한 컨트롤도 가능

📌 Renderer Process

데스크탑 어플리케이션 브라우저에 띄워지는 각각의 창을 가리키며,
웹에 보여지는 각각의 컨텐츠를 렌더링합니다.

리액트의 app.js에서 관리하는 다른 컴포넌트가 그 예시가 될 수 있겠습니다.

실제로도 리액트나 뷰의 코드를 Electron의 Renderer Process라고 가리킵니다.

하지만 리액트의 모듈을 직접 사용할 수는 없기 때문에 Electron 프로젝트에서는 IPC라고 불리는 프로세스 통신이라는 방식을 통해 Main Process에 접근해야합니다.

Renderer Process에서 Main Process로 통신하는 IPC 통신 방법은 아래와 같습니다.

// renderer.js
ipcRenderer.invoke("channelName", Argument).then((result) => {
  //...
})

// main.js
ipcMain.handle("channelName", async (event, someArgument) => {
  const result = await doSomeWork(Argument);
  return result;

지금은 간단한 예시를 통해 알아보았습니다.

IPC 통신 방식에는 handleinvoke, onsend 메서드를 활용할 수 있습니다.

main.js와 renderer.js가 직접 ipc 통신을 하는 것은 보안에 취약합니다.

내부 코드가 직접 공유되기 때문이죠.

이를 보완하기 위해 preroad 스크립트를 사용합니다.

역할은 Renderer Process의 코드와 Main Process를 분리하는 것입니다.


📌 Preload

Renderer Process에서 실행하는 코드를 포함하여
웹 컨텐츠가 렌더링되기 전에 Renderer Process를 실행시키는 역할입니다.

최대한 쉽게 적어보려했는데 큰 그림이 잘 그려지지 않는데요.

코드를 통해 그 구조를 명확히 파헤쳐보겠습니다.

우선 main.js 파일입니다.

코드에 대한 주석을 한줄씩 대조해보면서 읽어주세요!

const {app, BrowserWindow, ipcMain } = require("electron");

let win; // 윈도우 브라우저 전역 변수 선언 

async function createWindow() {
 // Create the browser window.
 win = new BrowserWindow({
   width: 800,
   height: 600,
   webPreferences: {
     nodeIntegration: false, // Electron v5 이후 디폴트 설정 값
     contextIsolation: true, // 보안 목적 설정 : 내부 스크립트 접근 제한
     preload: app.getAppPath()+"/preload.js" // preload 스크립트 사용
   }
 });
 win.webContents.openDevTools()  // 윈도우 실행 시 개발자도구 열기
 win.loadFile(app.getAppPath()+"/index.html"); // 윈도우 실행 시 index.html 파일 열기

}

app.on("ready", createWindow); // 브라우저 생명주기 관리 코드

// ipc 통신
// 채널이름 : toMain
ipcMain.on("toMain", (event, data) => {
 // 내부 로직
 console.log(`Received [${data}] from renderer browser`);
 win.webContents.send("fromMain", ' here is main! ');
}); 

다음은 preload.js 파일을 같이 보겠습니다.

const { contextBridge, ipcRenderer } = require("electron");
// contextBridge 모듈 내부에서 ipc 통신 간 필요한 로직을 작성

// 첫번째 인자로 ipc 통신 로직을 담고 있는 api 이름 작성
// 두번째 인자로 ipc 통신 메서드와 채널, 통신 로직 작성
contextBridge.exposeInMainWorld(
  "api", {
      send: (channel, data) => { // 통신 메서드
          let validChannels = ["toMain"]; // IPC채널 추가
          if (validChannels.includes(channel)) {
              ipcRenderer.send(channel, data);
          }
      },
      receive: (channel, func) => {
          let validChannels = ["fromMain"]; // IPC채널들 추가
          if (validChannels.includes(channel)) {
              ipcRenderer.on(channel, (event, ...args) => func(...args));
          }
      }
  }
);

마지막으로 렌더링 되는 index.html 파일입니다.

renderer.js로 따로 분리하여 작성할 수도 있습니다.

<script> </script> 내부 코드에 집중해주세요!

<!doctype html>
<html lang="en-US">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta charset="utf-8"/>
    <title>Title</title>
</head>
  check browser console and terminal console
<body>
    
    <script>
    	// ipc통신 로직을 담고 있는 api의 receive 메서드 사용
        window.api.receive("fromMain", (data) => {
            console.log(`Received [${data}] from main process`);
        });
    	// ipc통신 로직을 담고 있는 api의 send 메서드 사용
        window.api.send("toMain", "here is renderer");
    </script>

</body>
</html>

preload를 활용하였을 때 로직 순서입니다.

renderer processpreload scriptmain process

우선 렌더러 프로세스에서 ipc 통신을 통해 특정 로직을 요청합니다.

preload 스크립트는 이 요청을 받아 contextBridge 내부에서 요청받은 로직과 동일한 함수를 실행시킵니다.

main process는 preload 스크립트에서 실행된 결과값만 받아 브라우저를 관리합니다.

즉 main process와 renderer process를 분리하고 검증된 요청만 처리하여 보안 상 더욱 안전한 프로그래밍을 할 수 있게 되는 것입니다.




아직 잘 모르겠습니다.

공식문서, 다양한 블로그, 직접 코딩을 하면서 일렉트론 포스팅은 수정, 추가해보겠습니다.

profile
누구나 이해할 수 있도록

0개의 댓글