본문으로 바로가기

728x90

지난 포스트에서는 자바에서 TCP Socket Forwarding 하는 글을 올린 적이 있습니다. 되도록이면 GUI를 통하여 멋지게 만들면 좋겠지만 자바에서 GUI를 구현하는 일은 생각보다 쉽지는 않습니다. 웹 개발자 측면에서 운영체제 애플리케이션을 제작하는 좋은 방법은 JavaFX 또는 Electron.js 일 것입니다. JavaFX의 경우 오라클에서 지원을 중단하여 Electron.js의 GUI를 통하여 새로운 애플리케이션을 만들어보면 좋겠다고 생각하여 본 연재를 시작합니다.

JavaFX를 예전에 시도한 적이 있었는데 웹에서 사용하는 대부분의 스타일을 지원하였습니다. 하지만, JDK 1.8 이후로는 지원이 끊겨 지속적으로 JavaFX를 사용하여 개발하는 것이 개발자 경력관리 측면에서는 별로 도움이 되지 않는다고 판단하여 학습을 중단하였습니다. 그리고, 국내에서는 JavaFX관련 프로젝트(적어도 대규모로..)를 진행한다고 들어본 사례도 없었던 듯합니다.
 

TCP Socket Forwarding(Tunneling) by Java Socket

네트워크 방화벽 등으로 인해 외부로 접속이 불가능한 상황이 있을 수 있습니다. 아래의 예제는 접속이 상호 가능한 장치를 통하여 TCP연결을 포워딩하는 예시입니다. 아래의 코드는 특별하게 어려운 부분은 없어..

parandol.tistory.com

 

TCP Socket Router 예제를 통한 Electron Application 작성방법에 대한 연재가 완료되었습니다.

 

Electron + Vue.js 애플리케이션 만들기

1. Electron 프로젝트 생성하기(Create Electron Application Project)

2. Electron IPC 통신(Electron Architecture, IPC Main/IPC Renderer)

3. TCP Router 기능 구현(Implements TCP Router Communication)

4. 화면 UI 구성(Main Window UI)

5. 환경설정 구현하기(Preferences Window)

6. 메뉴 사용하기(Application Menu & Context Menu)

7. 시스템 트레이 사용 및 창 최소화(System Tray & Minimize to Tray)

8. Bootstrap Vue와 Font Awesome 사용하기(Using Bootstrap Vue & Font Awesome)

9. Dark Layout과 Frameless Window(Dark Layout & Frameless Window)

10. 빌드 및 배포, 자동 업데이트(Build, Auto Updater)

 

 

본 포스트에서는 기본 Electron 애플리케이션을 생성하는 방법에 대해서 설명하겠습니다.

 

웹 개발자의 관점에서 윈도우나 맥킨토시에서 애플리케이션을 만드는 방법은 어렵다고 생각하고 있었습니다. 그런데 Node.js를 개발하면서 MS VSCode를 접하게 되었습니다. VSCode는 간단하면서도 가벼웠습니다. 일반 애플리케이션보다 UI가 웹에 가깝다고 보였습니다. 찾아본 결과 놀랍게도 웹을 기반으로 Vue.js로 작성된 애플리케이션이더 군요. 그래서 웹개발자도 애플리케이션을 만들 수 있다는 동기 부여 및 공부하겠다고 생각하였습니다.

예전에 MFC를 접한 적이 있었는데 일반적인 구조가 아닌 스킨을 만들려면 BMP를 잘라서 위치마다 붙여 넣는 다소 복잡한 작업을 진행해야 했던 기억이 남아 있습니다. 이제 웹을 기반을 상당히 쉽게 애플리케이션 개발에 접근할 수 있습니다.

 

Microsoft VS Code

 

Electron 애플리케이션 생성하기

프로젝트 생성

아래의 명령을 수행하여 신규 프로젝트를 생성합니다. vue 및 electron을 개별적으로 설정하는 일은 생각보다 복잡합니다. 몇 번을 시도하여 보았으나 제가 원하는 구조로 나오지 않아 SimulatedGREG/electron-vue 에서 이미 만들어진 프로젝트를 다운로드하여 사용하였습니다.

 

아래 명령을 수행하여 프로젝트를 생성합니다.

# Install vue-cli and scaffold boilerplate
npm install -g vue-cli
vue init simulatedgreg/electron-vue my-project

# Install dependencies and run your app
cd my-project
yarn # or npm install
yarn run dev # or npm run dev

