<onWebFocus />

Knowledge is only real when shared.

Bun, Biome/OXC, AI Tools and Rsbuild

February 2, 2024

Reviewing some new Web Development tools on the horizon.

In this post, I will describe my preliminary experience with Bun, Biome/OXC, Cursor AI, CodeWhisperer, and Rsbuild. Bun serves as a replacement for Node.js and comes with its own package manager, replacing npm, which is typically installed alongside Node. Biome and OXC are a Rust-written toolchains that focus on linting and formatting JavaScript and TypeScript code. Bun as well as these new toolchains have been developed from the ground up, emphasizing enhanced performance in the Web Development space.

Cursor AI is a code editor based on VS Code, utilizing AI to assist in code writing. CodeWhisperer, similar to GitHub Copilot, is a free AI tool that provides automatic code suggestions. Rsbuild is a build tool using Rspack, which I have previously discussed in another post. Similar to vite, Rsbuild is primarily designed for building client-side rendered applications.

While I can only share my experience with any of the mentioned tools and recommend them, for readers who prefer a more hands-on approach, I have established a repository at tobua/bun-biome-rsbuild with a setup featuring Bun, Biome/OXC, and Rsbuild. This repository illustrates how these tools neatly integrate and can be effectively utilized. As all the tools are still undergoing significant development, I will actively maintain the project, ensuring it stays current and applying any necessary migrations.

Bun

Bun logoBun serves as an alternative to Node.js, the tool that initially introduced the concept of running JavaScript outside the browser. At this point, Bun has implemented virtually the entire Node.js interface and is compatible with almost every Node.js library. It has been built from the ground up in Zig, resulting in significantly improved performance compared to Node.js. While enhanced performance is one of its key features, Bun boasts various other distinguishing qualities.

One notable feature is its built-in support for running TypeScript seamlessly. Similar to Node.js, Bun accommodates both the traditional CommonJS (CJS) syntax and the newer ECMAScript Modules (ESM), allowing developers to use either or both approaches simultaneously. Additionally, Bun includes a built-in test runner that closely mirrors the interfaces of popular testing frameworks like Jest and Vitest. Notably, it far surpasses the recently introduced test runner in Node.js, as discussed in a previous post on testing.

For developers accustomed to working with Jest or Vitest, transitioning to Bun is straightforward, and migrating existing tests is usually achievable with reasonable effort. Moreover, Bun comes equipped with its own package manager, known for its remarkable speed, which seamlessly connects to the npm registry.

While nearly anything that runs on Node can also run in Bun, the reverse is not necessarily true, and it is typically not desired. Bun comes with its own GitHub Action called setup-bun and can be chosen as a runtime when deploying to Vercel. Jared Sumner, who received the Thiel Fellowship grant, wrote Bun. At present, Bun employs several programmers and has secured significant investments. The company behind Bun is called Oven, and its future goal appears to be offering serverless hosting specifically tailored for the Bun runtime. Although serverless functions on Vercel still run on Node, edge functions have a separate, smaller runtime based on V8. This usually isn't a problem since the interfaces provided by Bun aren't intended for use in serverless functions in the first place.

Bun is currently undergoing heavy development, resulting in frequent releases with numerous bug fixes. When working with it, it's advisable to first update to the most recent version using the command bun upgrade. While Windows support is still in the experimental phase, it's worth noting that I've encountered tests that passed successfully on macOS but failed on the Ubuntu GitHub action. In such instances, I resolved the issue by switching the action to macos-latest, an option provided as a runtime by GitHub.

@bunjavascripts avatar image

Bun

@bunjavascript · Jan 21

In 10 days, Bun for Windows ships.

If you encounter difficulties, a quick test run with Node.js may reveal that a library is not yet compatible with Bun. I faced this situation when I was using msw to mock fetch in certain tests. As mentioned on 𝕏 by the official Bun account, it is an official goal for the Bun team to become the default runtime for web development in 2024, and based on current progress, they appear to be on track to achieve this milestone. In my next system restoration, I plan to exclusively install Bun and forego Node.js.

@bunjavascripts avatar image

Bun

@bunjavascript · Dec 8, 2023

We have one goal for 2024

Flip the default backend JavaScript runtime from Node.js to Bun

What lies ahead for Node.js? In the past, an alternative to Node.js called Deno emerged. Deno, in pursuit of its distinct goals, deviates significantly from Node.js, resulting in frequent incompatibility. The lack of these goals being shared by developers and compatibility issues are likely reasons for Deno's struggles. When yarn sought to replace npm by introducing compelling new features, many users made the switch. However, npm maintained its relevance through a comprehensive rewrite, incorporating essential features pioneered by yarn. In the context of Node.js, a full rewrite or other significant overhauls seem highly improbable due to the sheer magnitude of the undertaking. This situation positions Bun to potentially emerge as the dominant runtime in the near future.

