Skip to main content

Electron Internals: Weak References

· 11 min read

가비지 컬렉션을 지원하는 언어인 자바스크립트는 사용자가 직접 리소스를 관리할 필요가 없다. 하지만 Electron은 이러한 환경을 호스팅하기 때문에 메모리와 리소스 누수를 방지하는 데 각별히 주의해야 한다.

이 글에서는 약한 참조(weak reference)의 개념과 Electron에서 리소스를 관리하기 위해 어떻게 활용하는지 소개한다.

약한 참조(Weak References)

자바스크립트에서 객체를 변수에 할당하면 해당 객체에 대한 참조가 추가된다. 객체에 대한 참조가 남아 있는 한, 객체는 메모리에 계속 유지된다. 객체에 대한 모든 참조가 사라지면, 즉 객체를 저장하는 변수가 더 이상 없으면, 자바스크립트 엔진은 다음 가비지 컬렉션에서 해당 메모리를 회수한다.

약한 참조(Weak Reference)는 객체를 가져올 수 있도록 하면서도, 가비지 컬렉션 여부에 영향을 미치지 않는 참조 방식이다. 또한 객체가 가비지 컬렉션될 때 알림을 받을 수 있어, 자바스크립트로 리소스를 관리하는 것이 가능해진다.

Electron의 NativeImage 클래스를 예로 들면, nativeImage.create() API를 호출할 때마다 NativeImage 인스턴스가 반환되고, 이 인스턴스는 C++에서 이미지 데이터를 저장한다. 인스턴스를 더 이상 사용하지 않고 자바스크립트 엔진(V8)이 객체를 가비지 컬렉션하면, C++ 코드가 호출되어 메모리의 이미지 데이터를 해제한다. 따라서 사용자가 이를 수동으로 관리할 필요가 없다.

또 다른 예로는 윈도우 사라짐 문제가 있다. 이 예제는 윈도우에 대한 모든 참조가 사라졌을 때 윈도우가 어떻게 가비지 컬렉션되는지를 시각적으로 보여준다.

Electron에서 약한 참조 테스트하기

자바스크립트는 약한 참조를 직접 할당할 수 있는 방법을 제공하지 않기 때문에, 순수 자바스크립트에서는 약한 참조를 직접 테스트할 방법이 없다. 자바스크립트에서 약한 참조와 관련된 유일한 API는 WeakMap이지만, 이 API는 약한 참조 키만 생성하기 때문에 객체가 가비지 컬렉션(Garbage Collection)되었는지 알 수 없다.

Electron v0.37.8 이전 버전에서는 내부 v8Util.setDestructor API를 사용해 약한 참조를 테스트할 수 있다. 이 API는 전달된 객체에 약한 참조를 추가하고, 객체가 가비지 컬렉션되면 콜백을 호출한다:

// 아래 코드는 Electron < v0.37.8 버전에서만 실행 가능.
var v8Util = process.atomBinding('v8_util');

var object = {};
v8Util.setDestructor(object, function () {
console.log('The object is garbage collected');
});

// 객체에 대한 모든 참조를 제거.
object = undefined;
// 수동으로 GC를 실행.
gc();
// 콘솔에 "The object is garbage collected" 출력.

이 API를 사용하려면 --js-flags="--expose_gc" 커맨드 스위치와 함께 Electron을 실행해 내부 gc 함수를 노출시켜야 한다.

이 API는 이후 버전에서 제거되었다. V8은 실제로 소멸자에서 자바스크립트 코드를 실행하는 것을 허용하지 않으며, 이후 버전에서 이를 사용하면 무작위로 크래시가 발생할 수 있기 때문이다.

remote 모듈에서의 약한 참조

Electron은 C++로 네이티브 리소스를 관리할 뿐만 아니라, 자바스크립트 리소스를 관리하기 위해 약한 참조(weak reference)도 필요로 한다. 대표적인 예가 Electron의 remote 모듈이다. 이 모듈은 원격 프로시저 호출(Remote Procedure Call, RPC)을 가능하게 하여, 렌더러 프로세스에서 메인 프로세스의 객체를 사용할 수 있도록 한다.

remote 모듈에서 가장 큰 도전은 메모리 누수를 피하는 것이다. 렌더러 프로세스에서 사용자가 원격 객체를 획득하면, remote 모듈은 렌더러 프로세스에서의 참조가 사라질 때까지 메인 프로세스에서 해당 객체가 계속 살아있도록 보장해야 한다. 동시에, 렌더러 프로세스에서 더 이상 참조가 없을 때 객체가 가비지 컬렉션될 수 있도록 해야 한다.

예를 들어, 적절한 구현이 없다면 다음 코드는 빠르게 메모리 누수를 일으킬 것이다:

const { remote } = require('electron');

for (let i = 0; i < 10000; ++i) {
remote.nativeImage.createEmpty();
}

remote 모듈의 리소스 관리 방식은 간단하다. 객체가 요청될 때마다 메인 프로세스로 메시지를 보내고, Electron은 해당 객체를 맵에 저장한 후 ID를 할당하고, 이 ID를 렌더러 프로세스로 보낸다. 렌더러 프로세스에서는 remote 모듈이 이 ID를 받아 프록시 객체로 감싸고, 프록시 객체가 가비지 컬렉션되면 메인 프로세스로 객체를 해제하라는 메시지를 보낸다.