실제 프로젝트는 아래와 같이 생성하였습니다. ESLint, unit testing 도구와 end-to-end 테스팅 도구는 설치하지 않았습니다. 이 부분에 대해서는 별도로 글을 올리도록 하겠습니다.

``` bash
PS D:\PERSONAL\ElectronJs> npm install -g vue-cli
+ vue-cli@2.9.6
added 2 packages from 1 contributor, removed 1 package and updated 36 packages in 8.307s

PS D:\PERSONAL\ElectronJs> vue init simulatedgreg/electron-vue kr.ejsoft.socket.router

? Application Name kr.ejsoft.socket.router
? Application Id kr.ejsoft.socket.router
? Application Version 0.0.1
? Project description Socket Tunneling Application
? Use Sass / Scss? Yes
? Select which Vue plugins to install (Press <space> to select, <a> to toggle all, <i> to invert selection)axios, vue-el
ectron, vue-router, vuex, vuex-electron
? Use linting with ESLint? No
? Set up unit testing with Karma + Mocha? No
? Set up end-to-end testing with Spectron + Mocha? No
? What build tool would you like to use? builder

   vue-cli · Generated "kr.ejsoft.socket.router".
warning Failed to append commit SHA on README.md

PS D:\PERSONAL\ElectronJs>

 

애플리케이션 컴파일 및 실행

만들어진 디렉토리로 이동한 후 아래의 명령을 수행합니다.

``` bash
# install dependencies
npm install

# serve with hot reload at localhost:9080
npm run dev

기본 프로젝트 실행화면

 

애플리케이션 수정하기

타이틀 변경하기

/src/index.ejs 의 title을 원하는 애플리케이션 이름으로 변경합니다.

애플리케이션 제목 수정하기

변경한 애플리케이션 이름은 아래와 같이 확인할 수 있습니다.

 

아이콘 변경하기

아이콘을 사전에 준비합니다. 아이콘은 윈도우의 경우에는 .ico 확장자 파일을 맥킨토시나 리눅스의 경우에는 png 이미지 파일을 사용합니다. 아이콘 준비하는 과정은 제 블로그의 다음 포스트를 참고하십시오.

무료로 아이콘 쉽게 생성하기/변환하기

 

무료로 아이콘 쉽게 생성하기/변환하기

아이콘 쉽게 생성하기/변환하기 이미지를 생성하는 작업은 디자이너가 아니라면 만드는 일이 쉽지 않습니다. 저작권을 준수한다면 이미지를 다운로드하여 사용하는 것도 개인적으로 나쁘지 않을 것입니다. 이번 포..

parandol.tistory.com

 

.ico 파일과 png 이미지 파일을 /src/static/에 apps.icon, apps.png 파일로 복사합니다. (이미지 이름은 임의로 변경해도 됩니다. 단, 아래에 작성하는 코드에서 파일명을 정확하게 지정해야 합니다.)

 

아이콘을 읽어오는 코드 작성하기

/src/main에 shared/common-utils.js 파일을 생성하고 아래와 같이 편집합니다.

import { nativeImage } from "electron";

const path = require("path");

class CommonUtil {
    static icon(size) {
        let iconname = "apps.png";
        // iconname = process.platform === "darwin" ? "icon.icns" : iconname;
        iconname = process.platform === "win32" ? "apps.ico" : iconname;
        const iconPath = path.join(__dirname, "..", "..", "..", "static", iconname);

        if(process.platform === "darwin") {
            let icon = nativeImage.createFromPath(iconPath);
            if(size && size > 0) {
                icon = icon.resize({ width: size, height: size });
            }
            return icon;
        }
        return iconPath;
    }
}

export default CommonUtil;

 

아이콘 적용하기

/src/index.js 파일 수정하기

BrowserWindow를 생성하는 옵션에 icon 속성을 추가합니다.

import { app, BrowserWindow } from 'electron'
import CommonUtils from "./shared/common-utils";		// <-- 공통 유틸을 불러옵니다.

......(중략)......

function createWindow () {
  /**
   * Initial window options
   */
  const appicon = CommonUtils.icon(64);		// <-- 아이콘 파일을 불러옵니다.
  mainWindow = new BrowserWindow({
    height: 563,
    useContentSize: true,
    width: 1000,
    icon: appicon,				// <-- 여기에 아이콘 파일을 지정합니다.
  })

  mainWindow.loadURL(winURL)

  mainWindow.on('closed', () => {
    mainWindow = null
  })
}

