Lesson 50 of 77 intermediate

Flutter Web + Wasm: What, Why & Deployment

WebAssembly compilation, performance gains, current limitations, deployment workflow

Open interactive version (quiz + challenge)

Real-world analogy

Think of CanvasKit Flutter Web as a powerful diesel generator — it gets the job done anywhere but is heavy and slow to start. Wasm Flutter Web is a Tesla — it has a bigger initial investment (browser compatibility requirements) but once it runs, it is dramatically faster and cleaner. Not all garages support Teslas yet (older browsers), but the direction is clear.

What is it?

Flutter Web with WebAssembly compilation is the current frontier of Flutter Web performance. It represents the maturation of Flutter's web support from an experimental curiosity to a genuinely competitive option for complex interactive web applications — while honest about its limitations for SEO-dependent use cases.

Real-world relevance

The SaaS collaboration platform's web dashboard migrates from CanvasKit to Wasm after Flutter 3.22 ships. The complex real-time collaborative canvas (whiteboard feature) benefits from 2-3x faster DOM-equivalent computation. The team updates their Firebase Hosting configuration to add COOP/COEP headers. They add package:web migrations for two plugins. Browser analytics confirm 95%+ of their enterprise users are on Chrome 119+ or Firefox 120+.

Key points

Code example

// === BUILD COMMANDS ===
// Build with Wasm (Flutter 3.22+)
// flutter build web --wasm

// Build with specific renderer fallback
// flutter build web --wasm --dart-define=FLUTTER_WEB_AUTO_DETECT=true

// Serve locally with correct Wasm MIME type
// flutter run -d chrome --wasm

// === REQUIRED RESPONSE HEADERS (firebase.json) ===
/*
{
  "hosting": {
    "headers": [
      {
        "source": "**",
        "headers": [
          { "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
          { "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" }
        ]
      },
      {
        "key": "Content-Type",
        "value": "application/wasm",
        "source": "**.wasm"
      }
    ]
  }
}
*/

// === JS INTEROP — MIGRATING FROM dart:html TO package:web ===

// OLD (dart:html — does NOT work with Wasm):
import 'dart:html' as html;

void downloadFile(String url, String filename) {
  final anchor = html.AnchorElement(href: url)
    ..setAttribute('download', filename)
    ..click();
}

// NEW (package:web + dart:js_interop — Wasm-compatible):
import 'package:web/web.dart' as web;
import 'dart:js_interop';

void downloadFile(String url, String filename) {
  final anchor = web.document.createElement('a') as web.HTMLAnchorElement;
  anchor.href = url;
  anchor.download = filename;
  web.document.body!.append(anchor);
  anchor.click();
  anchor.remove();
}

// === CONDITIONAL IMPORTS — SUPPORT BOTH MOBILE AND WEB ===
// web_utils_stub.dart (for non-web platforms)
void downloadFile(String url, String filename) {
  throw UnsupportedError('downloadFile only supported on web');
}

// web_utils_web.dart (for web platforms)
import 'package:web/web.dart' as web;
void downloadFile(String url, String filename) {
  // ... web implementation
}

// In your service:
import 'web_utils_stub.dart'
    if (dart.library.js_interop) 'web_utils_web.dart';

// === CHECKING WASM SUPPORT IN FLUTTER BOOTSTRAP ===
/*
flutter_bootstrap.js auto-detects Wasm GC support:
- if (WebAssembly.validate(wasmGcFeatureDetectionBytes)) -> load .wasm
- else -> fall back to CanvasKit JS bundle

You can read which path was taken:
*/
import 'dart:js_interop';

@JS('window.__flutter_loader_initialized_with_wasm')
external bool get isWasmEnabled;

