Bun — успішна революція чи невеличке повстання

💡 Усі статті, обговорення, новини про Front-end — в одному місці. Приєднуйтесь до Front-end спільноти!

Усім привіт, мене звати Олександр Зіневич, я працюю Engineering Director (Node.js, Ruby) в компанії Avenga.

Node.js сьогодні є однією з найпопулярніших технологій для веброзробки та, по суті, основним середовищем виконання JavaScript на сервері. З розвитком технологій, новими трендами у веброзробці та внаслідок критики Node.js від самого Раяна Дала (Ryan Dahl), створюються нові, альтернативні середовища виконання JavaScript на сервері, як от Deno чи Bun.

Середовище виконання Bun завдяки низці своїх особливостей та переваг позиціонується творцями як не просто гідна, а й часом беззаперечна альтернатива Node.js. Частота, з якою Bun згадується у більшості технічних інформаційних ресурсах, наштовхує на думку, що у світі серверного JavaScript відбувається революція, проте чи справді це так? Можливо, ми є свідками лише невеличкого локального повстання? У цій статті я спробую дати відповідь на ці запитання.

Що таке середовище виконання JavaScript (runtime)

Тривалий час єдиним місцем, де можна було використовувати JavaScript, був веббраузер. У 2009 році Раян Дал презентував світові нову технологію під назвою Node.js.

Node.js складався з рушія V8 (того самого, що був у браузері Google Chrome), а також набору бібліотек й інструментів для забезпечення асинхронної I/O-архітектури та взаємодії із операційною системою (сервером). Сукупність цих усіх складових і є середовищем виконання.

Минув час, і стали очевидними не тільки сильні, а й слабкі сторони платформи, які у своїй відомій доповіді 10 Things I Regret about Node.js освітив знов-таки Раян Дал.

До чого тут Bun

Поки автор Node.js активно просуває свою нову платформу Deno, Джаред Самнер (Jarred Sumner), виділивши для себе низку проблем у Node.js, вирішив створити нове середовище, яке зможе стати революційним у JavaScript-розробці на сервері.

Bun як альтернативне середовище виконання JS на сервері існує вже більше як рік. Майже весь цей час спільнота розробників та ентузіастів тестували це середовище, давала свої відгуки, долучалася безпосередньо до розробки. У вересні 2023 світ побачив офіційний реліз Bun 1.0, який, зі слів авторів, є production-ready. Bun неодноразово позиціонувався як drop-in-заміну для Node.js (інакше кажучи, що перейти з Node.js на Bun можна лише в кілька кліків та команд). Окрім цього, Bun має низку інших переваг над:

  • вбудована підтримка TypeScript;
  • вбудований bundler;
  • вбудований раннер для тестів;
  • підтримка нативного Node.js API;
  • покращена швидкодія.

Як це працює

Однією з основних причин створення Bun було намагання авторів спростити розробку на Node.js і покращити швидкодію там, де це можливо. Саме тому Bun написаний з використанням низькорівневої мови програмування Zig, що забезпечило хорошу якість усіляких оптимізацій. Орім цього, Bun використовує не V8, а JavaScriptCore Engine (що застосовується у браузері Safari і також однією з основних переваг у порівнянні з V8 має швидкодію).

Через те, що з коробки Node.js не надавав ні транспайлера, ні бандлера, ні підтримки Typescript, ні багато чого іншого, це вилилося в потребу інсталювання та підтримки великої кількості сторонніх бібліотек й інструментів лише для того, щоб запустити чи зібрати проєкт.

Bun, власне, вирішує цю проблему, бо надає багато чого відразу з коробки.

На зображені ви можете бачити порівняльну схему Node.js та Bun:

Так, наприклад, Bun надає змогу відразу після інсталювання запускати файли з розширеннями: .js, .jsx, .ts, .tsx завдяки вбудованому транспайлеру. Немає потреби встановлювати додаткові бібліотеки чи інструменти — це просто працює.

Те саме стосується і підтримки CommonJS та ES-модулів. Файли з розширенням .cjs, .mjs, звісно, підтримуються відразу після встановлення Bun, але якщо ви їх не використовуєте у вашому проєкті, то require та import працюватимуть без потреби в додаткових налаштуваннях.