remote.require API를 예로 들면, 간략화된 구현은 다음과 같다:

remote.require = function (name) {
// 메인 프로세스에 모듈의 메타데이터를 요청한다.
const meta = ipcRenderer.sendSync('REQUIRE', name);
// 프록시 객체를 생성한다.
const object = metaToValue(meta);
// 프록시 객체가 가비지 컬렉션될 때 메인 프로세스에 객체를 해제하라고 알린다.
v8Util.setDestructor(object, function () {
ipcRenderer.send('FREE', meta.id);
});
return object;
};

메인 프로세스에서는 다음과 같이 처리한다:

const map = {};
const id = 0;

ipcMain.on('REQUIRE', function (event, name) {
const object = require(name);
// 객체에 대한 참조를 추가한다.
map[++id] = object;
// 객체를 메타데이터로 변환한다.
event.returnValue = valueToMeta(id, object);
});

ipcMain.on('FREE', function (event, id) {
delete map[id];
});

약한 값을 가진 맵

이전의 간단한 구현에서는 remote 모듈의 모든 호출이 메인 프로세스에서 새로운 원격 객체를 반환한다. 각 원격 객체는 메인 프로세스에 있는 객체에 대한 참조를 나타낸다.

이 디자인 자체는 문제가 없지만, 동일한 객체를 여러 번 받기 위해 여러 번 호출할 경우 여러 프록시 객체가 생성된다. 복잡한 객체의 경우 이는 메모리 사용량과 가비지 컬렉션에 큰 부담을 줄 수 있다.

예를 들어, 다음 코드를 보자:

const { remote } = require('electron');

for (let i = 0; i < 10000; ++i) {
remote.getCurrentWindow();
}

이 코드는 먼저 프록시 객체를 생성하는 데 많은 메모리를 사용하고, 그 후 가비지 컬렉션과 IPC 메시지 전송을 위해 CPU를 점유한다.

명백한 최적화 방법은 원격 객체를 캐싱하는 것이다: 동일한 ID를 가진 원격 객체가 이미 있다면, 새로운 객체를 생성하는 대신 이전 객체를 반환한다.

이는 JavaScript 코어의 API로는 불가능하다. 일반적인 맵을 사용해 객체를 캐싱하면 V8이 객체를 가비지 컬렉션하는 것을 막는다. 반면에 WeakMap 클래스는 객체를 약한 키로만 사용할 수 있다.

이 문제를 해결하기 위해, 값이 약한 참조인 맵 타입이 추가되었다. 이는 ID를 가진 객체를 캐싱하는 데 완벽하다. 이제 remote.require는 다음과 같이 동작한다:

const remoteObjectCache = v8Util.createIDWeakMap()

remote.require = function (name) {
// 메인 프로세스에 모듈의 메타 데이터를 반환하라고 요청한다.
...
if (remoteObjectCache.has(meta.id))
return remoteObjectCache.get(meta.id)
// 프록시 객체를 생성한다.
...
remoteObjectCache.set(meta.id, object)
return object
}

remoteObjectCache는 객체를 약한 참조로 저장하기 때문에, 객체가 가비지 컬렉션될 때 키를 삭제할 필요가 없다.

네이티브 코드

Electron에서 약한 참조(weak references)의 C++ 코드에 관심이 있다면 다음 파일에서 확인할 수 있다.

setDestructor API 관련 파일:

createIDWeakMap API 관련 파일:

August 2016: New Apps

· 4 min read

8월에 사이트에 추가된 새로운 Electron 앱 목록입니다.


이 사이트는 커뮤니티의 풀 리퀘스트를 통해 새로운 미트업 정보로 업데이트됩니다. 저장소를 주시하면 새로운 추가 사항에 대한 알림을 받을 수 있습니다. 사이트의 모든 변경 사항에 관심이 없다면 블로그 RSS 피드를 구독해도 됩니다.

여러분이 Electron 앱을 만들었거나 미트업을 주최한다면, 풀 리퀘스트를 통해 사이트에 추가할 수 있습니다. 그러면 다음 업데이트에 반영될 것입니다.

새로운 앱들

Code RPGifyRPG 스타일 코딩 애플리케이션
PamFax팩스 송수신을 위한 크로스 플랫폼 앱
BlankUp명확성을 강조한 마크다운 에디터
Rambox여러 웹 애플리케이션을 하나로 통합한 무료 오픈소스 메시징 및 이메일 앱
Gordie카드 컬렉션을 관리하기 위한 최적의 앱
Ionic Creator더 빠르게 멋진 모바일 앱을 만들기
TwitchAlerts아름다운 알림으로 시청자를 만족시키기
Museeks간단하고 깔끔한 크로스 플랫폼 음악 플레이어
SeaPig마크다운을 HTML로 변환하는 도구
GroupMe비공식 GroupMe 앱
Moeditor모든 용도에 적합한 마크다운 에디터
Soundnode데스크톱용 Soundcloud 앱
QMUI WebQMUI Web 프레임워크 기반 프로젝트 관리 애플리케이션
SvgsusSVG 파일을 정리, 정돈 및 변환하기
Ramme비공식 Instagram 데스크톱 앱
InsomniaREST API 클라이언트
CorreoWindows, macOS, Linux용 메뉴바/태스크바 Gmail 앱
KongDashKong Admin API용 데스크톱 클라이언트
Translation EditorINTL ICU 메시지용 번역 파일 에디터 (formatjsio 참조)
5EClient5EPlay CSGO 클라이언트
Theme Juice로컬 WordPress 개발을 쉽게 만들어주는 도구

