Переезд с Node.js на Bun: реальные проблемы, баги и подводные камни

Решили попробовать Bun вместо Node.js? Узнайте о реальных подводных камнях миграции: несовместимость пакетов, проблемы с базами данных, отладка и другие баги, с которыми вы точно столкнётесь.

WebРазработка

6 мин

Введение: что такое Bun и почему о нём все говорят

Bun — это новый JavaScript runtime, который обещает быть в разы быстрее Node.js и Deno. Его разработчики утверждают, что установка пакетов происходит в 10-20 раз быстрее, а запуск приложений — в 3-4 раза. Звучит заманчиво, правда? Но что происходит, когда вы решаете перенести реальный проект на Bun?

В этой статье я расскажу о реальных проблемах, с которыми сталкиваются разработчики при миграции, и как их решить.

Что обещает Bun?

Перед тем как говорить о проблемах, давайте разберёмся, что вообще предлагает Bun:

  • Скорость: Написан на Zig, использует JavaScriptCore вместо V8

  • Встроенные инструменты: bundler, transpiler, package manager в одном флаконе

  • Совместимость: Заявлена поддержка большинства Node.js API

  • TypeScript из коробки: Не нужен отдельный компилятор

  • Web API: Поддержка современных браузерных API на сервере

Звучит идеально. Но практика показывает другое.

Проблема №1: Неполная совместимость с Node.js

Что ожидается?

Разработчики Bun обещают совместимость с Node.js API на 90%+. В теории ваш код должен просто заработать.

Реальность:

// Этот код работает в Node.js
const fs = require('fs');
fs.watch('./files', { recursive: true }, (event, filename) => {
  console.log(`${filename} changed`);
});

В Bun опция recursive для fs.watch() не работает на некоторых операционных системах. Вы получите ошибку или молчаливый отказ в работе.

Решение:

Проверяйте документацию Bun для каждого используемого API. Часто приходится использовать альтернативные библиотеки:

// Альтернатива для Bun
import { watch } from 'chokidar';

watch('./files', { 
  ignoreInitial: true 
}).on('all', (event, path) => {
  console.log(`${path} changed`);
});

Проблема №2: npm пакеты с нативными модулями.

Что ожидается?

Bun должен поддерживать большинство npm пакетов, включая те, что используют нативные модули.

Реальность:

Многие популярные пакеты просто не работают:

bun install sharp  # Популярная библиотека для работы с изображениями

При попытке использовать:

import sharp from 'sharp';
const image = sharp('input.jpg');
// Error: Cannot find module "sharp"

Проблемы с другими пакетами:

  • bcrypt — нативные биндинги не поддерживаются

  • node-gyp зависимости — требуют полной пересборки

  • sqlite3 — работает нестабильно

Решение:

Ищите альтернативы на чистом JavaScript:

// Вместо bcrypt используйте bcryptjs
import bcrypt from 'bcryptjs';
const hash = await bcrypt.hash('password', 10);

// Вместо sharp можно использовать Bun.file() + Canvas API
import { createCanvas, loadImage } from 'canvas';

Проблема №3: Различия в поведении EventEmitter

Что ожидается?

EventEmitter должен работать точно так же, как в Node.js.

Реальность:

const EventEmitter = require('events');
const emitter = new EventEmitter();

// В Node.js это работает
emitter.on('event', async () => {
  await someAsyncOperation();
});

emitter.emit('event');
console.log('Event emitted');
// Node.js: "Event emitted" → async operation
// Bun: Может упасть или выполниться в другом порядке

Обработка ошибок в асинхронных обработчиках событий отличается, что может привести к необработанным promise rejection.

Решение:

Всегда оборачивайте асинхронные обработчики:

emitter.on('event', (data) => {
  (async () => {
    try {
      await someAsyncOperation(data);
    } catch (error) {
      console.error('Error in event handler:', error);
    }
  })();
});

Проблема №4: Различия в работе с путями

Реальность:

