Blog
·12 min

WebAssembly nello Sviluppo Web Moderno: Oltre JavaScript

How WebAssembly is changing the web platform — from high-performance browser applications to serverless compute. A practical guide for developers.

JavaScript has been the undisputed language of the web for over two decades, but the browser runtime is no longer a single-language environment. WebAssembly has quietly become the second runtime on every modern browser, and it enables capabilities that JavaScript alone cannot deliver efficiently. Image editors, video codecs, database engines, 3D renderers, language runtimes — these are all shipping inside browser tabs today, compiled to a low-level binary format that runs at near-native speed.

What is WebAssembly and why it matters

WebAssembly is a low-level binary instruction format that runs in a stack-based virtual machine. It was designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications. The four browser vendors — Google, Mozilla, Apple, and Microsoft — collaborated on its design, and it became a W3C standard in 2019. Every major browser has supported it since 2017.

But WebAssembly is not a replacement for JavaScript. It is a complement. JavaScript remains the language of DOM manipulation, event handling, and UI logic. WebAssembly handles the compute-intensive work that JavaScript was never designed for. The two runtimes coexist in the same tab, with modules importing JavaScript functions and JavaScript calling Wasm exports through a well-defined API.

WebAssembly does not aim to replace JavaScript. It aims to solve the problems that JavaScript cannot solve efficiently. The web platform is stronger because developers can choose the right tool for each job within the same application.

Wasm fundamentals: binary format and linear memory

WebAssembly modules use a binary format (.wasm files) that is designed for fast parsing and small transmission size. The format is compact — a typical module is significantly smaller than equivalent JavaScript, even after minification. The binary format encodes types, functions, imports, exports, memory declarations, and instructions in a dense representation that browsers can validate and compile in a single linear pass.

Every WebAssembly module operates on a conceptual stack machine. Instructions push values onto an evaluation stack and pop values to perform operations. The supported value types are i32, i64, f32, and f64 — 32-bit and 64-bit integers and floating-point numbers. There are no strings, objects, arrays, or any other high-level types at the Wasm level. All complex data structures must be represented in linear memory as raw bytes.

Linear memory is the most important concept in WebAssembly. Each module can declare one or more blocks of contiguous, resizable memory. This memory is a flat byte array that the module can read and write using load and store instructions. The host (the JavaScript runtime or the Wasm runtime) controls the initial size and the maximum size, and it can grow the memory by requesting additional pages of 65,536 bytes each. From the host side, this memory is accessible as an ArrayBuffer, which means JavaScript can read and write the same memory that the Wasm module uses.

This shared-memory model is what makes WebAssembly efficient. The host and the module can exchange data without serialization or copying — they simply write to the same buffer. When a Rust function returns a string to JavaScript, it writes the string bytes into linear memory and returns a pointer (an integer offset) and a length. JavaScript reads those bytes directly from the ArrayBuffer. No JSON parsing, no message passing, no overhead beyond the function call itself.

Module structure and validation

A WebAssembly module is composed of sections: the type section defines function signatures, the function section declares functions, the code section contains the actual bytecode, the memory section defines linear memory, and the export section makes functions and memory available to the host. Before any module executes, the browser validates it structurally and enforces type safety. The validation ensures that all instruction operands match their expected types, all function calls reference valid signatures, and all memory accesses stay within declared bounds. This validation happens in milliseconds and guarantees that a malformed module cannot exploit the runtime.

The security model is explicit and constrained. A WebAssembly module cannot access the DOM, make network requests, read files, or perform any system interaction unless the host explicitly provides those capabilities through imported functions. The module operates in a sandbox with no access to anything outside its linear memory and its imported function table. This makes WebAssembly modules safe to execute from untrusted sources — a property that is critical for both browser and server-side Wasm.

Compiling to Wasm: Rust, C, Go, Zig comparison

The quality of the WebAssembly compilation target varies significantly by language. Some languages produce tight, efficient Wasm modules with minimal overhead. Others require a runtime that adds megabytes to the binary size. Choosing the right source language for your Wasm project depends on your performance requirements, your team's expertise, and the complexity of the integration you need.

Rust: the gold standard for Wasm

