Dart 61 🎯 Compiling Dart to WebAssembly (WASM) and Modern Browser Deployment
Dart has always compiled to JavaScript for the web. That path works, but it carries the limitations of JavaScript: the garbage collector, the dynamic dispatch, and the performance ceiling of the JIT. The WebAssembly target is the alternative. It compiles the Dart code to a compact binary format that the browser executes at near-native speed, with the garbage collector implemented in the Dart runtime and the DOM access through the dart:js_interop bridge. The result is the web application that loads faster, runs faster, and behaves more predictably than the JavaScript target, at the cost of the different build toolchain and the browser support baseline.
Key point: Dart’s WebAssembly target is not the standalone WASM of the C or the Rust. It is the WASM with the Dart runtime, the garbage collector, and the DOM bridge, all compiled into the one binary. The target requires the browser with the WasmGC proposal, which is the Chrome 119+, the Firefox 120+, and the Safari 18.2+.
Why the WASM target exists
The JavaScript ceiling problem. The Dart-to-JavaScript compiler produces the code that runs on the JavaScript engine. The engine’s JIT optimizes the hot paths, and the performance is the good. The ceiling is the JavaScript semantics: the dynamic types, the property lookups, and the garbage collector. The WASM target removes the ceiling by compiling to the typed binary.
The WasmGC problem. The early WebAssembly had no garbage collector. The languages with the managed memory could not compile to the WASM without shipping their own GC and the heap. The WasmGC proposal added the struct and the array types with the GC support, and the Dart, the Kotlin, and the Java could compile to the WASM natively. The Chrome shipped the WasmGC in the 119, and the Firefox in the 120, and the Safari in the 18.2.
The Dart runtime problem. The Dart’s WASM output includes the Dart runtime: the garbage collector, the type system, the async support, and the core libraries. The runtime is the compiled to the WASM and the shipped alongside the application code. The bundle is the larger than the JavaScript, and the runtime is the shared across the application.
The DOM problem. The WebAssembly has no direct DOM access. The Dart uses the dart:js_interop to call the JavaScript and the DOM APIs. The bridge is the typed, and the calls are the fast. The package:web library provides the typed bindings.
The toolchain problem. The WASM target requires the dart compile wasm command, the wasm-opt tool, and the server with the correct MIME type. The toolchain is the different from the JavaScript, and the deployment is the additional configuration.
The browser support problem. The WASM target requires the WasmGC, and the browsers that lack it cannot run the application. The JavaScript target remains the fallback, and the conditional import is the mechanism.
The Flutter problem. The Flutter web supports the WASM target. The flutter build web --wasm produces the WASM bundle, and the Skia renderer is the compiled to the WASM. The performance is the better, and the bundle is the larger.
a. The compilation
The dart compile wasm command produces the WASM module and the JavaScript loader. The output is the set of the files the browser loads.
The basic compilation
dart compile wasm main.dart -o main.wasm
The command produces the main.wasm and the main.mjs (the JavaScript loader). The main.mjs is the entry point the HTML references.
The output files
| The file | The purpose |
|---|---|
main.wasm | The WASM module with the Dart runtime |
main.mjs | The JavaScript loader and the glue |
main.wasm.map | The source map (the optional) |
main.mjs.map | The JS source map (the optional) |
The compile flags
| The flag | The purpose |
|---|---|
-o | The output path |
-O1 | The optimize for the size |
-O2 | The optimize for the speed |
-O3 | The maximum optimization |
-O4 | The aggressive optimization |
--no-source-maps | The skip the source maps |
The optimization levels
# The size
dart compile wasm main.dart -O1 -o main.wasm
# The speed
dart compile wasm main.dart -O4 -o main.wasm
The -O1 is the smaller, and the -O4 is the faster.
The conditional import
The JavaScript and the WASM targets share the source. The dart.library.js_interop condition selects the implementation.
export 'src/storage_none.dart'
if (dart.library.io) 'src/storage_io.dart'
if (dart.library.js_interop) 'src/storage_web.dart';
The dart.library.js_interop is true in the both the JavaScript and the WASM targets. The dart.library.html is the deprecated.
The dart:js_interop
The interop library is the same in the both targets. The JSString, the JSNumber, and the toJS/toDart conversions are the mechanisms.
import 'dart:js_interop';
@JS('console.log')
external void log(JSAny? message);
void main() {
log('Hello from the WASM'.toJS);
}
The @JS annotation and the external declaration are the same as the JavaScript target.
The package:web
The package:web library provides the typed DOM bindings.
import 'package:web/web.dart' as web;
void main() {
final div = web.document.createElement('div') as web.HTMLDivElement;
div.textContent = 'Hello';
web.document.body!.appendChild(div);
}
The bindings are the typed, and the calls are the interop.
The compilation table
| The element | The purpose |
|---|---|
dart compile wasm | The command |
main.wasm | The module |
main.mjs | The loader |
-O1 to -O4 | The optimization |
dart:js_interop | The interop |
package:web | The DOM |
b. The deployment
The WASM module is the static file. The server must serve it with the correct MIME type, and the HTML must load the loader.
The server MIME type
The .wasm file must be served with the application/wasm MIME type. The wrong type prevents the streaming compilation.
# The nginx
types {
application/wasm wasm;
}
The types block maps the extension to the MIME type.
The HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>The Dart WASM</title>
</head>
<body>
<script type="module">
import { compileStreaming } from './main.mjs';
// The loader is the entry.
</script>
</body>
</html>
The main.mjs is the loader, and the browser fetches the main.wasm.
The streaming compilation
The WebAssembly.compileStreaming compiles the module while the bytes are the streaming. The server must serve the application/wasm for the streaming.
const response = await fetch('main.wasm');
const module = await WebAssembly.compileStreaming(response);
The compileStreaming is the faster than the WebAssembly.instantiate with the buffered bytes.
The caching
The .wasm file is the immutable, and the content hash is the cache key.
location ~* \.wasm$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
The header tells the browser and the CDN to cache the file forever. The new build produces the new hash.
The compression
The .wasm file compresses well. The gzip and the brotli are the standard.
gzip on;
gzip_types application/wasm;
brotli on;
brotli_types application/wasm;
The compression reduces the transfer size by the 50% or more.
The conditional loading
The browser that lacks the WasmGC must fall back to the JavaScript. The feature detection and the conditional load.
<script type="module">
if (WebAssembly.validate(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0]))) {
// The WASM is the supported.
import('./main.mjs');
} else {
// The JS fallback.
import('./main.js');
}
</script>
The WebAssembly.validate checks the bytecode. The WasmGC detection is the more specific.
The deployment table
| The element | The purpose |
|---|---|
application/wasm | The MIME type |
main.mjs | The loader |
compileStreaming | The streaming |
The immutable | The cache |
| The gzip/brotli | The compression |
| The fallback | The JS |
c. The performance and the comparison
The WASM target is the faster, and the bundle is the larger. The trade-offs are the specific.
The startup
The WASM startup involves the fetch, the compile, and the instantiate. The compile is the fast, and the instantiate is the the initialization of the Dart runtime.
The fetch: the network
The compile: the streaming
The instantiate: the runtime init
The run: the main
The runtime
The runtime is the Dart’s garbage collector, the type system, and the async. The runtime is the compiled to the WASM, and the startup is the the runtime init.
The performance
The WASM is the faster than the JavaScript for the CPU-bound code. The DOM access is the interop, and the cost is the bridge.
| The workload | The JavaScript | The WASM |
|---|---|---|
| The CPU-bound | The good | The better |
| The DOM-heavy | The good | The comparable |
| The startup | The fast | The slower |
| The bundle | The smaller | The larger |
The bundle size
The WASM bundle is the larger because the runtime is the included. The JavaScript bundle is the smaller because the runtime is the browser’s.
The JS bundle: the ~1-2 MB
The WASM bundle: the ~2-4 MB
The gzip and the brotli reduce the transfer.
The browser support
| The browser | The WasmGC |
|---|---|
| The Chrome | 119+ |
| The Firefox | 120+ |
| The Safari | 18.2+ |
| The Edge | 119+ |
The older browsers need the JavaScript fallback.
The Flutter integration
flutter build web --wasm
The --wasm flag produces the WASM bundle. The Skia is the compiled to the WASM, and the performance is the better.
The comparison table
| The aspect | The JavaScript | The WASM |
|---|---|---|
| The performance | The good | The better |
| The bundle | The smaller | The larger |
| The startup | The fast | The slower |
| The support | The universal | The modern |
| The DOM | The native | The interop |
| The GC | The engine’s | The Dart’s |
Complete Example Session
// ============================================
// PART 1: THE DART SOURCE
// ============================================
import 'dart:js_interop';
import 'package:web/web.dart' as web;
void main() {
final div = web.document.createElement('div') as web.HTMLDivElement;
div.textContent = 'Hello from the WASM';
div.className = 'flex items-center p-4';
web.document.body!.appendChild(div);
}
// ============================================
// PART 2: THE COMPILATION
// ============================================
// dart compile wasm main.dart -o main.wasm
// The output:
// main.wasm
// main.mjs
// ============================================
// PART 3: THE OPTIMIZATION
// ============================================
// The size
// dart compile wasm main.dart -O1 -o main.wasm
// The speed
// dart compile wasm main.dart -O4 -o main.wasm
// ============================================
// PART 4: THE HTML
// ============================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>The Dart WASM</title>
</head>
<body>
<script type="module" src="./main.mjs"></script>
</body>
</html>
// ============================================
// PART 5: THE SERVER MIME TYPE
// ============================================
// The nginx
types {
application/wasm wasm;
}
// ============================================
// PART 6: THE CACHING
// ============================================
location ~* \.wasm$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
// ============================================
// PART 7: THE COMPRESSION
// ============================================
gzip on;
gzip_types application/wasm;
brotli on;
brotli_types application/wasm;
// ============================================
// PART 8: THE CONDITIONAL LOAD
// ============================================
<script type="module">
if (WebAssembly.validate(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0]))) {
import('./main.mjs');
} else {
import('./main.js');
}
</script>
// ============================================
// PART 9: THE FLUTTER WASM
// ============================================
// flutter build web --wasm
// ============================================
// PART 10: THE COMPLETE PATTERN
// ============================================
// 1. Use the dart:js_interop for the DOM.
// 2. Use the package:web for the typed bindings.
// 3. Use the dart compile wasm to build.
// 4. Use the -O1 for the size, the -O4 for the speed.
// 5. Serve the application/wasm MIME type.
// 6. Use the compileStreaming for the fast startup.
// 7. Cache the .wasm with the immutable header.
// 8. Compress with the gzip or the brotli.
// 9. Detect the WasmGC and the fall back.
// 10. The Flutter uses the --wasm flag.
The ten parts showed the Dart source, the compilation, the optimization, the HTML, the server MIME type, the caching, the compression, the conditional load, the Flutter WASM, and the complete pattern.
Quick Reference
The Compilation
| The command | The purpose |
|---|---|
dart compile wasm | The build |
-o | The output |
-O1 to -O4 | The optimization |
--no-source-maps | The skip |
The Output Files
| The file | The purpose |
|---|---|
main.wasm | The module |
main.mjs | The loader |
main.wasm.map | The source map |
The Server Config
| The element | The purpose |
|---|---|
application/wasm | The MIME type |
The immutable | The cache |
| The gzip | The compression |
| The brotli | The compression |
The Browser Support
| The browser | The WasmGC |
|---|---|
| The Chrome | 119+ |
| The Firefox | 120+ |
| The Safari | 18.2+ |
The Interop
| The element | The purpose |
|---|---|
dart:js_interop | The core |
package:web | The DOM |
toJS/toDart | The conversion |
@JS | The annotation |
Best Practices
✅ Do This:
// Use the dart:js_interop.
import 'dart:js_interop'; // ✅
// Use the package:web.
import 'package:web/web.dart' as web; // ✅
# Use the correct optimization.
dart compile wasm main.dart -O4 -o main.wasm // ✅
# Serve the application/wasm.
types { application/wasm wasm; } // ✅
# Cache the .wasm.
add_header Cache-Control "public, max-age=31536000, immutable"; // ✅
# Compress the .wasm.
brotli_types application/wasm; // ✅
<!-- Detect the WasmGC and the fall back. -->
<script type="module">
if (WebAssembly.validate(...)) import('./main.mjs');
else import('./main.js');
</script> // ✅
❌ Don’t Do This:
# Don't serve the .wasm as the application/octet-stream.
# The streaming compilation is disabled. // ❌
# Don't skip the optimization.
dart compile wasm main.dart // ⚠️ The larger
// Don't use the dart:html in the new code.
import 'dart:html'; // ❌ The deprecated
<!-- Don't forget the fallback. -->
<script src="./main.mjs"></script> // ❌ The no fallback
# Don't cache the .mjs forever.
# The loader is the updated. // ⚠️ The hash the .mjs too
// Don't assume the WASM is the always faster.
// The DOM-heavy is the comparable. // ❌
# Don't forget the MIME type.
# The server serves the octet-stream. // ❌
Common Pitfalls
| The pitfall | The reason | The fix |
|---|---|---|
| The streaming disabled | The wrong MIME | Serve application/wasm |
| The startup slow | The large runtime | Use the -O4 |
| The browser error | The no WasmGC | Add the fallback |
| The stale cache | The no hash | Use the content hash |
| The large transfer | The no compression | Use the gzip/brotli |
| The DOM slow | The interop overhead | Minimize the calls |
| The bundle large | The runtime | Accept or the JS |
Real-World Examples
1. The Basic Compilation
dart compile wasm main.dart -o main.wasm
2. The Optimization
dart compile wasm main.dart -O4 -o main.wasm
3. The HTML
<script type="module" src="./main.mjs"></script>
4. The MIME Type
types { application/wasm wasm; }
5. The Caching
add_header Cache-Control "public, max-age=31536000, immutable";
6. The Compression
brotli_types application/wasm;
7. The Conditional Load
<script type="module">
if (WebAssembly.validate(...)) import('./main.mjs');
else import('./main.js');
</script>
8. The Flutter WASM
flutter build web --wasm
9. The package:web
import 'package:web/web.dart' as web;
10. The Complete Pattern
dart compile wasm main.dart -O4 -o main.wasm
Visual
The Dart WASM Pipeline
┌────────────────────────────────────────────────────────────┐
│ THE DART WASM PIPELINE │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ THE DART SOURCE │ │
│ │ The main.dart │ │
│ │ The dart:js_interop, the package:web │ │
│ └────────────────────────┬─────────────────────────┘ │
│ │ │
│ v │
│ ┌──────────────────────────────────────────────────┐ │
│ │ THE dart compile wasm │ │
│ │ The -O4, the -o main.wasm │ │
│ └────────────────────────┬─────────────────────────┘ │
│ │ │
│ v │
│ ┌──────────────────────────────────────────────────┐ │
│ │ THE OUTPUT │ │
│ │ The main.wasm (the runtime + the app) │ │
│ │ The main.mjs (the loader) │ │
│ └────────────────────────┬─────────────────────────┘ │
│ │ │
│ v │
│ ┌──────────────────────────────────────────────────┐ │
│ │ THE SERVER │ │
│ │ The application/wasm │ │
│ │ The gzip/brotli, the immutable │ │
│ └────────────────────────┬─────────────────────────┘ │
│ │ │
│ v │
│ ┌──────────────────────────────────────────────────┐ │
│ │ THE BROWSER │ │
│ │ The compileStreaming │ │
│ │ The instantiate, the run │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
The Bundle Comparison
┌────────────────────────────────────────────────────────────┐
│ THE BUNDLE COMPARISON │
│ │
│ THE JAVASCRIPT │
│ ┌──────────────────────────────────────────────────┐ │
│ │ The app: the ~1-2 MB │ │
│ │ The runtime: the browser's │ │
│ │ The startup: the fast │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ THE WASM │
│ ┌──────────────────────────────────────────────────┐ │
│ │ The app + the runtime: the ~2-4 MB │ │
│ │ The runtime: the Dart's │ │
│ │ The startup: the slower │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ The WASM is the larger and the faster at the steady. │
└────────────────────────────────────────────────────────────┘
The Browser Support
┌────────────────────────────────────────────────────────────┐
│ THE BROWSER SUPPORT │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ The Chrome 119+ │ │
│ │ The Firefox 120+ │ │
│ │ The Safari 18.2+ │ │
│ │ The Edge 119+ │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ THE FALLBACK │
│ ┌──────────────────────────────────────────────────┐ │
│ │ The older browser → the JS bundle │ │
│ │ The WebAssembly.validate → the detection │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
The Deployment
┌────────────────────────────────────────────────────────────┐
│ THE DEPLOYMENT │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ The main.wasm │ │
│ │ → the application/wasm MIME │ │
│ │ → the immutable cache │ │
│ │ → the gzip/brotli │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ The main.mjs │ │
│ │ → the loader │ │
│ │ → the shorter cache │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ The index.html │ │
│ │ → the conditional load │ │
│ │ → the no-cache │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
Summary
| The item | The value |
|---|---|
dart compile wasm | The compilation |
The main.wasm | The module |
The main.mjs | The loader |
The application/wasm | The MIME type |
The compileStreaming | The fast startup |
The dart:js_interop | The interop |
The package:web | The DOM |
The -O1 to -O4 | The optimization |
| The WasmGC | The browser requirement |
| The fallback | The older browser |
Key takeaways:
- The Dart’s WASM target is the WASM with the Dart runtime. The runtime is the garbage collector, the type system, and the core libraries, compiled to the WASM and shipped with the application. The bundle is the larger, and the performance is the better.
- The WasmGC is the requirement. The Chrome 119+, the Firefox 120+, and the Safari 18.2+ support the garbage collector in the WASM. The older browsers need the JavaScript fallback.
- The
dart compile wasmproduces the two files. Themain.wasmis the module, and themain.mjsis the JavaScript loader. The HTML references the loader, and the loader fetches the module. - The
application/wasmMIME type is the requirement for the streaming. The wrong type disables thecompileStreaming, and the startup is the slower. The nginxtypesblock is the configuration. - The
dart:js_interopand thepackage:webare the DOM mechanisms. The interop library is the same in the both the JavaScript and the WASM targets, and the bindings are the typed. - The optimization levels trade the size for the speed. The
-O1is the smaller, and the-O4is the faster. The choice depends on the workload. - The compression and the caching are the deployment. The gzip and the brotli reduce the transfer, and the immutable cache reduces the repeat. The content hash is the cache key.
- The Flutter supports the WASM. The
flutter build web --wasmproduces the WASM bundle, and the Skia is the compiled to the WASM. The performance is the better for the Flutter web.
Remember: The Dart’s WASM target is the alternative to the JavaScript. It compiles the Dart to the WASM with the runtime, and the browser executes it at the near-native speed. The dart compile wasm produces the module and the loader, the application/wasm MIME type enables the streaming, and the dart:js_interop bridges the DOM. The result is the web application that is the faster and the more predictable, at the cost of the larger bundle and the modern browser baseline.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!