Accessibility Tools

· 3 min read

접근성 있는 애플리케이션을 만드는 것은 중요하다. 우리는 DevtronSpectron에 새로운 기능을 추가해 개발자들이 더 나은 애플리케이션을 만들 수 있도록 지원하게 되어 기쁘게 생각한다.


Electron 애플리케이션의 접근성 문제는 웹사이트와 유사하다. 둘 다 궁극적으로 HTML을 기반으로 하기 때문이다. 그러나 Electron 앱은 URL이 없어 접근성 감사 도구를 사용할 수 없다는 점에서 차이가 있다.

이 새로운 기능들은 이러한 감사 도구를 Electron 앱에 제공한다. Spectron을 통해 테스트에 접근성 감사를 추가하거나 Devtron을 통해 DevTools 내에서 사용할 수 있다. 이 도구들의 요약을 확인하거나 더 자세한 정보를 원한다면 접근성 문서를 참고하라.

Spectron

테스트 프레임워크인 Spectron에서 이제 애플리케이션의 각 윈도우와 <webview> 태그에 대한 접근성 검사를 수행할 수 있다. 예를 들어:

app.client.auditAccessibility().then(function (audit) {
if (audit.failed) {
console.error(audit.message);
}
});

이 기능에 대한 자세한 내용은 Spectron의 문서에서 확인할 수 있다.

Devtron에는 새로운 접근성 탭이 추가되었다. 이 탭을 사용하면 앱 내 페이지를 감사하고, 결과를 정렬하거나 필터링할 수 있다.

devtron screenshot

이 도구들은 모두 Google이 Chrome용으로 개발한 Accessibility Developer Tools 라이브러리를 사용한다. 이 라이브러리가 사용하는 접근성 감사 규칙에 대해 더 알고 싶다면 저장소의 위키를 참고하면 된다.

Electron용으로 유용한 다른 접근성 도구를 알고 있다면, 접근성 문서에 풀 리퀘스트를 통해 추가할 수 있다.

npm install electron

· 4 min read

Electron 버전 1.3.1부터는 npm install electron --save-dev 명령어를 사용해 앱에 최신 사전 컴파일된 Electron 버전을 설치할 수 있다.


npm install electron

사전 빌드된 Electron 바이너리

Electron 앱을 개발해 본 적이 있다면, electron-prebuilt npm 패키지를 접한 적이 있을 것이다. 이 패키지는 거의 모든 Electron 프로젝트에서 필수적인 부분이다. 설치하면 사용 중인 운영체제를 자동으로 감지하고, 해당 시스템 아키텍처에 맞게 컴파일된 사전 빌드된 바이너리를 다운로드한다.

새로운 이름

Electron 설치 과정은 종종 신규 개발자들에게 걸림돌이 되곤 했다. 많은 사람들이 npm install electron-prebuilt 대신 npm install electron을 실행하면서 Electron 앱 개발을 시작하려고 시도했지만, 원하는 electron이 아니라는 사실을 깨닫기까지 상당한 혼란을 겪어야 했다.

이러한 문제는 GitHub의 Electron 프로젝트가 생기기 전에 이미 npm에 존재하던 electron 프로젝트 때문이었다. 신규 개발자들이 보다 쉽고 직관적으로 Electron 개발을 시작할 수 있도록, 우리는 기존 electron npm 패키지의 소유자에게 이름 사용 권한을 요청했다. 다행히 그는 우리 프로젝트의 팬이었고, 이름을 재사용할 수 있도록 협조해 주었다.

Prebuilt의 지속

버전 1.3.1부터 electronelectron-prebuilt 패키지를 동시에 npm에 배포하기 시작했다. 두 패키지는 동일하다. 현재 프로젝트에서 electron-prebuilt를 사용하고 있는 수천 명의 개발자에게 불편을 주지 않기 위해, 당분간 두 이름으로 패키지를 계속 배포하기로 결정했다. package.json 파일을 업데이트해 새로운 electron 의존성을 사용할 것을 권장하지만, 2016년 말까지는 electron-prebuilt의 새 버전도 계속 배포할 예정이다.

electron-userland/electron-prebuilt 저장소는 electron npm 패키지의 공식적인 홈으로 남을 것이다.

감사의 말

우리는 @mafintosh, @maxogden, 그리고 많은 기여자들에게 특별한 감사를 표한다. 그들은 electron-prebuilt를 만들고 유지보수하며, JavaScript, Node.js, Electron 커뮤니티를 위해 끊임없이 노력해 왔다.

또한 @logicalparadox에게도 감사한다. 그 덕분에 npm에서 electron 패키지를 이어받을 수 있었다.

프로젝트 업데이트

