Running Node.js in your browser with WebContainers [ukr]

Talk presentation

We are already got used to continuous migration from desktop to the web and to the fact that Web Browser powered by Javascript, WASM, etc. can replace a huge amount of desktop applications. In this talk, I’ll talk about WebContainers as another step in this direction, which brings Node.js runtime directly to your browser. Why do we need Node.js in the browser at all? Will it work in any real use case, not just on the demo site? But is it a good step in general? All this and a few more questions I will try to answer in my presentation.

Oleksandr Zinevych
Avenga
  • More than 10 years in software engineering and especially web development. Started as a Junior .NET developer in a small outsourcing company, then worked with Java, Groovy, Ruby before finally reaching Javascript
  • Today, I’m a software engineer who switched to technology management and am responsible for growing an engineering culture in Node.js and Ruby departments in Avenga company
  • Technology and web development fan
  • Independent consultant, mentor, and career advisor in my free time
  • Twitter, LinkedIn, Medium

Talk transcription

Так, всім привіт. Мене так гарно представили, що навіть вже немає що додати. Скажу тільки, що взагалі в індустрії я працюю вже понад 10 років і працював з різними технологіями, але більшість цього часу це все-таки JavaScript, і я його використовував на фронтенді, на бекенді, писав хром екстеншени; в різних місцях використовував JavaScript, і це моя основна мова розробки зараз. Загалом я дуже люблю технології, програмування, все, що з цим пов'язано, і слідкую за всіма трендами та новинками.

І ось власне сьогодні я хочу поговорити з вами про таку відносно нову річ, як Web Containers. Вона з'явилася минулого року, стала більш публічною, і сьогодні, можливо, місяці-два тому, набула більшої популярності. І я розповім сьогодні про те, що це взагалі таке Web Containers, як вони працюють, як вони побудовані всередині, покажу дещо прикладів і поділюся власними думками щодо того, чи це класна технологія, які сильні та слабкі сторони цієї штуки.

Коли я вперше почув про Web Containers, перше питання, яке виникло, - "Для чого нам Node.js у браузері?" Та як все працює коректно. Ми всі знаємо, коли використовувати ту чи іншу технологію, і Node.js точно не та річ, яка задумувалася як технологія для браузера. Відповідь на це питання досить проста. Node.js у браузері може бути корисним для тих застосунків, які дають можливість писати який-небудь користувацький код, виконувати його на сервері та створювати окремі віртуальні машини для виконання цього коду. Потім користувачеві повертається результат виконання цього коду. Наприклад, це може бути документація. Ми вже давно відійшли від того, коли документація - це просто Google Doc; зараз це повноцінні інтерактивні додатки, які часто мають місця, де можна не тільки читати їх, але й взаємодіяти, писати код і запускати його, іншими словами, працювати з ними.

Це можуть бути засоби для розробки (code sandbox) або їх аналоги, де можна писати і запускати код, взаємодіяти з бібліотеками чи фреймворками. Це також можуть бути навчальні інструменти, інтерактивні туторіали по вивченню фреймворків, де поряд із документацією є місце для написання коду та взаємодії з його частинами. Або це може бути інструмент для тестування. Наприклад, компанія CloudFlare вже має свій CLI, який працює безпосередньо у браузері. Ви можете писати команди в браузері, але на серверах створюються окремі віртуальні машини, які виконують цей код, і потім результат повертається або передається по пайплайну для подальшої обробки.

Типово всі ці системи мають архітектуру на високому рівні, де є data layer для збереження інформації про користувачів і налаштування, server layer та client layer. На сервері, зазвичай, є бекенд і хоститься фронтенд, який при запиті користувачів створює окремий інстанс цього фронта. Ми можемо взаємодіяти з сервером за допомогою цього фронтенду. Бекенд має бізнес-логіку, яка відповідає за коректну роботу веб-додатку, і налаштування для створення окремих віртуальних машин для виконання користувацького коду та обробки результатів. Це може бути різна архітектура, від моноліту до мікросервісів, до розподілених систем.

