[{"data":1,"prerenderedAt":28},["ShallowReactive",2],{"articolo-flutter-web-e-webassembly-compilare-con-dart2wasm-per-performance-fluide":3,"comments-article-flutter-web-e-webassembly-compilare-con-dart2wasm-per-performance-fluide":27},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"status":16,"published_at":17,"meta_title":18,"meta_description":19,"category":20,"author":24},118,"Flutter Web e WebAssembly: compilare con dart2wasm per performance fluide","flutter-web-e-webassembly-compilare-con-dart2wasm-per-performance-fluide","Flutter può compilare in WebAssembly con dart2wasm e il renderer skwasm. Vediamo come abilitarlo, quali vincoli impone l'interop JS moderna, come configurare l'hosting e quando conviene davvero.","## Perché WebAssembly cambia le regole del gioco\n\nPer anni Flutter Web ha avuto un problema strutturale: il codice Dart veniva compilato in JavaScript (`dart2js`), un linguaggio dinamico eseguito da un motore che deve profilare, ottimizzare e talvolta deottimizzare il codice a runtime. Il risultato erano frame time poco prevedibili, soprattutto durante animazioni complesse o quando il garbage collector del browser entrava in azione.\n\nCon **dart2wasm** il codice Dart viene compilato in **WebAssembly** sfruttando l'estensione **WasmGC**, che permette di usare il garbage collector nativo del browser invece di spedirne uno dentro il bundle. Il codice è tipizzato staticamente e compilato AOT: niente warm-up, niente deottimizzazioni, prestazioni molto più costanti.\n\nIl supporto è stabile dalla release 3.22 ed è maturato parecchio: oggi `flutter build web --wasm` produce **sia** il bundle WebAssembly **sia** il fallback JavaScript, e il bootstrap sceglie automaticamente la variante migliore in base al browser.\n\n## Requisiti e stato dei browser\n\nWasmGC è supportato da:\n\n- **Chrome \u002F Edge 119+**\n- **Firefox 120+**\n- **Safari 18.2+** (supporto arrivato più tardi, verifica sempre la tua base utenti)\n\nSui browser che non supportano WasmGC il loader generato da Flutter carica il bundle JavaScript: non rischi la pagina bianca, ma il costo è che stai servendo due artefatti.\n\n## Il primo build\n\nIn sviluppo puoi lavorare direttamente in Wasm:\n\n```bash\nflutter run -d chrome --wasm\n```\n\nPer la produzione:\n\n```bash\nflutter build web --wasm --release\n```\n\nL'output in `build\u002Fweb` conterrà `main.dart.wasm`, `main.dart.mjs` (il glue code), il fallback `main.dart.js` e il file `flutter_bootstrap.js` che gestisce la selezione automatica.\n\nSe vuoi verificare a runtime su quale target stai girando, Flutter espone una costante dedicata:\n\n```dart\nimport 'package:flutter\u002Ffoundation.dart';\n\nvoid logTarget() {\n  if (kIsWeb) {\n    debugPrint(kIsWasm ? 'Runtime: WebAssembly' : 'Runtime: JavaScript');\n  }\n}\n```\n\n`kIsWasm` è una costante di compilazione: il compilatore elimina i rami morti, quindi puoi usarla anche per includere codice diverso nelle due varianti senza penalizzare il bundle.\n\n## Il renderer: skwasm\n\nCompilare in Wasm non riguarda solo il tuo codice Dart: anche il motore grafico cambia. Flutter Web oggi offre due renderer:\n\n- **canvaskit**: Skia compilato in WebAssembly, pilotato da JavaScript. È il default del build JS.\n- **skwasm**: la pipeline grafica pensata per il target Wasm, con supporto al rendering **multi-thread** tramite web worker.\n\nPer sfruttare il multi-threading di skwasm il documento deve essere in stato di **cross-origin isolation**, cioè servito con gli header:\n\n```\nCross-Origin-Opener-Policy: same-origin\nCross-Origin-Embedder-Policy: require-corp\n```\n\nSenza quegli header non si rompe nulla: viene usata la variante single-thread di skwasm. Attenzione però: la cross-origin isolation ha effetti collaterali (iframe di terze parti, script esterni, widget di pagamento) perché richiede che ogni risorsa cross-origin dichiari `Cross-Origin-Resource-Policy`. Abilitala solo dopo aver mappato le dipendenze esterne della pagina.\n\n## L'ostacolo vero: l'interoperabilità con JavaScript\n\nQui sta la parte di lavoro reale di una migrazione. In Wasm **non esistono** `dart:html`, `dart:js` e la vecchia `package:js` basata su annotazioni dinamiche. Tutto passa da **`dart:js_interop`** (interop statica) e da **`package:web`**, che sostituisce `dart:html` con binding generati dalle specifiche W3C.\n\nSe il tuo `pubspec.yaml` o quello di una dipendenza importa `dart:html`, il build Wasm fallisce.\n\n### Da dart:html a package:web\n\n```dart\n\u002F\u002F Prima\nimport 'dart:html' as html;\n\nvoid save(String token) {\n  html.window.localStorage['token'] = token;\n}\n\n\u002F\u002F Dopo\nimport 'package:web\u002Fweb.dart' as web;\n\nvoid save(String token) {\n  web.window.localStorage.setItem('token', token);\n}\n```\n\nI binding di `package:web` seguono fedelmente le API DOM: niente più mappe idiomatiche alla Dart, ma `setItem`\u002F`getItem`, `querySelector`, `addEventListener` e così via.\n\n### Chiamare una funzione JavaScript\n\n```dart\nimport 'dart:js_interop';\n\n@JS('navigator.clipboard.writeText')\nexternal JSPromise\u003CJSAny?> _writeText(JSString text);\n\nFuture\u003Cvoid> copyToClipboard(String text) async {\n  await _writeText(text.toJS).toDart;\n}\n```\n\nDue dettagli importanti:\n\n- I tipi che attraversano il confine devono essere **JS types** (`JSString`, `JSNumber`, `JSBoolean`, `JSObject`, `JSArray`...). Le conversioni avvengono con `.toJS` e `.toDart`.\n- Una `JSPromise` diventa una `Future` con `.toDart`.\n\n### Avvolgere una libreria JS con le extension type\n\nLe **extension type** di Dart 3 sono lo strumento consigliato per creare wrapper a costo zero:\n\n```dart\nimport 'dart:js_interop';\n\n@JS('Analytics')\nextension type Analytics._(JSObject _) implements JSObject {\n  external factory Analytics(JSString apiKey);\n\n  external void track(JSString event, JSObject properties);\n  external JSPromise\u003CJSAny?> flush();\n}\n\nextension AnalyticsX on Analytics {\n  void trackEvent(String name, Map\u003CString, String> props) {\n    final jsProps = JSObject();\n    for (final entry in props.entries) {\n      jsProps.setProperty(entry.key.toJS, entry.value.toJS);\n    }\n    track(name.toJS, jsProps);\n  }\n}\n```\n\nIl wrapper non alloca nulla a runtime: è solo una vista tipizzata su un `JSObject`.\n\n### Esportare una funzione Dart verso JS\n\n```dart\nimport 'dart:js_interop';\n\nvoid onFlutterReady(String route) {\n  debugPrint('Flutter pronto su $route');\n}\n\nvoid registerCallbacks() {\n  globalContext.setProperty(\n    'onFlutterReady'.toJS,\n    onFlutterReady.toJS,\n  );\n}\n```\n\n## Import condizionali: attenzione alle vecchie condizioni\n\nIl classico pattern con `dart.library.html` **non funziona più** in Wasm, perché quella libreria non esiste nel nuovo target. La condizione corretta è `dart.library.js_interop`:\n\n```dart\n\u002F\u002F storage.dart\nexport 'storage_io.dart'\n    if (dart.library.js_interop) 'storage_web.dart';\n```\n\n```dart\n\u002F\u002F storage_io.dart\nimport 'dart:io';\n\nFuture\u003CString> readConfig() => File('config.json').readAsString();\n```\n\n```dart\n\u002F\u002F storage_web.dart\nimport 'package:web\u002Fweb.dart' as web;\n\nFuture\u003CString> readConfig() async =>\n    web.window.localStorage.getItem('config') ?? '{}';\n```\n\nSe mantieni anche il supporto a vecchie versioni di Flutter, puoi combinare le condizioni usando `dart.library.io` per il ramo nativo e lasciare il web come default.\n\n## Checklist di migrazione\n\n1. **Audit delle dipendenze**: cerca `dart:html`, `dart:js`, `package:js` nel tuo codice e in `.dart_tool\u002Fpackage_config.json`. Verifica su pub.dev che i package abbiano il tag **WASM ready** (pub.dev mostra esplicitamente la compatibilità).\n2. **Sostituisci `dart:html` con `package:web`** e `dart:js` con `dart:js_interop`.\n3. **Aggiorna gli import condizionali** a `dart.library.js_interop`.\n4. **Sostituisci i plugin non compatibili**: molti plugin ufficiali (shared_preferences, url_launcher, firebase) sono già aggiornati; per quelli abbandonati valuta un wrapper interop scritto a mano.\n5. **Compila e testa**: `flutter build web --wasm --release` e prova su Chrome, Firefox e Safari.\n6. **Misura**: usa il pannello Performance di Chrome DevTools, non la sensazione a occhio.\n\n## Hosting: le configurazioni che fanno la differenza\n\nUn errore frequente è servire il `.wasm` con il MIME type sbagliato: senza `application\u002Fwasm` il browser non può usare lo streaming compilation e la partenza rallenta sensibilmente.\n\nEsempio per Nginx:\n\n```nginx\ntypes {\n    application\u002Fwasm wasm;\n}\n\nlocation ~* \\.(wasm|js|mjs)$ {\n    gzip_static on;\n    brotli_static on;\n    add_header Cache-Control \"public, max-age=31536000, immutable\";\n}\n\nlocation = \u002Findex.html {\n    add_header Cache-Control \"no-cache\";\n    # Solo se vuoi skwasm multi-thread:\n    # add_header Cross-Origin-Opener-Policy \"same-origin\";\n    # add_header Cross-Origin-Embedder-Policy \"require-corp\";\n}\n```\n\nLa compressione **Brotli** è particolarmente efficace sui binari Wasm: può ridurre il payload del 70-75%. Precomprimi gli asset in fase di build invece di comprimerli a ogni richiesta.\n\n## Quando conviene (e quando no)\n\nWasm **conviene** se:\n\n- l'app ha animazioni continue, canvas, grafici o scroll di liste pesanti;\n- la logica di business è intensiva (parsing, crittografia, calcoli, elaborazione di immagini);\n- gli utenti usano browser moderni e tornano spesso (la cache ammortizza il download iniziale).\n\nWasm **conviene meno** se:\n\n- l'app è essenzialmente una serie di form e liste leggere: il guadagno percepito è minimo;\n- dipendi pesantemente da librerie JS di terze parti non incapsulabili;\n- hai una quota rilevante di utenti su browser datati e non puoi permetterti di servire due bundle;\n- il time-to-first-render è la metrica più critica e il payload iniziale è già al limite.\n\nUn punto onesto da tenere presente: il bundle Wasm **non è sempre più piccolo** di quello JS, e con il fallback stai spedendo due artefatti sul server (anche se il client ne scarica solo uno). Il vantaggio è sul **tempo di esecuzione**, sulla **stabilità dei frame** e sulla prevedibilità, non necessariamente sul primo caricamento.\n\n## Conclusione\n\ndart2wasm è oggi la direzione ufficiale di Flutter Web, e il lavoro di adeguamento che richiede — passare a `dart:js_interop` e `package:web` — è utile a prescindere: l'interop statica è più sicura, più veloce e meglio ottimizzabile dal compilatore anche nel build JavaScript.\n\nIl consiglio pratico: inizia **oggi** la bonifica degli import legacy, anche se non attivi subito `--wasm`. Quando deciderai di passare, il build sarà questione di una flag invece che di un refactoring d'emergenza.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002Fb7bcb6ed-3d2a-43d3-94fd-764195527987.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1763568258533-d0597f86ce62?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3OTExNzI4OTB8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Daniil Komov","https:\u002F\u002Funsplash.com\u002F@dkomow","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fcomputer-screen-displaying-code-with-a-context-menu-dma_e1UKkig",null,"published","2026-10-05T04:01:30+00:00","Flutter Web con WebAssembly: guida a dart2wasm","Come compilare Flutter Web in WebAssembly con dart2wasm: requisiti, renderer skwasm, migrazione a dart:js_interop e package:web, hosting e trade-off reali.",{"id":21,"name":22,"slug":23},2,"Novità","novita",{"id":25,"name":26},1,"Flutter Bot",[],1792316907270]