Unlike npm, Bun can directly execute scripts from your package.json. This means that running npm run lint can simply be achieved with bun lint. However, it's important to note that this streamlined approach doesn't apply to bun build and bun test since Bun incorporates both a built-in bundler and a test suite. If you wish to override any of these in your scripts, execute them using bun run build.

OXC and Biome

Oxlint

OXC Logoeslint, which is responsible for highliting and fixing potential problems in your code, and prettier, which formats written code in a standardized way, are both written in JavaScript. Both tools run locally on the system and the same code is run frequently. In large codebases this opens the door for major performance improvements when using lower level languages. While linting and formatting are separate tasks they are very similar and could better be handled by a single tool. This is where oxlint which handles both tasks comes in. OXC is written in Rust and still heavily in development.

Both eslint and prettier have been pioneering tools when they came out. Both tools come with some defaults but each linting or formatting rule they apply can be configured. This at first lead to a large ecosystem of different configurations. By now the dust has somewhat settled and it's possible to devise reasonable default configurations that will work for a lot of developers.

VS Code Extension

Linting and formatting processes can be integrated into the build, but they are more conveniently applied directly in the code editor upon saving a file. OXC recently introduced the OXC VS Code Extension, making it accessible to everyday users. Although the extension may encounter occasional crashes, it is an essential element for any development workflow.

Typechecking

While OXC itself does not plan to include type checking, it will integrate with ezno. Currently, there exists only one type checking implementation written in JavaScript by the TypeScript team at Microsoft. While the syntax for TypeScript continues to settle, the language is still evolving at a rapid pace. This, coupled with the challenge of implementing a type checking mechanism that is reasonably compatible with the current TypeScript syntax, is what makes attempts to write a faster type checker in Rust difficult. Another endeavor to achieve this was stc, the Speedy Type Checker, which has recently been somewhat abandoned.

Many components of the current web development toolchain have transitioned to faster implementations in Rust or other languages, underscoring type checking as the slowest bottleneck. Since TypeScript remains a non-compiled language, performing type checking in the editor proves sufficient, as only the current file needs analysis after a change. Due to this, there is no real necessity for the build tool to recheck after a minor modification. However, type checking remains crucial for production builds, and in extensive projects, it may also impede the editor's speed. With this consideration, I remain optimistic that a swifter type checking implementation will emerge at some point.

Current Status

While OXC is already usable, it is still in development, and many linting rules are currently missing. Therefore, I do not recommend its adoption, except for trying it out. Linting and formatting are subjective aspects, and the absence of certain rules typically does not result in significant issues. The formatting feature is still in development and cannot be tested yet. Oxlint is included in the example project as an additional linter command. Oxlint aims to achieve parity with ESLint, and there is an eslint-plugin-oxlint designed to deactivate ESLint rules already covered by oxlint. This eliminates the need to run these rules with ESLint, resulting in a more efficient process since ESLint is comparatively slower.

Unlike OXC, Biome, which I'm describing below, is much more mature, and I can confidently recommend it for smaller projects.

Biome

Heads up: It's spelled Biome and not Binome! 😂 A biome refers to the biological community that has developed around a physical environment. In this context, Biome encompasses tools that have evolved around JavaScript code.

Biome Logo@biomejs/biome, born out of the now-abandoned Rome tools, aims to accomplish the same goals as OXC and deserves attention. Similar to Bun, Biome appears to be on the verge of becoming mainstream and is poised to replace eslint and prettier permanently. One of the creators of prettier recently initiated a bounty challenge, offering a prize of $25,000 for the tool that first passes 95% of the prettier test suite. Biome successfully claimed this prize within about a week with its formatter. Christopher Chedeau, also known as Vjeux, who sponsored the challenge, likely has a good understanding of the Biome team and was aware of their capability to create an effective formatter.

Partly due to the challenge, and unlike OXC, Biome already boasts a functional formatter that essentially implements the entire prettier specification. Its linting capabilities are also quite decent, featuring numerous already implemented rules. As such, it is well-suited for use in less critical projects where experimentation with new features can take place, and performance becomes a significant factor. In the example project I described earlier, I incorporated Biome as the linter and formatter. Furthermore, Biome already includes a VS Code extension that can be enabled on a per-project basis. Below is the .vscode/settings.json file from the example project, enabling formatting for JavaScript with Biome upon saving. Prettier is employed as a fallback, as Biome supports fewer languages at this point.

{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "[javascript][javascriptreact][typescript][typescriptreact]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "editor.formatOnSave": true
}