Зазвичай бекенд layer володіє бізнес-логікою для правильної роботи веб-додатку, а також налаштуваннями для створення окремих віртуальних машин для виконання користувацького коду та обробки результатів. Це може бути різноманітні архітектури, від моноліту до мікросервісів та розподілених систем. І коли у нас зростає кількість користувачів, цей бекенд отримує великий лоад і стає дорогим, оскільки кількість віртуальних машин і витрати стрімко зростають, що важко для нього. Тут виникає питання: чи можемо ми щось з цим зробити? Чи можемо ми використати сучасні технології, можливості браузера і вебу для того, щоб перенести частину бекенду, яка відповідає за ці віртуальні машини та виконання користувацького коду, на клієнтську сторону і якось передати відповідальність користувачам?

Теоретично це може нам принести переваги. З точки зору витрат наша інфраструктура стане дешевше, що призведе до покращення швидкості і продуктивності, оскільки не потрібно відправляти код на сервер; його можна виконати прямо в браузері і обробити результат. Також можливі бенефіти з точки зору безпеки через сек'юріті-імпрувменти та сендбокси в браузерах.

Web Containers, як технологія, розроблена компанією StickBlitz, є середовищем, яке дозволяє виконувати Node.js код, запускати Node.js-подібні додатки та виконувати команди, подібні до операційної системи, прямо в браузері. Це своєрідна операційна система, що працює тільки в конкретній вкладці браузера, і вона не розповсюджується на інші вкладки. Основою веб-контейнерів є Service Worker, WebWorker, WebAssembly та SharedArrayBuffer. Service Worker, по суті, є проксі між браузером і мережею, дозволяючи налаштовувати політику кешування, перехоплювати мережеві запити і створювати офлайн-режим для додатка. Це є базою прогресивних веб-додатків.

WebWorker – це засіб, що дозволяє нам реалізувати щось на кшталт мульти-трейдингу у браузері. Іншими словами, коли у вас є важка задача або обчислення, які займають багато часу, ви можете використовувати WebWorker. щоб виділити ці обчислення в окремий потік. Ви чекаєте, доки вони виконаються фоново, і потім отримуєте результат для подальшої роботи. Протягом цього часу ваш основний потік і веб-додаток залишаються реактивними, нічого не зависає, і ви можете виконувати інші завдання. WebAssembly – це бінарний формат, який на сьогодні є стандартом для веб-розробки, поряд з CSS, HTML та JavaScript. Він дозволяє досягати майже нативної продуктивності в веб-додатках, подібній до продуктивності десктопних застосунків.

Останньою річчю, яку я хотів би трошки детальніше розглянути, є SharedArrayBuffer. SharedArrayBuffer – це, по суті, звичайний array buffer, який зберігає бінарні дані, але він може ділити ці дані між різними потоками. Ця річ не є новою і була запроваджена вже, якщо я не помиляюся, у 2018 році, але тоді виявилася вразливість у SharedArrayBuffer, через яку хакери могли використовувати атаки.

Тоді її просто вимкнули, але зараз браузери досягли певної згоди стосовно того, як це все може працювати. Узгоджено, що ця річ буде працювати, коли ваш додаток працює у так званому режимі cross-origin-ізольованому. Тут я хочу пояснити, що це таке, оскільки не всі додатки можуть працювати у такому режимі. Cross-origin-ізольований режим можливий, якщо ваш веб-додаток завантажується з такими двома заголовками. Я розгляну їх трошки детальніше зараз.

Отже, почнемо з cross-origin-embedder policy. Це, власне, заголовок, який надає певні інструкції браузеру щодо завантаження сторонніх ресурсів. Наприклад, якщо у вас є додаток, який працює на домені A, а сервер із ресурсами розташований на домені B, то цей заголовок cross-origin-embedder policy може мати значення same corp, require corp. Тепер допустимо, що в додатку потрібно завантажити скрипт, картинку та відео, і всі ці ресурси знаходяться на сервері B, тобто на сторонньому сервері, який перебуває в іншому домені.

Якщо ми говоримо про JavaScript і цей файл має політику cross-origin-resource-policy cross-origin, то завантаження буде коректним, і скрипт відпрацює. Оскільки цей файл явно вказує серверу, що його можна завантажувати на інших доменах. Те саме стосується і картинки. Якщо коректно описана політика корс та в хедерах вказаний наш домен A, завантаження також пройде успішно.

А от щодо відео, якщо в хедерах не явно вказано, що цей файл може бути завантажений на інших доменах, то браузер не дозволить це. Це саме те, що робить цей заголовок. Він каже браузеру контролювати, з яких серверів ми завантажуємо зовнішні ресурси, файли тощо. Якщо це не вказано явно, браузер не дозволить завантаження, якщо файл не дозволений для завантаження на нашому домені.

