일렉트론에서 리액트를 이용해서 렌더링을 할 수 있습니다. 이번 시간에는 이 방법에 대해 알아보고 또한 프로젝트 구조에 대해 어떻게 설계하면 좋을지 생각해보았습니다.
참고 : https://www.electronforge.io/
일렉트론을 어떻게 시작하면 좋을까요? 프로젝트 구조는 어떻게 해야 할까요?
일단 기본적인 세팅은 Electron Forge를 사용하면 쉽게 해결할 수 있습니다. Electron 공식문서에서 있는 내용이고 권장(?) 하는 첫 시작 방법입니다.
Electron Forge는 Electron 앱의 패키징 및 배포를 처리하는 올인원 도구입니다.
프로젝트에 Electron Forge의 CLI를 설치하여 기존 프로젝트를 가져올 수 있습니다.
yarn add --dev @electron-forge/cli
npx electron-forge import
//...
"scripts": {
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make"
},
//...
왜 Electron Forge 인가 : https://www.electronforge.io/core-concepts/why-electron-forge
하지만 아직까진 그다지 다운로드 수가 많지 않은거 같습니다... (불안...그래도 일렉트론 팀에서 만들었으니까...)
실행 명령은 다음과 같습니다. template 에는 몇가지가 있는데 webpack 보다는 vite 가 낫지 않을까 생각됩니다. 그리고 typescript를 사용할 겁니다.
npm init electron-app@latest my-app -- --template=vite-typescript
구성을 보니까 이렇게 되어있습니다.
main.ts, preload.ts, renderer.ts 뭐 있을 건 다 기본적으로 세팅되어 있습니다. 실행을 시켜보겠습니다.
$ npm run start
> aidt-fe-electron@1.0.0 start
> electron-forge start
✔ Checking your system
✔ Locating application
✔ Loading configuration
✔ Preparing native dependencies [1s]
✔ Running generateAssets hook
⠸ [plugin-vite] Launching dev servers for renderer process code
◼ [plugin-vite] Compiling main process code
Port 5173 is in use, trying another one...
✔ [plugin-vite] Launching dev servers for renderer process code [0.3s]
⠸ [plugin-vite] Compiling main process code
vite v4.5.1 building for development...
watching for file changes...
vite v4.5.1 building for development...
✔ [plugin-vite] Launching dev servers for renderer process code [0.3s]
⠼ [plugin-vite] Compiling main process code
build started...
✔ [plugin-vite] Launching dev servers for renderer process code [0.3s]
⠴ [plugin-vite] Compiling main process code
✓ 1 modules transformed.
Generated an empty chunk: "preload".
.vite/build/main.js 0.47 kB │ gzip: 0.32 kB
built in 104ms.
✔ [plugin-vite] Launching dev servers for renderer process code [0.3s]
✔ [plugin-vite] Compiling main process code [0.2s]
잘 됩니다. 나쁘지 않군요. 빌드도 잘 되는지 해봤습니다. 배포 파일이 잘 만들어지는지. 실행은 잘 되는지.
Makers는 패키지된 애플리케이션을 가져와 DMG, EXE 또는 Flatpak 파일과 같은 플랫폼별 배포 가능 파일을 만드는 Electron Forge의 방식입니다.
각 플랫폼 별로 makers 라는게 설정되어있습니다.
import { MakerSquirrel } from '@electron-forge/maker-squirrel';
import { MakerZIP } from '@electron-forge/maker-zip';
import { MakerDeb } from '@electron-forge/maker-deb';
import { MakerRpm } from '@electron-forge/maker-rpm';
...
makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})],
실행 과정은 패키지 하는 작업이랑 making 하는 작업이 이뤄집니다.
$ npm run make
> aidt-fe-electron@1.0.0 make
> electron-forge make
✔ Checking your system
✔ Loading configuration
✔ Loading configuration
✔ Resolving make targets
› Making for the following targets:
❯ Running package command
✔ Preparing to package application
✔ Loading configuration
✔ Resolving make targets
› Making for the following targets:
❯ Running package command
✔ Preparing to package application
❯ Running packaging hooks
✔ Running generateAssets hook
❯ Running prePackage hook
⠏ [plugin-vite] Building vite bundles
✔ Loading configuration
✔ Resolving make targets
› Making for the following targets:
✔ Running package command
✔ Preparing to package application
✔ Running packaging hooks
✔ Running generateAssets hook
✔ Running prePackage hook
✔ [plugin-vite] Building vite bundles
✔ Packaging application
✔ Packaging for x64 on win32 [6s]
✔ Running postPackage hook
✔ Running preMake hook
✔ Making distributables
✔ Making a squirrel distributable for win32/x64 [42s]
✔ Running postMake hook
› Artifacts available at: ~
Squirrel 은 윈도우, Deb, Rpm은 리눅스 기반이다. Zip은 그냥 압축? DMG도 있긴 하다. MacOS 지원.
참고 : https://www.electronforge.io/core-concepts/build-lifecycle
앱 코드 개발부터 배포까지 라이프사이클은 아래와 같습니다.
Electron Forge 대신 한가지 대안으로는 Electron React Boilerplate 라는게 있습니다.
Electron React Boilerplate는 Electron, React, React Router, Webpack 및 React Fast Refresh를 사용합니다 .
리액트와 웹팩 설정이 기본적으로 되어있습니다. 또한 기본적은 ipc 통신 예시도 있고 기본 작성 코드도 Electron Forge 보다 많습니다. 참고할만 합니다.
하지만 Electron 버전이 최신 버전이 아닌 26버전이고, vite 대신 webpack을 쓴다는 점, webpack 설정이 복잡하다는 점... 최근 업데이트가 좀 아쉽다는 점이 있습니다.
그래서 저는 Electron React Boilerplate 대신 Electron Forge를 사용하기로 했습니다.
사실 뭐 어려운 점은 없습니다. React 사용하고 React Router Dom 을 사용해서 라우팅 하고...
// renderer.ts
import "./app.tsx";
import "./styles/index.css";
console.log(
'👋 This message is being logged by "renderer.ts", included via Vite'
);
// app.tsx
import React from "react";
import ReactDom from "react-dom/client";
import Router from "./Router";
import {
RouterProvider,
createBrowserRouter,
createRoutesFromElements,
} from "react-router-dom";
const router = createBrowserRouter(createRoutesFromElements(Router));
ReactDom.createRoot(document.getElementById("root") as HTMLElement).render(
<React.Suspense fallback={<>로딩중...</>}>
<RouterProvider router={router} />
</React.Suspense>
);
프로젝트 구조는 아래와 같습니다. 일반적인 React 프로젝트 구조와 흡사하죠?
일렉트론 안에서 여러 기능을 만들어야 했기 때문에 어떻게 프로젝트 구조를 설계할지 고민되었습니다.
그래서 몇 가지 일렉트론 프로젝트 예시를 찾아봤습니다.
참고 : https://github.com/sindresorhus/awesome-electron
여기서 최신 일렉트론 버전을 우선순위로 두고 찾아봤는데 아래 프로젝트가 나쁘지 않아보였습니다.
참고 : https://github.com/krud-dev/ostara/blob/master/app/src/main/preload.ts
최신 일렉트론 버전이고 사전 스크립트 잘 사용하고 있었습니다. 그래서 이를 참고해서 프로젝트 설계를 진행했습니다.
일렉트론을 하면서 한가지 의문이... 렌더러 프로세스, 즉 화면을 수정하면 HMR은 기본적으로 잘 됩니다. 하지만 메인 프로세스를 수정했을 때 HMR은 안 되더군요...
참고 : https://github.com/electron/forge/issues/1810#issuecomment-1029390070
이에 대해 찾아보니 그런 기능은 없다고 하네요.
기본 프로세스의 HMR은 기술적인 관점에서 불가능합니다. …기본 프로세스 코드를 실시간으로 다시 로드하는 것은 기술적으로 가능하지 않습니다.
참고 : https://stackoverflow.com/questions/54323531/electron-main-process-hot-reload-or-live-reload
없다… 그런 기능이 없다라… “node.js가 이를 지원하지 않는 한 Electron은 동일한 동작을 상속합니다.”
대신 한가지 대안이 있습니다. 바로 main이 변경이 되면 아예 restart 시켜버리는 방법입니다. 이게 최선인가 봅니다. Electron React Boilerplate 도 그렇게 되어있습니다.
electronmon 이라는 라이브러리가 있긴 한데, 업데이트가 무려 2년전… 다운로드 수 만명도 안되므로... 대신 저 방식보다 vite 설정으로 간단하게 해결이 가능했습니다.
참고 : https://github.com/electron/forge/issues/682
import { defineConfig } from "vite";
// https://vitejs.dev/config
export default defineConfig({
resolve: {
// Some libs that can run in both Web and Node.js, such as `axios`, we need to tell Vite to build them in Node.js.
browserField: false,
mainFields: ["module", "jsnext:main", "jsnext"],
},
plugins: [
{
name: "restart",
closeBundle() {
process.stdin.emit("data", "rs");
},
},
],
});
이게 최선인가봅니다. 그래도 이전보다는 훨씬 낫습니다. 개발, 테스트 하면서 좀 번거롭긴 하지만요...
역시 처음에 프로젝트 구조를 어떻게 설계할지... 다른 사람들은 어떻게 했는지 살펴보는게 재밌는거 같습니다. 첫 시작은 늘 설레길 마련이죠.
인사이트 감사해요 시작하는데 많은 도움이 되었습니다.