Rust has the best WebAssembly support of any language. The wasm-pack toolchain handles compilation, optimization, and JavaScript bindings generation seamlessly. Rust produces small Wasm binaries because it has no garbage collector, no heavy runtime, and its ownership model maps naturally to linear memory management. A typical Rust Wasm module with a few exported functions is between 10 KB and 50 KB after LTO and optimization, which is competitive with handwritten Wasm.

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
}

#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}! Wasm says hello.", name)
}

The wasm_bindgen crate generates JavaScript glue code that handles the linear memory dance automatically. Calling fibonacci from JavaScript looks and feels like calling any other JavaScript function. Behind the scenes, wasm_bindgen translates string parameters into pointer-length pairs, invokes the Wasm function, and converts the result back to a JavaScript string. This abstraction layer adds minimal overhead and makes Rust-Wasm integration practical for everyday use.

C and C++ via Emscripten

Emscripten is the original Wasm compilation toolchain. It compiles C and C++ code to Wasm using LLVM as the backend, and it provides a comprehensive POSIX compatibility layer that makes it possible to port existing native applications to the web with minimal source changes. Emscripten includes a virtual filesystem, OpenGL-to-WebGL translation, pthread emulation, and a JavaScript runtime that handles the module lifecycle.

Go with the official Wasm backend

Go added WebAssembly support in Go 1.11, and it continues to improve with each release. Compiling a Go program to Wasm is straightforward — you set GOOS=js GOARCH=wasm and run go build. However, Go's Wasm support has significant limitations compared to Rust. Go includes its runtime (goroutine scheduler, garbage collector, defer stack) in every Wasm binary, which means a minimal Go Wasm module starts at around 2 MB. This is acceptable for server-side Wasm where binary size matters less, but it is prohibitive for browser use cases where download time and parse time are critical.

Zig: the new contender

Zig positions itself as a modern replacement for C with first-class WebAssembly support. The Zig compiler can target Wasm directly without any toolchain wrappers like Emscripten. Zig produces Wasm modules that are competitive with Rust in size and performance, and its comptime feature enables metaprogramming that can eliminate runtime overhead entirely. Zig does not include a hidden runtime — no garbage collector, no allocator, no startup code — unless you explicitly link one in.

  • Rust produces the smallest Wasm binaries (10-50 KB for typical modules) and has the best tooling ecosystem with wasm-pack and wasm-bindgen.
  • C and C++ via Emscripten are the best choice for porting existing native codebases to Wasm, with the highest compatibility but larger binary sizes.
  • Go compiles to Wasm easily but produces large binaries (2 MB+) due to the included runtime, making it better suited for server-side Wasm.
  • Zig produces tight Wasm modules with no runtime overhead and is ideal for minimal embedded Wasm use cases.

Using Wasm in the browser

Loading and executing a WebAssembly module in the browser follows a standard API that is consistent across all modern browsers. The WebAssembly JavaScript API provides the WebAssembly namespace with compile, instantiate, and instantiateStreaming functions. The streaming variant is preferred because it starts compiling the module as the bytes download, overlapping network I/O with compilation.

async function loadWasm(url: string) {
  const response = await fetch(url);
  const results = await WebAssembly.instantiateStreaming(response);
  return results.instance.exports;
}

async function main() {
  const wasm = await loadWasm("/fibonacci.wasm");
  console.log(wasm.fibonacci(40));
}

main().catch(console.error);

The instantiateStreaming API returns an object containing an instance property (the compiled module instance) and an exports object with all the functions and memory declared as exports in the Wasm module. You can also pass an imports object as the second argument, which provides the module access to JavaScript functions and values it was compiled to expect.

For production use, you typically want to precompile the Wasm module during the build step or use a bundler plugin. Webpack 5 supports Wasm imports natively, and Vite has experimental Wasm support. These bundlers handle the fetch, instantiation, and lifecycle management automatically, letting you import Wasm modules as if they were JavaScript modules. The import pattern looks like import init from './pkg/fibonacci.js' when using wasm-pack, which gives you a promise-based initialization function that handles all the glue code.

A critical consideration for browser Wasm is the initial compilation. Wasm modules are compiled from their binary format to native code on first load. For small modules, compilation takes a few milliseconds. For large modules — those over 10 MB — compilation can take several hundred milliseconds and can block the main thread. The solution is to use WebAssembly.compileStreaming in a Web Worker, which compiles the module off the main thread and returns a compiled module object that the main thread can instantiate instantly. This pattern is essential for production applications that load large Wasm modules.