Тепер щодо Cross-Origin Opener Policy. Це політика, яка встановлює правила комунікації з порталами та вікнами, які ми відкриваємо. Наприклад, якщо у нас є наш сайт, який відкриває портал, і обидва вони належать одному домену, одному оріджину, браузер розглядатиме це як одну браузерну контекст-групу. Комунікація між дитиною та батьком буде відбуватися, і ми зможемо отримати доступ до цього вікна, всі посилання будуть коректні.

Але якщо ми відкриваємо портал із зовнішнім доменом, і якщо у нас увімкнено cross-origin-isolation-mod, така комунікація не буде працювати. Браузер блокує будь-які спроби доступу до цього модального вікна. Отже, cross-origin-isolation-mod не можна увімкнути для всіх додатків, оскільки це накладає певні обмеження. Наприклад, для авторизаційних або платіжних процесів це може викликати проблеми, оскільки там часто використовуються вбудовані i-frame чи інші сторонні сервіси. Це особливо стосується авторизаційних та платіжних процесів, де можливі проблеми з доступом до інформації через сторонні елементи, такі як i-frame.

Навіть самі автори зіткнулися з проблемою авторизації, коли використовували Web Containers. Наступним питанням є завантаження асетів, оскільки не завжди можна явно вказати, що асет має право завантажуватися на нашому домені. Це також може бути проблемою. Останнім аспектом є ембедінг. Якщо наш веб-аплікейшен працює в режимі cross-origin-isolation, ми не можемо його вбудовувати в аплікації, які не працюють в цьому режимі. Це серйозне обмеження. Отже, ці чотири аспекти можуть вплинути на використання веб-контейнерів.

Тепер, коли ми розглянули ці чотири основні аспекти веб-контейнерів, які побудовані на Service Worker, WebWorker, WebAssembly та SharedArrayBuffer, я хотів би повернутися до моєї тези, що Web Containers - це, по суті, псевдо операційна система. Вона надає термінал, з яким ми можемо взаємодіяти, так само як і в псевдо операційній системі. Вона також повинна надати нам інструменти для роботи з файлами, трьома та мережею. І саме ці чотири аспекти, які я перерахував, забезпечують здатність цього застосування. Шередери та буфери використовуються для побудови віртуальної файлової системи. WebWorker використовуються для досягнення мультипроцесорного використання. Service Worker використовуються для роботи з мережею, перехоплення запитів та їхньої коректної переадресації за необхідності.

Давайте тепер перейдемо трошки ближче до Web Containers і покажемо, як це все може працювати. Перш ніж ми взагалі почнемо роботу з Web Containers, ми вже встановили його пакет і тепер повинні запустити цей контейнер. Це робиться один раз для всього застосування. Потім ми можемо використовувати інстанс веб-контейнера, який ми отримали.

Під капотом, якщо ви відкриєте вкладку "Мережа" у веб-інструментарії та слідкуватимете, контейнер запускає запити на сервери StickBlitz, завантажує відповідні воркери та ініціює WebAssembly. Все це зливається в одну функціональну одиницю, і потім повертається нам як екземпляр Web Containers, з яким ми можемо взаємодіяти. Отриманий інстанс, по суті, відкриває для нас доступ до цієї міні-операційної системи. Власне, нам потрібно, щоб цей інстанс надавав можливості роботи з файлами, з самим веб-контейнером, можливість виконувати на ньому команди та, можливо, запускати середовище Node.js, оскільки ми визначили цей веб-контейнер як середовище для виконання Node.js-додатків.

Щодо файлової системи, на інстансі веб-контейнера ми маємо об'єкт fs з рядом методів для роботи з файлами. Я не буду глибоко зупинятися на них, оскільки документація доступна на сайті Web Containers. Варто зазначити, що при завантаженні Web Containers він є порожнім, містить лише файли, воркери та все необхідне для його коректної роботи..

