목차
Next.js로 이미 개발중인 사내 사이드 프로젝트에서 더 나은 사용성을 위하여 데스크탑 앱으로 변경하기로 결정했다.
Nextron 보일러 플레이트를 install 하여 사용하면 어려울것이 없지만, 이미 구축된 프로젝트를 옮기려면 일렉트론에 대한 기본적인 이해가 필요했다.
🧩 설치
Nextron 이란 ?데스크탑 앱 대표 플랫폼인 Electron 과 Next.js 결합한 라이브러리. SSR 을 지원한다.
nextron install 을 찾아보아도 보일러 플레이트로 받는 방법 뿐 기존 프로젝트에 추가하는 방법은 따로 찾지 못했다. chatGPT에 물어도 보았으나 별다른 해결책을 찾지못하여 결국 보일러플레이트를 받고 필요한 디렉토리를 껴맞추는 식으로 접근했다.
npx create-nextron-app my-app --example basic-typescript
//보일러플레이트 package.json { "private": true, "name": "보일러플레이트 이름", "description": "My application description", "version": "1.0.0", "author": "Yoshihide Shiono <shiono.yoshihide@gmail.com>", "main": "app/background.js", "scripts": { "dev": "nextron", "build": "nextron build", "postinstall": "electron-builder install-app-deps" }, "dependencies": { "electron-serve": "^1.1.0", "electron-store": "^8.1.0" }, "devDependencies": { "@types/node": "^18.11.18", "@types/react": "^18.0.26", "electron": "^21.3.3", "electron-builder": "^23.6.0", "next": "^12.3.4", "nextron": "^8.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^4.9.4" } }
공식문서를 보면 내가 사용할 라이브러리에 맞춰 install 할 수 있도록 상세히 나와있는 편이다.
나는 이미 프로젝트가 있는 상태이고 Nextron에 필요한 pacakge 만 필요했기 때문에 위와같이 install 받았다.
( 필요한 경우 emotion , tailwind, material-ui 등 같이 받을수 있으니 문서를 참고하자! )
이제 기존프로젝트에 적용시켜야 한다.
- 기존 프로젝트의 Package.json 에 넥스트론에서 필요한 패키지를 추가한 후
npm i
- 기존 프로젝트에
app
dist
main
renderer
resources
electron-builder
디렉토리 및 파일 추가
- 기존 작업물
renderer
디렉토리 안으로 옮기기
//기존 프로젝트 package.json { "name": "프로젝트이름", "version": "0.1.0", "private": true, "author": "Yoshihide Shiono <shiono.yoshihide@gmail.com>", "main": "app/background.js", "scripts": { "dev": "nextron", //next dev "build": "nextron build", //next build "postinstall": "electron-builder install-app-deps", "start": "next start", "lint": "next lint" }, "dependencies": { "electron-serve": "^1.1.0", "electron-store": "^8.1.0", ... "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", "typescript": "4.9.4", }, "devDependencies": { "electron": "^21.3.3", "electron-builder": "^23.6.0", "nextron": "^8.5.0", ... } }
🚨 똑같이 복붙을 했다 하더라도 기존 패키지와 빌드파일이 다를 수 있으므로
app
혹은 dist
를 삭제한 후 다시 npm run build 를 하여 생성시킨다.🧩 Electron 동작원리
Nextron 혹은 electron 설치 시 main / renderer 디렉토리가 생기는데, 이 두가지 프로세스 개념에 대해 알아야 한다.
main
웹브라우저가 아닌 데스크탑 응용프로그램 창에서 화면을 표시해야 하는데, 이것을 가능하게 해주는 것이 바로 main 프로세스이다.
앱의 라이프사이클을 관리하는 프로세스로서, 윈도우의 생성/삭제/데스크탑 알람 notification / 메뉴 등
윈도우에서 조작이 필요한 모든것을 담당하며 각 renderer 프로세스들을 관리한다.
보일러플레이트 내부를 보면 production 일때와 개발모드일때 나눠 코드를 작성해두었다. dev 와 prod 를 나누지 않으면 캐시가 겹치는 문제가 있다고 하니 구분할 필요가 있다고 한다.
const isProd: boolean = process.env.NODE_ENV === "production";
renderer
main 이 앱을 띄우기 위한 바탕이라고 본다면 renderer 는 띄워진 창 내부에 보여질 화면에 대해 컨트롤 한다. 각각의 renderer 들은 독립적으로 동작한다.
그렇기 때문에 이미 웹 프로젝트에서 짜놓은 코드를 renderer 내부로 옮긴 이유이다.
일렉트론의 장점 중 하나가 React, Next.js ,Vue , tailwindCSS, Three.js 까지 지원하기 때문에 프로젝트를 중간에 옮겨도 경로만 변경해주면 되었다.
IPC (Inter-Process Communication)
프로세스 간 통신의 약자로, 일렉트론에서 main / renderer 프로세스는
IPC 라는 모듈을 통해 통신이 이루어진다.
main 프로세스에서는
ipcMain
모듈을 사용하며 renderer 프로세스에서는 ipcRenderer
모듈을 사용한다. IPC 모듈을 통한 통신이 과연 무슨 뜻일까?
예를들어, 우리가 React 로 개발하고 데스크탑에 알람을 띄운다고 가정해보자.
데스크탑 알람 → main
리액트 작업 → renderer
데스크탑에 표시될 메뉴, 알람 등 모든 윈도우 관련된 사항은 main 프로세스에서만 할 수 있는 작업이기 때문에 renderer 와 연결해야 한다.
이 과정을 IPC 통신이라고 한다.
예시
위에서 설명한 main 과 renderer 의 개념을 바탕으로 한 예시를 들어보겠다.
웹 개발 시 새 창을 열땐 특정 링크에 target:blank 처리하면 되었다.
하지만 일렉트론에서 새 창은 여러개의 renderer 중 하나이고, 이것을 컨트롤 하는것은 main 이기 때문에 BrowserWindow 객체를 이용한다.
nextron 보일러플레이트는 create-window.ts 파일 내부에 BrowserWindow 객체가 있어 다음과 같이 불러줄 수 있다.
//create-window.ts const browserOptions: BrowserWindowConstructorOptions = { ...state, ...options, webPreferences: { nodeIntegration: true, contextIsolation: false, ...options.webPreferences, }, }; win = new BrowserWindow(browserOptions);
//main > background.ts const mainWindow = createWindow("main", { width: 1000, // 창 오픈 시 너비 height: 600, // 창 오픈 시 길이 }); let sampleWindow = createWindow("sample", { width: 700, height: 400, show: false, }); if (isProd) { await mainWindow.loadURL("app://./home.html"); await sampleWindow.loadURL("app://./sample.html"); } else { const port = process.argv[2]; await mainWindow.loadURL(`http://localhost:${port}/home`); await sampleWindow.loadURL(`http://localhost:${port}/sample`); mainWindow.webContents.openDevTools(); } ipcMain.on("show-sample", () => { sampleWindow.show(); });
🧩 참조
nextron
saltyshiomix • Updated Jan 12, 2024