이번 변경 사항에 영향을 받는 인기 패키지들을 커뮤니티와 함께 업데이트했다. electron-packager, electron-rebuild, electron-builder와 같은 패키지들은 이미 새로운 이름을 지원하도록 업데이트되었으며, 기존 이름도 계속 사용할 수 있다.

새로운 패키지를 설치하는 과정에서 문제가 발생하면 electron-userland/electron-prebuilt 저장소에 이슈를 열어 알려주기 바란다.

Electron과 관련된 기타 문제는 electron/electron 저장소를 이용해 주시기 바란다.

Electron Internals: Using Node as a Library

· 9 min read

이 글은 Electron의 내부 구조를 설명하는 시리즈의 두 번째 포스트다. 아직 보지 않았다면 이벤트 루프 통합에 관한 [첫 번째 포스트][event-loop]를 먼저 확인해 보자.

대부분의 사람들은 Node를 서버 측 애플리케이션에 사용한다. 하지만 Node의 풍부한 API와 활발한 커뮤니티 덕분에, Node는 내장 라이브러리로도 적합하다. 이 글에서는 Electron에서 Node가 어떻게 라이브러리로 사용되는지 설명한다.


빌드 시스템

Node와 Electron 모두 빌드 시스템으로 GYP를 사용한다. 앱 내부에 Node를 포함시키려면, 여러분도 GYP를 빌드 시스템으로 사용해야 한다.

GYP가 처음이라면, 이 글을 계속 읽기 전에 [이 가이드][gyp-docs]를 먼저 읽어보는 것이 좋다.

Node의 빌드 플래그

Node 소스 코드 디렉토리에 있는 node.gyp 파일은 Node가 어떻게 빌드되는지 설명한다. 이 파일에는 Node의 어떤 부분을 활성화할지, 특정 설정을 열어둘지 여부를 제어하는 여러 GYP 변수들이 포함되어 있다.

빌드 플래그를 변경하려면 프로젝트의 .gypi 파일에서 변수를 설정해야 한다. Node의 configure 스크립트는 일반적인 설정을 자동으로 생성해 준다. 예를 들어, ./configure --shared를 실행하면 Node를 공유 라이브러리로 빌드하도록 지시하는 변수들이 포함된 config.gypi 파일이 생성된다.

Electron은 자체 빌드 스크립트를 사용하기 때문에 configure 스크립트를 사용하지 않는다. 대신, Electron의 루트 소스 코드 디렉토리에 있는 common.gypi 파일에서 Node의 설정을 정의한다.

Electron에서 Node.js 연결하기

Electron에서는 GYP 변수인 node_sharedtrue로 설정하여 Node.js를 공유 라이브러리로 연결한다. 이로 인해 Node.js의 빌드 타입은 실행 파일(executable)에서 공유 라이브러리(shared_library)로 변경되며, Node.js의 main 진입점을 포함한 소스 코드는 컴파일되지 않는다.

Electron은 Chromium과 함께 제공되는 V8 라이브러리를 사용하기 때문에, Node.js 소스 코드에 포함된 V8 라이브러리는 사용되지 않는다. 이를 위해 node_use_v8_platformnode_use_bundled_v8 변수를 모두 false로 설정한다.

공유 라이브러리와 정적 라이브러리

Node와 링크할 때는 두 가지 옵션이 있다. Node를 정적 라이브러리로 빌드해 최종 실행 파일에 포함시키거나, 공유 라이브러리로 빌드해 최종 실행 파일과 함께 배포하는 방법이다.

Electron에서는 오랫동안 Node를 정적 라이브러리로 빌드했다. 이 방식은 빌드 과정을 단순화하고 최적의 컴파일러 최적화를 가능하게 하며, 추가적인 node.dll 파일 없이 Electron을 배포할 수 있게 했다.

하지만 Chrome이 [BoringSSL][boringssl]을 사용하도록 변경된 후 이 상황이 바뀌었다. BoringSSL은 [OpenSSL][openssl]의 포크 버전으로, 사용되지 않는 여러 API를 제거하고 기존 인터페이스를 많이 변경했다. Node는 여전히 OpenSSL을 사용하기 때문에, 두 라이브러리를 함께 링크하면 충돌하는 심볼로 인해 컴파일러가 수많은 링크 오류를 발생시켰다.

Electron은 Node에서 BoringSSL을 사용하거나 Chromium에서 OpenSSL을 사용할 수 없었기 때문에, 유일한 선택지는 Node를 공유 라이브러리로 빌드하고 각 컴포넌트에서 [BoringSSL과 OpenSSL 심볼을 숨기는][openssl-hide] 것이었다.

이 변경은 Electron에 몇 가지 긍정적인 부작용을 가져왔다. 이전에는 네이티브 모듈을 사용하는 경우 Windows에서 Electron 실행 파일의 이름을 변경할 수 없었다. 실행 파일 이름이 임포트 라이브러리에 하드 코딩되어 있었기 때문이다. Node가 공유 라이브러리로 빌드된 후에는 모든 네이티브 모듈이 node.dll에 링크되었고, 이 파일의 이름을 변경할 필요가 없어졌기 때문에 이러한 제약이 사라졌다.

네이티브 모듈 지원하기

