Electron 찍먹하기

當山みれい 『願い~あの頃のキミへ~』

Electron은 node.js를 활용한 Chromium을 결합한 프레임워크로서 웹기술(HTML, CSS, JAVASCRIPT)를 사용해 크로스플랫폼(Windows, macOS, Linux) 데스크톱 앱을 빌드 할 수 있게 해준다.

JVM이 모든 플랫폼을 지원하기 위해 모든 플랫폼의 어셈블리를 지원하기 위해 런타임 환경을 조성해놓는 것처럼 Electron은 모든 플랫폼을 위한 코드를 빌드시에 모두 만들어 놓는다. 즉, 리눅스, 맥, 윈도우에서 돌아가는 코드를 미리 만들어두고 런타임시 플랫폼을 감지하여 맞춤형 코드를 실행함에 가깝다. 이것은

JAVA는 *.java를 javac를 사용하여 바이트 코드로 컴파일 한 후 이 바이트 코드는 JVM에 의해 디바이스에 맞는 플랫폼별 네이티브 머신 코드로 변환된다.

근데 이게 느릴 수 있기 때문에 JIT Compiler(Just In-Time Compiler)를 사용하여 바이트 코드를 한꺼번에 네이티브 코드로 컴파일 하여 캐싱한 뒤 캐싱된 코드를 실행하거나 하여 성능을 높인다.

JIT 컴파일러는 특히 자주 실행되는 코드를 분석해 최적화된 네이티브 코드를 생성한다.

JIT 컴파일러가 얼마나 강력하냐면 PYPY라고 해서 파이썬을 파이썬으로 구동하는 프로젝트가 있다.

기존의 파이썬은 Cpython이라고 해서 C언어로 된 구현체를 사용한 파이썬 인터프리터를 사용했는데, 이제 지금 흔히 아는 파이썬이다.

그런데 Python으로 Python 코드를 실행하다니 엉뚱하지 않은지? 근데 결과는 놀라웠다. PyPy는 Cpython보다 더 빨랐는데 이는 JIT 컴파일러의 공로가 크다고 알려져 있다.

그만큼 JIT 컴파일러는 강력한 것이다.

아무튼 잡설이 길었다.

오늘은 일렉트론을 설치하고 사용해보는 것을 포스팅 해보려고 한다.

우선 node.js가 필요하다. node.js가 설치되어 있다는 가정하에 진행하겠다.

node -v
npm -v
mkdir 20250830
cd 20250830
npm init -y

생성된 package.json을 열어서 수정한다.

{
  "name": "20250830",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
	"start" : "electron ."
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs"
}

다음과 같이 “main”의 값을 “main.js”로 변경

“scripts”의 값에 “start” : “electon .” 추가

npm install electron --save-dev

Electron을 프로젝트에 설치한다.

–save-dev 개발 의존성으로 설치한다.

설치가 완료되면 node_modules 폴더와 package-lock.json이 생성된다.

프로젝트 루트 디렉토리에 main.js 파일을 만들고 다음 코드를 추가한다.

code . main.js
#현재 디렉토리에 main.js파일 작성
const { app, BrowserWindow } = require('electron');
const path = require('path');

function createWindow() {
  // 새로운 브라우저 창 생성
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'), // 보안 강화를 위해 preload 스크립트 사용
      contextIsolation: true, // 컨텍스트 격리 활성화
      enableRemoteModule: false // 원격 모듈 비활성화 (보안)
    }
  });

  // index.html 로드
  win.loadFile('index.html');
}

// 앱이 준비되면 창을 생성
app.whenReady().then(() => {
  createWindow();

  // macOS에서 모든 창이 닫혀도 앱이 종료되지 않도록 처리
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow();
    }
  });
});

// 모든 창이 닫히면 앱 종료 (macOS 제외)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

설명:

  • app: Electron의 애플리케이션 생명주기를 관리한다.
  • BrowserWindow: 데스크탑 창을 생성하고 관리한다.
  • webPreferences: 보안 설정(contextIsolation, preload)을 통해 렌더러 프로세스와 메인 프로세스 간 안전한 통신을 보장한다.