import path from 'path';
import { fileURLToPath } from 'url';

// Node.js + ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Bun ведёт себя по-другому
console.log(__dirname); // Может быть undefined или неправильным

Решение:

Используйте встроенные возможности Bun:

// Правильный способ для Bun
const currentFile = import.meta.path;
const currentDir = import.meta.dir;

console.log('Current file:', currentFile);
console.log('Current directory:', currentDir);

Проблема №5: Переменные окружения и dotenv

Что ожидается?

Bun автоматически загружает .env файлы, поэтому dotenv не нужен.

Реальность:

// В Node.js с dotenv
require('dotenv').config();
console.log(process.env.DATABASE_URL);

// В Bun
console.log(process.env.DATABASE_URL); // Может не работать

Bun загружает .env, но порядок приоритетов отличается, и некоторые значения могут не подхватываться.

Решение:

// Явно загружайте конфигурацию
import { config } from 'dotenv';
config({ path: '.env' });

// Или используйте Bun API
const env = Bun.env;
console.log(env.DATABASE_URL);

Проблема №6: Работа с базами данных

Реальность с PostgreSQL:

// Node.js + pg
import pg from 'pg';
const { Pool } = pg;

const pool = new Pool({
  connectionString: process.env.DATABASE_URL
});

// В Bun может работать нестабильно
const result = await pool.query('SELECT * FROM users');
// Иногда зависает или падает с timeout

Решение:

Используйте нативную поддержку SQLite в Bun или альтернативные драйверы:

// Bun имеет встроенную поддержку SQLite
import { Database } from 'bun:sqlite';

const db = new Database('mydb.sqlite');
const query = db.query('SELECT * FROM users');
const users = query.all();

// Для PostgreSQL используйте postgres.js
import postgres from 'postgres';
const sql = postgres(process.env.DATABASE_URL);
const users = await sql`SELECT * FROM users`;

Проблема №7: Тестирование

Реальность:

// Jest конфигурация не работает напрямую
// package.json
{
  "scripts": {
    "test": "jest"
  }
}

// bun test запускает собственный test runner

Bun имеет встроенный test runner, но он не совместим с Jest полностью.

Решение:

Перепишите тесты под Bun:

// test/example.test.ts
import { expect, test, describe } from 'bun:test';

describe('Math operations', () => {
  test('addition', () => {
    expect(2 + 2).toBe(4);
  });
  
  test('async operation', async () => {
    const result = await fetchData();
    expect(result).toBeDefined();
  });
});

Запуск:

bun test

Проблема №8: Hot Reload и Watch Mode

Реальность:

# Node.js с nodemon
nodemon server.js

# Bun
bun --watch server.ts
# Работает, но может не перезапускаться при изменении некоторых файлов

Watch mode в Bun иногда пропускает изменения или перезапускается слишком часто.

Решение:

Добавьте явные паттерны:

// bunfig.toml
[watch]
ignore = ["node_modules", "dist", ".git"]
include = ["src/**/*.ts", "src/**/*.js"]

Или используйте внешние инструменты:

npm install -D nodemon
nodemon --exec bun run server.ts

Проблема №9: Debugging

Реальность:

Node.js имеет отличные инструменты для отладки через Chrome DevTools или VS Code. В Bun это работает... иначе.

# Node.js
node --inspect-brk server.js

# Bun
bun --inspect server.ts
# Не всегда корректно показывает стек вызовов

Решение:

Используйте консольную отладку и логирование:

// Добавляйте подробное логирование
console.log('Debug point 1:', { variable1, variable2 });

// Используйте утилиты для форматирования
import util from 'util';
console.log(util.inspect(complexObject, { depth: null, colors: true }));

// Или Bun.inspect()
console.log(Bun.inspect(complexObject));

Проблема №10: Размер bundle и tree-shaking

Что ожидается?

Bun должен создавать оптимизированные bundle с автоматическим tree-shaking.

Реальность:

bun build ./src/index.ts --outdir ./dist
# Bundle может быть больше, чем ожидалось