Node의 네이티브 모듈은 Node가 로드할 수 있는 진입 함수를 정의한 다음, Node에서 V8과 libuv의 심볼을 검색하는 방식으로 동작한다. 하지만 이 방식은 임베더에게 다소 번거로운 문제를 일으킬 수 있다. 기본적으로 Node를 라이브러리로 빌드할 때 V8과 libuv의 심볼이 숨겨져 있기 때문에, 네이티브 모듈이 심볼을 찾지 못해 로드에 실패할 수 있다.

따라서 네이티브 모듈이 정상적으로 동작하도록 하기 위해, Electron에서는 V8과 libuv의 심볼을 노출시켰다. V8의 경우 Chromium의 설정 파일에서 모든 심볼을 강제로 노출하는 방식을 사용했다. libuv의 경우에는 BUILDING_UV_SHARED=1 정의를 설정하여 이를 달성했다.

앱에서 Node 시작하기

Node를 빌드하고 연결하는 모든 작업을 마친 후, 마지막 단계는 앱에서 Node를 실행하는 것이다.

Node는 다른 앱에 자신을 포함시키기 위한 공개 API를 많이 제공하지 않는다. 일반적으로 [node::Startnode::Init][node-start]을 호출해 Node의 새 인스턴스를 시작할 수 있다. 하지만 Node를 기반으로 복잡한 앱을 구축하는 경우, node::CreateEnvironment와 같은 API를 사용해 각 단계를 정밀하게 제어해야 한다.

Electron에서는 Node가 두 가지 모드로 시작된다. 하나는 메인 프로세스에서 실행되는 독립 실행 모드로, 공식 Node 바이너리와 유사하다. 다른 하나는 Node API를 웹 페이지에 삽입하는 임베디드 모드이다. 이에 대한 자세한 내용은 추후 포스트에서 설명할 예정이다.

2016년 7월: 새로운 앱과 밋업

· 3 min read

Electron 커뮤니티의 활동을 강조하기 위해 매달 라운드업을 시작한다. 각 라운드업에는 새로운 앱, 다가오는 밋업, 도구, 비디오 등이 포함될 예정이다.


이 사이트는 커뮤니티의 풀 리퀘스트를 통해 새로운 밋업 정보로 업데이트된다. 리포지토리를 주시하면 새로운 추가 사항에 대한 알림을 받을 수 있다. 사이트의 모든 변경 사항에 관심이 없다면 블로그 RSS 피드를 구독하면 된다.

Electron 앱을 만들었거나 밋업을 주최한다면, 풀 리퀘스트를 통해 사이트에 추가하고 다음 라운드업에 포함시킬 수 있다.

새로운 앱

Demio인바운드 영업과 마케팅을 위해 설계된 웨비나 플랫폼
ElectorrentuTorrent 서버를 위한 원격 클라이언트 앱
PhoneGap웹 기술을 사용해 멋진 모바일 앱을 만들 수 있는 오픈 소스 프레임워크
WordMark마크다운 작가를 위한 가벼운 블로그 출판 편집기
UbAuthOAuth 2.0을 사용해 Uber 애플리케이션의 액세스 토큰을 생성하는 도구
HyperTermHTML/JS/CSS 기반의 터미널
Marp마크다운 프레젠테이션 작성 도구
Glyphr Studio취미용 폰트 디자인에 초점을 맞춘 무료 웹 기반 폰트 디자이너
BitCryptWindows용 간단한 파일 암호화 애플리케이션
TrymSVG 아이콘을 보고 최적화하고 변환할 수 있는 macOS용 아름다운 작은 앱
Booker마크다운의 힘을 가진 텍스트 편집기
PhonePresenter스마트한 프레젠테이션 리모컨
Yout데스크톱에서 YouTube 플레이리스트를 보는 새로운 방법

새로운 모임 정보

Electron 오픈소스 데스크톱 프레임워크영국 런던

Electron Internals: Message Loop Integration

· 6 min read

이 글은 Electron의 내부 구조를 설명하는 시리즈의 첫 번째 포스트이다. 이 글에서는 Node의 이벤트 루프가 Chromium과 어떻게 통합되는지 소개한다.


GUI 프로그래밍을 위해 Node를 사용하려는 여러 시도가 있었다. 예를 들어 GTK+ 바인딩을 위한 node-gui와 QT 바인딩을 위한 node-qt가 있다. 하지만 이들은 프로덕션 환경에서 작동하지 않았다. GUI 툴킷은 자체 메시지 루프를 가지고 있는 반면, Node는 libuv를 사용해 자체 이벤트 루프를 돌리기 때문이다. 메인 스레드는 동시에 하나의 루프만 실행할 수 있다. 따라서 Node에서 GUI 메시지 루프를 실행하는 일반적인 방법은 매우 짧은 간격으로 타이머를 사용해 메시지 루프를 돌리는 것이다. 이 방법은 GUI 인터페이스의 반응 속도를 느리게 만들고 CPU 리소스를 많이 차지한다.

Electron을 개발하는 과정에서 우리는 비슷한 문제에 직면했다. 단, 반대 상황이었다: Node의 이벤트 루프를 Chromium의 메시지 루프에 통합해야 했다.

메인 프로세스와 렌더러 프로세스

메시지 루프 통합에 대한 자세한 내용을 살펴보기 전에, 먼저 Chromium의 멀티 프로세스 아키텍처에 대해 설명한다.

