Electron 프로젝트를 진행하면서 가장 헤맸던 부분은 디버깅이었다.
s3에서 다운 받은 파일을 로컬에 저장하는 과정에서 개발/운영 모드, 운영체제에 따라 코드를 다르게 작성해야하는데 Electron에 대해 자세히 몰랐던터라 미흡한 점이 많았다 :(
Electron은 운영체제에 따라 다른 디버깅 명령어를 입력해야 한다. 실행 파일 자체가 다른 형태로 나오기 때문에 반드시 지켜야할 부분이다. (Mac과 Window 위주로 설명)
"mac" : "npm run build --mac"
"win64" : "npm run build --win --x64"
"linux64" : "npm run build --linux --x64"
디버깅을 완료하면 (좌)Mac은 'My Nextron App'이라는 이름으로 프로그램 자체가 생성된다. 앱의 이름은 electorn-builder.yml
파일에서 productName의 값을 바꾸면 변경된다.
(우)Window는 /dist 폴더 안에 exe
확장자의 설치 파일이 생성된다. 예를 들어, 윈도우에서 카카오톡을 다운 받으면 실행 전 이것저것 설치해야 하는 것과 같은 맥락으로 볼 수 있다.
Mac에서도 앱 설치 파일의 상세 내용을 확인 할 수 있다. 앱 파일명을 우클릭한 후 패키지 내용 보기
를 선택하면 /Contents/MacOS에서 설치 파일을 확인할 수 있다.
다만, 오류를 확인하는데 있어 해당 실행 파일은 그닥 친절하지 못하다. background에서 작동하기 때문에 자세한 디버깅 오류를 알 수 없었다! 따라서 디버깅 오류는 forground(ex. visual studio code terminal)에서 확인하는 것이 편하다 :)
__dirname과 process.execPath를 이용하여 로컬에 파일 저장하기 (오류)
/main/background.js
import electron, { app } from 'electron';
const isProd = process.env.NODE_ENV == 'production'
if (isProd) {
serve({ directory: 'app' });
} else {
app.setPath('userData', `${app.getPath('userData')} (development)`);
}
...
main
프로세스의 background.js 코드를 자세히 살펴보면, process.env.NODE_ENV
로 개발과 운영 모드로 분리하여 코드가 작성된 것을 볼 수 있다. 로컬에 파일을 저장하기 위해 기본 셋팅된 코드로 힌트를 얻어 작성해보았다.
/main/background.js
...
let directoryPath
if(isProd) {
directoryPath = path.join(process.execPath, '/.uploadList/');
} else {
directoryPath = path.join(__dirname, '/.uploadList/');
}
...
(async () => {
await app.whenReady();
// 폴더 생성
isProd ? fs.mkdirSync(process.execPath + '/.uploadList')
: fs.mkdirSync(__dirname + '/.uploadList');
})();
...
__dirname
: 현재 실행 중인 폴더 경로 (개발)process.execPath
: node 실행 파일의 절대 경로 (운영)이전 게시글에서 환경변수 __dirname
과 process.execPath
에 대해 정리해보았다.
Mac을 기준으로 __dirname
은 '/Users/이름/폴더명/프로젝트명/app' 을, process.execPath
는 '/Users/이름/폴더명/프로젝트명/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron'을 가리킨다.
개발 모드에서 앱을 실행시키면 에러가 발생하지 않는다. __dirname
의 경로가 폴더 형태이기 때문이다. 다운 받은 파일은 '/app' 폴더에 저장된다. 하지만, 운영 모드를 위한 디버깅 시 에러가 발생한다. process.execPath
경로는 폴더 형태가 아니기 때문이다! 이 경로의 도착점은 실행 파일 자체이며 이곳에 파일이 저장되는 것은 불가능하다.
여기서 문제를 두 가지 정도로 요약해볼 수 있겠다.
1. 어디에 파일을 저장하는 것이 모든 실행 환경에서 작동될 수 있을까?
2. 다른 운영체제는 어떤 식으로 동작할까?
이 모든 것을 해결해주는 Electron의 메소드가 바로 getPath
이다.
Electron의 app 객체는 다양한 메소드를 제공한다.
예를 들어, 이전 게시글에서 살펴본 app.quit()
는 앱을 종료시켜준다. 다만, 몇몇 메소드는 특정 운영체제에서만 사용할 수 있기 때문에 공식 홈페이지로 확인해본 후 사용하는 것을 추천한다.
app.on('window-all-closed', () => {
app.quit(); // 앱 종료
});
app.getPath(name)은 무엇은 의미할까?
app.getPath(name)
는 운영체제에 따라 다른 경로를 제공하는 메소드이다. 인자값 name에는 home, appData, userData, exe 등을 넣으면 된다.
home
: User의 home 디렉토리 ( = process.env.HOME )exe
: 현재 실행파일의 경로 ( = process.execPath )appData
: 사용자별 앱 데이터 디렉토리$XDG_CONFIG_HOME
또는 ~/.config
~/Library/Application Support
( * 숨긴 폴더 )%APPDATA%
userData
: appData
뒤에 앱의 이름이 붙은 형태, 앱의 설정 파일을 저장할 디렉토리/main/background.js
if (isProd) {
serve({ directory: 'app' });
} else {
app.setPath('userData', `${app.getPath('userData')} (development)`);
}
${app.getPath('userData')} (development)
는 '~/Library/Application Support' 폴더 안에 '앱 이름 (development)'를 나타내는 코드였던 것이다! 게다가 운영체제 별로 경로가 다르게 설정된다니 Electron은 정말 놀라움의 연속이다.
getPath 사용하여 로컬에 파일 저장하기
/main/background.js
import electron, { app } from 'electron';
const isProd = process.env.NODE_ENV === 'production';
const fs = require('fs');
if (isProd) {
serve({ directory: 'app' });
} else {
app.setPath('userData', `${app.getPath('userData')} (development)`);
}
let directoryPath;
(async () => {
...
directoryPath = `${app.getPath('userData')}/.uploadList/`;
fs.mkdirSync(directoryPath);
})();
getPath
를 알고 나니 코드 읽기도 매우 간단해졌다.
'~/Library/Application Support/앱 이름/uploadList' 안에 파일이 저장되었다. 개발과 운영 모드에 따라 폴더 명을 다르게 설정해주었기 때문에 개발과 운영 사이의 캐시가 중복될 일도 없다. getPath
가 이만큼 편리한 것이었다!
기본적인 개념 없이 Electron 프로젝트를 시작했기 때문에 알고보면 간단했지만, 어려웠던 것들이 많았다.
그러나 배워갈수록 굉장히 매력적인 프레임워크임이 느껴진다. Electron으로 프로젝트를 다시 한 번 진행하게 된다면 그땐 정말 제대로 알고 사용해보고 싶다 :)
공식 홈페이지 https://www.electronjs.org/docs/api/app#appgetpathname
안녕하세요, vue project를 electron 을 통해 데스크탑앱으로 바꿀려고 노력중인 학생인데요, 실례가 안된다면 질문 할 수 있을까요??
제 파일 구조가 다음과 같은데,개발 모드에서는 public의 model.json들을 접근할 수 있는데, build된 모드에서는 public의 model.json들을 접근이 제대로 안되더라구요. 혹시 어떻게 해야 접근 가능한지 알려주실 수 있을까요??? 어케 해야 될지 모르겠습니다... 관련된 책도 너무 오래된 것밖에 없는 것 같구요...
파일 구조 :
Lopporter
.
.
.
ㄴpublic
ㄴㄴcnn
ㄴㄴㄴmodel.json
ㄴㄴyolo1
ㄴㄴㄴmodel.json
ㄴㄴyolo2
ㄴㄴㄴmodel.json
ㄴsrc
ㄴㄴcomponent
ㄴㄴㄴBossPredictor.vue
ㄴApp.vue
ㄴbackground.js
ㄴmain.js
.
.
.
코드 :
async loadModels() {
const isDevelopment = process.env.NODE_ENV !== "production";
if (isDevelopment) {
this.basePath = `http://localhost:8080/`; // Adjust if your dev server runs on a different port
} else {
this.basePath = `file://${modelsPath}/`;
}
try {
// Assuming models are stored in a folder named 'models' within your resources in production
yolo1Model = await tf.loadGraphModel(
`${this.basePath}models/yolo14/model.json`
);
console.log("YOLO model 1 loaded successfully.");
yolo2Model = await tf.loadGraphModel(
`${this.basePath}models/yolo23/model.json`
);
console.log("YOLO model 2 loaded successfully.");
resnetModel = await tf.loadLayersModel(
`${this.basePath}models/cnn/my-model.json`
);
console.log("ResNet model loaded successfully.");
this.modelReady = true;
} catch (error) {
console.error("Error loading models:", error);
}
},
와 이번 Nextron에 대해서 정리해주신 것을 보고 정말 많이 배울 수 있었습니다.
특히~ Next.js의 CSR, SSR 설명해주신 내용도 그렇구,
IPC 통신에 대해서 정말 이해할 수 밖에 없게 말씀해주셔서 너무너무 많이 도움이 됐습니다!ㅎㅎ
감사해용~~