Bun 0.6(bun.sh) |
Bun 0.6(bun.sh) |
> bun build --compile ./foo.ts
> This lets you distribute your app as a single executable file, without requiring users to install Bun.
> ./foo
This is big! Part of Go's popularity is due to how easy it is to produce self-contained executables.
And it seems to support amd64 and arm according too: https://twitter.com/jarredsumner/status/1657964313888575489
Though Bun doesn't use Node.js I believe, there's a reference
(At least it's not JVM Hotspot...)
The worrying part is that this is mostly code, not dead junk simply occupying space, this is part of the code path, filling the caches, or should I say trashing the caches…
This definitely took me from the 'eh, kinda cool project' to 'I cant wait to try this out immediately' camp.
The binaries are pretty huge, hoping they can bring that down in time.
I'm surprised the numbers are as high as they are and hope they can reduce them... but they'll never get down to the kind of numbers Go and Rust get to because Bun depends on JavaScriptCore, which isn't small, and unless they're doing some truly insane optimizations they're not going to be able to reduce its size.
FWIW QuickJS is a tiny JS runtime by Fabrice Ballard... that also supports creation of standalone executables: https://bellard.org/quickjs/quickjs.html#Executable-generati... though its runtime speed is considerably worse than JSC or V8.
`xz -z -9e` is good to compress it for distribution.
Rust too... :D
zig is by default static linked, it probably has the smallest binary size, smaller than static C.
also: there is a bug in `bun build --compile` I am currently working on fixing. Expect a v0.6.1 in a bit
Is it just because most Javascript developers have never learnt from any of the lessons that came from decades of compiled languages? (compilers, compiler tools, operating system and kernel development, etc).
Is there some benefit that Javascript bundlers have that I'm unaware of?
Truly curious.
edit: Oh well, after navigating to some pages on the blog I see that everything was already on browser cache, so that's why it was so fast. Reminds me I need to overwrite Netlify's cache-control on my website, even though it's already very fast to load (Netlify sets max-age=0, must-revalidate by default).
Netifly was equally fast (not free).
When you say paid Netlify is as fast as Cloudflare do you mean the Pro plan or the Enterprise plan? AFAIK the enterprise plans run on a different network, with more distributed servers, although I could be wrong.
It seems at least part of the noticed difference in speed has to do with my region, as pagespeed insights gives me sub-second FCP and LCP on my Netlify website [0], which feels a bit better than what I get at home (with 500mbps fiber). It's possible my ISP is at fault, but I'm not sure how I could diagnose this much better.
cache-control: public, max-age=0, must-revalidate
A few things I notice: - It uses Cloudflare cache (as you pointed out).
- All CSS is in the HTML file, so only one request is needed to display the page.
- The compressed webpage is reasonably lean considering it has all CSS in the same file and uses Tailwind.I've tried a couple and struggled with configuration and, on top of it all, bun is simply faster.
So, if you want to write a bunch of `.ts` files and point something at them, I really recommend `bun` (and, frankly, why would you write `.js` in 2023? Probably because you've not tried bun.
Edit: I don't care about bundle sizes, because I'm just using bun to run my @benchristel/taste sub-second test suite.
https://medium.com/deno-the-complete-reference/deno-vs-bun-p...
It's surprisingly convenient for munging around random json files or calling random apis that return json (or for dealing with an interaction between the two), especially now that fetch ships with node.
i'm sorry
can you file an issue with some code that reproduces it? will take a look
Edit: just saw a comment from the author indicating glibc is required.
Is Bun in a state where I can start thinking about replacing my Node.js production toolchains with it?
• Bun is not stable yet (0.6.0)
• Zig, the language Bun is built upon is not stable either (0.11.0)
Nothing against these awesome projects, I'm all in for a streamlined toolchain (TypeScript, bundling, binary generation...) and other excellent goals driving the Deno and Bun teams.
But...
• Node.js is a powerful piece of software, it's stable and full of battle-tested buttons and knobs
• NPM and Bun/Deno are not real friends at the moment, just acquaintances
• Take benchmarks with a pinch of salt. Real-world app performance depends on a well-greased system, not a particular 60,000 req/s react render benchmark. Remember the adage: your app will as fast as your slowest component.
On a side note, lately I've been extending Node.js with Rust + N-API (ie. napi-rs or neon) and it opens up excellent possibilities.
This seems like the most obvious thing yet to be built.
I wonder how hard it would be to take an existing systems language and add TS syntax to it. Seeing as they are all built on LLVM. Or maybe you could transpile TS to Zig.
was it ever offered for standardisation?
13.3.12.1.1 HostGetImportMetaProperties ( moduleRecord )
The host-defined abstract operation HostGetImportMetaProperties takes argument moduleRecord (a Module Record) and returns a List of Records with fields [[Key]] (a property key) and [[Value]] (an ECMAScript language value). It allows hosts to provide property keys and values for the object returned from import.meta.
https://tc39.es/ecma262/#sec-hostgetimportmetapropertiesState-of-the-art HTTP servers already do a pretty damn good job gzipping stuff on the fly, do we need this garbage?
If it is for obfuscation, fine, can we just call it that?
Does Node.js have lots of bugs still?
Let the downvoting of this simple observation begin.
Given that Oven has taken $7m in VC funding, how do you plan to monetize Bun, etc?
https://twitter.com/jarredsumner/status/1475238259127058433?...
have been doing this (using ES and CommonJS modules in the same file) in clientside code via Browserify or Rollup ever since ESM got popular but it's a bit more nuanced and annoying to do in NodeJS
How did you achieve that? Are there some shortcuts you took, or some feature you deemed not in scope (yet)?
Bun is written in Zig, but it takes the same approach that esbuild took to make things fast: a linear, synchronous pipeline fitting as much as possible in the CPU cache with as little allocation and other overhead as possible. Zig has more knobs to turn than Go.
Bun goes one step further and does a great job of offloading work to the kernel when possible, too (i.e. the large file I/O improvement with the Linux kernel)
Understatement.
Still, in the native-starved web space this sorta meal will be considered haute cuisine.
Any plans on adding "in-memory" / "virtual" file support to Bun.build? I'd be interested in using it for notebook-style use cases
--
Also, ways to do "on-the-fly" "mixed client/server" components (ala hyperfiddle/electric) + sandboxing (ala Deno) would be extremely exciting
Some projects in this vein - https://github.com/jhmaster2000/bun-repl and https://www.val.town/
Also, bun macros are very cool -- they let you write code that writes over itself with GPT-4. Just mentioning as a thing to keep on your radar as you keep pushing the boundaries of what's possible in javascript :) making it more lispy and preserving eval-ability is great
I have seen some desire and works expressed towards using Bun with Electron or Electron alternatives; this interests me greatly. Do you have any plans or aspirations to make any strong push in this direction?
2. Do you cross-compile Bun? If you do, how has your experience been cross-compiling with Zig when you have a C++ dependency?
Yes that is correct and not good. Pedantically, files which don't exist can be created between the call to check if it exists and after. In practice though, it is pretty annoying
> 2. Do you cross-compile Bun? If you do, how has your experience been cross-compiling with Zig when you have a C++ dependency?
We cross-compile the Zig part but not the C++ dependencies. zig c++ was too opinionated for us the last time we gave it a try. I'm optimistic this will improve in the future though.
How standalone are the standalone executables produced by `bun build`? Is a libc or equivalent expected to be present?
We haven't implemented polyfills for everything yet though, like Bun uses posix_spawn and doesn't have an exec + fork fallback
Bun's dependencies otherwise are statically linked
Glad to see you leading this, incredible work and nice to see the positive reception.
2. Running JS outside of the browser is similarly a layer that was built before any kind of standard existed for it (which still doesn't, really)
The browser standards are the only real standards. Everything else (which has turned into a lot) is "standard by implementation". Implementations usually try to agree with each other, because that's obviously beneficial for everybody, but sometimes they make choices to deviate either out of necessity or in an attempt to improve the ecosystem
So it's all pretty ad-hoc, but in practice most things are mostly compatible most of the time. They orbit the same general things, and the orbit has narrowed in the last few years as most of the big problems have been solved and the community is zeroing in on the agreed solutions (with the caveat of often having to maintain legacy compatibility)
Deno takes a stricter philosophy than most, where it prescribes a ~good way that everything should be done (which is almost entirely based on browser standards which have evolved since all this started), even though it runs outside of a browser, and requiring the ecosystem to fall in line
Bun on the other hand takes a maximalist approach to compatibility; it does its best to make everything from every other sub-ecosystem Just Work without any modifications
It's a pretty unique use-case that not many other programming languages deal with or care about. It's almost as if you are a demo scene coder trying to optimize for the absolute smallest code possible.
Linking native code doesn't really care about optimizing the size of the output and is just just trying to make sure all code paths can be called with some kind of code.
That is not true at all. There are many use cases where native code size is very important. Native code toolchains often target platforms with extremely limited ROM/RAM. Even on big machines, RAM is not free and cache even less so.
Native code linkers will GC unused code sections (`-Wl,--gc-sections `), fold identical data to remove duplication (see COMDAT). Native code compilers have optimization modes that specifically optimize for small code size (`-Os` and the like).
I guess Javascript uses a slightly unusual executable format, text instead of binary. Otherwise, it seems like very much the same thing?
This creates a situation where you need bundlers, whereas other languages don't have the concept at all, just to be able to minimize download time (and honestly, while we end up making rather large apps in comparison to web pages, they're pretty small in comparison to other kinds of applications), and then bundles are too opaque to share common code between applications.
And because there's no chance to benefit across projects from sharing, there's no force driving standardization of bundling, or adoption of said standard.
There are some Steve Souders books on optimization that are pretty good and still pretty relevant.
It would greatly simplify deployment to have the source and deployed code be identical. Obfuscation aside, given JS is an interpreted language, there is no reason to not use it for what it is. We've turned deploying JS into the same level of complexity as deploying C++ by adding building and packaging steps. Interpreted languages should never need build steps, and deployment should be no more than a simple rsync.
This is beyond the stripping comments, whitespace, renaming all symbols to short identifiers, replacing idiomatic constructs with shorter equivalents, etc. that people expect minifiers to be doing.
Bundling with tree shaking can get rid of a lot of code that would otherwise need to be downloaded. This is especially the case when using only a subset of functionality from a larger library.
Otherwise, for most libraries, if you pull in a single function, every single module from that library will also get loaded. This applies both the pre-bundled libraries (i.e. where there is only one large module, so obviously everything gets downloaded), and non-bundled libraries, because most libraries have you import from an "index.(m)js" module, that exports all the various public API of the library. Which means a browser with import maps will need to download all those files, and all files they import, which will be basically every module in the library.
Minimizers themselves often also have some sophisticated dead code elimination. Indeed, one potential (but inefficient) way to implement tree shaking is simply to bundler without shaking, while using module concatenation, and then simply passing it to a minifier with good dead code elimination capabilities. This would be able to eliminate basically anything tree shaking could, and more. The more comes from both from any eliminable code the bundler would not know how to eliminate, but also being able to eliminate anything that was only imported by said dead code.
This is one of reasons why uncompressed minified code can sometimes beat out the compressed original code, and is still at least somewhat compressible itself. Of course I have not even touched on having fewer files to download (which still has meaningful overhead), nor the smaller resulting codebase being faster to parse than the.
Lastly, but not least, many people want to use typescript or jsx when writing their code, which means the code needs to be pre-processed before a JavaScript engine will read it. If you already have a compile step, then adding bundling and minimizing on top of that can be relatively simple and make good sense for the above reasons. (Note can be simple. It depends a lot on what tools you use. Webpack for example can get really complicated, but it also offers some really powerful features.)
> many people want to use typescript or jsx when writing their code
However, if this is true, why don't we just add these languages to browsers (<script type="text/jsx">, <script type="text/typescript">) with some kind of client-side processor for old browsers that turns them into "text/javascript" in-line in the DOM if the browser doesn't support it?
It's kind of weird that JavaScript is becoming a common "bytecode" among other better-structured languages, one would think the bytecode language that becomes the compilation target should be one that is better designed of its own.
If we're compiling JS, JSX, and TS all to some sort of assembly, or even a statically and strongly typed language like C++, I would feel a bit better.
The first – I'm curious about Vite in bun. You have a bundler, you have a typescript transpiler, and you have a HTTP server. Does this mean eventually Vite (or at least some of it's functionality) will be "native" inside of bun, or will Vite continue to be it's own thing? (eg. Vite plugins for <framework>)
The second is more related to Oven. I'm curious what the value proposition of Oven's edge is, compared to other serverless providers? It's hard to imagine the runtime being the main selling point, with it's node compatibility and packages like hono being able to run everywhere. What will set Oven's edge hosting apart from the pack?
I was on their £19/mo plan, not Enterprise. And those websites were as fast as the ones on pages.dev.
It might even have resulted in a faster runtime as well, since it wouldn't need to load nearly as much of the binary into memory, and also wouldn't need to initialize as many special purpose data structures to deal with advanced performance cases.
If you mean code minification, that can depend, but in general with tree shaking it shouldn't be slower, typically. The computer doesn't care if a variable is aa or myDescriptiveVariableName.
> The jlinked runtime using the above command is about 95Mb.
Simple programs -- like Hello World -- can indeed exclude certain parts of the JRE using jdeps, for a smaller size.
Wait, are we talking about what Bun needs to run or what standalone executables produced by bun build need in order to run?
The service subscribes to redis messages, accepts websocket connections, authenticates the connections, then broadcasts messages through the websockets. Maybe around 1200 or so websocket connections per server.
The transpiling itself is written in Zig, which is the part that has the performance improvement. If Bun relied on JavaScript and JSC for the heavy lifting, it would be no faster than the other JS bundlers.
edit: no longer LLVM: https://webkit.org/blog/5852/introducing-the-b3-jit-compiler...
The Unicode part of ICU shouldn’t be that large, however (on the order of hundreds of kilobytes), it’s the locale data that’s big[1]. Does Bun implement ECMA-402 internationalization? Even without locales, one of the largest parts of ICU data is normalization and casing tables, which I think bare ECMAScript does not require. (It does mean bare ECMAScript cannot adequately process Unicode text, but meh, you get what you pay for.)
[1] https://unicode-org.github.io/icu/userguide/icu_data/buildto...
[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
[2] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
https://internals.rust-lang.org/t/jemalloc-was-just-removed-...
Insane performance gains, like the ones we see early in Zig, is something that can be easily eaten away by the natural evolution and maturity of a programming language.
Btw, Zig is beating trees with a stick to see what may fall down in this area: https://github.com/ziglang/zig/issues/12484
They could have just used C++ or Rust. Not saying that they shouldn't use Zig, just questioning speed as the (primary) motivation
The challenge of course is supporting two JS runtimes within the same codebase.
It still feels like a graceful idea, though.
Can we please not put the two next to each other? There is absolutely nothing similar between the two.. why don’t mention go and haskell, or go and D instead?
JavaScript was apocryphally "invented in 10 days", it came as an attempt to create competitive advantage, not to create a global standard. The first JavaScript came a year (1995) after the first Netscape, but the first major JS-heavy application didn't come for another 13 years (Google Maps, 2008).
Of course, the efforts for larger dev teams, optimizations and bundling were far less popular before then. Can't tell you how many poorly written sites/apps carried who knows how many versions/copies of JQuery for example. It was really bad for a while.
Now, at least there are more paying some attention to it. There's still some relatively large bundles that are easy to get overloaded. I mean as soon as you load any charting or graphing library it blows out everything else. Of course this is offset between bandwidth and compute advancements as well.
There was a popular developer site around 1998-2000 or so called 15seconds.com as that was the average point at which users would start dropping off from a load. Now that's measured at around a second or two.
The significance of Google Maps was that A) it had all the parts that we would recognize today as a JS single-page app, B) it had no alternative interface that people could opt to use instead (diluting the usage of that particular implementation versus the product as a whole), C) it had broad appeal and adoption, and D) it was significantly better than competitors specifically because of the "SPA-ness" of the app.
Google Maps had the features and penetration necessary to change the public perception at large of what could be done with browser-based apps.
A colleague of mine went diving through the JavaScript source and found a reference to an ActiveX component called XMLHttpRequest. We realised it was pretty useful and ended up using it to build an SPA that approximated a spreadsheet for global logistics planning. It worked very for 2003 standards.
Google maps came in 2005
I consider Google Maps to be the first well-adopted, no-traditional-alternative app to be what we recognize today as a JS SPA. Gmail had a pure HTTP mode, and otherwise was not interesting to people who were happy with their current email. Outlook wasn't really used by that many people, not at the scale that Google Maps was Google Maps has broad appeal and was significantly better because of it's SPA-ness to change how people thought about browser-based apps.
[1]: https://corecursive.com/json-vs-xml-douglas-crockford/#the-p...
well.... not for a surprisingly long time
0.0.2 (v8 v8.4.371.18) - file size: 15.2 MB, startup RSS: 8.4 MB current (v8 v10.6.194.9) - file size: 19.5 MB, startup RSS: 12.3 MB
so, that's roughly 30% binary size increase and 50% greater startup memory usage in 2.5 years. =/
Rust switched away from Jemalloc because it uses global allocation for everything. Zig's convention of explicit allocator argument passing means such a compromise will never be needed.
As for "beating trees with a stick", I'll probably end up doing what I did for WebAssembly, which is to ignore the preexisting work and make my own thing that is better. Here's my 160-line wasm-only allocator that achieves the trifecta: high performance, tiny machine code size, and fundamentally simple.
https://github.com/ziglang/zig/blob/c1add1e19ea35b4d96fbab31...
A Simple Language
Focus on debugging your application rather than debugging your programming language knowledge.
This is actually not that different from Go in practice; many Go binaries are linked to libc: it happens as soon as you or a dependency imports the very commonly used "net" package unless you specifically set CGO_ENABLED=0, -tags=netgo, or use -extldflags=-static.
Are we just talking about standard rop gadget vulnerabilities, or is there something else that's a problem with it?
The only issue is that some lib sometime do not compile (or at least without some workaround) with musl. Although it often concern one specific platform (looking at you Mac OS).
It does in general, though I don't really think this is a big pain or blocker in the general case, there are very version requirements around libc.
> Go doesn't have this problem afaik as it has its own stdlib and runtime.
That's also true, but it's not really a pure win. Choosing not to use a battletested libc has led to a variety of subtle and difficult to diagnose bugs over the years, e.g. https://marcan.st/2017/12/debugging-an-evil-go-runtime-bug/
I went on a wild goose chase to build static Rust but deno can't target musl yet and the issue is a few years old
Glad others are finding value there, but i wish it was more of a drop-in replacement for `node <script.js>`. Feels similar to `ts-node` (always having issues) and `tsx` (just works).
Still not small, but I'm not sure what Bun's doing to come out 20 MB larger than an app with actual functionality and dependencies (this one has express, sqlite, passport, bcrypt, and a bunch of others-- essentially everything you'd need for a web service).
Like, the whole point was to effectively use linux kernel namespaces with cgroups in an intelligent way to give VM-like isolation, but non-emulated performance - and supposedly not having to deal with image size bloat from the OS like you get in VMs.
What we got was an unholy mashup of difficult to debug, bloated images and ridiculously complex deployment and maintenance mechanisms like kubernetes.
I just do old school /home/app_name deployments with systemd unit files, and user-level permissions.
Oh, and it's webscale[1].
Dynamically loading hostname resolution policies doesn't happen often, but when it does happen it's a right pain to diagnose why some tools see the right hostname and other tools don't.
Depending on your setup it doesn't really matter anyway.
Look, I totally get the unholy hell that's (for example) python dependency management, and containers are a great solve for that.
Sometimes you don't have a choice of technology, so I get it.
What I don't understand is folks that use containers for stuff like go binaries. Or nodejs. I mean, it's just an "npm install". Or now bun with it's fancy new build option, you don't even need that.
I honestly don't get the point of containers with languages that have good dependency management, unless you're in a big matrix organization or something.
Or, as one HN user put it years ago, "containers are static compilation for millennials".
I snorted beer out of my nose the first time I read that.
There are plenty of good reasons to containerize even a single binary executable, as demonstrated by the fact that officially maintained images exist for containerizing processes like Postgres or haproxy. Sure, you could run both Postgres and haproxy as services directly on the host, but then you'd miss out on all the benefits either provided by or complementary to containerization, like isolated cgroups and network namespaces that make declarative service orchestration easily achievable with maintainable configuration.
With Python it's just "venv/bin/pip install -r requirements.txt".
All the tools needed to create an isolated environment (venv) and install packages (pip) come with the standard Python distribution these days. I wouldn't characterize that as "unholy hell".
There's plenty to like about containers.
Using the built-in tools, you can save the exact versions of dependencies (i.e. a lock file) using "pip freeze >dependencies.txt". This should give you the exact same set of packages in two years' time.
If you want to be even more sure, you can also store hashes in the lock file. This has to be generated by a separate tool at the moment [1][2] but can be consumed by the built-in tools [3], so "pip install -r requirements.txt" is still all you need in two years' time.
This is also explained in the pip documentation [4].
[1] https://github.com/pypa/pip/issues/4732
[2] https://pip-tools.readthedocs.io/en/latest/#using-hashes
[3] https://pip.pypa.io/en/stable/topics/secure-installs/#hash-c...
[4] https://pip.pypa.io/en/stable/topics/repeatable-installs/
1. https://sergiomartinrubio.com/articles/getting-started-with-...
I’d also compare against stripped Rust binaries statically linked against musl.
I assume this is a typo and they mean “does NOT”?
ICU locale data is pretty hefty and there aren't says to trim it down.
The OpenJDK runtime with the java.base and java.desktop modules is 64 MB. Replacing Swing with SWT (leaving out the java.desktop module) gets it below 50 MB. The full OpenJDK runtime with all modules is around 128 MB. (With Java 17 on Windows.)
[1] https://docs.python.org/3/using/windows.html#windows-embedda...
Approximately 2,700 times bigger than it could be.
What's the point in looking at size ? I can see two:
- want to email the exe, and you have limits on mail size - want to be ecofriendly, in that case stop watching netflix for 2 hours and you'll have your megs
Originally I thought the alternative was “8MB but supply your own virtual machine.”
A pure java bytecode (bring-your-own-JVM) Hello World can be under 2KB pretty easily. Smaller than the equivalent C program. But of course, that's not including the size of your system JVM.
That's very misleading. For one, Java running native still needs a Garbage Collector, maintains Object headers which increase memory usage, can do things like reflection for configured classes, can load services via the ServiceLoader, schedule Threads and manage executors, and many other things that are "expected" to any Java application... in summary: native executables still have a Java runtime embedded into them (called Substrate VM, by the way), making them very different from C (much more like Go).
Also,notice that native Java executables still tend to have a lower "peak performance" (i.e. once a "normal" JVM has warmed up, it will almost certainly be faster than a native executable because the latter cannot do JIT compilation to take advantage of runtime profiling, like a normal JVM does).
https://learn.microsoft.com/en-us/cpp/c-runtime-library/c-ru...
https://gcc.gnu.org/onlinedocs/gccint/Libgcc.html
https://software-dl.ti.com/codegen/docs/tiarmclang/compiler_...
And many more, not feeling like linking documentation from all C compilers.
In fact this is so relevant even for C, that ISO C has a special section for deployments without runtime support, named freestanding C.
"In a freestanding environment (in which C program execution may take place without any benefit of an operating system), the name and type of the function called at program startup are implementation-defined. There are otherwise no reserved external identitiers. Any library facilities available to a freestanding propram are implementation-defined."
From your link:
"Most of the routines in libgcc handle arithmetic operations that the target processor cannot perform directly."
You're trying to imply the Java native runtime is comparable to liggcc?? That's silly.