Electron에는 두 가지 타입의 프로세스가 있다: 메인 프로세스와 렌더러 프로세스(이 설명은 상당히 단순화된 것이다. 전체적인 그림을 보려면 멀티 프로세스 아키텍처를 참고한다). 메인 프로세스는 윈도우 생성과 같은 GUI 작업을 담당한다. 반면 렌더러 프로세스는 웹 페이지 실행과 렌더링만 처리한다.

Electron은 JavaScript를 사용해 메인 프로세스와 렌더러 프로세스를 모두 제어할 수 있다. 이는 두 프로세스 모두에 Node를 통합해야 함을 의미한다.

Chromium의 메시지 루프를 libuv로 교체하기

첫 번째 시도는 Chromium의 메시지 루프를 libuv로 재구현하는 것이었다.

렌더러 프로세스는 비교적 쉬웠다. 렌더러 프로세스의 메시지 루프는 파일 디스크립터와 타이머만 감시하기 때문에, libuv와의 인터페이스만 구현하면 됐다.

그러나 메인 프로세스는 훨씬 더 어려웠다. 각 플랫폼마다 고유한 GUI 메시지 루프가 존재한다. macOS의 Chromium은 NSRunLoop를 사용하고, Linux는 glib를 사용한다. 나는 네이티브 GUI 메시지 루프에서 기본 파일 디스크립터를 추출해 libuv에 전달하는 다양한 방법을 시도했지만, 여전히 작동하지 않는 예외 상황이 발생했다.

결국 나는 작은 간격으로 GUI 메시지 루프를 폴링하는 타이머를 추가했다. 이로 인해 프로세스가 일정한 CPU 사용량을 유지하게 되었고, 특정 작업에서 긴 지연이 발생했다.

별도의 스레드에서 Node의 이벤트 루프 폴링하기

libuv가 성숙해지면서 새로운 접근 방식이 가능해졌다. libuv는 이벤트 루프를 위해 폴링하는 파일 디스크립터(또는 핸들)인 backend fd 개념을 도입했다. 따라서 backend fd를 폴링하면 libuv에 새로운 이벤트가 발생했을 때 알림을 받을 수 있다.

Electron에서 나는 별도의 스레드를 생성해 backend fd를 폴링하도록 했다. libuv API 대신 시스템 호출을 사용해 폴링했기 때문에 이 방식은 스레드 안전했다. libuv의 이벤트 루프에 새로운 이벤트가 발생할 때마다 Chromium의 메시지 루프에 메시지를 전송했고, libuv의 이벤트는 메인 스레드에서 처리됐다.

이 방식을 통해 Chromium과 Node에 패치를 적용하지 않아도 됐고, 동일한 코드를 메인 프로세스와 렌더러 프로세스 모두에서 사용할 수 있었다.

코드 구현

메시지 루프 통합 구현은 electron/atom/common/ 디렉토리 내 node_bindings 파일에서 확인할 수 있다. Node를 통합하려는 프로젝트에서 쉽게 재사용할 수 있다.

업데이트: 구현이 electron/shell/common/node_bindings.cc로 이동되었다.

Electron Podcasts

· 2 min read

Electron을 소개하는 새로운 콘텐츠가 나왔다. Electron이 무엇인지, 왜 만들어졌는지, 어떻게 사용되는지에 대한 훌륭한 개요를 제공하는 두 개의 새로운 팟캐스트가 최근 공개됐다.


지금 바로 확인해 보세요:

Hanselminutes: 크로스 플랫폼 Electron 앱 만들기

Electron은 단순히 "프레임 안의 Chrome"일까, 아니면 그 이상의 의미가 있을까? Jessica는 Scott에게 올바른 방향을 제시하며, Electron 플랫폼이 개발 세계에서 어떤 위치를 차지하는지 명확히 설명한다.


JavaScript Air: Electron 앱 구축

Electron은 웹 기술을 활용해 멀티플랫폼 데스크톱 앱을 개발하는 점점 더 유용하고 인기 있는 방법이 되고 있다. 이 강력한 기술을 자세히 살펴보고, 이를 통해 우리의 경험과 사용자의 데스크톱 경험을 어떻게 향상시킬 수 있는지 알아보자.


Electron을 처음 접하는 사람이라면 첫 번째 에피소드를 들어보길 권한다. 두 번째 에피소드는 Nylas의 Evan Morikawa가 제공한 유용한 팁을 바탕으로 앱을 구축하는 방법을 더 깊이 다룬다.

다음 달에 출시될 두 개의 추가 팟캐스트를 준비 중이니, 업데이트 소식은 @ElectronJS 트위터 계정을 주시하길 바란다.

Electron 1.0

· 7 min read

지난 2년 동안 Electron은 HTML, CSS, 자바스크립트를 사용해 크로스 플랫폼 데스크톱 애플리케이션을 개발할 수 있게 도와왔다. 이제 우리는 프레임워크와 이를 만들어낸 커뮤니티를 위한 중요한 이정표를 공유하게 되어 기쁘게 생각한다. Electron 1.0이 electronjs.org에서 출시되었다.


Electron 1.0

