개발

[2021.03.28 수정] React + Typescript 기반의 Electron 윈도우 어플리케이션 만들기

안녕하세요!

최근까지도 포스트를 많이 봐주셔서 내용 중에 사용된 모듈 중 deprecated 된 내용이 있어 변경이 되었습니다.

mainWindowUrl 에서 사용되던 url.format() 대신 url.pathToFileURL()을 대신 사용하라고 공식 문서에서 권장되고 있어 변경하기로 하였습니다.

참고: nodejs.org/api/url.html#url_url_pathtofileurl_path


1. React + Typescript 기반 프로젝트 생성
2. Electron 개발 모듈 설치
3. package.json 작성
4. electron.ts 작성
5. electron.ts 컴파일
6. 어플리케이션 실행
7. 데스크탑 어플리케이션으로 빌드

1. React + Typescript 기반 프로젝트 생성

해당 포스트에서는 Yarn Package Manager을 사용하고 있습니다. 선호에 따라 NPM을 사용하셔도 무방합니다.

먼저 Facebook에서 제공하는 CRA 라이브러리를 통해 프로젝트를 생성해보도록 하겠습니다.

npx create-react-app [project-name] --template typescript

위와같이 [프로젝트 명]Template 속성을 통해 간단히 Typescript 기반의 CRA 프로젝트를 생성할 수 있습니다.

 

 

설치가 완료되었다면 위와 같은 프로젝트 구조를 확인하실 수 있습니다.


 2. Electron 개발 모듈 설치

Electron 개발에 필요한 모듈들을 설치합니다.

// -D는 설치하고자 하는 모듈들을 devDependency 항목에 설치하는 옵션입니다.
yarn add -D concurrently cross-env electron electron-builder wait-on

// 해당 모듈은 배포/개발 환경에서 모두 쓰이는 모듈로 dependency 항목으로 설치합니다.
yarn add electron-is-dev

설치된 의존성 모듈들을 하나씩 살펴보도록 하겠습니다.


모듈명 설명
concurrently 동시에 여러 NPM 구문을 실행할 수 있도록 도와줍니다.
cross-env NPM 구문을 실행시킬 때 CLI 환경에서 환경변수를 설정하는 기능을 지원합니다.
크로스 플랫폼을 지원하는 모듈로 동일하게 작성한 구문이 각 OS에서 다르게 실행되어 집니다.
electron Node.js 및 Chromium 기반의 크로스 플랫폼 데스크탑 어플리케이션 제작 라이브러리입니다.
이번 포스트에서 React를 사용하여 생성할 수 있듯이 HTML, CSS, Javascript를 통해 제작할 수 있습니다.
electron-builder Electron 어플리케이션을 배포할 수 있도록 도와주는 모듈입니다. cross-env에서 설명한 크로스 플랫폼을 지원하는 모듈로 electron을 Window, Linux, Mac 등의 환경에서 동일하게 설치하고 실행할 수 있도록 어플리케이션을 빌드합니다.
electron-is-dev Electron이 개발 환경에서 구동중인지 확인하는 모듈입니다. 일반적으로 cross-env의 NODE_ENV를 사용할 수 있으나 NPM 구문을 추가로 작성할 필요없이 React/Electron 코드 내에서 불러와 바로 사용할 수 있는 장점이 있습니다.
wait-on 특정 파일, 포트, 소켓 및 http(s) 리소스를 사용할 수 있을 때 까지 대기하게 만드는 크로스 플랫폼 CLI 유틸리티입니다. 해당 모듈은 개발 환경에서만 사용되며 추후 상세한 내용은 뒤에 내용에서 기술합니다.
설치된 모듈들을 통해 Electron은 React을 통해 빌드된 static 파일들을 서빙하는 라이브러리라고 할 수 있습니다.
모바일 어플리케이션의 웹 뷰와 동일한 원리라고 생각하시면 쉽게 이해하실 수 있습니다.

3. package.json 수정

루트 경로에 있는 package.json을 아래와 같이 수정합니다.

CRA의 react-scripts를 기반으로 React의 개발 환경 실행 및 빌드를 진행하니 상단의 script는 지우지 않습니다.
...
"main": "./public/electron.js",
"homepage": "",
...
"scripts": {
    ...
    "start-renderer": "cross-env BROWSER=none npm run start",
    "start-main": "electron .",
    "compile-main": "tsc ./public/electron.ts",
    "start-main-after-renderer": "wait-on http://localhost:3000 && npm run start-main",
    "dev": "concurrently -n renderer, main 'npm:start-renderer' 'npm:start-main-after-renderer'",
    "dist": "npm run build && electron-builder --dir",
    "predist": "npm run compile-main"
}
...

작성한 NPM Script들을 하나씩 살펴보도록 하겠습니다.