Earlier, in a similar manner, SWC, also written in Rust, has successfully replaced Babel for transpilation. In doing so, it has streamlined the extensive Babel plugin ecosystem, as developers have been able to largely agree on some common language syntax. Linting and formatting are somewhat more subjective. While some rules just make sense and have become standard, configurations still vary greatly between projects. For most of these rules—such as the use of semicolons or not, tabs, or spaces—the configuration can be extended quickly. However, nobody wants to study all the rules before getting started, so it's essential to use a base configuration. eslint-config-airbnb has been the most popular choice for ESLint. Configuring Biome still appears to be a tedious task, and I haven't come across any widely adopted shared configurations. The example project I've provided enables all rules, and so far, that seems to be a decent configuration.

New AI Tools: Cursor AI / CodeWhisperer

Cursor AI editor in action

Screeshot of Cursor AI with CodeWhisperer enabled.

Cursor AI logoBased on VS Code, the Cursor AI editor incorporates features specifically designed for coding with AI assistance. Pressing ⌘ + K on a line of code opens the inline chat prompt, generating code based on the current selection. On the right side of the editor, there is a chat window for asking questions, akin to ChatGPT, but within the editor and with awareness of the current code, as well as the ability to reference other files. To initiate a chat, press ⌘ + L and, if desired, the selected code will be inserted into the chat. The chat can also be used to inquire about the entire codebase, limited by the context size supplied to either gpt-3.5-turbo or gpt-4. Both GPTs won't be aware of recently launched updates, providing the option to crawl documentation sites from the web, which will then be added to the context. Lastly, there's an option to automatically have AI fix lint errors, similar to the built-in eslint --fix option (which I highly recommend, but only works for some rules).

CodeWhisperer is similar to GitHub Copilot, with the key distinction that it's free. Usage is straightforward, and it only takes a little time to become accustomed to it. Primarily, it completes lines of code that you are starting to write, but it can also generate code suggestions based on comment blocks that you insert. In comparison to ChatGPT, it offers the advantage of not requiring you to leave the editor to get code tailored for you. The AI is also aware of the code in the current file, enhancing the quality of suggestions. Suggestions automatically appear in light gray while typing and can be accepted by pressing Tab. To manually trigger suggestions from the current cursor, press ⌥ + C. The Up and Down arrow keys allow navigation between different suggestions, while Esc will reject the current suggestion. Installing CodeWhisperer will also automatically add Amazon Q (Preview), another chat assistant similar to the one built into Cursor AI.

Both Cursor and CodeWhisperer have one main advantage over ChatGPT, and that is context awareness, thanks to being integrated into the editor. CodeWhisperer makes suggestions based on the code of the current file, while Cursor's chat has access to the current file, and other files can also be referenced when asking questions.

Creating epic-language with AI

To test Bun along with these new AI tools, I have embarked on creating a new plugin as part of the upcoming Epic Framework. The source code and further usage instructions for epic-language can be found on GitHub. Translations are a common aspect of every application and, at least for client-side applications, are quite straightforward to implement.

import { create, Language } from 'epic-language'

const { Text } = create({
  // Initial translations in default language.
  translations: englishSheet,
  // Additional translated language sheets.
  sheets: {
    [Language.es]: spanishSheet,
    [Language.zh]: chineseSheet,
  },
})

const Heading = <Text>title</Text> // <p>My Title</p>

This plugin stands out by simplifying the automatic generation of translations using AI. Modern language models, such as OpenAI's GPT, excel at translating text. While these translations may not always be flawless, they can be individually overridden. Most importantly, they enable rapid development and especially the deployment of various realistic languages without additional effort.

In scenarios where there are few translations or, as in the case of a React Native application, where they will be bundled anyway, translations can be generated during the build using the CLI. For web applications, it's more efficient to load only the translations required by the user. To achieve this, translations can also be generated during the build using the CLI and stored on a CDN for quick access. By leveraging Serverless functions, there's an opportunity to avoid the need for any translations to be created during the build. The plugin provides integrations for Serverless and Edge functions that handle translation, caching, and create and return results only when needed. The example below illustrates a configuration where a route to a CDN or Serverless function is specified.

import { create } from 'epic-language'

const { translate } = create({
  // Initial translations in default language.
  translations: { title: 'My Title' },
  // Route to load translations for user language.
  route: '/api/translations',
})

translate('title') // My Title

My Sobering Experience

Alluding to a previous blog post in June titled AI to the Rescue? where I detailed my initial experiences using ChatGPT for programming, this time I delved deeper. Essentially, these improvements in developer quality of life may not have a profound impact on productivity. Nevertheless, it goes without saying that we should embrace any enhancements available. Coding with AI, as demonstrated, always appears impressive, but the true value emerges through consistent practice. The more you utilize it, the more adept you'll become at leveraging AI for assistance.