Це лише невеличка частинка всього того, що автоматизує та надає з коробки Bun, детальніше рекомендую почитати про це на основному вебсайті.

Чи справді все так добре

Розробники позиціонують Bun як заміну Node.js, що не вимагає додаткових конфігурацій і налаштування. Встановив — і готово!

Я вирішив перевірити, чи справді все так добре на невеличкому готовому проєкті, що використовує Node.js, Express та Mongo. Зробивши форк цього репозиторія, я відразу встановив Bun так, як про це написано в офіційній документації:

curl -fsSL <<a href="https://bun.sh/install">https://bun.sh/install</a>> | bash

Наступним кроком у відповідному фолдері з проєктом я видалив node_modules і виконав команду bun install.

Bun встановив усі необхідні залежності, а також згенерував свій бінарний файл bun.lockb, який чимось схожим на yarn.lock чи package.lock. Тут відразу хотів би відзначити швидкість, з якою Bun встановлює всі залежності. Це відбувається справді набагато швидше, ніж під час роботи з npm. Ця швидкість повʼязана з численними оптимізаціями, а також підходами до роботи із залежностями та кешом. Тому в інших розробників виникають труднощі та неочікувані баги, наприклад ось цей або ж цей.

Безпосередньо перед запуском я виправив команду, що відповідає за запуск у девелопмент-стані:

"dev": "cross-env NODE_ENV=development bun src/index.js",

Тепер запустити застосунок можна командою bun start.

На жаль, очікуваного старту так і не відбулось. У консолі помилка:

error: Cannot find package "mongodb-extjson" from "/Users/…./node_modules/mongodb/lib/core/utils.js”

З такою помилкою зіштовхнувся не тільки я, а й інші розробники тут. Bun не працює не тільки з моїм поточним проєктом, а й з проєктами, написаними з використанням Nest.js фреймворку.

Додатково є проблеми і з pm2 + Bun.

У багатьох випадках причинами цих та інших проблем є, власне, не повна сумісність із Node.js Native API. В офіційній документації ви можете переглянути відповідні статуси. Автори активно працюють над тим, щоб покращувати цю сумісність, і виправляти проблеми як із Node.js Native API, так і в роботі з окремими пакетами.

Що ж зі швидкістю

Розглянемо швидкодію Bun та Node.js на кількох прикладах.

Запис, читання файлів

Використаймо невеличкий код для запису та вичитування файлів:

 

const fs = require('fs').promises

const FILE_SIZES = [1024, 10 * 1024, 100 * 1024]; // 1KB, 10KB, 100KB

const FILE_NAME_TEMPLATE = 'testfile_{size}.txt';

async function writeFile(size) {

    const data = Buffer.alloc(size, 'a'); // creates a buffer filled with letter 'a'

    const fileName = FILE_NAME_TEMPLATE.replace('{size}', size);

    console.time(`Writing ${size} bytes`);

    try {

        await fs.writeFile(fileName, data);

        console.timeEnd(`Writing ${size} bytes`);

        console.log(`Written ${size} bytes to ${fileName}`);

    } catch (err) {

        throw new Error(`Error writing file: ${err.message}`);

    }

}

async function readFile(size) {

    const fileName = FILE_NAME_TEMPLATE.replace('{size}', size);

    console.time(`Reading ${size} bytes`);

    try {

        const data = await fs.readFile(fileName);

        console.timeEnd(`Reading ${size} bytes`);

        console.log(`Read ${data.length} bytes from ${fileName}`);

    } catch (err) {

        throw new Error(`Error reading file: ${err.message}`);

    }

}

async function performIOOperations() {

    for (const size of FILE_SIZES) {

        await writeFile(size);

        await readFile(size);

    }

}

performIOOperations()

    .then(() => console.log('I/O operations completed'))

    .catch((error) => console.error(`An error occurred: ${error.message}`));

Запустивши цей код у Node.js, матимемо результати:

Цей самий код, запущений на Bun, матиме такі результати:

Слід зауважити ще одну річ, яку Bun дає нам з коробки, — це форматування виводу в термінал 😍.