WASI and server-side Wasm

WebAssembly was originally designed exclusively for the browser, but its security model and performance characteristics make it equally attractive for server-side use cases. The challenge is that the browser provides specific host APIs — DOM, fetch, WebSocket — that server-side environments lack. The WebAssembly System Interface solves this by defining a standard set of POSIX-like system calls that Wasm modules can use outside the browser.

WASI provides abstractions for file I/O, networking, clock access, random number generation, environment variables, and command-line argument handling. A Wasm module compiled with WASI support can read files, open network sockets, and interact with the operating system through a standardized interface that works across any WASI-compliant runtime. This is the foundation for running Wasm as a server application.

The WASI standard is evolving through multiple snapshots. WASI preview 1 is the stable baseline that most toolchains support today. WASI preview 2 introduces a component-model-aware design with capability-based security, where each Wasm module explicitly declares which system resources it needs. Preview 2 is a significant architectural improvement because it enables fine-grained permission models — a module that only needs to read a specific file cannot access any other file on the system.

WASI transforms WebAssembly from a browser technology into a universal sandboxed runtime. The same module that runs in a browser tab can also run in a server-side Wasm runtime, in a serverless function, or on an edge node — without modification. Write once, run anywhere takes on new meaning when the anywhere includes environments that JavaScript cannot reach.

Wasm runtimes and deployment

Running WebAssembly outside the browser requires a server-side Wasm runtime. Several mature runtimes exist, each with different design philosophies and use cases. The three most important are Wasmtime, Wasmer, and the WAMR (WebAssembly Micro Runtime) for embedded systems.

Wasmtime

Wasmtime is a standalone Wasm runtime developed by the Bytecode Alliance, the same organization that drives the WASI standard. It is written in Rust, compiles Wasm modules using Cranelift (a code generator designed specifically for Wasm), and provides APIs in Rust, C, Python, and other languages. Wasmtime is the most widely used runtime for server-side Wasm and is the runtime behind Cloudflare Workers, Fastly Compute@Edge, and several other edge computing platforms.

use wasmtime::*;

fn main() -> Result<()> {
    let engine = Engine::default();
    let module = Module::from_file(&engine, "hello.wasm")?;
    let mut store = Store::new(&engine, ());
    let instance = Instance::new(&mut store, &module, &[])?;

    let hello = instance
        .get_typed_func::<(), ()>(&mut store, "hello")?;
    hello.call(&mut store, ())?;

    Ok(())
}

Wasmtime's embedding API is clean and idiomatic. You create an Engine (the compilation environment), load a Module from a file or bytes, create a Store (the execution context that holds Wasm linear memory), and instantiate the module to get access to its exported functions. The runtime handles memory management, trap handling, and WASI system call forwarding automatically.

Wasmer

Wasmer is another popular Wasm runtime that supports multiple compilation backends — Cranelift, LLVM, and Singlepass (for fast JIT compilation with minimal optimization). Wasmer provides language SDKs for Rust, C, C++, Python, Go, PHP, Ruby, Java, and others, making it accessible from virtually any programming environment. Wasmer also offers a package registry (WAPM) for distributing Wasm modules, similar to npm but for Wasm modules.

The key difference between Wasmer and Wasmtime is that Wasmer focuses on developer experience and ease of embedding, while Wasmtime focuses on standards compliance and security. Both are excellent choices, and your selection depends on whether you value runtime ecosystem breadth (Wasmer) or strict spec conformance and production track record (Wasmtime).

  • Wasmtime: Best for production server-side Wasm, strict WASI compliance, Cranelift JIT compiler, used by Cloudflare and Fastly.
  • Wasmer: Best for embedding Wasm in non-Rust applications, supports LLVM compilation for near-native speed, has the WAPM package ecosystem.
  • WAMR: Best for IoT and embedded devices, tiny footprint, interpreter and JIT modes, supports iwasm CLI tool.

Performance characteristics and when Wasm wins

The performance advantage of WebAssembly over JavaScript depends on the workload. For CPU-bound, numerically-intensive operations, Wasm typically runs 1.5x to 3x faster than JavaScript. For memory-intensive operations like image processing or data compression, the advantage can be larger because Wasm has direct control over memory layout and allocation patterns. For I/O-bound or DOM-heavy workloads, Wasm offers no advantage and may add overhead because of the bridging cost between the Wasm module and the JavaScript host.