// === PERFORMANCE MEASUREMENT ===
class PerformanceTracker {
  static void measureFrameTime() {
    // Compare Wasm vs CanvasKit rendering
    final stopwatch = Stopwatch()..start();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      final frameTime = stopwatch.elapsedMicroseconds;
      analytics.logEvent('frame_time', parameters: {
        'microseconds': frameTime,
        'renderer': isWasmEnabled ? 'wasm' : 'canvaskit',
      });
    });
  }
}

// === PUBSPEC.YAML — REQUIRED DEPENDENCIES ===
/*
dependencies:
  flutter:
    sdk: flutter
  web: ^1.0.0          # New Wasm-compatible web APIs (replaces dart:html)

dev_dependencies:
  flutter_test:
    sdk: flutter
*/

Line-by-line walkthrough

  1. 1. flutter build web --wasm — triggers dart2wasm compiler instead of dart2js; generates .wasm file alongside JS bootstrap
  2. 2. COOP/COEP headers in firebase.json — enables SharedArrayBuffer required by Wasm threading; must be set at hosting level
  3. 3. dart:html AnchorElement — the OLD approach; works with dart2js but fails to compile or misbehaves with dart2wasm
  4. 4. package:web HTMLAnchorElement — the NEW approach; uses Wasm-compatible IDL bindings generated from browser specs
  5. 5. conditional imports (stub/web pattern) — allows same Dart code to work on mobile (stub) and web (web implementation) by selecting the correct file at compile time
  6. 6. @JS() annotation with external keyword — dart:js_interop binding; tells the Dart compiler this function is implemented in JavaScript
  7. 7. flutter_bootstrap.js auto-detection — checks for Wasm GC support, loads .wasm bundle if available, falls back to CanvasKit JS otherwise
  8. 8. WidgetsBinding.addPostFrameCallback — fires after each frame completes; used here to measure actual frame render time and log renderer type

Spot the bug

// Flutter Web Wasm app — file download utility
import 'dart:html' as html;

class FileDownloadService {
  void downloadReport(String url) {
    final link = html.document.createElement('a') as html.AnchorElement;
    link.href = url;
    link.download = 'report.pdf';
    html.document.body!.children.add(link);
    link.click();
    html.document.body!.children.remove(link);
  }
}
Need a hint?
This code compiles with CanvasKit (dart2js) but fails with Wasm. Why?
Show answer
Bug: dart:html is incompatible with Flutter Wasm (dart2wasm). The dart:html library uses JavaScript interop patterns that predate the Wasm-compatible dart:js_interop specification. When building with --wasm, dart:html either fails to compile or produces incorrect behaviour. Fix: migrate to package:web and dart:js_interop — import 'package:web/web.dart' as web; then use web.document.createElement('a') as web.HTMLAnchorElement with the package:web API. For cross-platform code (mobile + web), wrap in a conditional import pattern so mobile platforms use a stub that throws UnsupportedError.

Explain like I'm 5

Imagine your Flutter app is a chef cooking in a foreign country (the browser). Before Wasm, the chef had to translate every recipe into the local language (JavaScript) on the fly — sometimes getting it wrong or slow. With Wasm, the chef learned to speak the kitchen's native dialect directly. The food looks identical but comes out twice as fast. Some very old kitchens (old browsers) don't understand the new dialect yet, so the chef still carries the translation book as a backup.

Fun fact

WebAssembly was designed by a consortium of four browser vendors (Mozilla, Google, Microsoft, Apple) and became a W3C standard in 2019. It is the fourth language natively understood by web browsers (after HTML, CSS, and JavaScript). The Dart team at Google contributed to Wasm GC design specifically because they needed garbage-collected language support — Dart's type-safe object model maps naturally to Wasm GC types.

Hands-on challenge

Design the migration plan for the SaaS collaboration platform moving their Flutter Web dashboard from CanvasKit to Wasm: (1) list the compatibility checks you would run before migration, (2) what plugin audit is required, (3) what server/CDN configuration changes are needed, (4) how you measure whether the migration actually improved performance.

More resources

Open interactive version (quiz + challenge) ← Back to course: Flutter Interview Mastery