Node-Webkit без вебкита
Как-то мы обсуждали десктопные приложения nw.js. Всё хорошо, но необходимость распространять весь движок браузера (который с течением времени и добавлением новых фич меньше не становится и сейчас весит сжатый 30МБ) — удручает.
А что если сделать модуль для node.js, умеющий показывать UI в системном браузере?
Под катом расскажу о попытке — как оно, стоит ли усилий, можно ли использовать, и что в результате получилось.
В основном проект был сделан скорее как proof-of-concept: поиграться, посмотреть, проверить, как оно. Сделал на нём 3–4 заказа в стиле «хочу маленькое приложение со встроенным webview» или «хочу обёртку для сайта в окне десктоп-приложения». Может быть, кому-то мой опыт окажется полезен, поэтому выложил в паблик. Код на гитхабе. Подробно описывать нюансы кода не буду, цель — не описать код, а скорее подумать над пригодностью результата.
Приложение статически собирается с node.js со своей точкой входа. При запуске в ноду добавляется нативный модуль с логикой отображения окон, и управление передаётся _thirdPartyMain.js (в ноде есть такая возможность). Оттуда загружается пользовательский main.js, где подключается модуль ui. Модуль стартует http-сервер, открывает окно с заданным конфигом и направляет webview на адрес встроенного сервера, который отдаёт пользовательский контент. Окно генерирует события (перемещение, сворачивание, …) и умеет выполнять методы (например, закрыться).
Под винды вставляется ActiveX-ный WebBrowser (это тот же MSIE, но без тубларов, адресных строк итд). По умолчанию он показывает много ненужных диалогов (которые отключаются странным образом), но в целом работает как хочется. IE, обновляется неважно, а поддержать XP надо было, поэтому для старых версий Windows предусмотрена возможность скачать вебкит (chrome embedded framework). При необходимости, если пользователь не против, приложение скачивает библиотеки, распаковывает их и работает уже с вебкитом.
Под маком всё более прозрачно, WebKit WebView по сравнению с IE в интеграции намного проще и понянтее; под линуксом есть webkit-gtk.
Нужно было, чтобы приложения паковались в один EXE. Самым подходящим форматом оказался ZIP: он читается с конца, позволяя дописывать в исполняемый файл что угодно, и распаковывать его из себя; по этому принципу и работают SFX. Для использование из node.js, можно повесить хук на обращения к fs и перенаправить запросы к файлам, которые есть в архиве, к виртуальной файловой системе вместо чтения с диска.
IE и WebKit предлагают способы взаимодействия как из C++ в JS, так и наоборот. В IE javascript-овый объект, в том числе и функция, представляет собой объект IDispatch. Если проверить typeof такого объекта, javascript-движок отдаст unknown, это так называемые host objects (использование таких результатов оператора typeof в стандарте ES6 уже не рекомендуется, но раньше про нестандартные типы ничего не было сказано). Что-то вызвать из C++ можно, обратившись к объекту window.external, при условии, что хост реализовал этот метод.
В вебките аналогично, можно добавить свойство window как объект с методми, доступными для вызова.
В ноде процесс создания плагинов прекрасно документирован. Аналогично создаются и встроенные аддоны, но регистрируются другим макросом. После создания класса аддона, он наследуются от EventEmitter-а и умеет генерировать события, получив ссылку на метод emit.
Node.js намеренно живёт в отдельном треде, чтобы длительные операции не блокировали UI, поэтому для отправки сообщения в ноду и синхронизации используется библиотека UV.
Размер приложения
Он составляет 2–3 МБ (незапакованного 7). В принципе можно сделать сборку и меньшего размера, если надо только открыть страничку в приложении, без node.js (вобщем-то ради размера всё и делалось).
Время запуска
Т.к. в приложении не используются дополнительные фреймворки, запускается оно быстро в среднем 1 секунду (про время первого запуска см. ниже); добавив профилирование, можно убедиться, что основное время уходит на инициализацию браузера.
Первый запуск
Windows оптимизирует запуск приложений и компонентов, которые вы используете чаще. Если IE пользуются редко, время первого запуска приложения составляет где-то 3…10 секунд, в зависимости от операционки и компьютера.
Версии браузеров
Очень часто IE используют как инструмент для скачивания браузера и не обновляют вообще никогда (хотя с последними версиями Windows ситуация улучшается). IE нельзя обновить принудительно (показать диалог обновите IE из приложения равносильно самоубийству: обновление IE сложное, долгое, часто требует перезагрузки — нельзя так издеваться над пользователем, он просто найдёт другое приложение).
Версии браузеров привязаны к операционке: для Windows XP нет IE10, для OS X Lion нет Safari 8.
Упаковка
Приложение запаковано UPX — некоторые антивирусы могут ругаться на него. Можно не паковать приложение и получить размер 6–7МБ. Снова много. Меньше, чем 60, но всё равно не 2 МБ.
Контексты javascript
Объекты node.js нельзя использовать в браузере и наоборот, взаимодействие происходит или как в классическом клиент-серверном приложении (xhr), или способом, предоставленным программой (postMessage в окне или ноде) — т.е. как в web worker, передать можно только простые данные. Можно было сделать context bridge, но это во-первых сложно и бажно, во-вторых недальновидно, потому что ES6 уже почти тут, а что делать, например, с объектом-прокси в IE10 — неизвестно.
Есть сборка под win/mac/linux, которую в принципе можно использовать, кастомизировать итд. Я и использую её для создания кастомизированных «приложений-браузеров», когда это бывает вдруг кому-то надо. Она относительно стабильна, но до ума и релизного качества я её не доводил, потому что…
Приложения скачиваются редко, интернет сейчас достаточно быстрый (и в случае установки приложений, EDGE действительно можно пренебречь, т.к. приложение — это не сайт, его не будут устаналивать в дороге), нерешаемые проблемы в использовании системного браузера присутствуют. В большинстве случаев сейчас игра не стоит свеч и использовать вебкит кажется более правильным. Если цель — создать маленькое приложение в основном под новые операционки —, а может быть, можно и так.