The compilation model is another factor. JavaScript uses JIT compilation, which starts executing code immediately but optimizes hot paths over time. Wasm is compiled ahead of time or at module load time, so it reaches peak performance immediately — there is no warm-up period. For short-lived functions or singleton executions, this eliminates the JIT warm-up delay that can make JavaScript slower on first invocation.

Predictability is where Wasm truly excels. JavaScript JIT optimization is non-deterministic — the same function might be optimized or deoptimized based on the types it receives, the number of times it is called, and the current state of the JIT compiler's heuristics. Wasm execution is deterministic. Every instruction has a fixed cost. There is no garbage collection pause, no type confusion, no optimization bailout. This makes Wasm the right choice for workloads where latency consistency matters more than theoretical peak throughput — audio processing, physics simulation, financial calculations.

  • Wasm wins for: image and video processing, audio synthesis and analysis, data compression and decompression, cryptographic operations, game engines, physics simulations, database query execution.
  • JavaScript wins for: DOM manipulation, event handling, network request orchestration, UI rendering with frameworks, string and text processing, small or infrequently called utilities.
  • The boundary is shifting: as Wasm gains access to web APIs through WASI and the component model, more workloads migrate to Wasm, but JavaScript remains the primary interface layer.

The Component Model and the future of Wasm

The WebAssembly Component Model is the most significant evolution of Wasm since its initial release. It addresses the fundamental limitation of early Wasm: that modules were isolated silos with no standardized way to interface with each other. The Component Model introduces a high-level, language-agnostic interface system that allows Wasm modules to be composed together like building blocks.

In the current Wasm model, a module exports functions that take and return only integers and floats. If a module needs to pass a string, an array, or a complex data structure to another module, the two modules must agree on a memory layout convention — they must share a linear memory region and coordinate allocation and deallocation. This is fragile and language-specific. The Component Model solves this by defining a standardized Interface Types system that handles strings, records, variants, lists, and nested data structures across module boundaries.

Interface Types describe the data that flows between components in a language-independent way. A component can export a function that takes a string and returns a record with two integer fields, and any other component — regardless of whether it was written in Rust, C, or Go — can call that function without knowing anything about the other module's internal memory layout. The runtime handles the adapter logic automatically, translating between the calling component's representation and the callee's representation.

Real-world case studies

WebAssembly is not a theoretical technology. It powers some of the most demanding web applications in production today, and its adoption is accelerating as the tooling matures and the performance benefits become undeniable.

Figma: the Wasm pioneer

Figma is the most well-known Wasm success story. Its core rendering engine is written in C++ and compiled to Wasm via Emscripten. Figma compiles the entire C++ codebase — including the Skia graphics library, HarfBuzz text shaper, and its custom layout engine — into a Wasm module. The JavaScript layer handles input events and DOM management while Wasm handles all rendering, hit testing, and layout calculations. The result is a design tool that runs in a browser tab with performance rivaling native desktop applications.

SQLite: database in the browser

SQLite is probably the most widely deployed Wasm module in the world. The SQLite project ships an official Wasm build that runs the full SQLite database engine in the browser. Users can query a SQLite database entirely on the client side, with no server component. The Wasm build is produced using the Emscripten toolchain and includes virtual filesystem support for persisting databases to IndexedDB.

The implications are significant. Applications that need client-side query capabilities — data analysis tools, reporting dashboards, scientific computing notebooks — can use SQLite in Wasm to run complex aggregations, joins, and window functions directly in the browser without sending data to a server. Libraries like sql.js and better-sqlite3-wasm wrap the SQLite Wasm module in clean JavaScript APIs, making it as easy to use as any other npm package.

Google Earth: Wasm for 3D rendering

Google Earth for browsers was rebuilt using Wasm for its 3D terrain processing and data decompression pipelines. As the user navigates, the Wasm module receives compressed binary data, decompresses it using optimized C++ algorithms compiled to Wasm, processes the geometry into renderable meshes, and feeds the results to WebGL — all within a single animation frame budget without any data copying overhead.

  • Figma uses Wasm for its C++ rendering engine, achieving native-class performance for vector graphics editing in the browser.
  • SQLite ships an official Wasm build that runs the full database engine client-side, enabling offline query capabilities without a server.
  • Google Earth relies on Wasm for real-time decompression and geometry processing of streaming geospatial data at 60 FPS.
  • Adobe uses Wasm in Photoshop for web (via Emscripten-compiled C++ code) for image processing filters and color space conversions.
  • Zoom uses Wasm in its web client for video decoding and background blur processing.

