About the file system stuff: modern browsers ship with SQLite which is available to JavaScript (is it available to webassembly? No idea) so I would probably use that instead. Ideally you could use the sqlite API directly in C and emscripten would bridge the calls to the browser SQLite db. Something to investigate.
As someone that has used module systems from dojo to CommonsJS to AMD to ESM with webpack and esbuild and rollup and a few others thrown in ... this statement hits hard.
I haven't thought about that in years. I didn't realize it had been solved.
Browser support looks pretty good.
I guess now I have to figure out how to get this to play nice with Vite and TypeScript module resolution.... and now it's starting to hurt my brain again, great.
There are many libraries that have switched to esm only (meaning they don't support commonjs), but even today, the best way to find the last commonjs version of those libraries is to go to the "versions" tab on npm, and find the most downloaded version in the last month, and chances are, that will be the last commonjs version.
Yes, in a vacuum, esm is objectively a better than commonjs, but how tc39 almost intentionally made it incompatible with commonjs (via top-level awaits) is just bizarre to me.
Given that, top-level await is a sensible affordance, which you’d have to go out of your way to block because async modules already have the same semantics.
Recently, Node has compromised by allowing ESM to be loaded synchronously absent TLA, but that’s only feasible because Node is loading those models from the file system, rather than any network-accessible location (and because it already has those semantics for CJS). That compromise makes sense locally, too. But it still doesn’t make sense in a browser.
But also, there's a special place in hell for the people that decided to add default exports, "export * from" and top level await.
Commonjs is also very weird as a "module" instance can be reassigned to a reference of another module
module.exports = require('./foo')
and there's no way to do this in ESM (for good reason, but also no one warned us). In fact major projects like React use CJS exports and the entire project cannot be optimized by bundlers. So, rather than porting to ESM, they created a compiler LOL
There is also a special place in extra-hell for those who export a function named 'then'.
I'm convinced that 90% of the JavaScript ecosystem only exists to build tools for itself. It's tools all the way down
Bun has replaced a massive number of tools and dependencies from our stack and really counteracted the tooling explosion that we were forced into with node.
Exactly, bun is killer. The test runner is extremely fast.
I can build apps with 1-5 total dependencies and everything just works, and works incredibly fast.
Isn't this more-or-less a self-inflected wound? Who forced you into working with node?
Attempting to replicate even a modicum of this in lower-level languages can be a real struggle. Rust is definitively the least-worst in this respect because there's been a concerted effort by the community to provide stable packages that do most things. But Rust is a complicated and unapproachable language. Using other low-level languages like C/Zig, and you immediately run into issues of libraries and static linking. And even if you find a library, its documentation is either lacklustre or outright missing (looking at you libuv and libxev respectively).
The amount of manual setup and third-party builds-system finagling just to: 1) run a TCP server; 2) fetch data over HTTP; 3) do both of these using a single event loop (no separate threads); 4) use SQLite for storage; and 5) have all this produce a single self-contained executable. Yet I cannot understate how trivial this is with Bun.
> Thanks to the combination of LLVM, Emscripten, Binaryen, and WebAssembly, the output is compact and runs at near-native speed.
Last week I never heard of Emscripten.
Integrating SDL for a project, there were CMake callouta for APPLE, MSVC, and EMSCRIPTEN.
And here we are seeing it again on hn in a few days.
I should put an afternoon aside for some deep diving on it for context.
There's a certain irony to being able to introduce you to the term "Baader-Meinhof Phenomenon" (which is the more-common name for what I assume you're referring to, as Google searches for "Yellow Bus Syndrome" didn't bring anything up for me). Now you know the name, you'll see it everywhere!
Various sources online say that it’s because only a certain number of cars fit in memory at the same time so they use the car of the player along with some others. It makes sense, but it would be cool to get that confirmed from someone who actually worked on the GTA games/engine.
This kind of subjective, no? I wonder what they consider "near native speed"? I couldn't find any real numbers in their documentation.
Although JavaScript is still an interpreted language, it basically gets "compiled" when the browser parses the bundle. On the surface, the only thing WebAssembly automatically gets you is you get to skip the runtime compilation phase.
I might be talking out of my ass, so take this with a grain of salt, but I wouldn't be surprised if once we start collecting real data on this stuff, SOME WebAssembly code could actually run slower than just using JS code. My hypothesis is that if you're starting with non-JavaScript code, you might be doing things in that language that would be slower to do the same way in JavaScript. I'm thinking of things like Array.map(), .filter() etc. ... which are hyper-optimized in V8. If you're taking an algorithm from C code or something which then gets compiled to WebAssembly, it's not an automatic given that it's going to compile to WebAssembly that is just as optimized as what V8 would do when it comes across those API calls. Again, this is just a hypothesis and I could be way off base.
In any case, what we need is real world data. I have no doubt that for certain applications you can probably avoid land mines by hiring devs who are experienced building certain performance-critical things at a lower-level than your average JS dev... and their experience in those languages may transfer very well to the browser. In this scenario, you're not getting huge perf wins from using WebAssembly per-se... you're getting huge perf wins for not doing typical stupid, lazy, ignorant things that most average JS devs do ... like cloning large objects using the spread operator and then doing that over and over and over again "because immutability."
It's astonishing how fast JavaScript has become. But even if it were fully compiled, it would still be a language with higher overhead.
You can still write bad code, or compile a language with high overhead into WASM. This remains valuable for porting existing libraries into the browser and reducing bandwidth usage. But properly done with a fast compiled language like c or rust.... wasm can unlock some magical things into the web ecosystem.
That's not at all the only reason WASM is slower than native. WASM is bytecode. It still has to be JIT compiled, just like JavaScript. And WASM to begin with does not have a very complex instruction set, so the code generated by your language's LANG-to-WASM backend can't be optimized as heavily as its native backend.
As a rule of thumb (from my experience), you're almost never going to achieve significantly better performance in WASM than the equivalent algorithm written in optimized JS.
Eeh. Comparing a garbage collected jit language to bytecode jit parsing is... quite possibly the most insane argument you could make.
And what does instruction count have to do with optimization? Most languages optimize in architecture invariant representations before creating the bytecode. So the wasm binary is already optimized.
From searching the web to make sure; the language barrier between wasm and js is the highest performance bottleneck. So its generally recommended to not bother for simple algorithms until it gets better.
Not understanding that WASM still has to be optimized and compiled to machine code, and then calling me insane over it, is certainly an approach to discourse
> And what does instruction count have to do with optimization?
Not going to bother with this one. Do some research into how compilers work, maybe.
> From searching the web to make sure; the language barrier between wasm and js is the highest performance bottleneck.
It certainly is. Not sure where I claimed it wasn't. What I'm saying is that there are also other reasons a program will run slower when compiled to WASM compared to when compiled to native.
> so the code generated by your language's LANG-to-WASM backend can't be optimized as heavily as its native backend.
https://cs.lmu.edu/~ray/notes/ir/ Intermediary representations. Most modern compiled languages are optimised independently of the target architecture. So the code has been optimised way before it even became was text. the LANG-to-WASM backend has most, if not all optimisations that LANG-to-arm64 would have done. The final parser is nearly trivial in compute and complexity, making its implementation a pretty approachable intermediate programming exercise.
Comparing it to running a modern compiler optimisation for a high-order language is apples and oranges. The only optimisation realistically remaining is the processor's speculative execution engine.
> Not sure where I claimed it wasn't
> Not only is it subjective but V8 does so much to optimize JavaScript code that I wouldn't be surprised if the benefits for most applications were negligible anyway.
At $WORK, we're also building with emscripten/C++. We'll add WebGPU/shaders and WebAudio for bonus pain.
Another issue porting native apps is, native apps are compiled for a specific platform and hardcoded to that platform's conventions. A good example of this is hardcoding Ctrl-C (copy), Ctrl-V (paste) at compile time, which maybe works on Linux and Windows but doesn't work on Mac.
IIRC the way you're supposed to handle this on the web is listen for copy and paste events. AFAIK Unity has this issue. They hard coded Ctrl-C, Ctrl-P and so copy and paste don't work on Mac. Most games don't need copy and paste but once in a while someone does something that does need it, then exports to the web and runs into this issue.
Syncronizing between threads involves thunking and copying data through RPC layers.
Sucks for me because our production app has grown faster than we are able to rewrite it and uses 70-100gb of ram (written before my time). To try to get around this, we are investigating exotic solutions like using native code to manually manage pages of shared memory that contains custom data structures and as little serialization/deserializing logic as possible - but of course v8 uses utf16 for string encoding which means working with JavaScript values in the native layer is expensive.
“We’d rather spend 3x the effort to bash things with open source rocks than ever use a proprietary hammer!”
“.NET is open source. It also comes with a steam roller.”
“We’ve made up our minds!”
PS: I just had to work on a Node.js app for the first time in my career. After dotnet core it feels like working with children’s wood blocks.
I'd write everything in it if I could, haha
In the mean time, a "build" using this Nodejs based tool takes about an hour and uses 100gb of RAM.
By contrast, preliminary unoptimized results of the Rust tool brings build times down from 1 hour to 1 minute and maxes out at 6gb of ram - but that'll be finished in 2 years.
It literally costs millions per year to run and is a nightmare to maintain.
To be fair, the JavaScript itself is actually surprisingly performant - it's the multi-threading that's the real problem.
It would be a non issue if there were memory sync primitives that could be sent between threads but because of how v8 isolates work, this isn't possible without changes to v8. So we are forced to copy gigantic graphs of data between threads and manually synchronize them with postMessages.
Running a build on the main JS thread (no workers) uses 10gb of ram and takes 5 hours. So shared memory would solve our issue and avoid the need to rewrite it.
Attempts to centralize the data (in a DB or similar) perform worse due to the de/serialization of the data. Other attempts of using a shared in-memory database shares the same issue. We also need to modify every shared structure in the codebase to use getters/setters with a copy on read approach (so memory blows up any way).
It's not that JavaScript itself is the wrong language, but that the isolated nature of the runtimes fail in multi-threaded use cases.
udev4096•8mo ago
sebtron•8mo ago
Later I looked into a better way to do this, but I could not fully work it out. I use OpenBSD's httpd, which does not support setting extra headers, and relayd. At some point I'll take a look at this again, or I'll move the tool to another domain.