Skip to content

Latest commit

 

History

History
324 lines (255 loc) · 12.1 KB

package-web.md

File metadata and controls

324 lines (255 loc) · 12.1 KB
title description
Migrate to package:web
How to migrate web interop code from dart:html to package:web.

Dart's package:web exposes access to browser APIs, enabling interop between Dart applications and the web. Use package:web to interact with the browser and manipulate objects and elements in the DOM.

import 'package:web/web.dart';

void main() {
  final div = document.querySelector('div')!;
  div.text = 'Text set at ${DateTime.now()}';
}

:::important If you maintain a public Flutter package that uses dart:html or any of the other Dart SDK web libraries, you should migrate to package:web as soon as possible. package:web is replacing dart:html and other web libraries as Dart's web interop solution long-term. Read the package:web vs dart:html section for more information. :::

package:web vs dart:html

The goal of package:web is to revamp how Dart exposes web APIs by addressing several concerns with the existing Dart web libraries:

  1. Wasm compatibility

    Packages can only be compatible with Wasm if they use dart:js_interop and dart:js_interop_unsafe. package:web is based on dart:js_interop, so by default, it's supported on dart2wasm.

    Dart core web libraries, like dart:html and dart:svg, are not supported when compiling to Wasm.

  2. Staying modern

    package:web uses the Web IDL to automatically generate interop members and interop types for each declaration in the IDL. Generating references directly, as opposed to the additional members and abstractions in dart:html, allows package:web to be more concise, easier to understand, more consistent, and more able to stay up-to-date with the future of Web developments.

  3. Versioning

    Because it's a package, package:web can be versioned more easily than a library like dart:html and avoid breaking user code as it evolves. It also makes the code less exclusive and more open to contributions. Developers can create alternative interop declarations of their own and use them together with package:web without conflict.


These improvements naturally result in some implementation differences between package:web and dart:html. The changes that affect existing packages the most, like IDL renames and type tests, are addressed in the migration sections that follow. While we only refer to dart:html for brevity, the same migration patterns apply to any other Dart core web library like dart:svg.

Migrating from dart:html

Remove the dart:html import and replace it with package:web/web.dart:

import 'dart:html' as html; // Remove
import 'package:web/web.dart' as web; // Add

Add web to the dependencies in your pubspec:

dependencies:
  web: ^0.5.0

The following sections cover some of the common migration issues from dart:html to package:web.

For any other migration issues, check the dart-lang/web repo and file an issue.

Renames

Many of the symbols in dart:html were renamed from their original IDL declaration to align more with Dart style. For example, appendChild became append, HTMLElement became HtmlElement, etc.

In contrast, to reduce confusion, package:web uses the original names from the IDL definitions. A dart fix is available to convert types that have been renamed between dart:html and package:web.

After changing the import, any renamed objects will be new "undefined" errors. You can address these either:

  • From the CLI, by running dart fix --dry-run.
  • In your IDE, by selecting the dart fix: Rename to 'package:web name'.

{% comment %} TODO: Update this documentation to refer to symbols instead of just types once we have a dart fix for that. {% endcomment -%}

The dart fix covers many of the common type renames. If you come across a dart:html type without a dart fix to rename it, first let us know by filing an issue.

Then, you can try manually discovering the package:web type name of an existing dart:html member by looking up its definition. The value of the @Native annotation on a dart:html member definition tells the compiler to treat any JS object of that type as the Dart class that it annotates. For example, the @Native annotation tells us that the native JS name of dart:html's HtmlElement member is HTMLElement, so the package:web name will also be HTMLElement:

@Native("HTMLElement")
class HtmlElement extends Element implements NoncedElement { }

To find the dart:html definition for an undefined member in package:web, try either of the following methods:

  • Ctrl or command click the undefined name in the IDE and choose Go to Definition.
  • Search for the name in the dart:html API docs and check its page under Annotations.

Similarly, you might find an undefined package:web API whose corresponding dart:html member's definition uses the keyword native. Check if the definition uses the @JSName annotation for a rename; the value of the annotation will tell you the name the member uses in package:web:

