This is a living document. Come back often to get updates.
The role of WebAssembly
Wasm in Composability
Do JavaScript developers need to learn WebAssembly?
In case you have arrived here from the outer world and have not had the chance to read my short explanation about WebAssembly and why it may matter to you, you can find it here.
In this page, we will deep dive into the core concepts of this technology, and why it's important in the current composability landscape. Do we need Wasm to build more efficient micro-architectures? I believe, yes, we do. And I will try to explain here why.
The Origin of WebAssembly
I first heard about WebAssembly at the end of 2016, but I later learned it was born one year earlier. At the time, I was working as a Senior Frontend Software Egnineer and was very much into Web Performance. I was preparing a talk about http/2 to convince the people running systems and architecture in the company I worked at, to flip the switch for our servers, and while investigating about performance improvements, I came across WebAssembly.
At that time, WebAssembly core features were already available in a lot of the browsers we needed to support. So it was potentially interesting. But I was too busy with JavaScript, and did not pay it much attention.
What exactly is WebAssembly?
Like I explained in the introduction, WebAssembly is binary format or low-level code that can be packaged into quite small modules with the file extension ´.wasm´, which is the reason why is known also as Wasm. The modules are typically smaller than JavaScript and more performant, because the code is binary, it's not mean to be read by humans. So the code is more lightweight.
Where does WebAssembly code run?
WebAssembly code runs on top of the JavaScript engine in place, that could be V8, SpiderMonkey, etc.
It runs in exactly the same runtime than JavaScript, client-side, server-side and even on devices, and therefore, a WebAssembly module has access to Web Platform APIs, like JavaScript programs do. The main difference is, that because the code is binary and it doesn't need to be interpreted by the engine, it is much faster in execution time, than JavaScript. How fast? It will depend on many factors, including the system resources or capacity -don't forget we spoke about the fact that those may greatly vary per user, in our architect for the user UX section, but the claims are that it may execute at near native speed.
WebAssembly Code
I know you're itching to see some code, already. But I won't show you the binary, first, because the binary won't tell you much. Also the actuall binary output will depend on the compiler.
Let's see what the Web Assembly text instruction would look like, if we wanted to write a program that was called `hello_world` that would print to the screen: "I love to compose micro frontends in the cloud".
hello_world.wat
(module (import 'env' 'println'func $println (param i32))) (func $hello_world (export 'hello_world') (block $block (call $println (i32.const 0)) )) (memory 1) (data (i32.const 0)'I love to compose micro frontends in the cloud 0')))
Dissecting the textual instructions above.
In `.wat`, each instruction between a pair of a beginning
`(` and closing `)`, is an `s-expression`. We can use them to form a tree of nodes.
Let's explain the code above: the first node is a top-level construct or root node that defines the structure, in this case, the fundamental unit of code of WebAssembly: a module.
The second node, a child node, is declaring an imported function, in this case `println`, from an external module -and this is the beauty of it, the ability to interoperate-, `env`, taking a single param of type `i32` -yes, integer 32-bit in this case-. When we see a `$` means we are declaring a local entity, in this case, the local name of the imported function `$println``
`block` declares a new block of instructions that we locally call `$block` and then we are invoking our imported function `$println` inside of this block with `call`. Pay attention to how we are now pushing a constant value 0 to the stack, that we will use later `(i32.const 0)`.
The following child node, declares the memory with 1 page, equivalent to 65536 bytes.
Finally, we use the `data` node to stora a string constant in memory, starting at the address 0 that we previously declared, with the `\00` or null terminator that represents the end of a string in WebAssembly.
Do you still want to write `.wat`? I don't. I'm sticking to JavaScript and Rust. In any case, you will need JavaScript to run this code (there are other ways, as we will see later). This is the invocation example.
You can play around with `.wat` local variable and references, and JavaScript, using the demo sandbox provided by developer.mozilla.org.
wat2wasm demo
If you want to see different modules in action, you can go to wat2wasm and play with the demo tool. I loved it!
Importing Wasm Modules to JavaScript
Like we could see, Wasm modules can be imported, instantiated and executed inside of any JavaScript program. And because they're more performant, for the reasons explained, they're more efficient at running resource intensive tasks, like media processing, data streaming, online gaming, etc, that JavaScript is not very good at.
In sum: Do developers write binary WebAssembly binary code?
No. I don't think so. At least I wouldn't. What developers may write, is WebAssembly Text like the one we just dissected, which is a more human-readable format and is saved in files of extension ´.wat´, that can then be compiled to ´.wasm´.
However, to write WebAssembly text, you need to learn all WebAssembly concepts first. And it's definitely not trivial learning. You would need to learn about modules, memory -ergo a resizable ArrayBuffer that stores all the bytes in your module-, tables -another array but for references- and the concept of instance, which encompasses code and state.
Additionally, you need to learn the JavaScript WebAssembly object interface and all the methods it implements, and do it in a way that is clean and efficient.
I still don't recommend you write WebAssembly text or ´.wat´files, yourself.
image caption: Wasm in the browser
Wasm as a compilation target
But don't despair! We don't need to learn to write WebAssembly Text in order to create Wasm code. In fact, WebAssembly wasn't really designed to be created in this way. It was designed to be a portable compilation target for higher-level languages.
Can we compile JavaScript to WebAssembly?
No. JavaScript doesn't compile to WebAssembly yet, but as we already discussed, they're interoperable given you can import and execute Wasm modules as part of your scripts. (Although in terms of speed, it may make sense to compile JavaScript to binary, so it doesn't have to be interpreted. But that's another story. And this work is in progress.)
Languages that compile to .wasm
Having other programming languages compile to binary code that can be ran in the browser, and has access to Web Platform APIs, is the most appealing feature of this technology, and why it is so successful. It means that developers that were restricted to writing server-side only code, can now run their code in the client.
The following high-level languages, compile to ´.wasm´ that can be executed in the browser, today. It is very likely that in the future, more languages compile to this code format, like JavaScript, since efforts to make that possible, are in progress.
Java
PHP
C# and .NET
C++
Ruby
C
Swift
R
Go
Kotlin
Rust
Haskell
Lua
Perl
Zig
Please notice that I said `in the browser`, as they can be compiled to code that runs in the JavaScript virtual machine browsers provide.
There are other contexts where `.wasm` code is potentially executable, that I will describe further down.
I will try to keep this list up to date as much as possible, but you can also visit Fermyon's site where for sure they keep much better track.
What do you need to compile languages to Wasm?
What makes it possible for languages to compile to Wasm, is called the toolchain that supports conformance with the target language.
The toolchain is basically a set of tools that play a role in compiling a language to `.wasm`, and then preparing them to be exported into a JavaScript program or executed in the Wasm runtime.
I am currently learning Rust, and the toolchain is described here.
It basically includes `cargo`, which is the default Rust CLI, `npm` for dependency management and `wasm-pack` to do the transformation to binary.
By the way, I'm learning Rust with Rust for JavaScript Developers, a course designed, written and published by Sid with whom we often talk about Wasm.
WASI
In the previous list, I made a reference to languages that support Wasm in the browser. But there are additional standarization efforts, to make it possible to run WebAssembly code not only in the client-side, but server-side and in IoT devices, for example.
WASI stands for WebAssembly System Interface and it's a modular API that provides the WebAssembly runtime sandbox, and the apps running on it, access to features of the operating system. You can read the introduction text here.
WASI Toolchain
Rust and C/C++ have WASI-enabled toolchains and WASI programs can run on its specific runtime Wasmtime or in the browser, with the necessary browser shim.
WASI and Node.js
WASI makes it possible to interact with applications running in Node.js as runtime. To learn more you can visit the official documentation for v.19.6.0.
image caption: Wasm toolchain
Wasm and Composability
So how can Wasm contribute to improve a composable architecture scenario?
We discussed that Wasm runs on JavaScript engines, like `V8`, for example, and that it runs fast, because of its binary format. And we know that some cloud execution contexts run on `V8`, particularly on CDN, at the edge of the network.
But we also know that one of the characteristics of WebAssembly, is portability, and that we have shims that allow to run Wasm (WASI enabled) server-side or in devices. That multiplies opportunities to have super-performant execution contexts, literally anywhere where you may have the necessary infrastructure -a virtual or physical machine, or other forms of virtualization like containers- in the form of functions.
Considerations
Of course, some special considerations are that when you're enabling a sort of synthetic runtime for JavaScript, and you want to guarantee interoperability between a JavaScript program, WebAssembly and the Web Platform, and use standard protocols and the data transported over that layer, your have to take certain measures.
For example, as my code example above illustrates, you may have to import definitions that your specify in your code, like I do to use `pintln` from `env`, in the `.wat` file above. Even when Wasm code's format makes it lighter and faster, we still need to learn to optimize it, and that is specially true when Wasm is a compilation target for other languages, since they will inject dependencies necessaries via the toolchain. That may suppose a resource strain for the system running Wasm, when it's clientside, deteriorating the user experience by straining resources like memory.
However, when it comes to innovative forms of composition, a lot of the frameworks we discussed in the corresponding section of the site, are designed to build applications that may be either rendered or composed on this super fast and performant runtimes in the cloud, and use service workers and other mechanisms to improve client-side performance.
Conclusion
To wrap it, the demand for immediate feedback and transaction completion by the end-user, when that's possible in terms of connection, device capability, and protocol availability, may contribute to a more consolidated composite in terms of frontend distributed applications.
At the same time, as you may have deduced if you've already read other sections of this website, I advocate for moderate to zero shipping of custom JavaScript to the end-user, to protect their user-experience regardless of where in the world they're at, and the device and connection speed they can afford, and standards like WebAssembly and the Web Platform API, allow us to do that.
In future iterations, I will talk about my experience compiling Rust programs to Wasm modules and how I integrate them to JavaScript apps.
This website uses a technical cookie to save your cookie preferences in your browser. Additionally, it uses Google Analytics to analyze the traffic.
If you continue to use this website, you consent to the use of cookies. Terms of Service and Privacy