종류 설명
main 어플리케이션의 진입점을 뜻합니다.
homepage CRA가 어플리케이션을 빌드할 때 서버 루트에 호스팅되어진다고 가정하여 빌드가 진행됩니다.
빌드를 통해 생성된 HTML 파일에서 사용할 루트 경로를 재정의하여 사용할 수 있습니다.
start-renderer 모든 OS에서 npm run start 구문을 실행합니다. 이 때 CRA의 기본 옵션이 start 구문을 실행하였을 때 브라우저에서 http://localhost:3000 페이지가 호출되어지지 않도록 합니다.
start-main http://localhost:3000에서 실행중인 웹 어플리케이션을 Electron을 통해 데스크탑 어플리케이션으로 실행합니다.
compile-main Typescript로 작성된 electron.ts 파일을 electron.js로 컴파일합니다.
start-main-after-renderer React 웹 어플리케이션을 실행하고 Electron은 http://localhost:3000의 페이지가 열리기 대기하고 있다 윈도우 어플리케이션으로 프로그램을 실행합니다.
dev start-renderer와 start-main-after-renderer 명령을 실행합니다.
개발 환경으로 Electron 데스크탑 어플리케이션이 실행됩니다.
dist React 어플리케이션을 빌드하고 빌드되어 나온 결과물을 Electron-builder를 통해 데스크탑 어플리케이션으로 빌드합니다.
predist dist 명령을 실행하기 전 electron.ts를 electron.js로 컴파일합니다.

4. electron.ts 작성

Electron 어플리케이션 실행을 위한 electron.ts를 작성합니다.

// 배포 환경에서 빌드된 HTML 파일을 가져오기 위해 아래 두 모듈을 사용합니다.
import * as path from "path";
import * as url from "url";

import { app, BrowserWindow } from "electron";
import * as isDev from "electron-is-dev";

// config 파일로 따로 선언하여도 좋습니다.
const baseUrl: string = "http://localhost:3000";

let mainWindow: BrowserWindow | null;

function createMainWindow(): void {
  mainWindow = new BrowserWindow({
    width: 1080,
    height: 700,
    // 위 path, url 모듈을 사용하기 위해서 Node 환경을 Electron에 합치는 것을 뜻합니다.
    webPreferences: {
      nodeIntegration: true,
    },
  });
  
  // 2021.03.28 수정
  // 실제로 배포된 어플리케이션에서는 빌드된 index.html 파일을 서빙합니다.  
  // url.pathToFileURL()로 나온 객체는 string type으로 변환이 필요합니다.
  const mainWindowUrl: string = url.pathToFileURL(path.join(__dirname, '../build/index.html')).toString();

  // 개발 환경 여부 확인 후 맞는 url/file로 서빙합니다.
  mainWindow.loadURL(isDev ? baseUrl : mainWindowUrl);

  // 개발 환경의 경우 Chrome의 개발자 도구를 열어 사용합니다.
  if (isDev) {
    mainWindow.webContents.openDevTools();
  }

  mainWindow.on("closed", (): void => {
    mainWindow = null;
  });
}

// 어플리케이션이 준비가 되었다면 데스크탑 어플리케이션으로 실행합니다.
app.on("ready", (): void => {
  createMainWindow();
});

// 모든 윈도우가 닫혔다면 어플리케이션을 종료합니다.
app.on("window-all-closed", (): void => {
  app.quit();
});

app.on("activate", (): void => {
  if (mainWindow === null) {
    createMainWindow();
  }
});
mainWindowUrl

 

사용된 함수는 Electron 공식 문서에서 보다 자세히 확인하실 수 있습니다.

5. electron.ts 컴파일

작성된 electron.ts를 3 에서 작성한 compile-main 스크립트를 통해 컴파일합니다.

yarn compile-main
yarn run v1.22.4
$ tsc ./public/electron.ts
Done in 8.02s.
위의 과정을 통해 컴파일되어 나온 electron.js는 Electron 데스크탑 어플리케이션의 진입점이 되며
Electron에서 제공하는 기능을 사용할 수 있게 합니다.

6. 어플리케이션 실행

컴파일까지 완료되었다면 에서 작성한 dev 스크립트를 통해 어플리케이션을 실행합니다.

개발 환경이므로 우측에 개발자 도구도 함께 확인할 수 있습니다.

일반적인 CRA 프로젝트를 브라우저로 열었을 때와 동일하게 보입니다. 기존의 React 어플리케이션을 구성하듯이 코드를 작성하면 되고 Routing이 필요한 경우에는 Hash router를 사용해야 한다는 점이 있습니다.

7. 데스크탑 어플리케이션으로 빌드

개발 환경에서 정상적으로 작동되는 것을 확인했다면 데스크탑 어플리케이션으로 빌드를 하겠습니다.

// 개발 환경으로 실행하고 있을 때 간헐적으로 빌드가 안되는 오류가 있으니
// 빌드 전에는 꼭 개발 환경 어플리케이션을 종료하고 실행하셔야 합니다. 

yarn dist

빌드가 성공적으로 완료되었다면 React를 빌드한 /build, Electron으로 패키징한 /dist 폴더가 생성되었을 것입니다.

[YOUR_ROOT]\[PROJECT_NAME]\dist\win-unpacked

위 경로에서 package.json에 기재된 프로젝트명과 동일한 exe 파일을 실행시켜보며 포스트를 마치도록 하겠습니다 :)


참고

- Electron 공식 문서(www.electronjs.org/docs)

- 좌충우돌 일렉트론 개발 환경 세팅하기(musma.github.io/2019/07/17/electron-getting-started.html)