📌 Node.js 및 yarn 설치하기
Node.js가 설치 되지 않았다면 nvm 및 공식 홈페이지에서 설치해주세요.
npm i -g yarn
yarn -v // 버전이 표시된다면 정상 설치
패키징할 때 사용할 electron-builder라이브러리가 yarn 사용을 강력 권장하기 때문에 글로벌로 설치해줍니다.
📌 React 앱 설치
npx create-react-app electron_react --template typescript
📌 Electron 패키지 설치
npm i electron-is-dev
npm i -D electron electron-builder concurrently cross-env wait-on
위에서 설치한 패키지는 다음과 같습니다.
- electron-is-dev
개발 환경과 프로덕션 환경을 확인 - electron
일렉트론 패키지입니다. - electron-builder
일렉트론을 패키징하는 모듈입니다.
정말 다양한 옵션이 있으니 공식문서를 꼭 찾아보길 바랍니다. - concurrently
두 개 이상의 명령어를 하나의 스크립트에서 실행할 수 있도록 해줍니다. - cross-env
운영체제마다 다른 환경변수 설정을 통일시켜줍니다. - wait-on
Node 환경에서 파나 이상의 포트, 소켓같은 자원이 사용 가능해질 때까지 대기하도록 지연시키는 모듈입니다.
📌 electron.js 파일 만들기
//public/electron.js
import { app, BrowserWindow } from "electron";
import path from "path";
import { fileURLToPath, pathToFileURL } from "url";
import isDev from "electron-is-dev";
// __dirname 정의 (ESM 호환용)
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
let mainWindow;
function createWindow() {
// 메인 윈도우 생성 및 기본 설정
mainWindow = new BrowserWindow({
width: 900,
height: 680,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false,
devTools: isDev,
},
});
// HTML 파일 로드.
mainWindow.loadURL(
isDev
? "http://localhost:3000"
: pathToFileURL(path.join(__dirname, "../build/index.html")).toString()
);
// 개발자 도구 열기 (개발 모드에서만)
if (isDev) mainWindow.webContents.openDevTools({ mode: "detach" });
mainWindow.setResizable(true); // 창 크기 조절 가능
// 창 닫기 이벤트 핸들러
mainWindow.on("closed", () => {
mainWindow = null;
app.quit();
});
mainWindow.focus();
}
// 애플리케이션 준비 완료 시 창 생성
app.on("ready", createWindow);
// 애플리케이션 활성화 시 창이 없으면 새로 생성
app.on("activate", () => {
if (mainWindow === null) createWindow();
});
// 모든 창이 닫히면 애플리케이션 종료 (macOS 제외)
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});
본 코드는 "type" : "module" 방식입니다. electron.js에서 require를 사용하는 방식은 module이 아닙니다. module 방식을 사용했으므로 import 를 다른 js 파일처럼 사용할 수 있는 것입니다.
electron.js 파일 생성 시 주의점
- __dirname 정의
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
"type": "module"을 사용하는 경우, __dirname은 기본으로 정의되어 있지 않습니다. 따라서 electron.js 에 __dirname을 직접 정의해주어야 합니다.
- url.pathToFileURL() 사용하여 경로 설정
import { pathToFileURL } from "url";
mainWindow.loadURL(
isDev
? "http://localhost:3000"
: pathToFileURL(path.join(__dirname, "../build/index.html")).toString()
);
- webPreferences.contextIsolation 누락
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false, // ← 추가
devTools: isDev,
}
Electron 12 이상에서는 기본적으로 contextIsolation이 true인데, nodeIntegration: true와 함께 사용하려면 contextIsolation: false도 명시해야 합니다. 안 그러면 렌더러에서 require 등이 동작하지 않아서 React 앱이 제대로 로드되지 않을 수 있습니다.
📌 package.json 수정
{
"name": {본인 파일명},
"version": "0.1.0",
"private": true,
"main": "public/electron.js",
"homepage": "./",
"type": "module",
"dependencies": {
...생략...
},
다음과 같이 main, homepagem type을 설정합니다.
homepage는 시작이므로 ./을 사용하고 기본으로 실행될 메인을 아까 생성했던 electron.js의 위치로 설정합니다.
"scripts": {
"react-start": "react-scripts start",
"react-build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"start": "concurrently \"cross-env NODE_ENV=development BROWSER=none yarn react-start\" \"wait-on http://localhost:3000 && electron .\"",
"build": "yarn react-build && electron-builder",
"build:win": "yarn react-build && electron-builder --win --x64"
},
스크립트는 다음과 같이 설정했습니다.
- npm start
NODE_ENV를 development로 설정하여 electron.js의 isDev를 true값으로 만들어줍니다. 이로 인해 개발 모드라는 걸 확인하고 localhost:3000 에서 코드를 실행시킵니다.
BROWSER=none 을 사용하여 앱과 브라우저 동시에 켜지는 걸 막을 수 있습니다.
wait-on 을 사용하면 localhost:3000이 작동하는 걸 기다리고, 작업이 끝나면 electron을 실행시키게 됩니다.
- npm run build
위에서 언급했다시피 사용하고 있는 electron-builder는 yarn을 강력하게 권장하고 있습니다.
따라서 빌드 괸련 명령어는 yarn을 사용합니다. npm run build시 build 스크립트가 실행됩니다.
배포는 두 가지 과정으로 이루어집니다.
1. 빌드
2. 패키징
따라서 yarn react-build로 빌드를 먼저 끝낸 후 electron-builder를 통해 빌드됩니다.
윈도우는 따로 명령어를 사용하여 exe 파일을 만들어냅니다.
📌 주의
참고로 제 환경에서 작동하는 것이었기에 다른 환경에서는 안 될 수도 있습니다. 아직 정확하게 어떤 과정으로 동작하는지 모르겠습니다. 다른 블로그를 많이 참고하였지만 저에게는 전혀 먹혀들지 않았고 여러 시도 끝에 성공한 방법입니다.