Setup
Prerequisites
튜토리얼을 토대로 개발 환경을 설정하였습니다.
Electron을 사용하려면 Node.js를 설치해야 합니다. 사용 가능한 최신 LTS 버전을 사용하는 것이 좋습니다.
플랫폼에 대해 사전 구축된 설치 프로그램을 사용하여 Node.js를 설치하십시오. 그렇지 않으면 다른 개발 도구에서 비호환성 문제가 발생할 수 있습니다.
Node.js가 올바르게 설치되었는지 확인하려면 터미널 클라이언트에 다음 명령을 입력합니다.
$node -v
$npm -v
참고: Electronic은 Node.js를 이진 파일에 포함하므로 코드를 실행하는 Node.js 버전은 시스템에서 실행 중인 버전과 관련이 없습니다.
Create application
Scaffold the project
폴더를 만들고 npm 패키지를 초기화하는 것부터 시작합시다.
$ mkdir Todo && cd Todo
$ npm init
진입점은 main.js여야 하며 author와 description은 아무 값이나 될 수 있으나, app packaging을 위해서는 반드시 필요하기 때문에 입력해줍니다.
//package.json
{
"name": "todo",
"version": "1.0.0",
"description": "Todo App with electron",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/balhyo-younjisang/TodoApp.git"
},
"author": "balhyo-younjisang",
"license": "MIT",
"bugs": {
"url": "https://github.com/balhyo-younjisang/TodoApp/issues"
},
"homepage": "https://github.com/balhyo-younjisang/TodoApp#readme"
}
위 단계를 마친 후 electron 패키지를 앱의 devDependencies에 설치합니다.
$ npm install --save-dev electron
마지막으로 Electron을 실행하기 위해 package.json config의 scripts 필드에 start 명령을 추가합니다.
{
"scripts": {
"start": "electron ."
}
}
main script를 초기화하기 위해 프로젝트의 루트 폴더에 main.js라는 빈 자바스크립트 파일을 생성합니다.
Create a web page
프로젝트의 루트 폴더에 index.html 파일을 만듭니다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
We are using Node.js <span id="node-version"></span>,
Chromium <span id="chrome-version"></span>,
and Electron <span id="electron-version"></span>.
</body>
</html>
Opening web page in a browser window
이제 웹페이지가 생겼으니 응용 프로그램 창에 로드합시다. 로드를 위해선 두 개의 Electron 모듈이 필요합니다.
- app module: 응용 프로그램의 이벤트 수명 주기를 제어하는 응용 프로그램 모듈
- BrowserWindow module : 응용 프로그램 창을 만들고 관리하는 모듈
기본 프로세스가 Node.js를 실행하므로 Node.js를 파일 상단에서 CommonJs 모듈로 가져옵니다.
const { app, BrowserWindow } = require('electron')
그런 다음 index.html을 로드하는 createWindow()함수를 새 BrowserWindow 인스턴스에 추가합니다.
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600
})
win.loadFile('index.html')
}
다음으로 createWindow() 함수를 호출하여 창을 엽니다.
Electron에서는 app 모듈의 ready 이벤트가 발생한 후에만 브라우저 창을 만들 수 있습니다. 이 이벤트는 app.whenReady() API를 사용하여 기다리면 됩니다. whenReady()가 Promise를 resolve 시킨 후 createWindow()를 호출합니다.
app.whenReady().then(() => {
createWindow()
})
Manage window's lifecycle
응용 프로그램 창은 OS마다 다르게 동작하며 Electron은 이러한 규칙을 구현하는 책임을 개발자에게 전가합니다.
Quit the app when all windows are closed (Windows & Linux)
윈도와 리눅스에서 모든 창을 종료하면 응용 프로그램이 완전히 종료됩니다.
이를 구현하려면 app 모듈의 window-all-closed 이벤트를 listen 하고 사용자가 macOS에 있지 않은 경우 app.quit()를 호출하여야 합니다.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
Open a window if none are open (macOS)
리눅스와 윈도 앱이 창을 열지 않으면 종료되는 반면, 일반적으로 macOS 앱은 창을 열지 않아도 계속 실행되며 사용할 수 있는 창이 없을 때 앱을 활성화하면 새로운 창이 열립니다.
이 기능을 구현하려면 app 모듈의 activate 이벤트를 listen 하고 브라우저 창이 열려 있지 않으면 기존 createWindow() 메서드를 호출하여야 합니다.
ready 이벤트 전에는 창을 만들 수 없으므로 초기화된 후에만 activate 이벤트를 listen 해야 합니다. 기존 whenReady() 콜백 내에서 이벤트 listener를 연결하여 이 작업을 수행합니다.
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
Dark Mode Implementation
본격적인 기능을 구현하기 전에 공식 문서의 힘을 빌려 nativeTheme을 이용한 다크 모드를 구현해봅시다.
https://www.electronjs.org/docs/latest/tutorial/dark-mode
Dark Mode | Electron
"Native interfaces" include the file picker, window border, dialogs, context menus, and more - anything where the UI comes from your operating system and not from your app. The default behavior is to opt into this automatic theming from the OS.
www.electronjs.org
<!--index.html-->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<link rel="stylesheet" type="text/css" href="./style.css">
<link rel="stylesheet" href="./fontawesome-free-5.15.4-web/fontawesome-free-5.15.4-web/css/fontawesome.min.css">
<title>ToDo</title>
</head>
<body>
<p>Current theme source: <strong id="theme-source">System</strong></p>
<button id="toggle-dark-mode">Toggle Dark Mode</button>
<!-- You can also require other files to run in this process -->
<script src="renderer.js"></script>
</body>
</html>
/*style.css*/
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
Arial, sans-serif;
margin: 0px;
}
@media (prefers-color-scheme: dark) {
body {
background: #333;
color: white;
}
}
@media (prefers-color-scheme: light) {
body {
background: #ddd;
color: black;
}
}
button 요소는 컨트롤러이며 css 파일은 prefers-color-scheme 미디어 쿼리를 이용해 body 요소의 배경색을 설정해줍시다.
// preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('darkMode', {
toggle: () => ipcRenderer.invoke('dark-mode:toggle'),
system: () => ipcRenderer.invoke('dark-mode:system')
})
preload.js 스크립트는 darkMode라는 window 객체에 새 API를 추가합니다. 이 API는 darkMode:toggle라는 IPC 채널을 renderer 프로세스에 노출합니다. 또 toggle이라는 메서드를 할당해 rendere에서 main 프로세스로 메시지를 전달합니다.
//renderer.js
document.getElementById('toggle-dark-mode').addEventListener('click', async () => {
const isDarkMode = await window.darkMode.toggle()
document.getElementById('theme-source').innerHTML = isDarkMode ? 'Dark' : 'Light'
})
document.getElementById('reset-to-system').addEventListener('click', async () => {
await window.darkMode.system()
document.getElementById('theme-source').innerHTML = 'System'
})
renderer.js 파일은 button 기능을 제어합니다. addEventListener를 사용하여 버튼에 click 이벤트 리스너를 추가해주고, 이벤트 리스너 핸들러는 window.darkMode.toggle API 메서드를 호출합니다.
//main.js
const { app, BrowserWindow, ipcMain, nativeTheme } = require('electron')
const path = require('path')
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
// and load the index.html of the app.
mainWindow.loadFile('index.html')
// Open the DevTools.
// mainWindow.webContents.openDevTools()
ipcMain.handle('dark-mode:toggle', () => {
if (nativeTheme.shouldUseDarkColors) {
nativeTheme.themeSource = 'light'
} else {
nativeTheme.themeSource = 'dark'
}
return nativeTheme.shouldUseDarkColors
})
ipcMain.handle('dark-mode:system', () => {
nativeTheme.themeSource = 'system'
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
ipcMain.handle 메서드를 이용해 main 프로세스가 버튼의 클릭 이벤트에 대응할 수 있습니다.
dark-mode:toggle IPC 채널 핸들러 메서드는 shouldUseDarkColors의 속성을 확인하고 해당되는 themeSource를 설정해줍니다.
'Programming Language > electron' 카테고리의 다른 글
[Electron] require is not defined 해결 방법 (0) | 2022.12.13 |
---|---|
[Electron] Electron을 사용하여 Destktop Todo App 만들기( 2 ) - Preferences, Todo CRUD (0) | 2022.12.12 |