 A bunch of people are talking about bun, which is basically no JS, but better. But unlike the other YouTubers, I'm not just going to read the blog post for you. Instead, I ported my production application to use bun instead of no JS, and I made a bunch of measurements. We're going to talk about bun in production environments, bun in CI, and bun on your development machine. So the first application is the analytics tool, and there's three main parts. We have two importers from different data sources, and then it spits out a chart. This work is done in production on a VPS that I have that hosts the website, and I will use that machine for the testing here as well. Porting the application was pretty easy. It's a small application, only 2,000 lines. The main thing I needed to change was the use of better SQLite to use bun's SQLite adapter instead. Other than that, the application worked pretty much out of the box. So when I wanted to deploy the application, I tried out bun's build compile option. This is one of the features that drew me to bun is that I can make a single executable and deploy it kind of like a go program, right? Unfortunately, it didn't work out of the box and tried a bunch of different options. It seems like this feature is immature. So what I ended up doing is shipping the bun executable and all the source code. Let's see how one of the import processes work. This one reads a bunch of Apache log files. Some of them are just plain text. Some of them are compressed and it shoves the data into an SQLite database. So for the Node.js version of this program, which uses better SQLite, it took about a minute to process a few hundred thousand records. Now in this example, this is an incremental import. So most of the data was already in the SQLite database tool needs to make sure that the data is there, but it doesn't actually insert anything. But what about bun? Let's see how long bun took and it definitely took much longer than one minute. In fact, it took over six minutes. Now in all the charts, I'm going to show you lower is better. Lower takes less time. I'm comparing time here. So I tried profiling bun Jared Sumner posted a tweet about how to run the profiler. So I was like, cool, bun has a profile of let me use it. I ran to a bunch of issues. One, I couldn't get the profiler connected at all on the VPS, even if I open up the port in the firewall, even if I set up SSH forwarding, I couldn't connect to the remote machine. So then I decided to run the program on my local machine. Unfortunately, even though you can record a profile, I can't really drill into the data. Every time I click to get more details, the tool just crashes. Fortunately, the program is pretty small. There's only a few steps that happen. So what I did was I just commented out different pieces, filtering out bot traffic was the slow part. I'm using a package called is bot and is bot basically just does a bunch of regular expressions on the user agent of all the log entries. And it turns out that regular expression is very, very slow in bun. I made a reduced test case for the bun team. Hopefully they'll get it fixed because is bot seems to be a really popular package. So okay, the Apache import didn't really work out too well for bun. Let's try that other import that we talked about. This import pulls data from a MariaDB database. So let's see if bun fares better for this job. Well, unfortunately it fails somewhere in the MariaDB NPM package and the error message is kind of useless. It just says undefined. So all I have to go on are the line numbers. The stack trace refers to a line and that line just has a comment in it. As I mentioned before, I couldn't get the profiler working remotely. Same issue happened with a debugger. It's the same tool. I didn't feel like debugging the MariaDB NPM package. So I just moved on. Unfortunately, we're not going to get data for how fast the MariaDB import is. But there's that third step that I talked about for the analytics tool, which is generating the charts. This is mostly reading data from an SQLite database. And most of the processing should be done in SQLite itself rather than like in JavaScript. So I expect the performance to be about the same. So no JS takes about 17 seconds and bun actually takes 21 seconds. So bun was actually slower, even though most of the job is done by SQLite and bun advertises itself as having a faster SQLite interface. Well so far it's not looking too hot and steamy for bun. Let's try a different application here. So the analytics tool uses a few NPM packages. A common workflow for developers is when you're ready to start work, you pull down the latest version of the code and then you run NPM install or whatever to make sure that your repo is consistent. So let's test that use case. I installed all the dependencies and then we install again using the same command. So here is yarn. Yarn took about 135 milliseconds. Now this is on my development machine, which is way faster than the VPS. Even still 130 milliseconds feels kind of slow. But for installing packages bun rises to the occasion and blows yarn out the water. This is on my Linux machine and I have seen some notes about how Mac is a bit different. So I tried on my Mac M1 and it's basically the same story. Bun is fast on both platforms, way faster than yarn. But this was on my analytics tool, which is a pretty small project. Let's try on a much bigger project around 50,000 lines. And as you can see, everything was a bit slower because it's more packages, but bun is still basically instant. But being instant is a low bar for something that does no work, right? We already had all the packages installed. We already had them downloaded. They're all under node modules. So the bun didn't need to do any work. The yarn also didn't need to do any work. Let's try something that needs some actual work done. So here we have a clean install, which is no-none modules. And also I cleared out the yarn and bun caches. So in this case, we have to download everything. Now this is going to be bottlenecked on my network. On my Linux machine, it's hardwired. On my Mac machine, it is on Wi-Fi. So I expect Mac to be a bit slower because Wi-Fi is more finicky. But here we see that there's still a significant speed improvement with bun. Perhaps bun better optimizes the downloads. Maybe it starts to download sooner. Maybe yarn is slow at unpacking. I don't really know the explanation, but I do know bun is beating the pants off yarn. One nice feature of bun's package manager is it can now put yarn files. So I can spit out a yarn lock file from bun and compare it to yarn's own lock file and make sure that everything's good. Unfortunately, I use yarn resolutions, which lets me consolidate some packages. So if two dependencies need a package, I make sure that they share the same version just to reduce the amount of stuff in node modules. Bun doesn't let me do that. So bun actually is installing more packages than yarn is. Some people have been concerned about node modules exploding in size because bun uses copy file on macOS. So I tested with VS Code, which has about 500 megabytes of node modules in it. I made 30 copies and basically used zero gigabytes. So I wouldn't worry too much about bun using a lot of disk space, at least not compared to other package managers. Another place where you might care about the performance of installation of node modules is on your continuous integration system. So here, what I did is I benchmarked bun install versus yarn install on the CI system. A common trick that you use on CI is to cache directories so that different runs can reuse the cache. So what we do is we put the yarn cache or the bun cache inside of the CI cache so that our jobs can reuse that data. So let's see how long it takes for bun and yarn to extract that cache data into node modules. Yarn only takes a few seconds, but bun is way faster. It's as if bun was bred to be a package manager. And this is on that VPS we talked about, which is a pretty slow machine compared to my development machine. Okay, so let's talk about that website. Website has a number of features. It's a static generated website, features EJS templates, SVG optimizations. It has markdown that needs to be converted to HTML. It has ASCII doc, which is converted to HTML using transpiled Ruby code. It uses EAS build with WASM to compile JavaScript code. It has a lot of web assembly for doing various tasks. And it's got HTML post processing, which scans the generated HTML and does some tweaks at the end. So the first thing I did was I ported the test suite and I ran into a bunch of issues. One issue with the tests framework is that expect rejects to throw doesn't work. This is a just feature and it's documented to work fine in Bunn's documentation, but it doesn't behave at all like it's supposed to. Even the example in the linked documentation doesn't work. Another issue I ran into is Bunn's emulation of Node.js file handles just doesn't work. The open function exists, but it doesn't return an object that has read or write on it. So I had to rewrite some functions. I decided to use Bunn's native API for that. Another issue is that path.relative on Node.js removes a trailing slash, but on Bunn it preserves the trailing slash and that caused some differences in how I generated paths for URLs. Speaking of URLs, the Bunn HTTP client and the Bunn HTTP server both normalize URLs for you. Dot and dot dot are resolved. And the problem with that is my tests wanna make sure that that's handled correctly, but my tests aren't able to test it if the client and the server both do that automatically. So unfortunately I can't test against malicious requests. Another issue I ran into is that the site tries to access git to query some information and the exec file that I was using is just totally broken. You can do exec file, but it never calls your callback, so it never returns. Another minor issue, which is also my fault is I'm using fs.readdir from Node.js and it's documented to return results in any order, but it just happens to always return results in sorted order. Bunn's implementation of reader does not return sorted order, it returns some arbitrary order. So that causes some differences. And I just forcefully sorted readers output as I should because it's documented to be in any order. But it's these kinds of incompatibilities that can surprise you. So let's look at the test results, huh? So my original test suite was built on top of the Jasmine test framework. If I ran it with yarn run, you know, I have a package JSON script there. It took about 1.1 seconds. If I ran it directly without yarn, it was faster, which makes sense because yarn kind of has a startup overhead. So let's pretend we don't use yarn. Let's just compare raw frameworks. So if I took the Jasmine code and used Bunn instead of Node.js, it was actually much slower. That's not too surprising. I bet the developers of Bunn didn't test Jasmine. In fact, I had an issue with Jasmine on Bunn where it returned error code three all the time, even if all tests pass. I don't know what was going on there. But Bunn advertises that they have their own test framework. So let's use that test framework instead. And it's roughly the same performance as Jasmine. Well, that's kind of unfortunate. They claim that the test framers way faster. It's not even matching the Node.js performance with Jasmine. Now there's a little asterisk there on Bunn test. This is Bunn test where I use the Node.js HTTP APIs. So there's a little asterisk there on Bunn test. That's because this isn't using all the Bunn APIs. I'm still relying heavily on Node.js APIs. In particular, I'm using the Node.js HTTP APIs in the web server. So let's try using Bunn's HTTP APIs and see how the tests fare. And it seems like maybe the HTTP endpoints made things faster. I mean, it's within the margin of error here, but it's still not beating the Node.js performance. Now this application was using Jasmine because it was developed in Node 14. And back then, Node.js didn't have a built-in test framework. But now in 18 and 20, Node.js does have a built-in test framework. So let's try that. And you can see it's like twice as fast as Bunn tests. There's some serious overhead in the test framework for Bunn. I don't know exactly what's going on here. Bunn in its marketing compares itself to Jest. When I was picking a test framework, I tried Jest and I was like, this is way too slow, even for Hello World. So I picked to Jasmine, which was way faster. Why would Bunn compare itself to one of the slow frameworks instead of one of the fast ones like Jasmine or Node.js? Maybe it's because Jest is really popular. It's not popular because it's fast. I don't know. But if you want to get faster testing, you can easily do that with Node tests. You don't need to switch to Bunn. All that testing I showed you is on my Linux machine. Let's see how testing is on my Mac. And as you can see, the ranking is roughly the same. It does look like on Mac, the gap is narrowed. So the difference between these frameworks isn't as dramatic as on my Linux machine, but it still shows that Node test is much faster than Bunn tests. I've been using a custom LiveReload script based on the Chokidar library. And what I did here is I measured after you change a file, how long until it prints the server started message. And the server started message obviously happens after binding the socket and stuff. Node.js also has a built-in watch feature. Now this is way slower. I don't know what's going on with Node here, but we're not here to talk about Node. We're here to talk about Bunn. So let's see how Bunn fares. And Bunn is about the same speed as my Chokidar thing with Node.js. Now, as you can see with the error bars, there's a lot of variability with Bunn here. I don't know why there's a lot of variability. Maybe there's a bunch of garbage collection happening. I don't know. But I think it's safe to say that Bunn isn't doing an amazing job here. It's just comparable to what's already up there when it comes to hard load. Well, speaking of web servers, once you save the file, you wanna reload the page on your web browser, right? So let's see how long that takes. So I benchmarked loading three different pages. I spun up a Chrome web browser. I loaded one page with cache disabled, waited a little bit, loaded the next page, waited a little bit, loaded the third page, waited a little bit, loaded the first page again, and just repeated round robin. So here we have a histogram of the different page loads. On the top, we have Node.js, and on the bottom, we have Bunn. You can see the three humps are for the three different pages here. One of them is the home page, which has a mix of various things. One of them is a documentation heavy page, which is basically pulling from ASCII doc. And a third one has a ton of SVGs that get optimized and embedded into the page. Now, for this first hump, you can see that Node.js is actually a little bit faster than Bunn is by around 20 milliseconds on average. For the second hump, Bunn beats out Node.js by around 40 milliseconds. And for the last hump over there, that's the one with a lot of SVGs. It seems like Bunn is going bananas with the SVGs on this page. So if you want a better hot reloading experience for development, you should probably pick Bunn here. But if you plan on shipping this to production, you gotta keep something really important in mind. I measured memory usage during that page load test. And you can see that Node.js used less than half the memory of Bunn. So if you have a production server, you care about memory usage as well, not just raw performance. I don't know how much of the performance is because Bunn used more memory. You know, there's often memory speed trade-offs. Bunn does have a low memory usage option. I didn't try that out. You know, keep this in mind when you're doing your own benchmarking. All right, let's talk about CI again. This time we're gonna talk about CI builds. So on the CI server, what I need to do is build the website. It's a static generated website. So we build all the HTML files and all the JavaScript files for the front end on the server, and then we just ship all those files statically. Therefore, the CI server needs to do a build. It needs to compile all the pages and build all the SVGs, all that stuff. So let's see how long that took on our VPS. So with Node.js using yarn build, it took about 30 seconds. If we didn't use the yarn wrapper script, it basically took the same amount of time. And then when I tried with Bunn, it also took the same amount of time. Which was suspicious. And it turns out Bunn sometimes uses Node.js. If you use Bunn run in a script, sometimes Bunn just decides, actually you didn't want Bunn, you wanna know. You need to do the hyphen hyphen bun option to make sure that Bunn runs Bunn. In my case, Bunn run doesn't run Bunn, but Bunn Bunn run runs Bunn. There's similar weirdness with Bunn run when running package.js on scripts. Here I did Bunn, Bunn run build and build as a package.js on script and it decided to show me Bunn's help text. Maybe that's because build is a Bunn command and run is also build command and Bunn got confused. I don't know, but it's just something weird to keep in mind. Anyway, if we use Bunn Bunn run build.mjs, then we see that Bunn is actually faster than Node.js at this job. So that's my application running on Bunn. There's an abundance of Bunn features that I didn't test, such as TypeScript support, heavy front end applications, WebSock as JSX, snapshot testing in Mox, JSON APIs, I didn't test Bunn on Windows. I didn't test Bunn with heavy web frameworks. My website uses its own framework. I didn't compare Bunn with other package managers like Barry or PNPM or whatever. If you use all those features or some of those other features, maybe you should look at other benchmarks or make your own benchmarks and make a more informed decision. So what does Bunn do better? Well, Bunn install package installation definitely blows it out of the park. Unfortunately it's lacking some features but I think those features could be implemented. Bunn also has a nicer API for certain things such as file IO and for HTTP servers. And Bunn does do a lot better on that page reload latency benchmark but it does use a lot more memory. So what does Bunn not do well? Well, there's that memory usage issue but maybe that could be tuned. I don't know. There's compatibility. I mentioned a bunch of Node.js compatibility issues. I'm sure plenty of other people will find other compatibility issues. Debugging and also profiling were a pain. Incorrect line numbers. I tried stepping through things in the debugger when it did work and it stepped me into who knows where. I think it's out of sync with something. Profiling, it couldn't evaluate it. It was crashing. Bunn's Reg X had a huge performance issue and the testing framework just doesn't appeal to me. It's not really faster than Jasmine. It's slower than Node test and it's also pretty new and has bugs in it. So I welcome you to try out Bunn yourself for your own project. We worry that you'll probably hit a ton of issues just like I hit. I'm definitely sticking with Node.js especially because Node.js 20 has much faster testing and performance just seems to be improving overall with Node.js as time goes on. I might consider switching away from Yarn if Bunn turns out to be a better package manager but it needs a few features before I switch. Class dismissed.