Tree-shaking не всегда работает эффективно, особенно с CommonJS модулями.

Решение:

Используйте только ES modules и проверяйте результат:

// ❌ Плохо
const lodash = require('lodash');

// ✅ Хорошо
import { map, filter } from 'lodash-es';

// Конфигурация сборки
bun build ./src/index.ts \
  --outdir ./dist \
  --minify \
  --splitting \
  --target browser

Практические рекомендации для миграции

Шаг 1: Начните с малого

Не переносите весь проект сразу. Создайте небольшой test-проект:

mkdir bun-test && cd bun-test
bun init

Шаг 2: Проверьте зависимости

Создайте список всех npm пакетов и проверьте их совместимость:

# Установите зависимости
bun install

# Запустите тесты
bun test

Шаг 3: Постепенная миграция

// Создайте переходный слой
// adapter.ts
export const runtime = {
  isNode: typeof process !== 'undefined' && !process.versions.bun,
  isBun: typeof process !== 'undefined' && !!process.versions.bun
};

export function getAdapter() {
  if (runtime.isBun) {
    return import('./adapters/bun');
  }
  return import('./adapters/node');
}

Шаг 4: Тестирование в production-like окружении

# Dockerfile для Bun
FROM oven/bun:1 as base
WORKDIR /app

COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile

COPY . .
RUN bun run build

CMD ["bun", "run", "start"]

Когда НЕ стоит переходить на Bun

Не переходите, если:

  1. Проект использует много нативных модулей — сэкономленное время на скорости потеряете на поиске альтернатив

  2. Критически важна стабильность — Bun всё ещё молодой, баги встречаются

  3. Команда не готова к экспериментам — придётся разбираться с новыми проблемами

  4. Используете специфичные Node.js features — потоки, workers, специальные API

  5. Нужна поддержка старых версий — Bun не поддерживает legacy код

Когда стоит попробовать Bun

Переходите, если:

  1. Создаёте новый проект — нет багажа старого кода

  2. Фокус на скорости разработки — быстрая установка пакетов реально ускоряет работу

  3. Используете современный стек — TypeScript, ES modules, современные библиотеки

  4. Готовы к экспериментам — можете потратить время на решение проблем

  5. Нужен встроенный bundler — не хотите настраивать webpack/vite

Реальный пример миграции простого Express-приложения

Node.js версия

// server.js
const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.json({ message: 'Hello from Node.js' });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Bun версия (что работает)

// server.ts
import { serve } from 'bun';

serve({
  port: 3000,
  fetch(req) {
    const url = new URL(req.url);
    
    if (url.pathname === '/') {
      return new Response(
        JSON.stringify({ message: 'Hello from Bun' }),
        { headers: { 'Content-Type': 'application/json' } }
      );
    }
    
    return new Response('Not Found', { status: 404 });
  },
});

console.log('Server running on port 3000')

Bun — это интересная технология, но она всё ещё сырая. Реальные проблемы при миграции включают:

Совет начинающим: Используйте Bun для новых pet-проектов и экспериментов. Для production-приложений пока лучше оставаться на Node.js, если у вас нет конкретных причин для перехода.

В Кодике мы не просто рассказываем теорию — вы получаете практические навыки через реальные задания и проекты.

Что вы получите:

  • Структурированные курсы — от основ до продвинутых тем

  • Практические задания — закрепляйте знания на реальных примерах

  • Пошаговые разборы — понимайте, как и почему код работает

  • Актуальные технологии — изучайте то, что используется в индустрии

Нужна поддержка и общение?

Присоединяйтесь к нашему активному Telegram-каналу — уже более 2000 единомышленников, которые:

  • Обсуждают технологии и делятся опытом

  • Помогают друг другу с решением задач

  • Делятся полезными материалами и находками

  • Вместе растут как разработчики

Переходите в Кодик — начните свой путь в программировании с поддержкой сообщества и качественными материалами! 🚀

Комментарии