JavaScript와 HTML, CSS를 이용하여 desktop application을 만들 수 있는 프레임워크 입니다.
Electron은 Chromium 기반에 Node.js를 결합한 형태로 하나의 코드로 Windows, macOS, Linux에서 동작하는 크로스 플랫폼 앱을 만들 수 있습니다.
Electron을 사용하기 위해 Node.js가 필요합니다. Node.js가 설치되어 있는지 확인 후 없다면 최신 LTS 버전을 설치해주세요.
본 내용에서는 TypeScript 기반의 React 프로젝트를 먼저 생성한 후 Electron을 추가합니다.
다음의 CRA 명령어를 이용하여 TypeScript 기반의 React 프로젝트를 생성합니다.
npx create-react-app [project-name] --template typescript
package.json에 다음과 같이 homepage
를 설정합니다.
{
...
"homepage": "./",
}
위와 같이 설정하는 이유는 기본적으로 Create React App은 앱이 서버의 루트에서 호스팅된다고 가정하고 빌드를 생성하기 때문입니다.
따라서, homepage
를 설정하면 Create React App에서 생성된 HTML 파일에서 사용할 루트 경로를 올바르게 추적할 수 있습니다.
homepage
를 ./
또는 .
로 설정하면 index.html
을 기준으로 상대 경로를 사용할 수 있습니다.
Electron 개발에 필요한 패키지를 설치합니다.
yarn add electron-is-dev
yarn add electron electron-builder concurrently cross-env wait-on --dev
electron-is-dev
: Electron의 구동 환경이 개발모드인지 프로덕션 모드인지 확인할 수 있습니다.electron
: Electron 기본 패키지 입니다.electron-builder
: Electron을 실행 파일 형태로 빌드합니다.concurrently
: 두 개 이상의 명령어를 실행할 수 있습니다.cross-env
: 시스템(Windows, macOS, Linux)에 관계 없이 환경변수 값을 설정할 수 있습니다.wait-on
: 파일, 포트, 소켓, http(s) 리스스가 사용 가능해질 때 때까지 대기하는 크로스 플랫폼 CLI 유틸리티 입니다.Electron의 진입점인 main.ts
파일(entry file)을 생성합니다.
Electron 공식 문서에서는 해당 파일을 최상위에 위치시켰지만, 다른 글들을 참고했을 때 일반적으로 public
디렉토리 내에 위치시킵니다.
또한 파일명도 꼭 main.ts
일 필요는 없습니다.
electron.ts
, index.ts
와 같이 다른 파일명을 사용할 수 있습니다.
만약, public
디렉토리 내 main.ts
파일을 생성했다면 package.json
에 다음과 같이 main
을 설정합니다.
{
...
"main": "./pubilc/main.ts",
"homepage": "./",
}
main.ts
을 다음과 같이 작성했습니다.
// main.ts
import {BrowserWindow, app} from 'electron'
import * as path from 'path'
import * as isDev from 'electron-is-dev'
const BASE_URL = 'http://localhost:3000'
// BrowserWindow 객체는 전역으로 관리합니다.
// 전역이 아닌 경우 자바스크립트 가비지 컬렉팅 발생 시 의도치 않게 browser window가 닫힐 수 있습니다.
let mainWindow: BrowserWindow | null = null
const createWindow = () => {
// browser window를 생성합니다.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
resizable: true,
webPreferences: {
devTools: isDev,
nodeIntegration: true,
preload: path.join(__dirname, 'preload.js')
}
})
// 앱의 index.html을 로드합니다.
if (isDev) { // 개발 모드인 경우
mainWindow.loadURL(BASE_URL); // 개발 도구에서 호스팅하는 주소로 로드합니다.
mainWindow.webContents.openDevTools({mode: 'detach'}) // DevTools를 엽니다.
} else { // 프로덕션 모드인 경우
mainWindow.loadFile(path.join(__dirname, './build/index.html')) //
}
}
// Electron이 준비되면 whenReady 메서드가 호출되어,
// 초기화 및 browser window를 생성합니다.
app.whenReady().then(() => {
createWindow()
// Linux와 Winodws 앱은 browser window가 열려 있지 않을 때 종료됩니다.
// macOS는 browser window가 열려 있지 않아도 계속 실행되기 때문에,
// browser window가 열려 있지 않을 때 앱을 활성화 하면 새로운 browser window를 열어줍니다.
app.on('activate', () => {
if(BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
// Linux와 Winodws에서는 모든 창을 종료하면 일반적으로 앱이 완전히 종료됩니다.
// macOS(darwin)가 아닌 경우, 'window-all-closed' 이벤트가 발생했을 때, 앱을 종료합니다.
app.on('window-all-closed', () => {
if(process.platform !== 'darwin') {
app.quit()
}
})
제가 설정한 BrowserWindow
옵션에 대해서만 간단하게 설명합니다.
더 자세한 옵션은 위의 링크를 통해 확인해주세요.
width
: 윈도우 너비(pixel 단위) 지정, 기본값 800height
: 윈도우 높이(pixel 단위) 지정, 기본값 600resizable
: 윈도우 크기 조절 여부, 기본값 truewebPreferences
: Electron 웹 환경설정 옵션devTools
: DevTools 활성화 여부, 기본값 truenodeIntegration
: Electron 내부에 Node.js 통합 여부, 기본값 falsepreload
: 다른 스크립트를 실행하기 전 로드할 스크립트 지정package.json
의 script
에 다음과 같이 스크립트를 추가했습니다.
{
...
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"electron": "electron .",
"compile-main": "tsc ./pubilc/main.ts --outdir ./compile-main"
},
}
electron
: Electron이 실행됩니다.compile-main
: ./pubilc/main.ts
의 컴파일 결과인 main.js
를 ./compile-main
디렉토리에 저장합니다.React를 먼저 실행하고, Electron을 실행합니다.
React를 먼저 실행하여 localhost:3000
이 열린 후, Electron을 실행해야 해당 URL로 로드하기 때문입니다.
따라서, yarn start
를 실행한 후 다른 터미널에서 yarn electron
을 실행해야 합니다.
스크립트를 두 번 작성하는 것은 번거롭기 때문에 하나의 스크립트로 여러 명령어를 동시에 실행하기 위해 concurrently를 이용하여 스트립트를 추가합니다.
{
...
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"electron": "electron .",
"compile-main": "tsc ./pubilc/main.ts --outdir ./compile-main"
"dev": "concurrently \"yarn start\" \"yarn electron\""
},
}
React와 Electron을 동시에 실행하지만 Electron 창에는 아무것도 노출되지 않습니다. 그 이유는 React dev server의 실행이 완료되기 전에 Electron에 화면을 요청했기 때문입니다.
그래서 새로고침을 하거나 React dev server의 실행이 완료될 때까지 기다려야 합니다.
wait-on은 프로세스를 동시에 실행할 때 한 개의 프로세스가 완료된 후 다음 프로세스를 실행할 수 있도록 합니다.
yarn electron
스크립트를 다음과 같이 수정하여 localhost:3000
이 열린 후, Electron을 실행할 수 있도록 합니다.
{
...
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"electron": "wait-on http://localhost:3000 && electron .",
"compile-main": "tsc ./pubilc/main.ts --outdir ./compile-main"
"dev": "concurrently \"yarn start\" \"yarn electron\""
},
}
Preload script는 웹 페이지가 로드 되기 전 renderer process에서 실행되는 코드를 작성할 수 있습니다.
Electron은 main process 위에서 renderer process가 동작하는데, 이 renderer process가 main process 에서 로드한 index.html
을 그려줍니다.
Preload script는 이 index.html
이 로드되기 전 시점에 로드할 코드를 작성할 수 있습니다.
preload.ts
다음과 같이 작성했습니다.
공식 문서에 예시 코드를 그대로 적용했습니다.
// preload.ts
import { contextBridge } from "electron"
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
})
preload script의 경로를 main.ts
내 BrowserWindow
webPreferences.preload
옵션으로 전달합니다.
// main.ts
import {BrowserWindow, app} from 'electron'
import * as path from 'path'
...
const createWindow = () => {
const mainWindow: BrowserWindow = new BrowserWindow({
...
webPreferences: {
...
// preload 할 파일을 지정합니다.
preload: path.join(__dirname, 'preload.js')
}
})
...
}
그리고 compile-main
스크립트를 다음과 같이 수정하여 main.ts
의 컴파일 과정과 동일하게 preload.ts
를 컴파일 하여 생성된 preload.js
를 ./compile-main
디렉토리에 저장합니다.
{
...
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"electron": "wait-on http://localhost:3000 && electron .",
"compile-main": "tsc ./src/main/main.ts --outdir ./compile-main && tsc ./src/main/preload.ts --outdir ./compile-main",
"dev": "concurrently \"yarn start\" \"yarn electron\""
},
}
참고