From node-webkit to Electron 1.0

이 글은 Github 소속 Electron의 메인 개발자인 Cheng Zhao(@zcbenz)의 기고글을 번역한 글이다.
원문: From node-webkit to Electron 1.0
시간에 쫓기고 작성하다 보니 오역이 있을 수 있다. 오역 교정 환영한다.

아니 프리랜서가 시간관리도 못하고 왜 시간에 쫓겨다녀?

한국 프리랜서는 용병이 아니라 그냥 사업자를 빙자한 근로자거든요 ㅋ


이 글을 통해 내가 node-webkit를 재구성하다가 Electron 프로젝트를 시작하게 된 내역을 기고한다. 내역을 좋아하는 애들을 위해 커밋 내역과 관련 링크를 달아주었다.

DOM에서 node.js 모듈 불러오기

모든 것의 시작은 2011년 @rogerwang 이 만든 마법의 모듈 node-webkit이었다.
이 node.js 모듈은 웹킷으로 하나의 브라우저 창을 띄워주는 것도 모자라, node.js 모듈까지 사용하는 마술까지 부렸다.

Node.js 코드 단은 이랬고,

var nwebkit = require('nwebkit')
nwebkit.init({'url' : 'index.html'}, function () {
  nwebkit.context.runScript('')
})

그리고 index.html 내용은 이랬다.

<html><body>
<p id="output"></p>
<script>
require('fs').readdir('.', function (err, files) {
  var result = ''
  files.forEach(function (filename) { result += filename + '<br/>' } )
  document.getElementById('output').innerHTML = result
});
</script>
</body></html>

이 데모로는 아직 시판하기엔 부족한 점이 많았지만, 충분히 성공 가능성이 있었다.

크롬 임베디드 엔진을 사용하다.

그 당시 node-webkit 에서 가장 큰 문제였던 게 있는데, 웹킷 라이브러리 기능을 리눅스 외에 사용하기엔 너무나 버거웠다.
그래서 @rogerwang 은 이 모듈을 웹킷에서 크로미움을 애플리케이션에 넣는 인터페이스를 제공하는 크롬 임베디드 프레임워크로 바꿔 해결했다.

당신은 그 CEF를 사용하여 개발한 node-webkit 코드를 볼 수 있다. node.js 비동기 기능을 수행하도록 만들기 위해, 거기에는 크로미움 메시지를 node.js 입출력 라이브러리인 libuv로 대체하는 패치가 있었다. 그러나 이제 그 패치는 더이상 찾을 수 없었다. 왜냐하면 NW.js 의 Chromium fork 저장소가 이미 오래전에 rebase 해버렸기 때문에. 이제 그 당시 커밋 내역으로 돌아갈 수 없게 됐다.

인턴으로 취직

당신은 @rogerwang 오랫동안 오픈 소스를 지원했던 인텔에 일하고 있다는 사실을 알고 있는가? 놀랍게도 인텔이 오픈소스를 위한 인턴을 구했다. 이보다 더 좋은 인턴쉽이 있었던가?

Intel OTC

2012년 여름, 나는 한참 졸업을 앞둔 대학생이었을 때 여름 인턴쉽을 찾으러 다녔다. 그 때 바로 인텔의 구인란이 눈에 보였는데, 인텔은 node.js의 오픈소스 프로젝트 node-webkit 개발자가 필요하다고 했다. 나는 이때다 싶어서 지원했고 운좋게 붙었다.

그리하여 나는 그 node-webkit 를 개발하기 시작했다.

컨텐트 쉘

첫번째로 내가 했던 업무는 node-webkit의 CEF 브랜치 개선이었다. 그리고 개발하면서 정말 어렵게 해냈다.
node.js가 V8 API를 직접 호출하면서 CEF는 크로미움의 컨텐트 모듈을 입히는 자체 API를 연동한다.
당신이 크로미움과 node.js를 연동하고 싶다면 중간에 CEF를 엮이지 않게 달아야 할 것이다.

그래서 기존 코드를 향상시키는 대신 아예 컨텐트 쉘을 재구성하는 쪽을 택했다. 크로미움을 최소한으로 사용한 가벼운 브라우저를 구현하기 위해.

결과는 꽤 좋았고 node.js 통합이 가능한 작은 브라우저를 획득했다. 그리고 난 그 node-webkit 0.2.1 버전으로 릴리스를 배포했다.

node-webkit를 프레임워크로

