Dart WebAssembly
last modified October 22, 2026
WebAssembly (Wasm) is a compact binary instruction format that
runs in modern browsers and in standalone runtimes. Dart can compile web
applications to WebAssembly with the dart compile wasm command,
which uses the dart2wasm compiler. The result is a
.wasm module and a small JavaScript runtime that loads it.
The Wasm backend is an alternative to compiling to JavaScript. It is a good fit for CPU-heavy web applications: the browser executes the generated instructions directly, while Dart's type system and garbage collector are supported by the WasmGC proposal.
How Dart compiles to WebAssembly
Dart targets three standardized WebAssembly proposals:
- WasmGC — garbage-collected objects, so Dart classes and collections do not need to be implemented on top of a linear memory allocator.
- JS String Builtins — efficient conversion between Dart strings and JavaScript strings.
- Exception handling — Dart exceptions and stack traces use the Wasm exception-handling proposal.
The compiler emits a .wasm module plus a companion
.mjs file. The JavaScript file contains the code that compiles and
instantiates the module, and the entry point is invoked with
invokeMain().
Browser support and requirements
Because Dart relies on WasmGC and the newer exception-handling instructions, the generated module needs a recent browser:
| Browser | Minimum version |
|---|---|
| Chrome / Edge | 137 |
| Safari (macOS and iOS) | 26.2 |
| Firefox | 134 (147 for Flutter web) |
Only Dart's next-generation JavaScript interop is supported in Wasm:
dart:js_interop and package:web. Legacy libraries
such as dart:html and package:js cannot be used.
Packages that avoid those libraries are marked as
wasm-ready on pub.dev.
The webdev tool does not support Wasm compilation in either
webdev serve or webdev build. Compile with
dart compile wasm and serve the resulting static files yourself.
Creating the project
We start with the standard web template, which already uses
package:web and has an #output element:
$ dart create -t web wasm_demo $ cd wasm_demo
Replace the generated web/main.dart with an example that computes
a list and writes it into the page:
import 'package:web/web.dart' as web;
void main() {
final output = web.document.querySelector('#output') as web.HTMLDivElement;
final squares = [for (var i = 1; i <= 5; i++) i * i];
output.textContent = 'Squares: $squares';
}
The template's web/index.html already loads
main.dart.js and provides the output element. The relevant parts
look like this:
<div id="output"></div> <script defer src="main.dart.js"></script>
Compiling to Wasm
Compile the entry point into a site directory. The
-O2 option enables optimizations that reduce code size and improve
performance:
$ mkdir -p site $ dart compile wasm -O2 web/main.dart -o site/main.wasm Generated wasm module 'site/main.wasm', and JS init file 'site/main.mjs'.
The compiler writes several files:
| File | Purpose |
|---|---|
main.wasm | The compiled WebAssembly module. |
main.mjs | JavaScript runtime that compiles and instantiates the module. |
main.support.js | Feature-detection support code for the generated runtime. |
main.wasm.map | Source map for debugging. |
Loading the module in the browser
Copy the static files into the output directory:
$ cp web/index.html web/styles.css site/
The template's index.html expects a main.dart.js file,
so we create one that imports the generated runtime, fetches the module, and
calls the Dart entry point:
(async function () {
const { compileStreaming } = await import('./main.mjs');
const app = await compileStreaming(fetch('main.wasm'));
const instantiatedApp = await app.instantiate({});
instantiatedApp.invokeMain();
})();
compileStreaming compiles the module while it is being downloaded,
instantiate creates an instance, and invokeMain runs
the Dart main function. The second argument to
instantiate is an optional import object; the basic example needs
no extra imports.
Serving the app
Wasm modules must be served over HTTP; browsers do not load them from
file:// URLs. The dhttpd package serves the
site directory with the correct MIME types:
$ dart run dhttpd@ --path site Serving site at http://localhost:8080
Opening the page shows the compiled Dart code running in the browser:
Squares: [1, 4, 9, 16, 25]
Calling JavaScript from Dart
Wasm uses dart:js_interop for JavaScript calls. The following
example writes a message to the browser console:
import 'dart:js_interop';
@JS('console.log')
external void log(JSString message);
void main() {
log('Hello from Dart WebAssembly!'.toJS);
}
The @JS annotation maps log to
console.log, external declares that the
implementation lives in JavaScript, and .toJS converts the Dart
string to a JSString. Compile and serve the app as before; the
browser console displays:
Hello from Dart WebAssembly!
Deferred loading
Deferred loading is experimental in Wasm. With the
--enable-deferred-loading flag, the compiler splits the program
into multiple modules based on its deferred imports. The loader must provide a
callback that fetches the additional modules.
First, move part of the code into a deferred library:
String message() => 'Deferred module loaded';
import 'package:web/web.dart' as web;
import 'lazy.dart' deferred as lazy;
void main() async {
final output = web.document.querySelector('#output') as web.HTMLDivElement;
output.textContent = 'Loading...';
await lazy.loadLibrary();
output.textContent = lazy.message();
}
Compile with the experimental flag:
$ dart compile wasm -O2 --enable-deferred-loading web/main.dart -o site/main.wasm
The output now includes an extra module such as
main_module1.wasm. Update the loader to fetch deferred modules
when the runtime asks for them:
(async function () {
const { compileStreaming } = await import('./main.mjs');
const app = await compileStreaming(fetch('main.wasm'));
const instantiatedApp = await app.instantiate({}, {
loadDeferredModules: (modules, handleModuleByteSource) => {
return Promise.all(
modules.map((m) =>
fetch(m).then((r) => handleModuleByteSource(m, r))),
);
},
});
instantiatedApp.invokeMain();
})();
The page first shows Loading... and then switches to the text from
the deferred library:
Deferred module loaded
Optimizing the output
The optimization level has a large effect on the size of the Wasm module. For the small squares example, compiled with Dart 3.13.5, the sizes were:
| Option | main.wasm |
|---|---|
-O0 | 63.8 KB |
-O2 | 10.2 KB |
-O4 | 10.1 KB |
Production builds usually use -O2 or higher. Additional options
include --minify for shorter runtime names,
--no-source-maps to omit the .wasm.map file, and
--no-strip-wasm to keep static symbol names for debugging.
Standalone modules
For non-browser runtimes, --standalone produces a module without
JavaScript interop:
$ dart compile wasm --standalone -O2 main.dart -o main.wasm
Standalone modules require a host that provides Dart's runtime imports. For
Wasmtime 39 or later, enable the required proposals with
-W gc,exceptions,function-references=y. Because
dart:js_interop is not available in this mode, the entry point
must be pure Dart and must not import browser libraries such as
package:web.
Limitations
dart:iois not available in browser Wasm; use web APIs throughpackage:web.- Legacy web libraries (
dart:html,dart:js,package:js) are not supported. Migrate todart:js_interopandpackage:web. - In conditional imports,
dart.library.isolateisfalsefordart2wasm, matchingdart2jsand DDC. - Only wasm-ready packages can be used. Check the package page on pub.dev before adding a dependency.
- Deferred loading and standalone output are more advanced paths; start with a normal browser build and add them only when needed.
Source
Dart: WebAssembly (Wasm) compilation
Dart: JavaScript interop
Dart: dart compile
In this article, we compiled a Dart web app to WebAssembly, loaded the module
in the browser, called JavaScript from Dart, and enabled experimental deferred
loading. Wasm is a second compilation target for Dart web apps; the source code
stays the same, but the build and loading steps differ from
dart2js.
Author
List all Dart tutorials.