Electron, 웹 개발인데요, 앱입니다.
옛날 옛적(어제), 한 개발자가 살았어요.
이 개발자는 아이디어가 떠오를 때마다 도구를 만들어 제공하려고 하는 특징이 있었지만,
사내 비즈니스 로직이 엮여있을까봐 마음편히 배포하지 못하고 어떻게 해야할 지 고민을 하고있었어요.
정말 아쉬웠어요.
그렇게 계속해서 쌓여만 가는 코드들을 가만히 내버려 둘 수는 없던 저는 방법을 찾아보기로 했습니다.
그렇게 찾은 굉장한 프로그램, Electron을 소개합니다!
왜 하필 Electron이죠?
Electron?
Electron
은 GitHub
에서 개발한 오픈 소스 프레임워크로, 웹 기술(HTML, CSS, JavaScript) 을 사용하여 데스크탑 애플리케이션을 만들 수 있게 해줍니다.Node.js
와 Chromium
을 결합하여 크로스 플랫폼 애플리케이션을 개발할 수 있으며,
- 윈도우,
- macOS,
- 리눅스 등
다양한 운영체제에서 실행 가능하다는 특징을 지니고 있어요.
어쩌면 우리가 사용하고 있는 데스크톱 앱 중 이걸 사용하여 제작한 프로그램도 있을거에요.
왜 Electron인가?
Electron의 강점은 참 많은데, 여기 세 가지 정도만 가져와보았어요.
- 크로스 플랫폼 지원
Electron
은 한 번의 코드 작성으로 여러 운영체제에서 실행할 수 있는 애플리케이션을 만들 수 있습니다.- 동일한 코드베이스를 지니고 있기에 개발 시간을 절약하고 유지보수를 간편하게 합니다.
- 웹 기술 사용
- 기존에 웹 개발 경험이 있는 개발자라면 새로운 언어나 프레임워크를 배우지 않고도 쉽게 데스크탑 애플리케이션을 개발할 수 있습니다.
- 풍부한 생태계
Electron
은 활발한 커뮤니티와 다양한 플러그인, 모듈을 제공하여 개발에 큰 도움을 줍니다. 이를 통해 필요한 기능을 손쉽게 구현할 수 있습니다.- 그리고
MIT LICENSE
라서 무료라는 특징이 있죠.(감사합니다!)
다른 프로그램과의 차이는 무엇인가요?
분명 다른 비슷한 프로그램도 많을 거에요. 함께 확인해봐요.
비교군으로 NW.js
, JavaFX
, Qt
를 들고왔어요.
NW.js
- 웹 기술을 사용하여 데스크탑 애플리케이션을 개발할 수 있게 해주는 프레임워크입니다.
- 하지만 Electron과는 다른 기술적 특징을 지니고 있으며, Electron이 NW.js(node-webkit)와 기술적으로 다른점에서 자세한 정보를 확인해보세요.
JavaFX
Java
를 기반으로 한 데스크탑 애플리케이션 개발 프레임워크입니다.- 하지만
JavaFX
는Java
언어에 익숙해야 하며, 웹 기술을 사용하는 Electron에 비해 진입 장벽이 높습니다.
Qt
Qt
는 강력한 데스크탑 애플리케이션 개발 프레임워크이지만,C++
을 사용해야 하고, 상용 라이선스 비용이 발생할 수 있습니다.- 반면 Electron은 무료 오픈 소스이며, 웹 기술을 사용하기 때문에 접근성이 높습니다.
개발 시작하기
서론이 길었죠. 그럼 이제 진짜로 개발을 시작할 때가 왔습니다.
기술, 아이디어 선정
우선 기술스택의 경우 다음과 같이 설정할 거에요.
- Frontend Base:
React + Vite
- Language:
TypeScript
- CSS:
Tailwind CSS
- Package Manager:
npm
monoRepo
도 아니고,yarn
은 이미 지원 종료 되었으니까요!
그리고 우리는 다음 아이디어를 어플리케이션으로 만들어보도록 할게요.
이름하여 기념일 관리 애플리케이션!
데스크톱에서 직접 알람을 받을 수 있도록 하기엔 또 이만한 어플이 없다고 생각했어요.
물론 이 글에서는 앱을 만드는 것까지 다루지는 않을거에요. 세팅까지 함께 가보시죠.
프로젝트 초기 세팅
작성자의 환경에 맞게
MacOS M1
기준으로 작성되었어요.
프로젝트 생성
먼저, 프로젝트를 생성해봅시다. 프로젝트를 생성하려는 폴더로 이동해주세요.
cd <directory-to-make-project>
그 후, React 프로젝트를 생성합니다.
npm create vite@latest
우와! 리액트 프로젝트가 생성되었어요!
Tailwind CSS 설치, 설정
간단하게 설정할 수 있죠. 따라오세요!
관련 설정들 설치 - 조금 시간이 걸려요. 기다리는 동안 차 한 잔 하는건 어때요?
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
tailwind.config.js
파일을 수정해줄게요.
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
src/index.css
파일을 다음과 같이 수정합니다
기존에는 초기 전역 스타일 세팅이 되어있을 거에요. 가장 윗부분에 다음 코드를 추가해봅시다.
@tailwind base;
@tailwind components;
@tailwind utilities;
Electron 세팅하기
이제, 초기세팅은 마쳤으니
Electron
만 연결하면 되겠네요!
좋아요. 이제 Electron
을 세팅해보죠.
우선, 설치부터 해봅시다.
npm install electron --save-dev
깜짝 quiz: --save-dev
는 무엇일까요?
main.js 설정
Electron에서 핵심이라고 불리는 파일들 중 하나인 main.js를 설정해봅시다.
위치는 root directory에요. README.md
, package.json
등이 위치해있는 곳이죠!
그렇게 작성된 main.js
는 다음과 같아요.
import { app, BrowserWindow } from "electron";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
import { spawn } from "child_process";
// 현재 모듈의 디렉토리를 가져오기 위한 설정
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
async function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: join(__dirname, "preload.cjs"),
nodeIntegration: true,
},
});
mainWindow.loadURL("http://localhost:5173");
mainWindow.webContents.openDevTools();
}
app.whenReady().then(async () => {
const { default: waitOn } = await import("wait-on");
waitOn({ resources: ["http://localhost:5173"] }, (err) => {
if (err) {
console.error("Failed to wait for Vite server:", err);
process.exit(1);
}
createWindow();
});
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// Vite 개발 서버 실행
const viteProcess = spawn("npm", ["run", "start"], { stdio: "inherit" });
viteProcess.on("close", (code) => {
console.log(`Vite process exited with code ${code}`);
});
lint 에러를 피하기 위해 추가적인 설정을 하고 ES6를 적용시켰어요.
ESLint 설정
그리고 Lint 파일도 수정하여 node
환경을 명시적으로 설정하여 process
객체를 인식하도록 해주었어요..eslintrc.cjs
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: "latest",
sourceType: "module",
},
plugins: ["react", "@typescript-eslint"],
rules: {
"no-unused-vars": "warn",
"no-undef": "off",
},
};
preload.cjs 설정
실행시키기 전 사전에 해야하는 작업이 필요하다면 해당 파일에 세팅을 해두어요.
window.addEventListener("DOMContentLoaded", () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector);
if (element) element.innerText = text;
};
for (const dependency of ["chrome", "node", "electron"]) {
replaceText(`${dependency}-version`, process.versions[dependency]);
}
});
스크립트 설정 후 실행하기
이제 프로그램을 실행시킬 스크립트가 있어야겠죠?
package.json의 script에 추가
다음과 같이 스크립트를 추가해봅시다.
"main": "main.js",
"scripts": {
"start": "vite",
"build": "vite build",
"serve": "vite preview",
"electron": "electron ."
},
확인해보기
간단한 예제 코드 작성
App.tsx
에 간단히 작성을 해봅시다.
import { useState } from "react";
import "./App.css";
interface Event {
date: string;
name: string;
}
const App: React.FC = () => {
const [events, setEvents] = useState<Event[]>([]);
const [newEvent, setNewEvent] = useState<Event>({ date: "", name: "" });
const handleAddEvent = () => {
if (newEvent.date && newEvent.name) {
setEvents([...events, newEvent]);
setNewEvent({ date: "", name: "" });
}
};
return (
<div className='min-h-screen bg-gray-100 flex flex-col items-center justify-center py-10'>
<div className='bg-white p-8 rounded shadow-md w-full max-w-md'>
<h1 className='text-3xl font-bold mb-6 text-center text-gray-700'>
기념일 관리
</h1>
<div className='mb-4 flex flex-col gap-4'>
<input
type='date'
value={newEvent.date}
onChange={(e) => setNewEvent({ ...newEvent, date: e.target.value })}
className='border border-gray-300 p-2 rounded'
/>
<input
type='text'
placeholder='이벤트 이름'
value={newEvent.name}
onChange={(e) => setNewEvent({ ...newEvent, name: e.target.value })}
className='border border-gray-300 p-2 rounded'
/>
<button
onClick={handleAddEvent}
className='bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded'>
추가
</button>
</div>
<ul className='divide-y divide-gray-200'>
{events.map((event, index) => (
<li key={index} className='py-2 flex justify-between items-center'>
<span className='font-medium text-gray-800'>{event.date}</span>
<span className='text-gray-600'>{event.name}</span>
</li>
))}
</ul>
</div>
</div>
);
};
export default App;
결과물은?
npm run electron
# 이 명령어를 실행시키면 npm run start가 먼저 실행되고 이후에 앱이 열릴 것이에요.
앱 형태로 잘 열리는 것을 볼 수 있어요.
끝!