KahWee - Web Development, AI Tools & Tech Trends

Expert takes on AI tools like Claude and Sora, modern web development with React and Vite, and tech trends. By KahWee.

Switching from Node to Bun - Part 1

I've been refactoring this blog's codebase to transition from Node.js to Bun as the primary runtime and build tool. The results have been remarkable - build times reduced from 37 seconds to around 20 seconds (a 46% improvement), and significantly cleaner code throughout the project. This is Part 1 covering why I made the switch and the key improvements. Part 2 covers the step-by-step migration process.

Why Make the Switch?

Node.js has served me well, but Bun offers compelling advantages:

Native speed: Built on JavaScriptCore (WebKit's JS engine) instead of V8

Integrated tooling: Package manager, bundler, test runner, and more in one binary

TypeScript support: First-class TypeScript without extra dependencies

Modern APIs: Native File and Glob implementations that outperform Node alternatives

TOML support: Built-in parser for cleaner configuration files

The migration promised both performance improvements and code quality benefits - exactly what I look for in technology upgrades.

File Operations: Before and After

The most impactful changes involved replacing Node's file system operations with Bun's native File API. Here's a representative example:

Before (Node.js with fs-extra):

// Check if file paths exist
if (await fs.pathExists(withSlash)) {
  filePath = withSlash;
} else if (await fs.pathExists(withoutSlash)) {
  filePath = withoutSlash;
}

// Read file content
const content = await fs.readFile(filePath);
let htmlContent = content.toString();

After (Bun's File API):

// Check if file paths exist
if (await Bun.file(withSlash).exists()) {
  filePath = withSlash;
} else if (await Bun.file(withoutSlash).exists()) {
  filePath = withoutSlash;
}

// Read file content
const bunFile = Bun.file(filePath);
let htmlContent = await bunFile.text();

The Bun version is not only more concise but also significantly faster. The native File API handles both checking file existence and reading content with less overhead than Node's implementation.

Bun's Native Glob API

File searching was another area where Bun offered substantial improvements. I replaced complex recursive directory traversal with Bun's native Glob API:

Before (Node.js recursion):

async function getMarkdownFilesRecursively(dir: string): Promise<string[]> {
  const entries = await fs.readdir(dir, { withFileTypes: true });

  const files = await Promise.all(
    entries.map(async (entry) => {
      const fullPath = path.join(dir, entry.name);
      if (entry.isDirectory()) {
        return getMarkdownFilesRecursively(fullPath);
      } else if (entry.isFile() && entry.name.endsWith(".md")) {
        return [fullPath];
      }
      return [];
    }),
  );

  return files.flat();
}

After (Bun's Glob API):

async function getMarkdownFilesRecursively(dir: string): Promise<string[]> {
  const glob = new Bun.Glob("**/*.md");
  const files: string[] = [];

  for await (const file of glob.scan({
    cwd: dir,
    absolute: true,
  })) {
    files.push(file);
  }

  return files;
}

The Bun implementation is not only more efficient but also dramatically simpler - the code is easier to read and maintain, with fewer potential edge cases to worry about.

The Secret to Bun's Speed

After completing the migration, I wanted to understand why Bun delivers such impressive performance gains:

JavaScriptCore engine: Bun uses Apple's JSC engine, which is typically faster than V8 for many operations

Native implementations: File, HTTP, and other core APIs are written in Zig rather than JavaScript, eliminating layers of abstraction

Optimized I/O: Bun's native file operations bypass Node's libuv layer

Single binary: All tools (runtime, bundler, package manager) share the same process, eliminating inter-process communication overhead

TypeScript performance: Built-in TypeScript support without separate compilation steps

Zero-copy architecture: Bun's design minimizes memory copies, especially beneficial for file operations

Streamlined dependency handling: Bun's dependency resolution is faster and more efficient than npm

These architectural differences add up to significant performance improvements, especially for I/O-intensive tasks like static site generation.

Continue to Part 2 for the detailed migration process, challenges, and results.