으로
데스크탑 어플리케이션
만들어본 이야기
Electron?
Electron은
Electron을 선택한 이유
- 적은 비용으로 멀티 플랫폼을 지원하는 앱 개발 가능
- macOS, Windows, Linux
- 웹 기술로 개발 - HTML, CSS, JavaScript
- 크로스 브라우징 지원을 위한 코딩 불필요
- 활성화된 커뮤니티
- 문서화가 잘 되어 있음
최신 기술 사용 가능
-
비교적 최근 버전의 Chromium을 탑재
- Chromium 업데이트에 빠르게 대응
- Chromium 58.0.3029 (@Electron 1.7.5)
- ES6 커버리지: 97%
- ES2016+ 커버리지: 81%
- Node 7.9.0
- V8 5.8.283.38
커버리지 자료: https://kangax.github.io/compat-table/es6/
지원 플랫폼
- macOS 10.9+
- Windows 7+ (x86 / x64)
- Linux (x86 / x64)
- Ubuntu 12.04+
- Fedora 21+
- Debian 8
Electron으로 만들어진 앱들
More apps in https://electron.atom.io/apps/
간보기
Quickstart
- Electron API 데모
- https://github.com/electron/electron-api-demos
$ git clone https://github.com/electron/electron-api-demos
$ cd electron-api-demos
$ npm install
$ npm start
REPL
$ electron --interactive
> const electron = require('electron');
> const win = new electron.BrowserWindow({width: 800, height: 600});
> win.loadURL('https://m.naver.com');
Windows에서는 사용 불가 :-(
프로세스 구조
Renderer
Main
패키징
수동 빌드
- 모든 소스 및 리소스를 ASAR로 묶기
- 묶은 app.asar 파일을 resources 디렉토리로 이동
- 앱 아이콘과 정보를 수정
- macOS - Info.plist 파일 수정
- Windows - electron.exe 를 rcedit로 수정
=> 실행 가능한 Electron 앱 생성
electron-builder
- 크로스 플랫폼 빌드 툴
- 빌드 과정에서 코드사인 자동 처리 (macOS, Windows)
- 자동 업데이트 시 필요한 메타데이터 생성
- 다양한 패키지 형태 지원
- dmg, pkg, mas, deb, rpm, apk, nsis, portable, Squirrel.Windows, AppX
macOS 코드사인
- 애플 개발자 센터에서 인증서를 받아 키체인에 추가
- 인증서에 코드사인 권한 추가
- 인증서명을 CSC_NAME 환경변수로 등록
- 등록된 환경변수를 바탕으로 electron-builder가 코드사인을 자동 수행
Windows 코드사인
- 임의의 CA로부터 코드사인용 인증서 구매
- 인증서 관리자에 추가
- signtools 로 실행파일과 DLL에 서명
- signtools는 Visual Studio와 Windows SDK에 포함
EV 인증서
- EV 인증서 미사용 시 설치 과정에서 SmartScreen Filter의 경고 표시됨
- 경고를 해제하려면 충분한 평판 점수가 쌓여야 함
- 매 새로운 버전마다 평판 점수가 초기화됨
소스코드 보호
zeit/pkg (former EncloseJS)
- V8 내부 컴파일러를 이용해 JavaScript 코드를 바이너리 스냅샷으로 변환
- 단독 실행 가능한 바이너리를 얻을 수 있음
- 소스코드가 노출되지 않음
PROJECT_ROOT
├─node_modules
└─src
├─client
├─electron
└─server
PROJECT_ROOT
├─node_modules
└─output
├─client
├─electron
└─server.exe
단점(?)
- 외부 파일 경로 접근법이 달라짐
- error stack trace에 라인 정보가 제거되어 추적에 어려움 발생
Auto Update
내장 autoUpdater의 제약
- 업데이트 체크만으로도 다운로드가 자동 개시됨
- 업데이트 다운로드 진척을 알 수 없음
- 중간에 취소할 수 없음
업데이트 중개 모듈 추가
- 다운로드 진척 이벤트 제공
- 다운로드된 파일의 체크섬 검증 기능 추가
- 내부에 HTTP서버를 띄워 autoUpdater에게 업데이트 파일 제공
1. 업데이트 체크
2. 다운로드
3. 내장 업데이트 모듈에 전달
4. 설치 및 재시작
electron-updater
- 단계적 공개 기능 제공
- 다운로드 진척 이벤트 제공
- electron-builder와 잘 통합되어 있음
Platform limitations and workarounds
Menu is read only
- 메뉴의 항목을 동적으로 추가하거나 레이블을 변경할 수 없음
- 변경하려면 메뉴 전체를 다시 빌드해서 교체해야 함
Clipboard
- 지원 포맷: Text, HTML, Image
- 제약
- 커스텀 클립보드 포맷을 지원하지 않음
- 텍스트로 클립보드에 넣으면 다른 어플리케이션에 이상한 문자열이 붙여지게 됨
- 대책
- 가상 클립보드 + HTML 타입 + 메타태그에 클립보드 ID
- 어플리케이션 종료 시 가상 클립보드를 디스크에 저장
Clipboard
- 1.7.5 부터 clipboard 모듈이 Buffer 타입을 지원
- 이젠 꼼수 쓸 필요 없음
Proxy
- NodeJS는 시스템 proxy를 자동으로 사용하지 않음
- Electron은 독자적인 net 모듈을 제공
- Chromium의 내장 네트워크 라이브러리
- 시스템 proxy설정을 가져옴
- 프록시 인증 (basic, digest, NTLM, Kerberos..) 을 지원
- 트래픽 모니터링 프록시 지원 (like Fiddler)
ProtoPie 아키텍처 설계와 고려사항
원하는 건: 프로세스 구분이 없는 듯한 API
- 모든 코드가 한 프로세스 (renderer)에 있는 듯한 코드 스타일
- API 사용 시 항상 프로세스를 떠올리지 않았으면
undoButton.addEventListener('click', e => {
protopie.history.undo();
});
protopie.history.on('undostackchange', (undoCount, redoCount) => {
if (undoCount === 0) {
undoButton.disabled = true;
}
});
제약
- 모든 로직은 서버에 둬야 함
- 클라이언트 코드는 EncloseJS로 패키징할 수 없으므로
- 소스코드는 보호할 수 있지만 대신 복잡성이 증가
Transparent server API
- Add ipc tunnel between client and server
- Client can send event to server
Virtual API
- server는 실제 API로부터 메타데이터를 추출
- decorator, decorator-metadata 기반
- client는 메타데이터로부터 server API를 재구축
- client에 대한 API 공개 범위를 조절할 수 있음
export class CommandHistory {
@CallbackApi
public undo() {
}
@ListenerApi
public on(eventType, callback) {
}
}
const commandHistory = new CommandHistory();
virtualApi.addService('history', commandHistory);
const server = new VirtualApiClient(tunnelClient);
undoButton.addEventListener("click", () => {
server.history.undo();
});
Command History
- 모든 모델 변경은 완전히 되돌리거나 재실행할 수 있어야 함
- Undo / Redo / Copy / Paste
- 모델을 변경하기 위한 별도의 Command 생성
- 모델 변경은 Command를 통해서만 할 수 있도록 수정 API 노출 차단
- 멱등성이 중요함
- Undo시 모든 실행 순서는 반대 순서로 되돌리는 게 좋음
- Layer tree는 가장 위에서부터 수정하고, 가장 아래서부터 undo 해야 멱등성을 유지하기 편리함
Command History
- 모든 모델 변경은 항상 이벤트로 클라이언트에 전달됨
- 클라이언트는 최대한 Passive View가 되도록 구성
- 상태를 갖지 않아야 함
export class Layer {
@Watch
x = 0;
@Watch
y = 0;
}
server.command("layer_edit", layerId, {x: 10, y:20});
server.layers.on('change', (layer, prop, newValue, oldValue) => {
this.element.style.left = layer.x + 'px';
this.element.style.top = layer.y + 'px';
});
export class CommandHistory {
@CallbackApi
undo() { ... }
@CallbackApi
redo() { ... }
@ListenerApi
on(eventType, callback) { ... }
}
this.apiServer.addService('history', new CommandHistory(context));
server.history.on('undostackchange', (undoCount, redoCount) => {
...
});
server.history.undo();
Context
- 다중 윈도우 어플리케이션은 최소 2종류의 context를 가짐
- 어플리케이션 전체 Context
- e.g., DeviceConnectionManager, LicenseManager
- 윈도우별 Context(ProtoPie에서는 Authoring Context라 부름)
- e.g., ModelRoot, SelectionManager, CommandHistory
- 어플리케이션 전체 Context
Writing Desktop application with Electron
By gloridea
Writing Desktop application with Electron
- 1,192