app.on('ready', createWindow)

......(중략)......

 

실행 확인하기

다이얼로그의 왼쪽 상단에 지정한 아이콘으로 변경되었음을 확인할 수 있습니다.

아이콘 변경 후

 

메인 다이얼로그 코드 모듈화하기

/src/main/index.js 파일의 createWindow() 함수 내용을 잘라내어 main-window.js 파일을 아래와 같이 생성합니다. 윈도우를 생성하는 코드를 분리하는 이유는 창이 많이지게 되면 나중에는 코드들이 복잡하게 뒤엉켜 관리가 힘들어집니다.

 

/src/main/window/main-window.js

import { BrowserWindow } from 'electron'
import CommonUtils from "../shared/common-utils";

const winURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080`
: `file://${__dirname}/index.html`

class MainWindow {
  constructor () {
    /**
     * Initial window options
     */
    const appicon = CommonUtils.icon(64);
    let window = new BrowserWindow({
      height: 563,
      useContentSize: true,
      width: 1000,
      icon: appicon,
    })
  
    window.loadURL(winURL)
    // window.on('closed', () => {
    //   window = null
    // })

    this.mainWindow = window;
  }

  get() {
    return this.mainWindow;
  }

  static create () {
    return new MainWindow().get();
  }
}

export default MainWindow;

/src/main/index.js 파일에 init() 함수를 생성하고 기존 createWindow() 함수를 호출하던 부분에 init()함수로 변경합니다.

import { app } from 'electron'
import MainWindow from './window/main-window'

......(중간생략)......

/**
 * Set `__static` path to static files in production
 * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
 */
if (process.env.NODE_ENV !== 'development') {
  global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
}

let mainWindow = null;
function init() {
  mainWindow = MainWindow.create();		// <-- 윈도우 생성하기
  mainWindow.on('closed', () => {		// <-- 창닫기 이벤트 처리
    mainWindow = null
  })
}

app.on('ready', init)			// <-- createWindow -> init으로 수정

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  if (mainWindow === null) {
    init()			// <-- createWindow -> init으로 수정
  }
})

......(이하생략)......

 

Electron 디렉토리 구조 살펴보기

SimulatedGREG/electron-vue 를 통해서 생성된 디렉토리 구조는 아래와 같습니다. 처음에 참조하는 라이브러리에 따라 상이할 수 있습니다.

.electron-vue : Elecron 을 실행하기 위한 라이브러리 실행파일

build : 제품을 생성하기 위한 관련 파일 경로

dist : 제품 빌드 후에 결과물이 생성

node_modules : node.js 참조 라이브러리가 저장되는 경로

src : 프로젝트 소스

└ main : 일반 어플리케이션의 기능 수행 소스코드(Back-end)

    index.js : 애플리케이션을 구동하기 위한 실행 코드(프로그램 시작)

 renderer : Vue의 화면을 그려주기 위한 소스코드(Front-end)

    assets : Vue의 자원

    components : 공통으로 사용하는 템플릿 파일

    router : Vue의 페이지 정보를 정의

    store : Vue 애플리케이션의 상태를 저장하기 위한 설정

    App.vue : Vue.js의 기본 구조 템플릿파일

    main.js : Vue.js를 구동하는 메인 코드

 index.ejs : 프로그램의 기본적인 구조를 정의하는 파일

static : 정적 컨텐츠가 들어가는 경로

package.json : 프로젝트 정보 및 라이브러리가 저장되는 경로

 

Vue의 실행관련 코드들은 /src/rederer 에 위치합니다. 디렉토리의 의미는 기존 Vue 프로젝트들과 동일합니다.

운영체제의 자원(예, 디렉토리 및 파일 열기 등)에 접근하는 코드들은 /src/main에 위치합니다. Vue에서는 구동할 수 없습니다. 실제 Vue의 동작은 웹 브라우저(Chromium)를 에뮬레이팅하여 구동되기때문에 보안상의 이유로 운영체제의 자원에 접근할 수 없습니다.

Electron.js 애플리케이션의 동작은 /src/renderer의 코드와 /src/main의 코드간의 IPC 통신을 통하여 이루어집니다. 다음 시간에는 Vue.js와 Electron.js간 통신에 대해서 설명할 예정입니다.

 

참고사이트

 

소스코드

본 포스트 관련 소스코드는 여기에서 다운로드 가능합니다.

 

 

 

 

728x90