node-webkit는 node.js 모듈이기 이전 독립적인 브라우저였었다. 왜 여태까지 이 프레임워크로 HTML과 자바스크립트로 데스크탑 앱을 만들지 않았지? 나는 그 때 몇 개월간 그 아이디어를 행동에 옮겼다.

먼저 나는 node-webkit의 패키징 시스템을 추가했다. 아이디어는 게임 프레임워크인 LÖVE framework 를 슬쩍했다. 그 아이디어는 앱을 ZIP 아카이브로 만들고 node-webkit 실행 파일을 합치면, node-webkit 실행 시 그 아카이브를 풀고 아카이브에 포함된 앱을 실행하는 식이었다.

Packaging node-webkit

몇가지 구현 항목이 있다면, package.json 에 윈도우 속성을 넣거나, DOM 요소 확장, 브라우저 보안 모델 삭제, DOM 요소에 네이티브 파일시스템 도입, 그리고 끝없는 버그 수정 등이다.

한가지 재밌는게 가장 어려운 도전이 있다면 아무래도 node.js 네이티브 모듈 도입이었다. 나는 V8에 OpenSSL을 도입하기 위해 따로 기호(Symbols)를 추가해 크로미움에 패치하고, NSS와 OpenSSL 사이에 충돌이 일어나는 node.js 기호도 수정해 패치했다. 이렇게 윈도우용 원시 코드 node.lib 커스텀 패치를 제공하여 node-webkit에 네이티브 모듈이 돌아가도록 했다.

이리하여 node-webkit v0.2.5 배포 후, 왠지 안정된 느낌을 받았다.

자체 API 추가

이번엔 API 제공하는 데 DOM을 여전히 격리되고 있고, DOM 제공이 너무 제한적인 부분인 현실에 내 생각을 말해본다.
예를 들면 네이티브 메뉴와 여타 텍스트가 들어간 메시지박스 등.

그때 나는 자체 API를 제공하는 제공할 수 있는 걸 생각해 냈다. 바로 require('nw.gui') 같은 빌트인 모듈. NW.js 써봤다면 알 것이다. 그때 나는 메뉴를 생성하거나, 트레이, 메시지 창 등을 API에 넣었다. 지금 NW.js의 원형인 셈이다.

내가 새 릴리스를 배포할 때마다 이런 아이디어를 유지했고, node-webkit v0.3.6 버전으로 피날레를 장식했다.

다른 GUI 프레임워크와는 달리 크로미움의 특별한 점 하나가 있는데, 바로 멀티 프로세스 아키텍쳐라는 점이다. 크로미움 브라우저는 웹 페이지를 실행하고 GUI를 띄우면서 응답해야 하기에 웹 페이지에 GUI 모듈 제공은 너무나 힘들었다.

이렇게 해서 웹 페이지 내에 브라우저 처리를 하기 위한 내 첫번째 시도는 이랬다.
먼저 크로미움을 싱글 프로세스로 돌린다. 허나 이건 너무나도 불안정하게 돌아간다.
이번엔 그냥 네이티브 GUI API를 렌더러 프로세스에 호출하도록 해봤다.
하지만 몇몇 플랫폼에서 GUI 요소와 프로세스를 직접 핸들링할 수가 없었다.
그래서 찾은 마지막 방법은 IPC 메시지를 통해 GUI 모듈을 node-webkit에 입혔다.
이렇게 해서 렌더러 프로세스 중에도 상시 행동이 가능했고,
브라우저 프로세스 간이나 주어진 일을 완료하면 메시지를 보낼 수 있게 됐다.

node-webkit 홍보

node-webkit 같은 프레임워크는 사용자가 가장 중요하다. 많은 사용자를 가지고 만드는 데 있어서 가장 중요한 것들도.

그래서 내가 node-webkit를 개발하는 동안 최대한 홍보도 해왔다. node.js 메일링 리스트에 소개글도 올렸고, 답글도 달아줬다.
node-webkit 게시들을 달며 node-webkit 발표도 했고, node-webkit 샘플 앱도 제작했다.
이렇게 나는 전 세계에 내 힘으로 node-webkit를 알리는 계기를 만들었다.

그리고 node-webkit 개발을 중단하기 전에 Github 별을 천여개 씩이나 받았다. Github에 상위 C++ 프로젝트 10위 안에도 들었다.

첫 사용자

내게 node-webkit 첫 사용자는 정말 중요한 순간이었다. @ibdknoxLight Table editor 을 node-webkit 를 통한 웹 기술로 다시 탄생시켰다. 이후 많은 이들이 node-webkit 에 주목받고 많은 도움이 들어왔다. 이렇게 기쁜 순간을 만들어준 @ibdknox 에게 감사의 말을 전한다.

