Electron์ผ๋ก ์ฑ์ ๊ฐ๋ฐํ๊ณ ์์์ต๋๋ค.
๊ธฐ๋ฅ๋ ์ ๋ฒ ์ ๋์๊ฐ๊ณ , UI๋ ๋ค๋ฌ์ด์ง๊ณ , "์ด์ ์ข ์์ฑ ๋๋ ๋๋ค" ์ถ์ ๊ทธ ํ์ด๋ฐ.
๊ทธ๋ฐ๋ฐ์.
๋๊ตฐ๊ฐ ์์ฃผ ๊ฐ๋ณ๊ฒ,
์ ๋ง ์๋ฌด๋ ์ง๋ ์๊ฒ ๋งํ๋๊ตฐ์.
"์ค์นํ์ผ ์ฃผ์ธ์~ ๐"
...
๋จธ๋ฆฟ์์ผ๋ก ์ด๋ ๊ฒ ์ธ์ณค์ต๋๋ค:
โ๋โฆ ๋๋ฆฐ๋ค๊ณ ํ๋๋ฐโฆ ๊ทธ๊ฑธ ๋ด๊ฐ ๋ง๋ ์ ์ด ์์๋โฆ?โ
React, Vite, Electron ์กฐํฉ์ผ๋ก ๋ฐ์คํฌํ ์ฑ์ ๊ฐ๋ฐํ๋ ์ ๋
๊ธฐ๋ฅ ๊ตฌํ, UI ๊ตฌ์ฑ, ์ํ ๊ด๋ฆฌ๊น์ง ์์ ์์์ง๋ง
"์ค์น ํ์ผ ๋ง๋ค๊ธฐ",
๊ทธ๊ฑด ๋ญ๊ฐ ๊ฑด๋ค๋ฉด ์ ๋ ๊ฒ ๊ฐ์ ๊ธ๋จ์ ์์ญ์ฒ๋ผ ๋๊ปด์ก์ต๋๋ค.
์ ์ง ์ง์ ํ๋ฉด,
...๊ทธ๋ฌ์ต๋๋ค.
๊ทธ.๋ฐ.๋ฐ.
๋ง์ ํด๋ณด๋?
์ธ์ ๊ฐ๋จํฉ๋๋ค.
์ด์ ์ ๋ ๋งํฉ๋๋ค:
โํ ์คํธ์? ์ค์นํ์ผ ์์ผ๋ ์คํ๋ง ํด๋ณด์ธ์~ ๐โ
์ ๊ฐ ํด๊ฒฐํ ๋ชฉํ๋ ์๋์ ๊ฐ์์ต๋๋ค:
.exe
์ค์น ํ์ผ๋ก ํจํค์งํญ๋ชฉ | ๋ด์ฉ |
---|---|
ํ๋ก ํธ์๋ | React + Vite |
๋ฐ์คํฌํ ํตํฉ | Electron + vite-plugin-electron |
์ํ๊ด๋ฆฌ | Zustand |
๋น๋ ๋๊ตฌ | electron-builder |
์๋ ์ ๋ฐ์ดํธ | electron-updater , electron-log |
๋ฐฐํฌ ์๋ํ | Node.js ์คํฌ๋ฆฝํธ + scp |
๋ฐฐํฌ ์๋ฒ | ์ฌ๋ด๋ง HTTP ์๋ฒ (ex. http://192.168.x.x:8000/updates/ ) |
project-root/
โโโ src/ # React ์ฑ ์ฝ๋
โโโ electron/
โ โโโ main.ts # Electron ์ง์
์
โ โโโ preload.ts # Preload ์คํฌ๋ฆฝํธ
โโโ dist/ # Vite ๋น๋ ๊ฒฐ๊ณผ๋ฌผ
โโโ dist-electron/ # Electron ๋น๋ ๊ฒฐ๊ณผ๋ฌผ
โโโ release/ # ์ค์นํ์ผ ๋ฐ ์๋์
๋ฐ์ดํธ ํ์ผ
โโโ scripts/deploy.js # ๋ฐฐํฌ ์๋ํ ์คํฌ๋ฆฝํธ
โโโ vite.config.ts
Electron์์ ์์ฃผ ๊ฒช๋ ๋ฌธ์ ์ค ํ๋๊ฐ ์ด๊ฒ๋๋ค:
๋น๋๋ ์ ๋๊ณ .exe๋ ์ ์คํ๋๋๋ฐโฆ
์ฐฝ์ด ๋จ๊ธด ๋ด๋๋ฐ ์๋ฌด๊ฒ๋ ์ ๋์ด ๐ฑ
Electron์ ๋ธ๋ผ์ฐ์ ์ฒ๋ผ public ๊ฒฝ๋ก๊ฐ ์์ต๋๋ค.
์ ํํ๊ฒ ๊ฒฝ๋ก๋ฅผ ์ก์์ฃผ์ง ์์ผ๋ฉด ๋ก๋ฉ ์คํจ + ํฐ ํ๋ฉด์ด ๋ฐ์ํฉ๋๋ค.
// main.ts
const startUrl = process.env.NODE_ENV === 'development'
? 'http://localhost:3000'
: url.format({
pathname: path.join(__dirname, '../../dist/index.html'),
protocol: 'file:',
slashes: true
});
// vite.config.ts
export default defineConfig({
base: './', // ์ด๊ฑฐ ์ ๋ฃ์ผ๋ฉด JS, CSS ๋ชป ๋ถ๋ฌ์ด
})
์๋ ์
๋ฐ์ดํธ๋ฅผ ์ํด electron-updater
์ electron-log
๋ง ์ค์นํ๋ฉด ๋ฉ๋๋ค:
npm install electron-updater electron-log
main.ts
)
import { autoUpdater } from 'electron-updater';
import log from 'electron-log';
log.transports.file.level = 'info';
autoUpdater.logger = log;
autoUpdater.checkForUpdatesAndNotify();
autoUpdater.on('update-downloaded', () => {
autoUpdater.quitAndInstall(); // ์ฑ ๊บผ์ง๊ณ ์ฌ์์๋จ
});
package.json
์ค์
"build": {
"publish": {
"provider": "generic",
"url": "http://192.168.0.100:8000/updates/" // ๋ด๋ถ ์๋ฒ ์ฃผ์
}
}
์๋์ผ๋ก latest.yml
์ ๊ธฐ์ค์ผ๋ก ์ ๋ฒ์ ์ด ์์ผ๋ฉด ๋ค์ด๋ก๋ + ์ฌ์ค์น๊น์ง ํด์ค๋๋ค.
๋น๋ ์๋ฃ ํ release/
ํด๋์๋ ๋ค์ ํ์ผ์ด ์๊น๋๋ค:
release/
โโโ MyApp Setup 1.0.0.exe โ ์ค์น์ฉ
โโโ MyApp-1.0.0.exe โ ์๋ ์
๋ฐ์ดํธ์ฉ
โโโ latest.yml โ ์
๋ฐ์ดํธ ๋ฉํ ์ ๋ณด
์ด ์ธ ํ์ผ์ ์๋ฒ์ ์ฌ๋ ค๋๋ฉด ์๋ ์ ๋ฐ์ดํธ๊ฐ ๋ฉ๋๋ค.
์๋์ผ๋ก ์๋ฒ์ ์ฎ๊ธฐ์ง ์๊ณ , ๋ฐฐํฌ๋ฅผ ์๋ํํ์ต๋๋ค.
scripts/deploy.js
const { execSync } = require('child_process');
const files = ['latest.yml', 'MyApp Setup 1.0.0.exe', 'MyApp-1.0.0.exe'];
const server = 'user@192.168.x.x';
const remote = '/var/www/updates/';
files.forEach(file => {
console.log(`[๋ฐฐํฌ ์ค] ${file}`);
execSync(`scp release/"${file}" ${server}:${remote}`);
});
npm run electron:build
node scripts/deploy.js
๋น๋ํ๊ณ ๋ฐฐํฌ๊น์ง 30์ด ์์ ๋๋ฉ๋๋ค.
์ค์นํ์ผ ์์ฒญ ๋ค์ด์ค๋ฉด ์ด์ โ์ ๊น๋ง์, ๋ฐ๋ก ์ฌ๋ฆด๊ฒ์โ ํ๋ฉด ๋ฉ๋๋ค.
Electron ์ฑ์ด ์๋ ์ ๋ฐ์ดํธ๋ฅผ ํ ์ ์๋ ๊ฑด,
electron-updater
์ electron-builder
๊ฐ ์์ฃผ ์ ์ง์ฌ์ง ๊ท์น์ ๊ณต์ ํ๊ณ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
์ด ์์คํ ์ ์๋์ฒ๋ผ ์๋ํฉ๋๋ค:
autoUpdater.checkForUpdatesAndNotify()
๋ฅผ ํธ์ถํ๋ฉดpublish.url
)์ ์๋ latest.yml
ํ์ผ์ ๋ค์ด๋ก๋ํฉ๋๋ค.latest.yml
์๋ ํ์ฌ ๋ฐฐํฌ ์ค์ธ ์ต์ ๋ฒ์ ์ ๋ณด๊ฐ ๋ค์ด ์์ต๋๋ค.
version: 1.0.1
path: MyApp Setup 1.0.1.exe
sha512: <๋ฌด๊ฒฐ์ฑ ๊ฒ์ฌ์ฉ ํด์>
์ฑ์ package.json
์ ๋ช
์๋ ํ์ฌ ๋ฒ์ ๊ณผ ๋น๊ตํด์,
์ต์ ๋ฒ์ ์ด ๋ ๋์ผ๋ฉด path
์ ๋ช
์๋ .exe
๋ฅผ ๋ค์ด๋ก๋ํ๊ณ
์ฌ์ฉ์๊ฐ ์ฑ์ ์ข
๋ฃํ๋ฉด autoUpdater.quitAndInstall()
์ ํตํด
์ ์ค์นํ์ผ์ด ์๋์ผ๋ก ์คํ๋๊ณ ๊ธฐ์กด ์ฑ์ ๊ต์ฒด๋ฉ๋๋ค.
electron-builder
๊ฐ ๋น๋ํ ๋ ์๋์ผ๋ก latest.yml
๊ณผ ๋ฒ์ ๋ณ .exe
๋ฅผ ์์ฑํด์ค๋๋ค.electron-updater
๋ ์ด latest.yml
์ ๊ตฌ์กฐ๋ฅผ ์ ํํ ์ดํดํ๊ณ ๋ค์ด๋ก๋ + ์ค์น๊น์ง ์ฒ๋ฆฌํฉ๋๋ค.์ฆ, ์ฐ๋ฆฌ๊ฐ ์๋ํํ ๊ฑด ๋ฐฐํฌ๊ณ , Electron์ด ์๋ํํด์ค ๊ฑด ์ ๋ฐ์ดํธ ๋ก์ง์ ๋๋ค.
๊ตฌ์ฑ ์์ | ์ญํ |
---|---|
latest.yml | ์ต์ ๋ฒ์ ๋ฉํ์ ๋ณด ์ ๊ณต |
electron-updater | ๋ฒ์ ๋น๊ต + ๋ค์ด๋ก๋ + ์ค์น |
electron-builder | .exe , latest.yml ์๋ ์์ฑ |
๋ด ๋ฐฐํฌ ์คํฌ๋ฆฝํธ | ์๋ฒ ์
๋ก๋ ์๋ํ (scp ) |
์ฌ์ฉ์๋ | ๊ทธ๋ฅ ์คํ๋ง ํ๋ฉด ์์์ ์ต์ ์ผ๋ก ์ ๋ฐ์ดํธ๋จ |
appId
๋ ๊ณ ์ ๊ฐ์ด์ด์ผ ์๋ ์
๋ฐ์ดํธ๊ฐ ์ ํํ ๋์ํฉ๋๋ค.version
ํ๋๋ ๋ฐ๋์ ์๋์ผ๋ก ์ฌ๋ ค์ผ ํฉ๋๋ค (์๋ ์
๋ฐ์ดํธ ๋น๊ต์ฉ).win.target: 'nsis'
๋ ์ค์น ๋ง๋ฒ์ฌ๋ฅผ ๋ง๋ค์ด์ค๋๋ค.autoUpdater.checkForUpdatesAndNotify()
๋ ์ฑ ์คํํ ๋๋ง๋ค ์
๋ฐ์ดํธ ์ฒดํฌํฉ๋๋ค.electron-log
๋ก ๋จ๊ธฐ๋ฉด /AppData/Local/...
์ ์๋ ์ ์ฅ๋ฉ๋๋ค.๋ค, electron-updater๋ GitHub๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ผ์ง๋ง
provider: "generic"
์ ์ฐ๋ฉด ๋ด๋ถ๋ง ์๋ฒ๋ ๋ก์ปฌ ์๋ฒ์์๋ ์๋ฒฝํ ์๋ํฉ๋๋ค.์ฌ์ง์ด ์ฌ์ค IP๋ ์ฌ๋ด ํ์ผ์๋ฒ๋ OK์ ๋๋ค.
Setup.exe
๋ 1.0.0.exe
์ฐจ์ด๋ ๋ญ์์?โSetup.exe: ์ฌ์ฉ์๊ฐ ์ฒ์ ์ค์นํ ๋ ์ฐ๋ ์ ์ฒด ์ค์นํ์ผMyApp-1.0.0.exe: ์๋ ์ ๋ฐ์ดํธ์ฉ ํจ์น ํ์ผ (silent install์ฉ)
์๋ ์ ๋ฐ์ดํธ๋ ์ด ์์
-๋ฒ์ .exe
๋ง ๋ค์ด๋ก๋ํด์ ์ค์นํฉ๋๋ค.
package.json์ "version": "1.0.1" ๊ฐ์ ์ฌ์ฉํฉ๋๋ค.
์๋ ์ ๋ฐ์ดํธ๋
latest.yml
์ ๋ฒ์ ๊ณผ ๋น๊ตํด์ํ์ฌ ์ฑ๋ณด๋ค ๋์ ๋ฒ์ ์ด๋ฉด ์คํ๋ฉ๋๋ค.
๋ฒ์ ์ ์ฌ๋ฆฌ๊ณ ๋ฐฐํฌํ๋ฉด ์ ๋ฐ์ดํธ ์ ๋ฉ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก Windows์
C:\Users\{username}\AppData\Local\Programs\MyApp\
๊ฒฝ๋ก์ ์ค์น๋ฉ๋๋ค.
electron-builder
์์installDirectory
, ์์ด์ฝ, ์คํ ๊ถํ ๋ฑ๋ ์ค์ ๊ฐ๋ฅํฉ๋๋ค.
build ์ค์ ์ .ico ํ์ผ์ ์ง์ ํ๋ฉด ๋ฉ๋๋ค.
"build": {
"win": {
"icon": "public/icon.ico"
}
๋ค! ๋ค์ ์ต์ ์ ์ถ๊ฐํ๋ฉด ๋ฉ๋๋ค:
"win": {
"requestedExecutionLevel": "requireAdministrator"
}
๊ทธ๋ฌ๋ฉด ์ค์นํ ๋ UAC ๊ฒฝ๊ณ ์ฐฝ์ด ๋จ๋ฉฐ ๊ด๋ฆฌ์ ๊ถํ์ผ๋ก ์คํ๋ฉ๋๋ค.
๋ค, ์ด๋ฐ ๊ฒฝ์ฐ์๋ ๋์ํ์ง ์์ต๋๋ค:
latest.yml
์ด ์๋ฒ์ ์๊ฑฐ๋ URL ์ค์ ์ด ํ๋ฆผversion
์ ์ด์ ๋ฒ์ ์ผ๋ก ๋ค์ ๋ฎ์ท์ ๊ฒฝ์ฐ (๋ค์ด๊ทธ๋ ์ด๋ ๋ฏธ์ง์)- ์ฑ์ UAC ๊ถํ ์์ด ์ค์นํ๋๋ฐ, ์ ๋ฐ์ดํธ ํ์ผ์ด ๊ด๋ฆฌ์ ๊ถํ์ ์๊ตฌํจ
- ๋คํธ์ํฌ์์ ์ ๋ฐ์ดํธ ์๋ฒ ์ ๊ทผ์ด ๋ถ๊ฐ๋ฅํ ๊ฒฝ์ฐ
React + Electron ์ฑ์ ์ค์นํ์ผ๋ก ๋ฐฐํฌํ๋ ค๋ฉด ๋ญ๊ฐ ๋ณต์กํ๊ณ ๋ฌด์์ธ ๊ฒ ๊ฐ์์ต๋๋ค.
ํ์ง๋ง ํด๋ณด๋ ๊ฝค ์ฌ๋ฐ๊ณ , ์๊ทผํ ๋ฟ๋ฏํ๊ณ ,
๋ฌด์๋ณด๋ค โ๋ด๊ฐ ๋ง๋ ์ฑ์ด ์ง์ง๋ก ๊น๋ฆฐ๋คโ๋ ๊ฒฝํ์ ๊ต์ฅํ ์ง๋ฆฟํฉ๋๋ค.
์น ๊ฐ๋ฐ์๋ ์ค์นํ ์ฑ ๋ง๋ค ์ ์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ๊ทธ ์ฑ์ด ์์์ ์ ๋ฐ์ดํธ๋ ํฉ๋๋ค.
๊ทธ๊ฑธ ์๋ฒ์ ์๋์ผ๋ก ๋ฐฐํฌํ๋ ์คํฌ๋ฆฝํธ๊น์ง ๊ตด๋ฌ๊ฐ๋๋ค.
์ด ๊ธ์ ์ฌ๊ธฐ๊น์ง ์ฝ์ผ์ ๋น์ ๋, ์ง๊ธ ๋ฐ๋ก npm run electron:build
์ ํด๋ณด์ธ์.