Для запуску будь-якого файлу чи додатка потрібно завантажити ці файли в веб-контейнер. Ми можемо використовувати методи, зазначені на цьому слайді, або, якщо потрібно завантажити багато файлів одночасно, краще використовувати метод mount, який приймає об'єкт з інформацією про файли. Тобто це великий JSON, де ключ – назва файлу, а значення – його вміст. Таким чином ми можемо передати багато файлів, викликати mount, і наш інстанс завантажить їх у Web Containers, готуючи їх до подальшої роботи. Щодо виконання команд в веб-контейнері, у нас є метод spawn, який дозволяє виконувати команди операційної системи. Це може бути створення папок, читання файлів, запуск інших системних команд і т.д. Для цього використовується метод spawn, який приймає команду та параметри для виконання цієї команди..

Щодо Node.js, на момент написання цієї доповіді веб-контейнер мав встановлену версію 16-тої версії Node.js. Існує також менеджер пакетів. Хоча може здатися, що відразу доступні npm і yarn, насправді вони використовують свій власний менеджер пакетів, який називається turbo. Цей turbo потрібен для обходження обмежень браузера, оскільки ми намагаємося використовувати в браузері те, що туди взагалі не повинно потрапити. Тому turbo, наприклад, не викликає скрипти install. Якщо він виявляє, що в пакеті є такий скрипт, він динамічно замінює його на відповідний поліфіл або аналог web-assembly. Це може здатися нещасливим, оскільки це призводить до багатьох проблем, і на GitHub-репозиторії можна побачити численні проблеми з популярними пакетами, такими як nesta, які не завжди коректно інсталюються виконанням turbo.

Також можна запускати сервери в Web Containers. Якщо у вас є, наприклад, API на Express, ви можете запустити його стандартно за допомогою команди npm start. Сервер буде запущений, і в основній частині вашої програми ви можете слухати подію старту сервера, яка вам поверне URL і порт, за яким можна звертатися до цього сервера всередині веб-контейнера. Про це я розкажу трошки пізніше, оскільки це дещо складно реалізовано. Перший раз, коли я запустив цей веб-сервіс на Node.js у своєму браузері і зміг до нього звернутися, це трохи призвело мене в оману, це дуже дивно. Я раджу всім спробувати погратися з веб-контейнером, щоб побачити, як це все працює. Я також підготував кілька прикладів взаємодії з веб-контейнером та можливості його використання.

Ось базові команди, які ми можемо виконувати в веб-контейнерах, які можна викликати як окремі процеси. Серед цих команд - створення директорії, читання файлу, створення файлу тощо. Тут ми маємо власні кнопки, які викликають мій метод із певним ключем, і потім за цим ключем ми вибираємо відповідну команду для виконання. Цей приклад я також розмістжу на своєму GitHub, ви можете подивитися. Тут ми використовуємо команду echo в веб-контейнері, читаємо вміст файлів, створюємо файл і записуємо у нього якийсь вміст. Ви можете бачити, що вміст цього файлу змінився, тут ми знову читаємо вміст цього файлу, і остання стрічка - це використання встановленої в веб-контейнері Node.js для виконання цього файлу. Важливо зазначити, що там, де використовується output pipe to, на 18-ій стрічці, ми можемо запустити якийсь процес в веб-контейнері і далі виводити його результат.

У цій реалізації ми просто виводимо результат у нашу консоль. Ще один приклад, який я хочу показати, і одразу праворуч ви бачите використання GSHIP, як воно зросте відповідно до моїх дій. Це, по суті, я просто завантажив порожній веб-контейнер, в ньому нічого немає. Ось тут я викликаю list, просто пустий якийсь environment, і потім запускаю Create React App. Поки він працює, я зазначу, що тут використовується NPX, але, як я сказав, це Turbo під капотом. Їхній менеджер пакетів, крім того, що не викликає скрипти install і не пропускає деякі речі, намагається замінити їх на якісь поліфіли і оптимізувати для роботи в браузері. Цей менеджер пакетів також завантажує всі пакети серверів. Він не взаємодіє безпосередньо з NPM, а замість цього отримує всі пакети з реєстру, який є на стороні.