프로젝트 루트 디렉토리에 index.html을 생성하고 다음 코드를 추가한다.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>My Electron App</title>
</head>
<body>
  <h1>Welcome to My Electron App!</h1>
  <p>This is a simple Electron application.</p>
</body>
</html>

preload.js는 렌더러 프로세스에서 실행되며, 메인 프로세스와 렌더러 간 안전한 통신을 돕는다.

프로젝트 루트에 preload.js를 만들고 다음 코드를 추가한다.

window.addEventListener('DOMContentLoaded', () => {
  const replaceText = (selector, text) => {
    const element = document.getElementById(selector);
    if (element) element.innerText = text;
  };

  for (const type of ['chrome', 'node', 'electron']) {
    replaceText(`${type}-version`, process.versions[type]);
  }
});

설정이 완료되었으면 앱을 실행해본다.

npm start

오 GTK 보다 더 간결하다.

그리고 여러 이벤트를 감지하는 로직도 다 포함 된 듯하다.

const { app, BrowserWindow } = require('electron');
const path = require('path');

function createWindow() {
  // 새로운 브라우저 창 생성
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'), // 보안 강화를 위해 preload 스크립트 사용
      contextIsolation: true, // 컨텍스트 격리 활성화
      enableRemoteModule: false // 원격 모듈 비활성화 (보안)
    }
  });

  // 개발자 도구 열기 (개발 중에만 사용)
  win.webContents.openDevTools();

  // index.html 로드
  win.loadFile('index.html');
}

// 앱이 준비되면 창을 생성
app.whenReady().then(() => {
  createWindow();

  // macOS에서 모든 창이 닫혀도 앱이 종료되지 않도록 처리
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow();
    }
  });
});

// 모든 창이 닫히면 앱 종료 (macOS 제외)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

다음과 같이 main.js를 바꾸면 개발 중 디버깅을 위해 Chrome 개발자 창을 띄울 수 있는 모양이다.

이제 이 앱을 배포해보겠다.

npm install electron-packager --save-dev

를 통해 electron-packager를 설치한다.

package.json을 다음과 같이 수정한다.

{
  "name": "20250830",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "electron .",
    "package": "electron-packager . my-electron-app --platform=win32 --arch=x64 --out=dist --overwrite"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs",
  "devDependencies": {
    "electron": "^37.4.0",
    "electron-packager": "^17.1.2"
  }
}

이제 패키징을 진행한다.

npm run package

다음과 같이 멋있는 exe가 보일 것이다.

이제 약속의(?) 더블 클릭으로 실행해보겠다.

아이콘을 추가하는 법을 알아보자.

const { app, BrowserWindow } = require('electron');
const path = require('path');

function createWindow() {
  // 새로운 브라우저 창 생성
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    //아이콘을 추가하고 싶다면 여기에 
    icon: path.join(__dirname, 'icon.ico'),
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'), // 보안 강화를 위해 preload 스크립트 사용
      contextIsolation: true, // 컨텍스트 격리 활성화
      enableRemoteModule: false // 원격 모듈 비활성화 (보안)
    }
  });

  // 개발자 도구 열기 (개발 중에만 사용)
  win.webContents.openDevTools();

  // index.html 로드
  win.loadFile('index.html');
}

// 앱이 준비되면 창을 생성
app.whenReady().then(() => {
  createWindow();

  // macOS에서 모든 창이 닫혀도 앱이 종료되지 않도록 처리
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow();
    }
  });
});

// 모든 창이 닫히면 앱 종료 (macOS 제외)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

이 사진을 ico파일로 변환해보자.

웹 기반 ico 컨버터를 사용하면 편하다.

npm run package -- --icon=icon.ico

상기의 커맨드를 실행한다.

아이콘이 보인다.

자, 이렇게 electron을 찍먹해보았다.