그 다음 해에 Light Table는 node-webkit 에서 Electron 으로 자리를 옮겼다. 왠지 오랜 친구가 돌아온 것 같았다.

GitHub으로 이동

어느날 Github 에서 비밀리에 Atom 에디터를 시작하면서, @nathansobo 가 node.js로 데스크탑 앱을 개발하는 사람을 구하고 있었다.
나는 Atom을 node-webkit에 마이그레이션하기 위해 필요할 때마다 node-webkit를 개선하는 조건으로 그와 계약했다.

이렇게 반년동안 node-webkit를 개발하다가 인텔 인턴쉽은 이렇게 끝났다. 바로 나는 Github 고객지원부로 일하기 시작했다. 그리고 동시에 node-webkit 개발을 중지하고 뒷일은 @rogerwang 이 모두 맡게 됐다.

Contribution Graph

왜 이렇게 뜨거운 감자인 node-webkit 개발을 중단했을까? 어자피 내가 Github으로 이동하는데 전혀 영향도 없었고, 누가 프로젝트를 운영하는데 나는 상관이 없으니까.

아시다시피, 난 인텔에서 다른 사람의 프로젝트를 진행하고, 프로젝트의 결정권을 가질 수도 없었다. 아무렴 어떤가.
그냥 난 누구의 방해도 없이 node-webkit을 재구성할 수 있고 기능을 추가하면서 장난감 조립하듯이 일하는 게 좋았으니까.
어느날 node-webkit 오픈 소스 규모 커지기 시작했고 말 나오기 무섭게 나의 node-webkit의 권한은 상실됐다.
새 버전을 배포하는 데 있어서 책임이 상급 엔지니어에게 이관되어
내가 일할 수 있는 범위가 사실상 줄어들었고, 내가 좋아하는 기능을 넣을 수 없었다.

비난할 이유가 없다. 인텔 말고도 다른 회사도 그러니. 하지만 단도직입적으로, 더이상 참을 수 없었다.

더 나은 데스크탑 프레임워크 제작

내가 Atom 개발에 참여했을 때, Atom은 CEF에 파일시스템을 운영하기 위한 자바스크립트 바인딩으로 이루어져 있었다. 이게 Atom이 node-webkit로 마이그레이션한 계기가 됐지만, node-webkit가 그때당시 결함이 많다 보니 앞길이 순탄치 않았다.

그래서 난 결국 자체 네이티브 쉘을 만들어 해결하기로 했다. 새로운 쉘은 node-webkit를 개선하는 대신 몇가지 근본적 구조를 바꾸는 방향으로 재구성했다.

이리하여 이 쉘에게 atom-shell 이라고 이름 지었다.

node-webkit 에서 Electron 1.0 까지

그 이후 Electron과 NW.js 에 무슨 일이 일어났는지 내역을 정리했다. Atom과 Atom-shell 은 오픈 소스로 배포했고 그 atom-shell은 Electron으로 개명했고, node-webkit는 NW.js 로 이름을 바꿨다.

그리고 저번 주말에 드디어 Electron 1.0 을 배포했다.

Contribution Graph

Electron은 NW.js의 파생인가?

아마 내가 받은 질문 중 가장 많이 받은 질문이고, 내가 이 글을 쓴 이유이기도 하다. 기술적으로는 Electron은 node-webkit 0.3.6을 재구성했다. 내가 마지막으로 배포한 버전이고 이걸 끝으로 node-webkit 개발을 중단했으니. 그러고 보니 Electron은 원래 node-webkit와 아주 다른 노선을 밟았다.

그러니까 재대로 얘기 좀 하자면, NW.js는 내가 node-webkit 개발한 걸로 시작했고, Electron은 내가 다시한번 node-webkit에 파고든 작품이다.


복터진 개발자네. 군기잡힌 인턴 개발자에서 Electon 메인 개발자가 되기까지…

composite / 2016년 5월 25일 / Piss Development, Waldo Translation
태그:, , , , ,

Comments

  1. 정환 - 2016년 5월 26일 @ 11:18 오전

    재밌게 읽었습니다.

    “Githb” -> “Github” 오타 발견이요

    Reply
    • composite - 2016년 5월 30일 @ 12:17 오후

      오타발견 감사합니다. 수정했습니다.

      Reply

답글 남기기

Your email address will not be published / Required fields are marked *