Що таке runtime і чому код не працює скрізь однаково
Є одна річ, яка часто плутає новачків: ти пишеш код на JavaScript, він ідеально працює в браузері — а коли намагаєшся запустити його на сервері, нічого не відбувається. Або навпаки: скрипт працює в Node.js, але в браузері падає з помилкою про відсутню функцію.
Проблема не в коді. Проблема в тому, де цей код запускається. І ця «де» — це і є runtime (середовище виконання).
Проста analogія: JavaScript — це мова. А runtime — це країна, де ти нею говориш. Граматика та сама, але закони, культура і доступні сервіси — різні.
Проблема / контекст: чому «воно ж працює в мене» — це не те саме
JavaScript спочатку створювався для браузерів. Це мова, яка жила всередині вебсторінки і могла маніпулювати DOM, реагувати на кліки, міняти кольори. Але вона не вміла читати файли, не вміла працювати з мережою напряму, не мала доступу до файлової системи.
Потім з’явився Node.js — і JavaScript вийшов за межі браузера. На сервері він отримав доступ до файлів, мережі, баз даних — але втратив DOM. Тут немає window чи document. Це вже інший світ.
Потім з’явилися й інші: Bun, Deno, Cloudflare Workers — і кожен має свої особливості.
Той самий синтаксис JavaScript працює в них усіх. Але набір інструментів, які йому доступні — різний. І саме тут виникають помилки.
Як це працює простими словами
Runtime — це три речі:
1. JavaScript-двигун
Це те, що власне розуміє і виконує JavaScript-код. Більшість сучасних runtime використовують один із трьох основних двигунів:
- V8 (Google) — стоїть у Chrome, Node.js, Bun. Швидкий, з хорошою оптимізацією.
- JavaScriptCore (Apple) — стоїть у Safari. Працює на всіх Apple-пристроях.
- SpiderMonkey (Mozilla) — стоїть у Firefox. Відкритий і незалежний.
Двигун парсить (читає) твій код, трансформує його в машинні інструкції, оптимізує і запускає.
2. Глобальні об’єкти і API
Ось тут починаються відмінності між runtime:
У браузері:
window // глобальний об'єкт браузера
document // DOM — вся сторінка
fetch() // мережеві запити
setTimeout() // таймери
localStorage // зберігання даних на стороні клієнта
У Node.js:
global // глобальний об'єкт (замість window)
require() /import файлів
fs // доступ до файлової системи
http /создание сервера
process // інформація про процес (PID, змінні оточення)
У Bun:
Bun.serve() // вбудований вебсервер
Bun.file() // зручна робота з файлами
fetch() // працює вбудовано, без залежностей
Безпосередньо JavaScript (змінні, функції, класи) працює скрізь однаково. А от API — ні.
3. Модульна система і залежності
Кожен runtime по-різному підходить до питання «як підключити код з іншого файлу».
- Node.js (ранні версії) — CommonJS:
const x = require('./module') - Node.js (нові версії) і більшість сучасних — ES Modules:
import x from './module' - Bun — підтримує обидва підходи без додаткових налаштувань
- Deno — використовує прямі URL для імпорту:
import x from 'https://example.com/mod.ts'
Навіщо це потрібно на практиці
1. Вибір інструменту під задачу
Якщо ти робиш сервер — тобі підійде Node.js або Bun. Якщо працюєш з frontend-кодом — браузер сам по собі runtime. Якщо запускаєш функції в хмарі — можливо Cloudflare Workers або AWS Lambda.
Не існує «правильного» runtime. Існує «відповідний для конкретної задачі».
2. Розуміння, чому код не працює
Коли ти розумієш різницю між runtime-ами, стає очевидною природа багатьох помилок:
ReferenceError: window is not defined— ти намагаєшся використати браузерний API в Node.js або Bun.require is not defined— ти запускаєш код у режимі ES Modules, деrequire()не працює.fs is not defined— ти намагаєшся працювати з файловістю систем у браузері (що неможливо без спеціальних API).
3. Продуктивність
Різні runtime мають різну продуктивність. Node.js — зрілий, з найбільшою екосистемою. Bun — значно швидший запуску, менше пам’яті, але молодший. Deno — має вбудовану безпеку і TypeScript з коробки.
Для маленького скрипту продуктивність не грає ролі. Для високонавантаженого сервера — може бути критичною.
Практичний приклад: один і той самий код, різні runtime
Ось простий файл:
// hello.js
console.log("Привіт!");
console.log(typeof window); // чи є browser API?
console.log(typeof process); // чи є Node.js API?
console.log(typeof Bun); // чи є Bun API?
Запускаємо в браузері (через devtools console):
Привіт!
object // window — об'єкт браузера
undefined // process — немає
undefined // Bun — немає
Запускаємо в Node.js:
Привіт!
undefined // window — немає
object // process — є
undefined // Bun — немає
Запускаємо в Bun:
Привіт!
undefined // window — немає
object // process — є (Bun емілює Node.js API)
object // Bun — є
Той самий код, три різних результати. Не через помилку — через різне оточення.
Як обрати runtime для свого проєкту
Питання не в тому, який runtime “найкращий”. Питання в тому, що тобі потрібно:
Node.js — якщо:
- потрібна найбільша екосистема пакетів (npm);
- знаєш, що підтримка спільноти і стабільність важливі за швидкість;
- команда вже знайома з Node.js.
Це стандартний вибір. Він не завжди найшвидший, але завжди найбезпечніший.
Bun — якщо:
- потрібна максимальна швидкість запуску і виконання;
- хочеш вбудовані API (сервер, файли, тестування) без встановлення додаткових пакетів;
- проєкт новий і ти не обмежений старими бібліотеками, які працюють тільки в Node.js.
Bun набирає популярність, але він молодший. Не все в npm працює з ним без проблем.
Deno — якщо:
- хочеш TypeScript з коробки (не треба налаштовувати);
- важлива безпека за замовчуванням (Deno не дає доступу до мережі чи файлів без явних дозволів);
- подобається ідея імпорту без менеджера пакетів.
Deno цікавий концептуально, але його екосистема менша.
Типові помилки
1. Припускати, що npm-пакет працює скрізь
Не кожен npm-пакет універсальний. Якщо пакет використовує fs або process, він не працюватиме в браузері чи Cloudflare Workers. Завжди перевіряй документацію пакета.
2. Плутати мову зі середовищем
JavaScript ≠ Node.js. Це дві різні речі. JavaScript — це мова (синтаксис, оператори, функції). Node.js — це одне з середовищ, де ця мова виконується, і воно додає свої API.
3. Ігнорувати версії
Node.js 14, 18, 20 — це різні runtime з різними можливостями. Те, що працює в Node.js 20, може не працювати в 14. Завжди перевіряй мінімальну підтримувану версію.
4. Використовувати неправильний runtime для задачі
Браузер не підходить для серверної логіки. Node.js не підходить для frontend-рендерингу (без додаткових інструментів). Cloudflare Workers не підходить для задач, що потребують тривалого виконання.
Висновок / план дії
Runtime визначає, які інструменти доступні твоєму коду. Розуміння цієї різниці — ключ до того, щоб «воно працює» не було сюрпризом або проблемою.
Що зробити найближчим часом:
- З’яси, який runtime зараз використовуєш (браузер, Node.js, чи щось інше).
- Запусти простий JS-файл у двох різних runtime-ах і порівняй вивід.
- Перевір, чи використовуєш ти залежності, які не працюють у твоєму runtime.
- Якщо проєкт новий — розгляни альтернативи (Bun, Deno) і порівняй продуктивність.
- Додай перевірку версії runtime в скрипти збирання, щоб уникнути несподіванок.
Коли ти перестанеш думати «JavaScript скрізь однаковий» і почнеш думати «який runtime мені потрібен» — рівень розуміння вирастет відчутно.