diff --git a/examples/create_libraries/lib/hw_mp.dart b/examples/create_libraries/lib/hw_mp.dart index 2d5c2d84b2..176e03942b 100644 --- a/examples/create_libraries/lib/hw_mp.dart +++ b/examples/create_libraries/lib/hw_mp.dart @@ -4,4 +4,4 @@ library hw_mp; // #docregion export export 'src/hw_none.dart' // Stub implementation if (dart.library.io) 'src/hw_io.dart' // dart:io implementation - if (dart.library.html) 'src/hw_html.dart'; // dart:html implementation + if (dart.library.js_interop) 'src/hw_web.dart'; // package:web implementation diff --git a/firebase.json b/firebase.json index dee896c35b..a0561b4b5d 100644 --- a/firebase.json +++ b/firebase.json @@ -176,7 +176,7 @@ { "source": "/go/null-safety-migration", "destination": "/null-safety/migration-guide", "type": 301 }, { "source": "/go/package-discontinue", "destination": "/tools/pub/publishing#discontinue", "type": 301 }, { "source": "/go/package-retraction", "destination": "/tools/pub/publishing#retract", "type": 301 }, - { "source": "/go/package-web", "destination": "https://pub.dev/packages/web", "type": 301 }, + { "source": "/go/package-web", "destination": "/interop/js-interop/package-web", "type": 301 }, { "source": "/go/pub-cache", "destination": "/tools/pub/cmd/pub-cache", "type": 301 }, { "source": "/go/pubignore", "destination": "/tools/pub/publishing#what-files-are-published", "type": 301 }, { "source": "/go/publishing-from-github", "destination": "/tools/pub/automated-publishing#publishing-packages-using-github-actions", "type": 301 }, diff --git a/src/_data/side-nav.yml b/src/_data/side-nav.yml index e077773cb7..c3a1d4fbb0 100644 --- a/src/_data/side-nav.yml +++ b/src/_data/side-nav.yml @@ -261,7 +261,7 @@ permalink: /interop/js-interop/past-js-interop - divider - title: Web interop - permalink: /interop/js-interop/dom + permalink: /interop/js-interop/package-web - title: Tools & techniques expanded: false diff --git a/src/content/guides/libraries/create-packages.md b/src/content/guides/libraries/create-packages.md index b1ab5799ce..a7f786135a 100644 --- a/src/content/guides/libraries/create-packages.md +++ b/src/content/guides/libraries/create-packages.md @@ -72,7 +72,7 @@ by importing a single file. The lib directory might also include other importable, non-src, libraries. For example, perhaps your main library works across platforms, but -you create separate libraries that rely on `dart:io` or `dart:html`. +you create separate libraries that rely on `dart:io` or `dart:js_interop`. Some packages have separate libraries that are meant to be imported with a prefix, when the main library is not. @@ -151,13 +151,13 @@ A common use case is a library that supports both web and native platforms. To conditionally import or export, you need to check for the presence of `dart:*` libraries. Here's an example of conditional export code that -checks for the presence of `dart:io` and `dart:html`: +checks for the presence of `dart:io` and `dart:js_interop`: ```dart title="lib/hw_mp.dart" export 'src/hw_none.dart' // Stub implementation if (dart.library.io) 'src/hw_io.dart' // dart:io implementation - if (dart.library.html) 'src/hw_html.dart'; // dart:html implementation + if (dart.library.js_interop) 'src/hw_web.dart'; // package:web implementation ``` Here's what that code does: @@ -165,9 +165,9 @@ Here's what that code does: * In an app that can use `dart:io` (for example, a command-line app), export `src/hw_io.dart`. -* In an app that can use `dart:html` +* In an app that can use `dart:js_interop` (a web app), - export `src/hw_html.dart`. + export `src/hw_web.dart`. * Otherwise, export `src/hw_none.dart`. To conditionally import a file, use the same code as above, diff --git a/src/content/interop/js-interop/dom.md b/src/content/interop/js-interop/dom.md deleted file mode 100644 index 5cdb7b30a3..0000000000 --- a/src/content/interop/js-interop/dom.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: How to interop with DOM APIs -description: How to use package:web to communicate with the browser in Dart. ---- - -// *Introdcution paragraphs* - -## Background concepts - -The following sections provide some extra background and information -around the technologies and concepts used in the tutorial. -To skip directly to the tutorial content, -go to [Steps](#steps). - -### Topic/concept 1 - -### Topic/concept..n - -## Steps - -### Prerequisites - -Before you begin, you'll need... -* - -### Actionable step 1 - -Step subsections should be actionalble, like "Build...", "Retrieve...", "Configure...", etc. - -### Actionable step...n - -## What next? - -Point to other tutorials, reference, etc. \ No newline at end of file diff --git a/src/content/interop/js-interop/package-web.md b/src/content/interop/js-interop/package-web.md new file mode 100644 index 0000000000..1dd357ebda --- /dev/null +++ b/src/content/interop/js-interop/package-web.md @@ -0,0 +1,306 @@ +--- +title: Migrate to package:web +description: 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. + +```dart +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`][html] and [`dart:svg`][svg], + are **not supported** when compiling to Wasm. + +2. **Staying modern** + + `package:web` uses the [Web IDL][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](#renames) and +[type tests](#type-checks), +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`: + +```dart +import 'dart:html' as html; // Remove +import 'package:web/web.dart' as web; // Add +``` + +Add `web` to the `dependencies` in your pubspec: + +```yaml +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 %} +Maybe a pic here of menu selection in IDE? +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 built-in fix, let us know by +filing an [issue][]. +You can manually discover the `package:web` type name +by looking up the `dart:html` class' `@Native` annotation. +You can do this by either: + +- Ctrl or cmd clicking the name in the IDE and choosing **Go to Definition**. +- Searching for the name in the [`dart:html` API docs][html] + and checking its page under *Annotations*. + +The `@Native` annotation tells the compiler to treat any JS object of that type +as the class that it annotates. + +Similarly, if you find an API with the keyword `native` in `dart:html` that +doesn't have an equivalent in `package:web`, check to see if there was a rename +with the `@JSName` annotation. +`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. + +```dart +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. + +```dart +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: + +```plaintext +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: + +```dart +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: + +```dart +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](#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`][dart-lang/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][zones] by binding the callback. See [#54507] for more +details. +There is no conversion API or [helper](#helpers) 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`][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 `Stream`s without writing that code yourself. + +```dart +// dart:html version +InputElement htmlInput = InputElement(); +await htmlInput.onBlur.first; + +// package:web version +HTMLInputElement webInput = document.createElementById('input'); +await webInput.onBlur.first; +``` + +You can find all the helpers and their documentation in the repo at +[`package:web/helpers`][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`: + +- [Upgrading `url_launcher` to `package:web`][] + +{% comment %} +Do we have any other package migrations to show off here? +{% endcomment -%} + +[`package:web`]: {{site.pub-pkg}}/web +[Wasm]: https://github.com/dart-lang/sdk/blob/main/pkg/dart2wasm/README.md +[html]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-html/dart-html-library.html +[svg]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-svg/dart-svg-library.html +[`dart:js_interop`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/dart-js_interop-library.html +[`dart:js_interop_unsafe`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop_unsafe/dart-js_interop_unsafe-library.html +[idl]: https://www.npmjs.com/package/@webref/idl +[interop members]: /interop/js-interop/usage#interop-members +[interop types]: /interop/js-interop/usage#interop-types +[dart-lang/web]: https://github.com/dart-lang/web +[issue]: https://github.com/dart-lang/web/issues/new +[helpers]: https://github.com/dart-lang/web/tree/main/lib/src/helpers +[zones]: /articles/archive/zones +[Conversions]: /interop/js-interop/js-types#conversions +[interop methods]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension.html#instance-methods +[alternative interop declarations]: /interop/js-interop/usage +[Compatibility, type checks, and casts]: /interop/js-interop/js-types#compatibility-type-checks-and-casts +[Upgrading `url_launcher` to `package:web`]: https://github.com/flutter/packages/compare/main...johnpryan:wasm/url-launcher +[stream helpers]: https://github.com/dart-lang/web/blob/main/lib/src/helpers/events/streams.dart +[not possible]: /language/extension-types +[`JSObject`]: https://api.dart.dev/dev/dart-js_interop/JSObject-extension-type.html +[`isA`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/isA.html +[restricts]: /interop/js-interop/js-types#requirements-on-external-declarations-and-function-tojs +[#54507]: https://github.com/dart-lang/sdk/issues/54507 +[mocking tutorial]: /interop/js-interop/mock \ No newline at end of file diff --git a/src/content/libraries/dart-html.md b/src/content/libraries/dart-html.md index 3dbb59cde8..f87617fa69 100644 --- a/src/content/libraries/dart-html.md +++ b/src/content/libraries/dart-html.md @@ -6,6 +6,13 @@ prevpage: title: dart:io --- +:::warning +`dart:html` is being replaced with [`package:web`][]. +Package maintainers should migrate to `package:web` as +soon as possible to be compatible with Wasm. +Read the [Migrate to package:web][] page for guidance. +::: + Use the [dart:html][] library to program the browser, manipulate objects and elements in the DOM, and access HTML5 APIs. DOM stands for *Document Object Model*, which describes the hierarchy of an HTML page. @@ -29,6 +36,9 @@ To use the HTML library in your web app, import dart:html: import 'dart:html'; ``` +[`package:web`]: {{site.pub-pkg}}/web +[Migrate to package:web]: /interop/js-interop/package-web#migrating-from-darthtml + ### Manipulating the DOM To use the DOM, you need to know about *windows*, *documents*,