@JSName('appendChild')
Node append(Node node) native;

native is an internal keyword that means the same as external in this context.

Type tests

It's common for code that uses dart:html to utilize runtime checks like is. When used with a dart:html object, is and as verify that the object is the JS type within the @Native annotation. In contrast, all package:web types are reified to JSObject. This means a runtime type test will result in different behavior between dart:html and package:web types.

To be able to perform type tests, migrate any dart:html code using is type tests to use interop methods like instanceOfString or the more convenient and typed isA helper (available from Dart 3.4 onward). The Compatibility, type checks, and casts section of the JS types page covers alternatives in detail.

obj is Window; // Remove
obj.instanceOfString('Window'); // Add

Type signatures

Many APIs in dart:html support various Dart types in their type signatures. Because dart:js_interop restricts the types that can be written, some of the members in package:web will now require you to convert the value before calling the member. Learn how to use interop conversion methods from the Conversions section of the JS types page.

window.addEventListener('click', callback); // Remove
window.addEventListener('click', callback.toJS); // Add

{% comment %} TODO: Think of a better example. People will likely use the stream helpers instead of addEventListener. {% endcomment -%}

Generally, you can spot which methods need a conversion because they'll be flagged with some variation of the exception:

A value of type '...' can't be assigned to a variable of type 'JSFunction?'

Conditional imports

It is common for code to use a conditional import based on whether dart:html is supported to differentiate between native and web:

export 'src/hw_none.dart'
    if (dart.library.io) 'src/hw_io.dart'
    if (dart.library.html) 'src/hw_html.dart';

However, since dart:html is not supported when compiling to Wasm, the correct alternative now is to use dart.library.js_interop to differentiate between native and web:

export 'src/hw_none.dart'
    if (dart.library.io) 'src/hw_io.dart'
    if (dart.library.js_interop) 'src/hw_web.dart';

Virtual dispatch and mocking

dart:html classes supported virtual dispatch, but because JS interop uses extension types, virtual dispatch is not possible. Similarly, dynamic calls with package:web types won't work as expected (or, they might continue to work just by chance, but will stop when dart:html is removed), as their members are only available statically. Migrate all code that relies on virtual dispatch to avoid this issue.

One use case of virtual dispatch is mocking. If you have a mocking class that implements a dart:html class, it can't be used to implement a package:web type. Instead, prefer mocking the JS object itself. See the mocking tutorial for more information.

Non-native APIs

dart:html classes may also contain APIs that have a non-trivial implementation. These members may or may not exist in the package:web helpers. If your code relies on the specifics of that implementation, you may be able to copy the necessary code. However, if you think that's not tractable or if that code would be beneficial for other users as well, consider filing an issue or uploading a pull request to package:web to support that member.

Zones

In dart:html, callbacks are automatically zoned. This is not the case in package:web. There is no automatic binding of callbacks in the current zone.

If this matters for your application, you can still use zones, but you will have to write them yourself by binding the callback. See #54507 for more details. There is no conversion API or helper available yet to automatically do this.

Helpers

The core of package:web contains external interop members, but does not provide other functionality that dart:html provided by default. To mitigate these differences, package:web contains helpers for additional support in handling a number of use cases that aren't directly available through the core interop. The helper library contains various members to expose some legacy features from the Dart web libraries.

For example, the core package:web only has support for adding and removing event listeners. Instead, you can use stream helpers that makes it easy to subscribe to events with Dart Streams without writing that code yourself.

// dart:html version
InputElement htmlInput = InputElement();
await htmlInput.onBlur.first;

// package:web version
HTMLInputElement webInput = document.createElement('input') as HTMLInputElement;
await webInput.onBlur.first;

You can find all the helpers and their documentation in the repo at package:web/helpers. They will continuously be updated to aid users in migration and make it easier to use the web APIs.

Examples

Here are some examples of packages that have been migrated from dart:html to package:web:

{% comment %} Do we have any other package migrations to show off here? {% endcomment -%}