З результатів можна зробити висновки, що Bun не завжди має кращу швидкодію, якщо порівнювати з Node.js. У цьому конкретному прикладі ми не вносили жодних змін у написаний код, але Bun дає нам власні API для роботи з файлами, такі як: Bun.file(), Bun.write() тощо. Детальніше про саме API можете почитати тут. Використавши нативне API від Bun, отримаємо такий код:

//const fs = require('fs').promises;

const FILE_SIZES = [1024, 10 * 1024, 100 * 1024]; // 1KB, 10KB, 100KB

const FILE_NAME_TEMPLATE = 'testfile_{size}.txt';

async function writeFile(size) {

    const data = Buffer.alloc(size, 'a'); // creates a buffer filled with letter 'a'

    const fileName = FILE_NAME_TEMPLATE.replace('{size}', size);

    console.time(`Writing ${size} bytes`);

    try {

        await Bun.write(fileName, data);

        console.timeEnd(`Writing ${size} bytes`);

        console.log(`Written ${size} bytes to ${fileName}`);

    } catch (err) {

        throw new Error(`Error writing file: ${err.message}`);

    }

}

async function readFile(size) {

    const fileName = FILE_NAME_TEMPLATE.replace('{size}', size);

    console.time(`Reading ${size} bytes`);

    try {

        const data = await Bun.file(fileName);

        console.timeEnd(`Reading ${size} bytes`);

        console.log(`Read ${data.length} bytes from ${fileName}`);

    } catch (err) {

        throw new Error(`Error reading file: ${err.message}`);

    }

}

async function performIOOperations() {

    for (const size of FILE_SIZES) {

        await writeFile(size);

        await readFile(size);

    }

}

performIOOperations()

    .then(() => console.log('I/O operations completed'))

    .catch((error) => console.error(`An error occurred: ${error.message}`));

І результати:

Використання нативного Bun API дає значно кращу швидкодію.

Сервер

Спробуємо виміряти, як буде працювати простий тестовий сервер із мок-даними, з використанням Node.js та Bun. Для прикладу використаймо такий код:

const express = require('express');

const app = express();

const PORT = 3000;

// Sample in-memory data store

let posts = [

    { id: 1, title: 'First Post', content: 'This is the content of the first post.' },

    { id: 2, title: 'Second Post', content: 'This is the content of the second post.' },

    // Add more sample posts as needed...

];

// Middleware to parse JSON requests

app.use(express.json());

// Fetch all posts

app.get('/posts', (req, res) => {

    res.json(posts);

});

// Fetch a single post by ID

app.get('/posts/:id', (req, res) => {

    const post = posts.find(p => p.id === parseInt(req.params.id));

    if (!post) return res.status(404).send('Post not found.');

    res.json(post);

});

// Create a new post

app.post('/posts', (req, res) => {

    const post = {

        id: posts.length + 1,

        title: req.body.title,

        content: req.body.content,

    };

    posts.push(post);

    res.status(201).json(post);

});

// Update a post by ID

app.put('/posts/:id', (req, res) => {

    const post = posts.find(p => p.id === parseInt(req.params.id));

    if (!post) return res.status(404).send('Post not found.');

    post.title = req.body.title || post.title;

    post.content = req.body.content || post.content;

    res.json(post);

});

// Delete a post by ID

app.delete('/posts/:id', (req, res) => {

    posts = posts.filter(p => p.id !== parseInt(req.params.id));

    res.status(204).send();

});

app.listen(PORT, () => {

    console.log(`Server running at <a href="http://localhost">http://localhost</a>:${PORT}/`);

});

Маємо такий результат для Node.js:

І такий для Bun:

Хоч такий сервер не містить комплексної логіки та працює локально, ми також можемо простежувати показники, кращі, ніж у Node.js.

Робота з даними

Розглянемо випадок, коли нам потрібно опрацювати великий об’єкт. Для цього я створив JSON-файл розміром 45 МБ, що містить масив об’єктів такого типу:

