Migrating Node.js to Bun: Really a drop in replacement?
Intro
Over the last few months, Bun has sparked interest in the web development community, claiming for it to be a drop in replacement for Node.js that is much faster at the same time.
We work with Node.js all the time here in the Codesphere Marketing team and wanted to put that to the test.
We tried out, if Bun would really be able to be dropped into the existing repo for our main page and if we could finally ditch Node.
Additionally, we drafted a list of buns most important functionalities and peculiarities at the bottom of this article.
Buns Bundler
Watch mode
Building the code with bun is actually really easy. After installing bun via npm (What the bun team claims will be our last npm command ever), executing bun run index.js instead of node index.js worked fairly well, except for the websockets we use for tracking. More on that later.
What we really liked about it was the —watch flag provided by Bun, which essentially eliminated the need for a watcher like nodemon. To run and then watch a specific file, simply run bun —watch run ./your-file.js
. For more complex setups, a watcher might still be necessary, however for our use case, this worked just fine as we use Tailwind for our CSS and compile them that way and our EJS templates get built on the fly when a user accesses one of our pages.
Entrypoints
To be able to build more than one file at the same time, you need to configure entrypoints for the bundler to go through and bundle your code into usable pieces. Since we use EJS and JS, we only need to bundle our JS code. We created a bundling script called bundle.js in the root of our project which we then simply run using bun run ./build.js
.
This bundles the files according to the configuration.
Seems easy enough, right? Well… yes and no.
While this works really well, Bun currently doesn’t allow for naming entrypoints like Webpack does. This meant for us to actually be able to use Buns Bundler, we needed to write some extra code to be able to give the right names to our bundles so everything would work without us having to touch all of our EJS templates.
We ended up with this for the configuration:
const path = require('path');
async function buildProject() {
const entrypoints = [
"./src/js/features/index.js",
"./src/js/global/index.js",
"./src/js/home/index.js",
"./src/js/main/index.js",
"./src/js/pricing/index.js",
"./src/js/tracking/index.js",
];
for (let entry of entrypoints) {
const dirName = path.basename(path.dirname(entry));
await Bun.build({
entrypoints: [entry],
outdir: './public/js',
minify: true,
naming: `${dirName}.[ext]`
});
}
}
buildProject();
Installing Dependencies
Using the Bun package manager to install packages worked just as expected. Simply run bun install to install any npm package. No issues here!
The Websocket issue
We work with Websockets for our UX tracking. Our current server setup features an Express server that uses the wslib package to handle our Websocket operations.
This unfortunately didn’t work together with bun. This left us to handling the Websockets with Bun and leaving the rest to Express which also isn’t a very clean solution.
If we were to implement Bun into our production system, this would essentially mean we would need to refactor our server. For a smaller website project like ours this might be sufficient but depending on the size of the project, this might become a daunting task.
However, Bun doesn't promise to be an Express and wslib replacement, rather a Node replacement. Depending on your setup, you will need some time for configuration though.
Conclusion
In our little migration experiment, we noticed that especially build times were A LOT faster than on our current setup using Node and Webpack in combination with nodemon.
Bun is a great and very fast tool and while most tasks worked fine, not all did. Depending on your use case, you might be able to just drop in and get going with Bun but depending on your installed packages, you might need to reconfigure some things to get going.
Now, is Bun a drop in replacement for Node?
Well, this is hard to answer. We would still say yes, as most things worked just fine and you can't expect Bun to replace each and every package you have installed.
We will definitely be considering Bun for our next project. This time not as a drop in replacement though but rather to start out with it from the beginning!
Most important bun functionalities and peculiarities at a glance:
1. Package Manager
Bun streamlines package management, enhancing efficiency:
Adding & Removing Packages: Use bun add
instead of npm install
for quicker package installation. To remove packages, bun remove
is efficient and straightforward.
Dependency Lock File: Bun utilizes bun.lockb
instead of package.json
. If all dependencies are installed via Bun, there's no need for package.json
Package Runner: Replace the use of npx
with Bun's built-in runner bunx
, streamlining command executions.
2. Bundler
Bun’s bundling capabilities come with notable peculiarities:
File Watching: There’s no need for third-party libraries for file watching, as Bun incorporates this feature natively.
Defining Entrypoints: Use the Bun.build
function to specify entrypoints in a distinct file, then build them efficiently with bun run
.
Complex Setups: For more intricate configurations, you might need custom JavaScript in your Bun configuration or a separate bundler like Webpack.
3. Test Runner
Bun enhances testing processes with its integrated features:
Automated Test Execution: The bun test command automatically locates test files and executes them, requiring no additional setup.
Compatibility Note: Adjustments may be necessary, as JSDom isn't supported yet. Instead, using the happy-dom
library is recommended for the time being.
4. Setting Up a Server
Bun simplifies server setup, particularly for smaller projects:
No Need for Extra Frameworks: For straightforward projects, Bun eliminates the need for frameworks like Express.
Server Setup: Use Bun.serve to establish a server efficiently. For WebSockets, upgrade all requests to a simple HTTP server. A detailed tutorial is available in the Bun documentation.
5. SQLite Integration
Bun offers seamless interaction with SQLite, avoiding external libraries:
Built-in Driver: Bun includes an inherent SQLite driver, removing the need for additional libraries.
SQLite Module: Utilize the bun:sqlite
module for direct SQLite operations, simplifying database interactions.
6. File Handling
Bun proposes a unique approach to file handling:
Bun's File Handlers: Use Bun.file
and Bun.write
for interacting with files directly through Bun's API.
Additional File Operations: For operations not covered by Bun's handlers, the node:fs
module remains available, ensuring comprehensive file management.
For more detailed insights, please refer to the bun documentation.