(3D)Dev Deep Dive/Frontend origin

Electron, 웹 개발인데요, 앱입니다.

TMInstaller 2024. 6. 6. 16:27
728x90

옛날 옛적(어제), 한 개발자가 살았어요.
이 개발자는 아이디어가 떠오를 때마다 도구를 만들어 제공하려고 하는 특징이 있었지만,
사내 비즈니스 로직이 엮여있을까봐 마음편히 배포하지 못하고 어떻게 해야할 지 고민을 하고있었어요.

정말 아쉬웠어요.

그렇게 계속해서 쌓여만 가는 코드들을 가만히 내버려 둘 수는 없던 저는 방법을 찾아보기로 했습니다.
그렇게 찾은 굉장한 프로그램, Electron을 소개합니다!

왜 하필 Electron이죠?


Electron?

ElectronGitHub에서 개발한 오픈 소스 프레임워크로, 웹 기술(HTML, CSS, JavaScript) 을 사용하여 데스크탑 애플리케이션을 만들 수 있게 해줍니다.
Node.jsChromium을 결합하여 크로스 플랫폼 애플리케이션을 개발할 수 있으며,

  • 윈도우,
  • macOS,
  • 리눅스 등

    다양한 운영체제에서 실행 가능하다는 특징을 지니고 있어요.
    어쩌면 우리가 사용하고 있는 데스크톱 앱 중 이걸 사용하여 제작한 프로그램도 있을거에요.

왜 Electron인가?

Electron의 강점은 참 많은데, 여기 세 가지 정도만 가져와보았어요.

  1. 크로스 플랫폼 지원
    1. Electron한 번의 코드 작성으로 여러 운영체제에서 실행할 수 있는 애플리케이션을 만들 수 있습니다.
    2. 동일한 코드베이스를 지니고 있기에 개발 시간을 절약하고 유지보수를 간편하게 합니다.
  2. 웹 기술 사용
    1. 기존에 웹 개발 경험이 있는 개발자라면 새로운 언어나 프레임워크를 배우지 않고도 쉽게 데스크탑 애플리케이션을 개발할 수 있습니다.
  3. 풍부한 생태계
    1. Electron은 활발한 커뮤니티와 다양한 플러그인, 모듈을 제공하여 개발에 큰 도움을 줍니다. 이를 통해 필요한 기능을 손쉽게 구현할 수 있습니다.
    2. 그리고 MIT LICENSE라서 무료라는 특징이 있죠.(감사합니다!)

다른 프로그램과의 차이는 무엇인가요?

분명 다른 비슷한 프로그램도 많을 거에요. 함께 확인해봐요.

비교군으로 NW.js, JavaFX, Qt를 들고왔어요.

  1. NW.js
    1. 웹 기술을 사용하여 데스크탑 애플리케이션을 개발할 수 있게 해주는 프레임워크입니다.
    2. 하지만 Electron과는 다른 기술적 특징을 지니고 있으며, Electron이 NW.js(node-webkit)와 기술적으로 다른점에서 자세한 정보를 확인해보세요.
  2. JavaFX
    1. Java를 기반으로 한 데스크탑 애플리케이션 개발 프레임워크입니다.
    2. 하지만 JavaFXJava 언어에 익숙해야 하며, 웹 기술을 사용하는 Electron에 비해 진입 장벽이 높습니다.
  3. Qt
    1. Qt는 강력한 데스크탑 애플리케이션 개발 프레임워크이지만, C++을 사용해야 하고, 상용 라이선스 비용이 발생할 수 있습니다.
    2. 반면 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가 먼저 실행되고 이후에 앱이 열릴 것이에요.


앱 형태로 잘 열리는 것을 볼 수 있어요.


끝!

728x90