{

    "userId": "b1ba31ac-25ce-4432-8e9f-b4cd89da167a",

    "session": "a0dd197b-22bb-4934-8c60-2408912a2a16",

    "timestamp": "2023-09-08T07:43:00.421Z",

    "activity": "LOGOUT",

    "ip": "214.76.47.2",

    "userAgent": "Mozilla/5.0 (X11; Linux i686; rv:7.9) Gecko/20100101 Firefox/7.9.5",

    "location": "Mohammadchester, Virginia, Andorra"

  }

Маємо такий код для Node.js:

import fs from 'fs/promises';

const logs = JSON.parse(await fs.readFile('../userLogs.json', 'utf-8'));

function detectWindowsUsers(logs) {

    return logs.filter(log => log.userAgent.includes('Windows'));

}

console.time('detectWindowsUsers');

const windowsUsers = detectWindowsUsers(logs);

console.timeEnd('detectWindowsUsers');

Запустивши його, отримаємо:

Для Bun використаємо такий код:

const logs = JSON.parse(await Bun.file('../userLogs.json', 'utf-8').text());

function detectWindowsUsers(logs) {

    return logs.filter(log => log.userAgent.includes('Windows'));

}

console.time('detectWindowsUsers');

const windowsUsers = detectWindowsUsers(logs);

console.timeEnd('detectWindowsUsers');

Результат:

У цьому тесті бачимо, що результати майже однакові.

Регулярні вирази

У тому самому файлі виконаймо такий код:

import fs from 'fs/promises';

const logs = JSON.parse(await fs.readFile('../userLogs.json', 'utf-8'));

function detectIPv4(logs) {

    const ipv4Pattern = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g;

    const allIPs = logs.map(log => log.ip.match(ipv4Pattern)).flat();

    return [...new Set(allIPs)];  // Unique IPs

}

function detectChromeUsers(logs) {

    return logs.filter(log => /Chrome/.test(log.userAgent));

}

// Benchmark IPv4 Detection

console.time('IPv4 Detection');

const ipv4Addresses = detectIPv4(logs);

console.timeEnd('IPv4 Detection');

console.log(`Detected ${ipv4Addresses.length} unique IPv4 addresses.`);

// Benchmark Chrome User Detection

console.time('Chrome User Detection');

const chromeUsers = detectChromeUsers(logs);

console.timeEnd('Chrome User Detection');

console.log(`Detected ${chromeUsers.length} Chrome users.`);

Для Node.js результат буде таким:

Для Bun (з відповідними змінами у вичитуванні файлу):

У цьому випадку Bun показує значно кращу швидкодію.

Що ж у підсумку

Bun — це ще дуже молода й амбітна технологія. Її автори вибрали лозунги, з якими змогли б швидко завоювати увагу публіки. «Можливість швидкої заміни Node.js на Bun і зворотна сумісність з переважною частиною Node.js екосистеми», «швидкодія, вища в десятки разів», — хіба ж це все не прекрасно?

Прекрасно поки це лише на відео, а не на проєкті. У реальності бачимо, що легка заміна Node.js на Bun інколи вимагає великих зусиль; швидкодія багато в чому краща за Node.js, але не завжди; npm-пакети працюють, але не всі; а на момент написання цієї статті в репозиторії Bun понад 1800 відкритих тікетів.

Це все типові проблеми для молодої технології, та творці Bun зробили неймовірну роботу. Попереду ще більше праці, оскільки, крім простого фіксу багів, необхідно сформувати повноцінне комʼюніті, яке саме буде підтримувати та розвивати Bun. Попри таку популярність, зараз список основних контрибуторів не надто великий, а основним з них є власне творець Bun.

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

Тому на сьогодні Bun — це лише невеличке локальне повстання, яке здатне перерости в революцію. Уже зараз Bun можна використовувати в тестовому режимі для окремих утиліт, окремих маленьких підпроєктів, але зарано повністю мігруватись і використовувати його для великих кодових баз.

👍ПодобаєтьсяСподобалось9
До обраногоВ обраному1
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Я звісно не профі, але мені здається що коли бан адаптер дороблять до рівня, то буде фестіваль! :-)
Для Astro використовую останнім часом, але і з Rspack подобається)
А ось коли все разом, взагалі чудесно)))) але ще не вийшло)))

Ось рекомендую для ознайомлення.

farm-fe.github.io