Wasm at the edge and in serverless

Serverless platforms have adopted WebAssembly as a runtime for edge computing because of its cold-start advantage over containers. A Wasm module starts in microseconds — not milliseconds — because there is no operating system to boot, no process to fork, and no runtime to initialize. For serverless platforms where cold start latency directly affects user experience, this is transformative.

Cloudflare Workers was the first major platform to embrace Wasm. Workers run V8 isolates that can execute both JavaScript and Wasm modules. A Worker that uses a Wasm module for its computation logic can start serving requests in under 5 milliseconds from cold start. The Worker environment provides a limited set of APIs based on the Service Worker specification — fetch, Cache, KV storage, and Durable Objects — and Wasm modules compiled with WASI support can use these APIs through the host environment's import mechanism.

Fastly's Compute@Edge uses Wasmtime as its core runtime and requires all computations to be compiled to Wasm. Developers write their edge logic in Rust, Go, or JavaScript (which Fastly compiles ahead of time to Wasm), and the resulting Wasm module runs on Fastly's edge nodes. This model provides strong security isolation — each request runs in a fresh Wasm instance with no shared state — combined with the startup speed that makes edge computing practical.

The edge Wasm ecosystem is still young but growing rapidly. Platforms like Fermyon Spin and Deislabs provide frameworks specifically designed for building serverless Wasm applications. These frameworks abstract the WASI runtime details and provide familiar patterns — HTTP handlers, key-value storage, scheduled tasks — while executing everything through Wasm. The promise is that you can write your business logic once in any language that compiles to Wasm and deploy it across any WASI-compatible platform without modification.

Getting started with Wasm today

The best way to start with WebAssembly depends on your goal. If you want to use Wasm in the browser, the fastest path is Rust with wasm-pack. Install Rust, add the wasm32-unknown-unknown target, and create a new project with wasm-pack new. The generated project includes a working example with a Wasm function callable from JavaScript, a build script that produces an npm-compatible package, and a test harness that runs your Wasm module in a headless browser.

If you want to explore server-side Wasm, start with Wasmtime and its CLI tool. Install wasmtime, compile any C or Rust program with WASI support, and run it with wasmtime program.wasm. You will see that a command-line program compiled to Wasm runs just like a native binary — it reads from stdin, writes to stdout, accesses files, and exits with a status code. The experience of running a Wasm-compiled program in a terminal is surprisingly seamless.

For edge and serverless experimentation, Cloudflare Workers provides a free tier that supports Wasm. Write a Rust function, compile it to Wasm, and deploy it to Cloudflare's global network. You will see response times under 10 milliseconds from most locations, and you will experience firsthand how Wasm eliminates cold start as a concern.

The most practical first step with Wasm is not to rewrite your entire application. Find one computationally expensive function — a filter, a transformation, a validation — rewrite it in Rust, compile it to Wasm, and integrate it into your existing codebase. Measure the performance difference. When you see a 3x speedup for fifty lines of Wasm, you will understand why the technology matters.

The tooling landscape is improving rapidly. wasm-pack, cargo-generate, and wasm-tools provide a mature development workflow. The Binaryen toolchain offers wasm-opt for optimizing Wasm modules, reducing binary sizes by 20 to 30 percent. The wasmtime and wasmer runtimes continue to close the performance gap with native execution. And the Component Model will soon make Wasm modules interoperable in ways that were impossible when Wasm first launched.

WebAssembly is not a replacement for the web as we know it. It is an enrichment of the platform — a second runtime that does what JavaScript cannot. The next time you face a performance problem that feels like you are fighting against JavaScript's dynamic nature, consider whether the compute-heavy portion of your code could be extracted into a Wasm module. The tools are production-ready, the performance is real, and the future of Wasm — with the Component Model, WASI preview 2, and broader language support — is only getting better.

The web platform was designed to be extensible. WebAssembly is the most important extension to that platform in a decade. Understanding it is not optional for developers who want to build applications that push the boundaries of what the web can do.