За їхніми словами, цей спосіб встановлення працює набагато швидше, ніж локальний, оскільки вони якось оптимізують і видаляють непотрібне з цих проєктів. Але ось проблема: вони залежать від своїх серверів. Тут ми взяли Create React App, створили всі необхідні файли за замовчуванням, і зараз ми можемо запустити просто npm start. Він трошки подумає, і ми вже можемо бачити, що наша програма запустилася, і, власне кажучи, ми можемо побачити в iFrame, що ми отримали доступ до додатка, який працює в WebContainer. Тут я трошки зупинюсь. Я сказав iFrame, а ви, мабуть, не чули це, оскільки це єдина можливість доступу з нашого поточного контексту браузера до чогось, що працює в WebContainer. Є багато обмежень через курс та обмеження браузерів. І через ці обмеження, якщо ви запускаєте, наприклад, REST-апі, яке працює на localhost, ви не можете отримати доступ до цього localhost та порту з браузера через всі ці обмеження безпеки. Тому WebContainer створює свою власну унікальну URL, додає туди якісь хеші і т.д.

Коли сервер-старт-івент відпрацьовується, вам приходить ця унікальна URL і порт. Я використовував цю URL у вигляді iFrame, через яку ми отримали доступ до WebContainer. Під капотом ця довга унікальна URL інтерсептується сервіс-воркером, який дозволяє запроксівати її так, як вам потрібно. Ще один приклад, який я хотів показати, - це три файли: невеликий express.api з двома ендпоінтами (post і get), тест, який використовує супер-тест для тестування цих ендпоінтів, і файл package.json. Я їх маунтую і далі викликаю npm install, щоб встановити всі залежності. Потім викликаю npm run test, щоб запустити тестовий файл з використанням Jest.

Це дуже цікавий досвід працювати з цим у браузері, зокрема виконувати дії, які зазвичай виконуються на робочій машині. Однак не все так гладко. Є деякі проблеми, які я вже згадував. В команді StickBlitz довелося написати багато речей самостійно, включаючи свій кастомний event loop та віртуальну систему. Це створює декілька проблем, зокрема зі специфікою event loop, яка відрізняється від того, який є на локальних машинах.

Є також проблема з офлайн режимом. Хоча команда StickBlitz стверджує, що веб-контейнер працює офлайн, на практиці для ініціалізації програми потрібен інтернет. Це може бути проблемою, особливо якщо ви вирішили перезавантажити вкладку або втратили з'єднання. Також є питання з залежністю від серверів StickBlitz для встановлення та ініціалізації веб-контейнера.

Найбільшою проблемою є те, що веб-контейнер не є open source. Це ускладнює розвиток технології, адже команда StickBlitz визначає всі напрямки та імплементації. Це також призводить до невизначеності щодо майбутнього проекту і його можливого переходу на платну модель. Зараз користувачі можуть використовувати веб-контейнери безкоштовно, але у майбутньому може бути введено обмеження для приносу прибутку команді StickBlitz.

Наступною проблемою є Turbo Package Manager, який, з одного боку, ефективно виконує свої завдання, зокрема встановлює Jest в браузері на віртуальній файловій системі. Але через велику кількість кастомних імплементацій, які відрізняються від локальних робочих станцій, це може призводити до потенційних багів та непередбаченої поведінки. Щодо Network, вони мають зареєстровану багу, яку, сподіваюся, швидко виправлять. Зараз веб-контейнер може звертатися до сторонніх сервісів через REST, але немає підтримки для побудови підключення через сокети, що є обмеженням.

Підсумовуючи, веб-контейнери є логічним наступним кроком у розвитку веб-платформи, спробою перенести десктопні функції у браузер. Однак через вставлення у браузер функціоналу, який туди не мав би потрапити, це ґрунтується на workaround, що може призводити до проблем та обмежень.З іншого боку, ці workaround і спроба втілити нативний бекенд прямо у браузер стимулюють розвиток самого браузера. Команда StickBlitz називає себе "лого-тестерами браузерів" і активно допомагає вдосконалити браузери, виявляючи та повідомляючи про баги.

Веб-контейнери, можливо, не для щоденного використання, але є вузькі сценарії, де вони можуть бути корисними. Технологія все ще не є ідеальною для продакшн використання, але розвиток ідеї, можливо, призведе до виникнення інших інновацій, таких як веб-пакети, які дозволять запускати нативні бекенди безпосередньо у браузері.

Я б рекомендував вам спробувати це, оскільки, незважаючи на деякі обмеження, ідея сама по собі обіцяюча. Зокрема, можна скористатися готовими середовищами для експериментів, такими як Node.new і WebContainer.new, надавши можливість взаємодіяти з веб-контейнерами та спробувати їхні можливості.

Sign in
Or by mail
Sign in
Or by mail
Register with email
Register with email
Forgot password?