Може і смішно звучить а бан використовую якраз для написання ось таких маленьких скриптиків з консоль логами на ts як у статті. P.s. console.table не працює) базова штука

Поки є лише у прев’ю, але вже можна побачити порівняння роботи самих лише рідних вебсерверів з «Hello, World!» на node.js, bun та deno на сайті TechEmpower.

До речі, про TechEmpower. Хто не чув ще про Ditsmod, він теж (як і bun) перший раз з’явився на TechEmpower і сходу має найкращі результати на платформі Node.js, обганяючи навіть Fastify. Окрім цього, холодний старт у Ditsmod-застосунку 20-30 ms (для порівняння — у NestJS він у 10-20 разів повільніший). Споживання пам’яті одне з найекономніших серед Node.js-фреймворків. Починаючи з v2.47, вже працює на ESM (що дуже сприяє хорошому холодному старту).

Якщо захочете змінити фільтр чи походити по закладкам на цьому сайті, то фільтри злітають, але перезавантаження сторінки після кожної зміни лікує цей баг. Офіційний релізу «TechEmpower Round 22» стартане у наступну середу, а зупинеться десь через тиждень. Тоді фільтри при переходах не злітатимуть.

Пояснення чому обрали Zig як основну мову програмування
www.reddit.com/...​am_use_zig_to_create_bun
www.reddit.com/...​sed_for_bun_according_to

все рівно дивний вибір на мою думку) вибирати мову яка ще офіційно не production ready

для світу серверного JS Bun нічого не дав (поки що), а от в світі serverless, де саме холодний старт це критично, Bun дуже добре підходить як заміна node.js

Тому, якщо ви використовуєте серверлесс на JS, то Bun це для вас.

Не платити за них. 20мс на одному запиті, але коли питання доходить до сотні мільнів запитів щомісячно, то які аргументи будуть проти економії коштів ентерпрайзу?

Тоді краще на Go переписати.

то які аргументи будуть проти економії коштів ентерпрайзу?

Golang не панацея. Треба вирішувати проблему найбільш підходящими інструментами, особливо в serverless.

Ну так і Bun не панацея. Треба вирішувати проблему найбільш підходящими інструментами, особливо в serverless. Ці 20ms нічого не зекономлять, а на го можливло зекономити більше.

тут вже питання вартості переписування постає. якщо перехід на Bun займатиме 10хв то виграш очевидний

У вас немає ніяких гарантій що код Bun буде працювати так як на Node.js. Тому ні про які 10хв на перехід мови не йде. 20ms економії не варто того щоб отримати баги в самих неочікованих місцях.

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

Мабуть же писали код на JavaScript чи TypeScript? На скільки важче чи простіше писати на Go?

Ну це можна було і не казати, це ясно усім. Цікаво таки почути порівняння від людини, яка писала обома мовами.

Чому ви вирішили що я пишу на го? В даному випадку я їх порівнював по швидкодії. Я колись дивився го. Але він мені не сподобався.

Я колись дивився го. Але він мені не сподобався.

Так ото ж. Багато хто каже, що Go швидкий, але на ньому значно важче писати, ніж на Node.js. Вище ви так сказали що типу «нащо та економія 20 ms, краще вже тоді для цього використовувати Go». Але простота написання і підтримки багато що важать.

Чоми ви вірішили що на go важко писати? Він простий як двері такий як і js.

це так не працює, не можна вирішувати все однією технологією, бо це те й саме як закручувати шестигранник викруткою phillips. Наведу простий приклад який показував на конфі по Golang колись: візьміть C++ CV, в вас є вибір, або ви берете Python, бо вже знаєте як написать OpenCV hello-world (або классифікатор Хаара), або візьмете GoCV. Результат один і той же — задача виконана, але диявол в деталях і кошторисі на виконання та підтримку. Ви 100% знайдете більше відповідей на StackOverflow по OpenCV на Python, але ви також заплатите за повільний інтерпретатор або, як із GoCV, заплатите часом на вивчення API та відладкою.

цілком підтримую. як і завжди у всьому, питання трейдоффів

