I’d also caution that using Web Workers isn’t always so obvious either (and the same applies to server side threading eg on Node). There’s significant overhead (runtime) in spinning up a worker, and in every communication between threads—enough to negate their benefit for many use cases. Both WASM and workers have roughly the same perf downsides, and both should generally be justified by real measurement of their impact.
This is one of the advantages to using WebAssembly: you can use a language like Rust that makes multithreaded code easier to write and faster to run.
Out of the box with JavaScript if you want to share an object with a worker you need to send a message (which internally serializes / deserializes the object) or manually manage serializing / deserializing bytes to a SharedArrayBuffer.
With Rust/Wasm + Web Workers you can use Rust's synchronization primitives and just pass a pointer to the worker. You pay no cost for serialization / deserialization.
> if anyone tells you they need to use WebAssembly to make the UI snappy I'd advise you interrogate that assertion thoroughly.
As you pointed out the JavaScript VM is incredibly optimized but where Wasm shines is consistency. JavaScript requires some care to not produce garbage every frame, otherwise you may unpredictably trigger a lengthy garbage collection. With most Wasm languages you can easily avoid allocating any JavaScript garbage.
The lower-level Wasm languages also tend to be easier to reason about performance. When assessing Rust code performance you typically want to look for Big-O and excessive memory allocations. If you do find a performance bottleneck typically you know where to try to improve. With JavaScript you'll sometimes hit situations where you fall off the VM's golden path, or where one JavaScript VM performs fine and another does not.
The Figma team would say differently. For 99% of CRUD app use cases you are absolutely correct. But WASM has enabled functionality on the web we could have only dreamed of 5 years ago.
> WASM has enabled functionality on the web we could have only dreamed of 5 years ago
Like what? I ask the question genuinely. WebAssembly makes it dramatically easier to do a lot of performance-sensitive things but to my mind it doesn't actually enable a whole lot of new functionality. I don't mean to write off making things easier, it's a huge deal. But if anyone asserts that we absolutely must use WebAssembly for Project X I'd really want to dive into exactly why. "It's fast" is not a good enough answer.
Where Wasm is a massive improvement:
* Ability to use existing C++/C/Rust code without a rewrite.
* Performance consistency through languages with manual memory-management and more straightforward performance characteristics.
* Performance of working with multithreaded code by using languages that can pass pointers and avoid message-passing overhead.
* This last point is somewhat unproven as it's related to my own personal work, so take it with a grain of salt! Wasm has unique properties that allow it to be augmented to run complex, seemingly unnetworked, code perfectly synchronized between computers with very little latency in the UX. I'm convinced it can eliminate a whole class of complex networking / sync issues. I first demonstrated the concept earlier this year with tanglesync.com, but I'm currently working on a follow-up.
https://adobe-flash.github.io/crossbridge/
Still waiting for those wonderful WebAssembly + WebGL games that can match Infinity Blade for iOS from 2010, the game Apple used to show off iOS GL ES 3.0 capabilities.
Or something better that citadel demo, beyond the asm.js port done by Mozzilla .
It's a bit like the Slack of graphic software.
Like what?
The key differentiation, as the parent says, is using Web Workers to make sure you're not doing work on the main thread.
I think you would be wrong about that. Certainly, it's not just WASM that makes Figma fast, but it's an important piece. Look at it this way: why would they have built out the product with WASM five years ago if Javascript was just as good for their use case? It's a huge investment in a new technology with relatively few people you can hire for it, versus quite possibly the most well-known, well-supported language in the world. They identified some significant advantage that made the cost-benefit analysis line up.
Normally, I don't like the argument that because somebody did it, it must be the right choice. But, Figma made so many correct decisions out of the gate that, in this case, I'd give them the benefit of the doubt.
I don't know if this is true or not but I don't rate the fact that they used it as much evidence that they should have used it. IME decisions like this are because some early engineer wanted to use that particular bit of technology.
No doubt. But we don’t know what that advantage was. You could assume it was for performance related reasons. But maybe they wanted cross-platform compatibility, something WASM allows far more than JS. Or maybe they hired a load of developers experienced in making a graphic design tool rather than making a webapp and decided to use WASM to provide those developers with a more familiar environment.
Anyway, without knowing any of this it’s very difficult to say “Figma did it so we should too”.
https://www.figma.com/blog/webassembly-cut-figmas-load-time-...
AssemblyScript and Rust/WASM implementation of these relatively simple but computationally heavy algorithms didn’t result in any meaningful improvements of their JS counterparts. In the end moving computations that took 10ms or more (some up to 700ms) off main thread and using tooling to simplify the WebWorker API was definitely more gainful, simpler and better for code hygiene, and in most cases as fast or faster than the WASM implementations.
as a user, much less than 99% of apps are "absolutely fine" to use.
I just finished a UI that requires a few HTTP requests to the API and a bit of dynamic behavior (but not a ton), and it's done inline with ES6, with no transpilers or minifiers, using ArrowJS. It does the job, and it rips.
you cant really divorce the language from its ecosystem.
Get prepared to be blown away by Makepad [0]. I have no affiliation with them, but just watched their most recent conference presentation [1]. The slides were made with Makepad itself and included, embedded, a full-blown IDE, a synthesizer app, a Mandelbrod to zoom in endlessly, and more. All running at 120fps. The presentation is for the most part live-coding with this setup.
What they want to do is bring coders and designers closer together, and while some code is in Rust they developed a DSL for the GUI parts that is close to how Figma works. These GUI's can run anywhere.
And I couldn't help thinking "Why would people have complicated stacks to create Web 2.0 apps for the Google Web, when they have this?", in other words an opportunity to break out of the browser straitjacket.
Btw. WebAssembly/WebGL isn't the only way in which Makepad is available. And while running well in the browser for a time, there were issues to be solved here (addressed in the presentation).
Perhaps because they still believe in the promise of Web 1.0, where their app is a graceful-enhancement over an initial server-rendered document, that can be easily worked with at a DOM level by any HTML scraper, easily baked to a PDF and printed, easily re-laid-out for better readability just by changing the browser font size, easily text-to-speech'ed (including ARIA roles, alt text, etc), easily re-styled with a user-agent stylesheet, easily intermediated by browser extensions, and so forth.
I've yet to see a WASM-driven web application that's any less opaque to these technologies than a Flash or ActiveX applet would be.
Also mentioned Web 2.0 for a reason. The "cram full-blown app into browsers" web, that uses Rube Goldberg machines behind the scenes. Joking aside, these (btw, also opaque) dynamic applications can hugely benefit from a new paradigm. While the Web itself can go back more to its original 1.0 roots of hypermedia, and allowing more and simpler browsers to wield its content.
It's nearly ten years old now but I remember being absolutely blown away by React Canvas, a UI toolkit that leveraged React but instead of rendering DOM nodes it rendered to a <canvas/> tag. Beautiful, 60fps stuff. All written in JS. Unfortunately all the demos seem to have disappeared since but here's a blog post about it:
https://engineering.flipboard.com/2015/02/mobile-web
Point is, both Makepad and React Canvas have something in common: they ditched the DOM. There are both advantages and disadvantages to doing so but the relevant point is that you don't need to use WebAssembly to do it.
I strongly recommend NOT doing this. It sounds like a fantastic idea in theory (who wouldn't want pixel-perfect control over all client viewports?), but there are so many caveats and edge cases that make it a nightmare in practice. You don't even have to get into accessibility or internationalization stuff to find the kinds of sharp edges that scared me away. Simple things like HiDPI, resizing viewports and drawing text.
I spent a solid 3-4 months of time on this path. I was so tired of fighting platform/browser quirks that it made sense. Now, I accept those quirks as battle-tested features and don't try to fight them anymore.
The only reason you should actually ditch the DOM is if you are doing something like Overwatch in the browser. Even then, I'd argue you would be a complete dumbass if you skip out on the power of CSS, etc. for purposes of handling your Menu/HUD elements.
The frontend does all the rendering for the editor, which we want to stay within the frame budget. That's why we offload all data synchronization work (applying CRDT deltas, encrypting/decrypting data to/from websockets, IndexedDB caching, search, parsing JSON and so on) to the "backend" thread.
I think for apps like this, splitting the UI and data part up (kind of like a frontend and a backend in a classic client/server web app) is very useful to prevent blocking the main thread. In our case this "backend" is actually a SharedWorker, which has the added benefit that it's very easy to keep all state in sync with multiple open tabs/windows, and we just multiplex all incoming websocket events to all connected "frontends".
I agree passing messages is a bit clunky, but we’ve taken a similar approach to comlink so we can simply "await" a function call on the backend from the frontend. Besides setting that up once (or just using something like comlink), it's not much work. Okay except I found debugging on Chrome a bit clunky, as I think Firefox allows you to see all SharedWorker console output from within the main thread console for example (with Chrome you can open the inspector for shared workers separely). Plus we also have all kinds of other neat features with modern browsers these days, like the SharedWorker, and zero-copy communication using Transferable objects.
https://www.youtube.com/watch?v=4KtotxNAwME
The benchmarks being displayed have leptos beating react in nearly every category.
I don't want to speak to this example too deeply because I don't know it (I see they're doing all sorts of stuff with audio so maybe they do need WebAssembly) but modern JavaScript VMs are very, very fast. 99% of webapps are absolutely fine using JavaScript.
The far more important part of making snappy UIs is the Web Worker aspect. To my mind it's one of the key reasons native apps feel so much better than web ones: it's trivial to move an operation off the main thread, do an expensive calculation and then trivial to bring it back to the main thread again. Unfortuately the API for doing so on the web is extremely clunky, passing messages back and forth between a page context and a worker context. I'd recommend anyone thinking about this stuff to take a look at Comlink:
https://www.npmjs.com/package/comlink
it lets you wrap a lot of this complication up in simple promises, though you still have to think hard about what code lives inside a worker and what does not. In my ideal world all the code would live in the same place and we'd be freely exchanging stuff like you can in Swift. "Don't do it on the main thread" feels like a mantra taught to every new native developer as soon as it can be, the same discipline simply doesn't exist on the web. But neither do the required higher level language features/APIs.