Electron 1.0은 API의 안정성과 성숙도 측면에서 중요한 이정표를 의미한다. 이번 릴리스를 통해 Windows, Mac, Linux에서 진정한 네이티브 애플리케이션처럼 동작하고 느껴지는 앱을 구축할 수 있다. 새로운 문서, 도구, 그리고 Electron API를 안내하는 새로운 앱 덕분에 Electron 앱을 개발하는 것이 이전보다 훨씬 쉬워졌다.

첫 번째 Electron 앱을 만들 준비가 되었다면, 시작을 도와줄 빠른 시작 가이드를 참고하길 바란다.

여러분이 Electron으로 무엇을 만들어낼지 기대가 된다.

Electron의 여정

우리는 약 2년 전 Atom을 출시하며 Electron을 공개했다. 당시 Atom Shell로 알려졌던 Electron은 Atom을 구축한 프레임워크였다. Atom의 초기 버전을 출시하기 위해 노력하던 시절, Atom은 Electron이 제공하는 기능과 기능성의 주요 동력이었다.

이제 Electron은 이메일, 채팅, Git 앱부터 SQL 분석 도구, 토렌트 클라이언트, 로봇에 이르기까지 다양한 분야의 개발자와 기업들로 구성된 커뮤니티가 이끌고 있다.

지난 2년 동안 우리는 기업과 오픈 소스 프로젝트들이 Electron을 자신들의 앱 기반으로 선택하는 모습을 목격했다. 지난 한 해 동안만 해도 Electron은 120만 번 이상 다운로드되었다. Electron 앱 둘러보기에서 몇 가지 멋진 Electron 앱을 확인하고, 아직 등록되지 않았다면 여러분의 앱을 추가해보자.

Electron 다운로드 수

Electron API 데모

Electron 1.0 릴리스와 함께, Electron API를 탐색하고 Electron 앱을 네이티브처럼 만드는 방법을 배우는 데 도움이 되는 새로운 앱을 공개한다. Electron API 데모 앱은 앱을 시작하는 데 도움이 되는 코드 스니펫과 Electron API를 효과적으로 사용하는 팁을 포함하고 있다.

Electron API 데모

Devtron

Electron 앱을 디버깅하는 데 도움을 주는 새로운 확장 기능을 추가했다. DevtronChrome 개발자 도구의 오픈소스 확장 기능으로, Electron 앱을 검사하고 디버깅하며 문제를 해결하는 데 유용하다.

Devtron

주요 기능

  • 의존성 그래프 시각화: 앱의 내부 및 외부 라이브러리 의존성을 메인 프로세스와 렌더러 프로세스에서 모두 시각적으로 확인할 수 있다.

  • IPC 모니터: 앱 내 프로세스 간에 주고받는 메시지를 추적하고 표시한다.

  • 이벤트 검사기: 윈도우, 앱, 프로세스 등 코어 Electron API에 등록된 이벤트와 리스너를 확인할 수 있다.

  • 앱 린터: 앱에서 흔히 발생하는 실수와 누락된 기능을 점검한다.

Spectron

마지막으로, Electron 앱을 위한 통합 테스트 프레임워크인 Spectron의 새 버전을 출시한다.

Spectron

Spectron 3.0은 Electron API 전체를 포괄적으로 지원한다. 이를 통해 다양한 시나리오와 환경에서 애플리케이션의 동작을 검증하는 테스트를 더 빠르게 작성할 수 있다. Spectron은 ChromeDriverWebDriverIO를 기반으로 하므로, 페이지 네비게이션, 사용자 입력, 자바스크립트 실행을 위한 완전한 API도 제공한다.

커뮤니티

Electron 1.0은 수백 명의 개발자들이 함께 노력한 결과물이다. 코어 프레임워크 외에도 Electron 앱을 더 쉽게 빌드, 패키징, 배포할 수 있도록 도와주는 수많은 라이브러리와 도구들이 공개되었다.

이제 커뮤니티 페이지에서 다양한 Electron 도구, 앱, 라이브러리, 프레임워크를 확인할 수 있다. 또한 ElectronElectron Userland 조직에서 이 멋진 프로젝트들을 살펴볼 수 있다.

Electron이 처음이라면? Electron 1.0 소개 영상을 시청해 보자:

Electron 0.37의 새로운 기능

· 7 min read

Electron 0.37이 최근 릴리스되었으며, Chrome 47에서 Chrome 49로의 주요 업그레이드와 여러 새로운 코어 API가 포함되었다. 이번 최신 릴리스는 Chrome 48Chrome 49에서 제공된 모든 새로운 기능을 가져온다. 여기에는 CSS 커스텀 프로퍼티, ES6 지원 강화, KeyboardEvent 개선, Promise 개선, 그리고 이제 Electron 앱에서 사용할 수 있는 다양한 새로운 기능이 포함된다.

새로운 기능

CSS 커스텀 속성