На це можуть звернути увагу при умові що Bun вже такий же стабільний як Node, мають купу сацес кейсів, та знайшли гроші все переробити та всіх перевчити.
Реальність трохи інакша. Купа застарілих неефективних технологій і ніхто нічого не спішить переробляти — бо вже працює.
Бізнес чухається тільки якщо є ризик повністю стати, а не зекономити три копійки.

Тому на сьогодні Bun — це лише невеличке локальне повстання, яке здатне перерости в революцію.

Суть цього «повстання» банальне запихування функціоналу чи самих third-party пакетів в бінарник і переписування модулів на найтіві. Це був би плюс, якби нода не дозволяла завантажувати native модулі, а так просто перегружений комбайн який буде рідше оновлюватися, чим в вигляді окремих third-party модулів.
Ну і якщо порівнювати бенчмарками, тоді код на node треба прогрівати, а то негоже порівнювати натів з неоптимізованим js та бенчмаркінгом на console.time на одному проході.

Стосовно оновленнь то припускаю що Ви праві і так і буде 🙂
Навіть прогрітий код в усіх випадках показував у співвідношенні схожі результати.

Так і не зрозумів навіщо він потрібен і які проблеми вирішує?

вбудована підтримка TypeScript;

Вони самі написали свій TS компілятор? Чи використовують tsc або swc? Ніде не знайшов інформацію яку версію TS вони підтримують. Не зрозуміло навіщо це тянути в JS рантайм.

вбудований bundler; вбудований раннер для тестів;

Не зрозуміло навіщо це тянути в JS рантайм.

покращена швидкодія.

Це ж було вже!

По перше, вони ніколи не зможуть досягти повної зворотньої сумістності. Це фізично не можливо, доведено MS Word, який має купу аналогів і не один не відкриває оригінальний файл так як треба. Бо щоб відкривав, треба повторити всі баги оригінального продукту. В вашому випадку всі баги ноди.
По друге, кричати революція і з придиханням розповідати що там якісь пакети швидше ставлятся, ви серйозно ??
Це як кричати що лінукс краще віндовс бо встановлюється на компьютер на 30% швидше чим віндовс і .... шо ?
Де революція??

Короче, bun в ban. А автору, перш ніж застрибувати в кролячу нору непуганих ельфів, трохи більше критичного мислення.

Дякую за Ваш коментар.
Стосовно повної сумісності, цілком погоджуюсь. Проте не впевнений що це є кінцевою метою авторів Bun і що це їм дуже потрібно. На мою думку їм потрібно покрити більшість того що використовується у сучасних проектах і на більшості ринку. Цього буде цілком достатньо щоб рухатись далі.
Стосовно революції, то у своїй статі я задаюсь питанням чи це революція(як кажуть автори) чи все таки ні? і у самому завершенні даю відповідь на це питання — зараз це не революція, але цілком може нею стати. Тому не впевнений де Ви побачили будь які викрики 😉
Ну і загалом приклад з встановленням пакетів цілком собі те місце на яке вартує звернути увагу, бо швидкість білда наприклад у CI/CD пайплайнах чітко конвертується у гроші.
Критичного мислення мені досить вистачає, якраз для того щоб стверджувати що з Bun не все так добре. Більше того він ще зовсім не Production-ready 😉

TLDR: Дива не сталося, що в Bun перевели у нативний код — працює швидше, що не перевели (або що і у Ноді було у нативному коді, як-то парсінг джсона) — працює плюс-мінус однаково.

Мати аналог ноди з іншим двіжком джаваскрипта, з одного боку, добре (конкуренція і все таке), з іншого — за відсутності стандарту на АПІ платформи буде куча дрібних відмінностей та запитів на написання коду, який працює на обох платформах, і куча гемору, повʼязанного з цим (по типу того, як було в епоху ускладнення CSS до запровадження більш-менш нормальної підтримки браузерами стандартів). Але для цього Bun треба стати популярним спочатку. Бо вже є Deno і він не так щоб дуже використовується, наскільки я розумію.

Bun скоріш за все навіть буде рухатись по схожому з Deno шляху. Вибудовувати таку собі платформу де за гроші будуть доступні всякі цікаві фічі. Все ж таки гроші інвесторам треба буде віддавати 🙂

Інвесторам? Відомо що у Bun є інвестори?

Підписатись на коментарі