Bun is much faster than Node.js 22 at decoding Base64 but both rely on same lib
twitter.com
twitter.com
- TSX is baked in, so I’m using that as my HTML templating layer.
- SQLite is baked in, so I’m using that as my database.
- Bun shell replaced the need for something like “concurrently”.
- I wired together live reloading plumbing using easily using the file watcher.
Its documentation is excellent. It starts up fast. When you do need to install dependencies, that is impressively fast, too.
If you haven’t checked it out yet, I’d highly recommend it. 9/10.
(The reason for the 9 is that there are still some lingering APIs and behaviors missing from Node which might trip you up.)
> Bun shell replaced the need for something like “concurrently”.
It's kind of a PITA to use, and just calling a Bun script is better.
await Promise.all([
$`bun watch:server`,
$`bun watch:tailwind`,
$`bun watch:client`,
]); await Promise.all([
child_process.exec(...),
child_process.exec(...),
child_process.exec(...),
]);
Syntax might be off but you get the idea ...Can you expand on that? I was looking for something that lets me reload JS code just yesterday, and apparently the recommended way to do it is to restart the process :D.
I run bun in `--watch` mode which means the server restarts when I make changes.
The live-reload logic is around 100 LOC.
To make the client refresh when the server restarts, I wrote a little middleware function that runs in dev mode, but not production. It inspects the response, and if it detects a `text/html` response, it replaces `</body>` with `<script>...</script></body>` where the script performs a long-poll fetch to an endpoint that the middleware also injects (listening on `/{someuuid}`). If the long-poll connection drops, the client begins a reconnect loop, and once it reconnects, it reloads.
That's the gist of it, anyway. It's not as complex as it sounds, and it works quite well for my needs.
If so, then node can take exactly the same strategy.
[1] https://ziglang.org/documentation/master/#String-Literals-an...
Codepoint-level string abstractions in particular are complete nonsense that only serve to give you the illusion of making things easier before learn the hard way that Unicode is more complicated than that. This also goes for UTF-16 which is only a cope extension uf UCS-2 for those that already made this mistake before additionally realizing that 2 bytes are not enough to encode all human languages.
Now you might think that declaing all your strings are UTF-8 wouldn't have any of these problems and is the way to go .. until you find out that there are strings you can't represent as (valid) UTF-8 including things that are almost UTF-8 like filenames and other OS-provided data under most POSIX operating systems. This also applies to UTF-16 under Windows btw.
And yes, UTF-16, used by Java and C#/.NET is a pain-point as it forces conversion from sockets/files from UTF-8 into UTF-16 so they can be used, then another one when writing back. But that's beside the point when talking about Zig.
You actually cannot implement substring replacement at the byte level with Unicode, just think about what happens if there is a modifier right after the substring in the original text. You cannot just avoid the fact that Unicode (and human writing in general) is a mess.
Zig also has a concept of "sentinel terminated arrays/slices" which allows easy interop with C APIs for string data, but the details and implications go a bit too far for a comment :)
Why do you say that std::string_view is more awkward to use as a result of being part of the stdlib?
As far as I'm aware, the type of string literals in C++ is still a "raw" char pointer and not a string view. Also few libraries actually make use of std::string_view, while in Zig everything is built around strings as slices, from the language to the stdlib to 3rd party libs (easy to do of course in a new language ecosystem).
You can define a constexpr std::string_view with a literal if you so choose. And if you pass a string literal to a function that accepts a string_view, then the compiler has enough information to (and typically will) construct the string_view in constant time. (A sibling commenter also points out that you can use the sv suffix: https://en.cppreference.com/w/cpp/string/basic_string_view/o...)
(Meanwhile, if you pass a string literal to a function that accepts const std::string&, then that will be a linear-time operation, as it copies that data since std::string owns its data. But with any amount of indirection, you'll end up with an implicit strlen call. So this is absolutely a pitfall.)
> few libraries actually make use of std::string_view
This is a fair point, as it's a relatively new addition to the language and by no means mandatory.
That's bun in a nutshell. Node could be faster, but they are not. Bun is essentially using the same architecture as Node, just implemented in a more efficient way.
Therefore, the hopes that Bun would herald a breakthrough in JS performance are ill informed. However, best case-scenario Node gets a good nudge to become more efficient.
We spend many hours reading JavaScriptCore (the engine)’s code and when we wrote the code for Buffer and related APIs we spent a lot of time benchmarking and iterating on different approaches. A lot of performance work looks like this
I really want a runtime that actually leverages the type hints for JIT optimization.
But ultimately the way a JIT like Turbofan works is that it will do better optimizations if your JS code is already monomorphic (functions signatures, object fields and so on).
Now TS sort of nudges you that way if you write simple TS code. But due to structural typing, expressive type golf features and the fact that TS is generally unsound I doubt it would be easy to leverage type hints much beyond that.
Here are two talks that are very much related and give some insights into how difficult these kinds of problems are:
https://thestrangeloop.com/2022/of-javascript-ahead-of-time-...
https://www.thestrangeloop.com/2019/typing-the-untyped-sound...
It would be an extra constraint for an extra layer of guarantee and ideally performance.
Although my feeling is that typescript’s idea of a type system living on a separate plane was one of the greatest ideas of all time. Never felt so good about something that is basically js. With strict semantics like Rust/C++/Zig/Go it would be just a weaker clone of those with all the usual strings attached.
But I’ve got projects that are now as “correct” as possible, with full validation at deserialization and such and I just wish a compiler could take things an extra mile.
(I find the experience of -using- what it actually has to be excellent but 'native' is definitely an overloaded usage here)
Asking as someone who has yet to try either.
Surprised I haven't run into any of these incompatibilities.
Sounds like I have some reading to do.
Bun has better speed and great documentation. And they're shipping new features very fast.
(a) With npm, Microsoft is the gatekeeper of everything NodeJS. URLs are the most decentralized way to do dependencies.
(b) Self-hosting can help with some security issues with npm. Makes it easier for private projects to not have to trust npm hosted third party libraries, which is important in many corporate environments.
Deno made a very deliberate compatibility break from the Node ecosystem. They wanted fresh start, to make smarter choices and ditch historical baggage. They thought people would be motivated to push through the adoption friction. I think that plan has been less successful than hoped, and Deno keeps walking it back. They're more compatible than before, but aren't a drop-in replacement. IMHO they prefer it that way.
Bun, on the other hand, explicitly feels that any compatibility gap with Node is a bug. Bun wants to beat Node at its own game, wants adoption to be as easy as running `bun index.js` instead of `node index.js`. Then you opt into their special APIs as-needed. Bun's headline feature is "free speedup", but they also target many of the same DX conveniences Deno does, like trivial TS integration.
When Deno came out, the question was "how is Deno better than Node?". Deno had strong answers, give or take the compatibility differences. But today you could instead ask, "why port to Deno instead of just dropping in Bun?", and that's more complicated to decide.
Knowing that even if there's a bad package, it can't call any external server or write/read files from disk.
Even generally, I now know that the code I wrote don't do any unauthorized things I didn't explicitly tell it to do.
If you think someone is reviewing all the code every time a new release is cut... popularity means it's one of the hottest targets.
I got some problems and it started leaking Node diagnostic messages all over the place. Was not impressed with how easily the facade fell over when I tried to do things just a little bit different from the happy path.
When did you use it?
I do think it's a gotcha to run a script with another runtime than the one I launched from the terminal, though. If I launch a script directly with a runtime, I expect it to disregard any shebangs.
I strongly suspect that what you saw was from running a script with a #! line that specifies node, because bun honours #! lines by default.
If you use <bun run --bun> that will tell it to use bun instead when it sees a node shebang.
It’s also possible you ran a package.json script that had the #!/usr/bin/env node shebang at the top, which Bun by default respects. You can force it to use bun by prefixing the command with “--bun”, like “bun --bun my-executable”
What error did you run into?
I think it's a gotcha to run a script with another runtime than the one I launched from the terminal, though.
If I launch a script directly with a runtime, I expect it to disregard any shebangs.
But I really want to have explore alternatives to Noe, so I will give Bun a new look soon.
Nodejs is nearly impossible to embed. Deno is difficult to embed and error prone due to difference with Nodejs.
Bun doesn't appear to have an embedding API but I'd certainly give it a crack if they offered a turn key integration.
- Packages using Node functions Bun hasn't implemented yet. Google generative ai sdk streaming mode doesn't work, the rest of packages work fine for me though.
- Bun won't shutdown at the end of the script if there are async functions running in the background. I have to close the DB at the end of every script instead of just using pre-exit hook.
One thing Bun did, that I wish Deno had was a built in API for Sqlite databases. Deno has had the library in the runtime for a while, but has resisted exposing it directly. While I get the reasons, it would help with security integration over the other options IMO.
I think integrating SQLite (libsql) and even Tuirso/AstroDB (libsql-server) support in the box could really be a boost to app development productivity myself. While the latter would be more difficult in self-hosted scenarios.
GitHub could “buy” Bun, fire the entire npm team outside the registry and directly compete with Node.