Sass나 Less 같은 전처리 언어를 사용해 본 적이 있다면, 색상 스키마나 레이아웃 같은 재사용 가능한 값을 정의할 수 있는 변수에 익숙할 것이다. 변수는 스타일시트를 DRY(Don't Repeat Yourself)하게 유지하고 더 유지보수하기 쉽게 만든다.

CSS 커스텀 속성은 전처리 변수와 비슷하게 재사용 가능하지만, 더 강력하고 유연한 독특한 특징이 있다: 자바스크립트로 조작할 수 있다. 이 미묘하지만 강력한 기능은 CSS의 하드웨어 가속을 활용하면서도 시각적 인터페이스를 동적으로 변경할 수 있게 해준다. 또한 프론트엔드 코드와 스타일시트 간의 코드 중복을 줄이는 데도 도움이 된다.

CSS 커스텀 속성에 대한 더 자세한 정보는 MDN 문서Google Chrome 데모를 참고하면 된다.

CSS 변수 활용 예제

앱에서 실시간으로 조정할 수 있는 간단한 변수 예제를 살펴보자.

:root {
--awesome-color: #a5ecfa;
}

body {
background-color: var(--awesome-color);
}

변수 값은 자바스크립트에서 직접 가져오거나 변경할 수 있다:

// 변수 값 '#A5ECFA'를 가져옴
let color = window
.getComputedStyle(document.body)
.getPropertyValue('--awesome-color');

// 변수 값을 'orange'로 설정
document.body.style.setProperty('--awesome-color', 'orange');

변수 값은 개발 도구의 스타일 섹션에서도 편집할 수 있어 빠르게 피드백을 받고 조정할 수 있다:

스타일 탭의 CSS 속성

KeyboardEvent.code 속성

Chrome 48에서는 KeyboardEvent 이벤트에 새로운 code 속성을 추가했다. 이 속성은 운영체제의 키보드 레이아웃과 상관없이 물리적으로 눌린 키를 나타낸다. 이를 통해 여러 기기와 설정에서 일관된 커스텀 키보드 단축키를 구현할 수 있다.

window.addEventListener('keydown', function (event) {
console.log(`${event.code} was pressed.`);
});

이 예제를 통해 실제 동작을 확인할 수 있다.

Promise 거부 이벤트

Chrome 49에서는 거부된 Promise가 처리되지 않았을 때 이를 알려주는 두 가지 새로운 window 이벤트를 추가했다.

window.addEventListener('unhandledrejection', function (event) {
console.log('거부된 Promise가 처리되지 않았습니다', event.promise, event.reason);
});

window.addEventListener('rejectionhandled', function (event) {
console.log('거부된 Promise가 처리되었습니다', event.promise, event.reason);
});

실제 동작을 확인하려면 이 예제를 참고하라.

V8 엔진의 ES2015 업데이트

현재 Electron에 포함된 V8 버전은 ES2015 기능의 91%를 지원한다. 플래그나 사전 컴파일러 없이 바로 사용할 수 있는 몇 가지 흥미로운 기능을 소개한다:

기본 매개변수

function multiply(x, y = 1) {
return x * y;
}

multiply(5); // 5

구조 분해 할당

Chrome 49부터 구조 분해 할당이 추가되어 변수와 함수 인자를 더 쉽게 할당할 수 있게 되었다.

이 기능 덕분에 Electron에서의 요구 사항을 더 깔끔하고 간결하게 할당할 수 있게 되었다:

브라우저 프로세스 요구 사항
const { app, BrowserWindow, Menu } = require('electron');
렌더러 프로세스 요구 사항
const { dialog, Tray } = require('electron').remote;
다른 예제들
// 배열 구조 분해 할당에서 두 번째 요소 건너뛰기
const [first, , last] = findAll();

// 함수 매개변수 구조 분해 할당
function whois({ displayName: displayName, fullName: { firstName: name } }) {
console.log(`${displayName} is ${name}`);
}

let user = {
displayName: 'jdoe',
fullName: {
firstName: 'John',
lastName: 'Doe',
},
};
whois(user); // "jdoe is John"

// 객체 구조 분해 할당
let { name, avatar } = getUser();

새로운 Electron API

아래는 몇 가지 새로운 Electron API다. Electron 릴리스 노트에서 각 API에 대한 자세한 내용을 확인할 수 있다.

BrowserWindowshowhide 이벤트

이 이벤트들은 윈도우가 보여지거나 숨겨질 때 발생한다.

const { BrowserWindow } = require('electron');

let window = new BrowserWindow({ width: 500, height: 500 });
window.on('show', function () {
console.log('Window was shown');
});
window.on('hide', function () {
console.log('Window was hidden');
});

appplatform-theme-changed 이벤트 (OS X)

이 이벤트는 시스템의 다크 모드 테마가 전환될 때 발생한다.

const { app } = require('electron');

app.on('platform-theme-changed', function () {
console.log(`Platform theme changed. In dark mode? ${app.isDarkMode()}`);
});

app.isDarkMode() for OS X

이 메서드는 시스템이 다크 모드 상태일 때 true를 반환하고, 그렇지 않으면 false를 반환한다.

OS Xscroll-touch-beginscroll-touch-end 이벤트를 BrowserWindow에 추가

이 이벤트들은 스크롤 휠 이벤트 단계가 시작되거나 끝날 때 발생한다.

const { BrowserWindow } = require('electron');

let window = new BrowserWindow({ width: 500, height: 500 });
window.on('scroll-touch-begin', function () {
console.log('Scroll touch started');
});
window.on('scroll-touch-end', function () {
console.log('Scroll touch ended');
});