Switching to Cursor AI has proven to be a great decision for me, and I plan to continue using this editor. The ability to swiftly edit a line with ⌘ + K and engage in a contextual discussion about a selected paragraph by pressing ⌘ + L creates a much smoother workflow compared to switching to ChatGPT in the browser and pasting the code there. Although ChatGPT remains valuable for tackling specific, more challenging problems that require a back-and-forth approach. CodeWhisperer, even though it may only assist occasionally, has become an indispensable tool for me. The suggestions it provides are often helpful and do not significantly disrupt the regular workflow. I appreciate its free availability, but regrettably, it's necessary to reauthorize through the browser almost every day when reopening the editor.

@xlr8harders avatar image

xlr8harder

@xlr8harder · Jan 10

I have gotten out of the habit of using ChatGPT and Cursor while coding. Not sure why.

As the above tweet indicates, some developers are already distancing themselves from these AI tools. Although they are undoubtedly helpful, and I continue to recommend them, some developers who may have been more optimistic in the beginning appear to be experiencing a degree of disillusionment recently. For a comprehensive understanding of what everyday usage of AI in coding entails, consider watching the following live coding video by Steve. Steve, who works at builder.io, possesses extensive experience with AI and has implemented sophisticated AI tools to facilitate development. Nonetheless, you'll observe that he isn't doing much more than confirming some GitHub Copilot suggestions.

Despite the increased integration of AI in developing the epic-language plugin, it has still demanded a considerable amount of effort. The tool proves beneficial for numerous tasks, yet more intricate responsibilities, such as deciding which features to implement and determining the interface's appearance, remain mentally taxing, with limited assistance from AI in these aspects. When dealing with newer or less documented features, such as the Edge runtime, the AI may not provide assistance. Likewise, when encountering challenging debugging issues where essential information is hard to access, the AI may be unable to offer support.

Rspack: Rsbuild, Rspress and Modern.js

Rsbuild LogoWhen creating websites with a frontend framework like React, there are three fundamental approaches to serving the application, with the best choice usually depending on the context. The first is client-side rendering (SPA), which is what Rsbuild does. The second one involves prerendering during the build and then serving each page as a multi-page application; this is the approach taken by Rspress. The last and most dynamic approach involves rendering on the server, combining a single-page application (SPA) with a multi-page application (MPA), and in the Rspack ecosystem, it is handled by Modern.js.

Rsbuild is a framework for constructing client-side web applications in various frameworks. It is equipped with an interactive CLI npm create rsbuild@latest that assists in getting started by generating a template and incorporating the required plugins. It bears a striking resemblance to my wrapper around Rspack, called papua. During testing, I haven't encountered any issues, and it appears to be a viable alternative to vite.

Please note that Rspack is still under heavy development, and none of the tools associated with it have reached mainstream status. However, opting to use Rspack as a replacement for webpack in a project is currently the best choice. Rsbuild is well-suited for individuals who have previously used create-react-app or are seeking something familiar. While vite is very similar to Rsbuild, for those accustomed to webpack's configuration, it is likely the better choice.

Rspress is a static site generator useful for creating websites based on static content, such as documentation. For this approach to make sense, it's important that all the data and content are available at build time. Modern.js is a fully dynamic framework, like Next.js, capable of handling various approaches but requires additional effort and resources. Due to the low complexity of the approach and its designed purpose, Rspress will be a good choice in many cases. On the other hand, Modern.js is unlikely to be on par with other full-fledged frameworks like Next.js.

Configuration

Connoisseurs of this blog may be eager to restore their absolute imports with this setup. Although not well-documented, Rsbuild provides an escape hatch that enables direct configuration of Rspack. The following rspack.config.ts will prioritize matching absolute imports in the project root before checking node_modules (the default).

import { defineConfig } from '@rsbuild/core'
import { pluginReact } from '@rsbuild/plugin-react'

export default defineConfig({
  plugins: [pluginReact()],
  tools: {
    rspack: {
      resolve: {
        modules: ['.', 'node_modules'],
      },
    },
  },
})

From what I can tell so far, Rsbuild is already a very solid build tool. Upon examining its source code, it appears to be a much more complex wrapper for Rspack than create-react-app was for webpack. Given its comprehensive features and coverage of various edge cases, it is generally a better choice than creating a project-specific Rspack configuration. In my case, I replaced my own Rspack wrapper, papua, in an Electron application with Rsbuild because I couldn't get ace-builds to compile with Rspack. It works flawlessly in Rsbuild, whereas with Rspack, the compilation faced issues. Specifically, ace-builds overrides require with a custom implementation in one file; however, this approach didn't work for me with Rspack and its predecessor, @rspack/cli. Both Rspack and the CLI do not allow overriding require, and the files being imported do not actually exist on the filesystem.

This post was revised with ChatGPT a Large Language Model.