Vue + Electron — Vue 3 데스크톱 앱 만들기
"Vue + Electron도 생산성 높은 조합" — Vue의 간결한 문법과 Electron의 네이티브 접근이 만나면 빠르게 데스크톱 앱을 만들 수 있습니다.
프로젝트 설정
electron-vite + Vue 3
npm create @quick-start/electron my-vue-app -- --template vue-ts
cd my-vue-app
npm install
npm run dev
프로젝트 구조
my-vue-app/
├── src/
│ ├── main/
│ │ └── index.ts # 메인 프로세스
│ ├── preload/
│ │ └── index.ts # 프리로드 스크립트
│ └── renderer/
│ ├── src/
│ │ ├── App.vue
│ │ ├── main.ts
│ │ └── components/
│ └── index.html
├── electron.vite.config.ts
└── package.json
Composable로 Electron API 래핑
// renderer/src/composables/useElectron.ts
import { ref, onMounted, onUnmounted } from 'vue';
// 파일 다이얼로그 Composable
export function useFileDialog() {
const openFile = async () => {
return await window.electronAPI.openFile();
};
const saveFile = async (content: string) => {
return await window.electronAPI.saveFile(content);
};
return { openFile, saveFile };
}
// 앱 버전 Composable
export function useAppVersion() {
const version = ref('');
onMounted(async () => {
version.value = await window.electronAPI.getVersion();
});
return { version };
}
// 메인 프로세스 이벤트 수신 Composable
export function useMainEvent(eventName: string, handler: (...args: any[]) => void) {
let cleanup: (() => void) | null = null;
onMounted(() => {
cleanup = window.electronAPI.on(eventName, handler);
});
onUnmounted(() => {
cleanup?.();
});
}
Vue 컴포넌트에서 사용
<!-- renderer/src/components/Editor.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import { useFileDialog, useMainEvent } from '../composables/useElectron';
const content = ref('');
const filePath = ref<string | null>(null);
const { openFile, saveFile } = useFileDialog();
const handleOpen = async () => {
const path = await openFile();
if (path) {
filePath.value = path;
}
};
const handleSave = async () => {
if (content.value) {
await saveFile(content.value);
}
};
// 메뉴에서 저장 단축키를 누르면 호출
useMainEvent('menu:save', () => handleSave());
</script>
<template>
<div class="editor">
<div class="toolbar">
<button @click="handleOpen">열기</button>
<button @click="handleSave">저장</button>
<span v-if="filePath">{{ filePath }}</span>
</div>
<textarea v-model="content" />
</div>
</template>
Pinia 상태 관리와 Electron
// renderer/src/stores/app.ts
import { defineStore } from 'pinia';
export const useAppStore = defineStore('app', {
state: () => ({
theme: 'light' as 'light' | 'dark',
recentFiles: [] as string[],
}),
actions: {
async loadSettings() {
const settings = await window.electronAPI.getSettings();
this.theme = settings.theme;
this.recentFiles = settings.recentFiles;
},
async setTheme(theme: 'light' | 'dark') {
this.theme = theme;
await window.electronAPI.setSetting('theme', theme);
},
addRecentFile(path: string) {
this.recentFiles = [
path,
...this.recentFiles.filter(f => f !== path),
].slice(0, 10);
window.electronAPI.setSetting('recentFiles', this.recentFiles);
},
},
});
Vue Router 설정
// renderer/src/router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router';
// Electron에서는 Hash 모드 사용
const router = createRouter({
history: createWebHashHistory(),
routes: [
{ path: '/', component: () => import('../views/Home.vue') },
{ path: '/settings', component: () => import('../views/Settings.vue') },
{ path: '/editor', component: () => import('../views/Editor.vue') },
],
});
export default router;
면접 포인트 정리
- Vue 3 Composition API의 Composable로 Electron API를 래핑하면 재사용성이 높아짐
onUnmounted에서 이벤트 리스너를 반드시 정리- Vue Router는
createWebHashHistory()사용 (Electron은 file:// 프로토콜) - Pinia 스토어에서 Electron 설정을 동기화하는 패턴이 실용적
Vue 통합을 다뤘으면, 다음은 Svelte + Electron 조합을 알아봅시다.
댓글 로딩 중...