From b6ddf7349a73808ed2f32fdff7ca21f885faaaf2 Mon Sep 17 00:00:00 2001 From: Marya Belanger Date: Mon, 6 Feb 2023 12:10:01 -0800 Subject: [PATCH 01/29] outline and first drafts for some pages remove broken link add tutorial outline restructure new pages left out of last commit --- src/web/js-interop/dom.md | 38 ++++++ src/web/js-interop/index.md | 125 ++++++++++++++++++++ src/web/js-interop/js-app.md | 38 ++++++ src/web/js-interop/js-util.md | 50 ++++++++ src/web/js-interop/js_util.md | 6 + src/web/js-interop/past-js-interop.md | 59 ++++++++++ src/web/js-interop/reference.md | 162 ++++++++++++++++++++++++++ src/web/js-interop/test-and-mock.md | 45 +++++++ src/web/js-interop/tutorials.md | 29 +++++ 9 files changed, 552 insertions(+) create mode 100644 src/web/js-interop/dom.md create mode 100644 src/web/js-interop/index.md create mode 100644 src/web/js-interop/js-app.md create mode 100644 src/web/js-interop/js-util.md create mode 100644 src/web/js-interop/js_util.md create mode 100644 src/web/js-interop/past-js-interop.md create mode 100644 src/web/js-interop/reference.md create mode 100644 src/web/js-interop/test-and-mock.md create mode 100644 src/web/js-interop/tutorials.md diff --git a/src/web/js-interop/dom.md b/src/web/js-interop/dom.md new file mode 100644 index 0000000000..025183c311 --- /dev/null +++ b/src/web/js-interop/dom.md @@ -0,0 +1,38 @@ +--- +title: How to interop with DOM APIs +description: +--- + +{{site.why.learn}} + * How to... +{{site.why.end}} + +// *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/web/js-interop/index.md b/src/web/js-interop/index.md new file mode 100644 index 0000000000..54c53b9bc6 --- /dev/null +++ b/src/web/js-interop/index.md @@ -0,0 +1,125 @@ +--- +title: JavaScript interoperability +short-title: JS interop +description: Integrate JavaScript code into your Dart web app. +--- + +The [Dart web platform](/overview#web-platform) supports communication with +JavaScript apps and libraries, and browser DOM APIs, using the `js_interop` +package, also known as `dart:js_interop`. +Web developers can benefit from using external JS libraries in their Dart code, without +having to rewrite anything in Dart. + +## Static interop + +The principal model of JS interop in Dart is **static interop**. +Static interop means interop that requires staticly typing external members, +rather than allowing dynamic invocations. +This enables [features](#features) like better performance, type soundness, and more. + +Performant static interop model is made possible by **[inline classes][]**. +Inline classes are a special class type that wrap an existing type into a new static type, +without the overhead of a traditional class, enabling zero cost wrapping. + +### Why static interop? + +Static interop represents the desire to decouple Dart's "core" from the platform +where it's embedded +(*maybe definition of "embedded" or examples here, like the browser, Flutter, etc.?*). + +Our [previous iterations of JS interop][] provided the capabillity +to access the embedder, but were not primed to handle that decoupling. +Too much of the process was entwined into the Dart side, +like writing all the bindings to the browser +(*not sure if I understood that note correctly*). +They also had limitations around using the DOM, +typing (which is a cornerstone of Dart), +and expanding to new interop types +(*this is me trying to refer to wasm without actually saying, idk if "interop types" is the right term*). + +Static interop addresses all of the shortcomings of Dart's previous JS interop solutions. +Check out the [Features](#features) section for a summary of improvements. + +[previous iterations of JS interop]: /web/js-interop/past-js-interop + +## Usage + +The following example implements inline class-based, static, JS interop in Dart, +using its key members and syntaxes: + +```dart +@JS() +library; +// library names aren't cool anymore... + +import 'dart:js_interop'; + +inline class SomeThing { + @JS('JSON.stringify') + external String stringify(Object obj); +} +// idk where `inline` fits into this but basically just show the key components as briefly as possible +``` + +// *Below the example, go through the steps line by line **and why**:* + +1. Append the `@JS` annotation to a `library` directive *so that....* + +2. Import `dart:js_interop`, which provides most of the tools needed for static interop, +including annotations, the representation type for inline classes, *... etc.* + +3. Create an `inline` class because it *allows you to...*, +which is the core of static interop. + +4. Use the `@JS` annotation to call a main-spaced function, or top level external member, from your JS app + + // *(or, call the external member from outside an inline class if you don't care about types?* +*idk if that's worth mentioning)* + +5. Use the `external` keyword to.... *(allow external and non-external members to communicate? idk)* + +6. *show external and non-external members interacting if that's not already shown?* + +## Features + +Inline class-based static interop enable the following features: + +
+| **Rename external members** | Complex JS member names can be re-written and represented in Dart | +| **Interact external and non-external members** | Process external calls, but "keep it on the type itself" (*idk what that means?*), without writing a separate function | +| **Zero cost wrapping** | Inline classes require virtually no overhead for re-typing external members as static types. (*idk*) | +| **Static error checking** | Since external members must be statically typed, static error checking on types and other parts of the interop is possible (partially sound, more work coming) | +| **Interop with DOM types** | *You can interop with DOM types because...?* | +| **Compatibility with Wasm** | Disallowing generative constructors from the model makes `js_interop` compatible with Wasm | +{:.table .table-striped .nowrap} +
+ + +## Up next + +For a complete summary of static JS interop concepts, members, and syntaxes: +* The [static interop reference][]. + +For tutorials and help: +* [How to interop with DOM APIs][] +* [How to interop with JavaScript libraries and apps][] +* [How to test and mock JavaScript interop in Dart][] + +For additonal types of documentation on `js_interop`: + * [pub.dev site page][] + * [API reference][] + +For information on other iterations of JS interop in Dart: + * [`dart:js_util`][] + * [Past JS interop][] + + +[inline classes]: / +[static interop reference]: /web/js-interop/reference +[How to interop with DOM APIs]: /web/js-interop/dom +[How to interop with JavaScript libraries and apps]: /web/js-interop/js-app +[How to test and mock JavaScript interop in Dart]: /web/js-interop/test-and-mock +[pub.dev site page]: / +[API reference]: / +[`dart:js_util`]: /web/js-interop/js-util +[Past JS interop]: /web/js-interop/past-js-interop diff --git a/src/web/js-interop/js-app.md b/src/web/js-interop/js-app.md new file mode 100644 index 0000000000..599b4a55c8 --- /dev/null +++ b/src/web/js-interop/js-app.md @@ -0,0 +1,38 @@ +--- +title: How to interop with JavaScript libraries and apps +description: +--- + +{{site.why.learn}} + * How to... +{{site.why.end}} + +// *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/web/js-interop/js-util.md b/src/web/js-interop/js-util.md new file mode 100644 index 0000000000..ccc902193b --- /dev/null +++ b/src/web/js-interop/js-util.md @@ -0,0 +1,50 @@ +--- +title: The dart:js_util library +short-title: dart:js_util +description: Overview of the utility library for JS interop +--- + +[**`dart:js_util` API docs**][] + +**We will continue to support `dart:js_util` alongside static interop.** + +The `dart:js_util` library, or just `js_util`, is a low-level utility library +for performing JS interop. Because `js_util` is so low-level, +it could potentially be able to provide more flexibility than static interop, +for example, in rare edge cases where `js_interop` is not expressive enough. +This is an exception to the rule; +**please always use static, inline-class based interop by default**. + +The `js_util` library is supported by the JS and `dart2wasm` backends. +It is slower and less ergonomic than `js_interop`. + +The best example of the difference in ergonomics between `js_interop` and +`js_util` is calling equivalent [`external`][] methods. +Each interop solution generates JavaScript code upon calling an `external` method: + +```dart +// js_util external call: +... + +// javascript generated: +... +``` + +The JavaScript code `external` generates for `js_util` is very verbose, +compared to the efficient, compact generation for `js_interop`: + +```dart +// js_interop external call: +... + +// javascript generated: +... +``` + +For optimal JS interop, only use `js_util` over static interop if you encounter +a use case that `js_interop` cannot address +(and please [let us know][] if you encounter such a use case). + +[**`dart:js_util` API docs**]: {{site.dart-api}}/dart-js_util/dart-js_util-library.html +[`external`]: /web/js-interop/reference#external +[let us know]: https://github.com/dart-lang/sdk/issues/new?assignees=&labels=web-js-interop&template=1_issue_template.md&title=Create+an+issue \ No newline at end of file diff --git a/src/web/js-interop/js_util.md b/src/web/js-interop/js_util.md new file mode 100644 index 0000000000..60ea261ee2 --- /dev/null +++ b/src/web/js-interop/js_util.md @@ -0,0 +1,6 @@ +--- +title: About `dart:js_util` +description: +--- + +// *I don't know if this needs to be a page or can just be on the "other" page* \ No newline at end of file diff --git a/src/web/js-interop/past-js-interop.md b/src/web/js-interop/past-js-interop.md new file mode 100644 index 0000000000..474d6ad979 --- /dev/null +++ b/src/web/js-interop/past-js-interop.md @@ -0,0 +1,59 @@ +--- +title: Past JS interop +description: Archive of past JS interop implementations +--- + +This page addresses previous iterations of JS interop for Dart: +* `package:js` +* `dart:js` + +**We do not recommend using any JS interop solution other than [static interop][].** + +Each of these tools still exist and are usable. +However, the static interop model +is more performant, provides more capabilities, +and will continue to be supported and developed. +If you are just starting out with JS interop in Dart, +please start with the static interop library, [`js_interop`][]. + +[static interop]: /web/js-interop +[`js_interop`]: {{site.dart-api}}/js_interop + +## `package:js` + +// *This section probably doesn't make any sense* + +[**`package:js` API docs**] + +**We will not continue to support `package:js` alongside static interop.** + +The `package:js` library can represent objects in different ways with its +class type annotations: + +* [`@JS`] +* [`@anonymous`] +* [`@staticInterop`] + +Because `package:js` supports dynamic invocations of external members (the +opposite of static interop), its static type checking capabilities are +much more limited than static interop, and therefore cannot be fully sound. +For the same reason, `package:js` can not interop with [DOM APIs][]. + +[**`package:js` API docs**]: {{site.pub-pkg}}/js +[`@JS`]: /web/js-interop/reference#js +[`@Anonymous`]: /web/js-interop/reference#others +[`@staticInterop`]: /web/js-interop/reference#staticinterop +[DOM APIs]: /web/js-interop/dom + +## `dart:js` + +[**`dart:js` API docs**] + +**We will not continue to support `dart:js` alongside static interop.** + +The `dart:js` library is a low-level API for non-static interop with JavaScript. +It's wrapper-based model requires much more overhead, +and is much more expensive and slow, +than static interop's zero-cost wrapper model. + +[**`dart:js` API docs**]: {{site.dart-api}}/dart-js/dart-js-library.html \ No newline at end of file diff --git a/src/web/js-interop/reference.md b/src/web/js-interop/reference.md new file mode 100644 index 0000000000..911f10abd7 --- /dev/null +++ b/src/web/js-interop/reference.md @@ -0,0 +1,162 @@ +--- +title: JS interop reference +description: A glossary of key terms and concepts in JS interop. +--- + +## Members + +// *I'm not sure if members, syntax, etc are interchangeable or different categories,* +*so rearrange / add more headings as needed* + +### Inline classes + +[Inline classes][] are a language feature independent of JS interop and static interop. +They are a special kind of class that wraps an existing type into a new static type, without the overhead of a traditional class. + +In the context of JS interop and static interop, +inline classes are an entirely static type representation of a JavaScript object. +They consume no additional memory, as opposed to using the representation type directly. +This zero-cost wrapper capability makes static interop possible. + +Inline classes can call *non-static* external fields, getters, setters, +and methods, as well as *static* external getters, setters, and methods. + +// *You should always wrap external types with inline classes?* + + +#### Usage: + +```dart +// + +// + +// +``` + +[Inline classes]: / + +// *TO DO: link to inline class page* + +### `external` + +The [`external` keyword][] calls a method from an external source. +It is not exclusive to JS interop or static interop. +To specify external *JavaScript* members, +you need to use the `@JS` annotation with `external`. + +#### Usage: + +```dart +// + +// + +// +``` + +[`external` keyword]: https://spec.dart.dev/DartLangSpecDraft.pdf#External%20Functions + +### Top-level external members + +Communication with top-level external members is a core feature of static interop, +though not exclusive to it. We have improved it to be more sound for static interop. + +You can call a main-spaced function from a JavaScript app +without concern for type using the [`@JS` annotation](#js). + +// *Why/when will users want to use this/ not be concerned with type?* + +#### Usage: + +```dart +// +@JS(‘...’) + +// + +// +``` + +### External factories + +External factories are not exclusive to static interop, +but are an importatnt part of the interface. +You can use a factory to instantiate a JavaScript object. + +// *You will want to do this because/when...* + +#### Usage: + +```dart +// + +// + +// +``` + +### Extensions with external members + +// *Does this go under external, or extensions, or...?* + +Extensions extend types. When you have an external class that you don't own, +and you don't want to add members to it, you can simply extend it. +Just write an extension and add members. + +// *More detail on when/why you might want to use this maybe?* + +#### Usage: + +```dart +// + +// + +// +``` + +## Annotations + +### `@JS` + +The `@JS` annotation belongs to the `dart:js_interop` library. +It specifies that you're using JavaScript interop, +as opposed to any other kind of interop. +It provides the bindings between a JavaScript API and your Dart API. +The use of `@JS` interop, combined with [inline classes](#inline-classes), +is what makes static interop possible. + +#### Usage: + +```dart +// append to library directive + +// call main-spaced JS function when you're not concerned with type (if that's a feature? idk) + +// specify `external` declaration is JS +``` + +The `@JS` annotation was also a member of one of Dart's past JS interop solutions, +`package:js`. You can read more about that on the [Past JS interop][] page. + +[Past JS interop]: /web/js-interop/past-js-interop + +### `@staticInterop` + +The `@staticInterop` annotation of `package:js` was an intermeditate static interop +solution before developing the complete, inline-class based, static interop model. +Inline classes and the `@JS` annotations replace the functionality of `@staticInterop`. + +#### Usage: + +```dart +// using @staticInterop +... + +// same example using @JS + inline classes +... +``` + + + diff --git a/src/web/js-interop/test-and-mock.md b/src/web/js-interop/test-and-mock.md new file mode 100644 index 0000000000..23678bc557 --- /dev/null +++ b/src/web/js-interop/test-and-mock.md @@ -0,0 +1,45 @@ +--- +title: How to test and mock JavaScript interop in Dart +description: +--- + +{{site.why.learn}} + * How to... +{{site.why.end}} + +// *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 + +## Export steps + +// *make sure to link to this content: https://pub.dev/packages/js/versions/0.6.6#jsexport-and-js_utilcreatedartexport* + +### Actionable step 1 + +Step subsections should be actionalble, like "Build...", "Retrieve...", "Configure...", etc. + +### Actionable step...n + +## Test/mock steps + +// *make sure to link to this content: https://pub.dev/packages/js/versions/0.6.6#js_utilcreatestaticinteropmock + +### Actionable step 1 + +... + +### Actionable step...n + +## What next? + +Point to other tutorials, reference, etc. \ No newline at end of file diff --git a/src/web/js-interop/tutorials.md b/src/web/js-interop/tutorials.md new file mode 100644 index 0000000000..74eef4c408 --- /dev/null +++ b/src/web/js-interop/tutorials.md @@ -0,0 +1,29 @@ +--- +title: JS interop tutorials +description: Tutorials for common JavaScript interop use cases in Dart. +--- + +## Tutorials + +### [How to interop with DOM APIs][] + +The browser exposes a number of DOM APIs accessible to Dart through the `dart:js_interop` library. +DOM APIs are the most common set of APIs Dart users will want to expose or interact with while using JS interop. This tutorial shows how to access these APIs and some of the common reasons you may want to use them. + +### [How to interop with JavaScript libraries and apps][] + +Have you found a JS library or app that does exactly what you want to do in your Dart code, but it's way too complex to rewrite in Dart? + +This tutorial will show you how to incorporate methods from an existing JS library. +It will also discuss build and serving options. + +### [How to test and mock JavaScript interop in Dart][] + +Exporting Dart objects with `@JSExport` creates a mock of the object at the JS level, which essentially allows you to test your Dart JS interop code. + +This tutorial will walk through both cases: simply exporting Dart objects to JS, +and then using that same functionality to test JS interop code. + +[How to interop with DOM APIs]: /web/js-interop/dom +[How to interop with JavaScript libraries and apps]: /web/js-interop/js-app +[How to test and mock JavaScript interop in Dart]: /web/js-interop/test-and-mock \ No newline at end of file From c6c417cccd7189247738a194cc8b20bf434ae441 Mon Sep 17 00:00:00 2001 From: Marya Belanger Date: Wed, 10 Jan 2024 14:07:49 -0800 Subject: [PATCH 02/29] add descriptions to fix liquid errors --- src/web/js-interop/dom.md | 2 +- src/web/js-interop/js-app.md | 2 +- src/web/js-interop/js_util.md | 2 +- src/web/js-interop/test-and-mock.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/web/js-interop/dom.md b/src/web/js-interop/dom.md index 025183c311..8117433540 100644 --- a/src/web/js-interop/dom.md +++ b/src/web/js-interop/dom.md @@ -1,6 +1,6 @@ --- title: How to interop with DOM APIs -description: +description: How to use package:web to communicate with the browser in Dart. --- {{site.why.learn}} diff --git a/src/web/js-interop/js-app.md b/src/web/js-interop/js-app.md index 599b4a55c8..6157c48af3 100644 --- a/src/web/js-interop/js-app.md +++ b/src/web/js-interop/js-app.md @@ -1,6 +1,6 @@ --- title: How to interop with JavaScript libraries and apps -description: +description: placeholder description --- {{site.why.learn}} diff --git a/src/web/js-interop/js_util.md b/src/web/js-interop/js_util.md index 60ea261ee2..f65502fc56 100644 --- a/src/web/js-interop/js_util.md +++ b/src/web/js-interop/js_util.md @@ -1,6 +1,6 @@ --- title: About `dart:js_util` -description: +description: placeholder description --- // *I don't know if this needs to be a page or can just be on the "other" page* \ No newline at end of file diff --git a/src/web/js-interop/test-and-mock.md b/src/web/js-interop/test-and-mock.md index 23678bc557..bd0a864e26 100644 --- a/src/web/js-interop/test-and-mock.md +++ b/src/web/js-interop/test-and-mock.md @@ -1,6 +1,6 @@ --- title: How to test and mock JavaScript interop in Dart -description: +description: placeholder description --- {{site.why.learn}} From 2d7c60649caa9223df83b064c683458c77c13868 Mon Sep 17 00:00:00 2001 From: Marya Belanger Date: Wed, 10 Jan 2024 16:51:26 -0800 Subject: [PATCH 03/29] readd sidenav after rebase, move files under interop --- src/_data/side-nav.yml | 13 +- src/interop/js-interop.md | 123 ------------------ src/{web => interop}/js-interop/dom.md | 0 src/{web => interop}/js-interop/index.md | 12 +- src/{web => interop}/js-interop/js-app.md | 0 src/{web => interop}/js-interop/js-util.md | 0 .../js-interop/past-js-interop.md | 8 +- src/{web => interop}/js-interop/reference.md | 2 +- .../js-interop/test-and-mock.md | 0 src/{web => interop}/js-interop/tutorials.md | 6 +- src/web/js-interop/js_util.md | 6 - 11 files changed, 26 insertions(+), 144 deletions(-) delete mode 100644 src/interop/js-interop.md rename src/{web => interop}/js-interop/dom.md (100%) rename src/{web => interop}/js-interop/index.md (93%) rename src/{web => interop}/js-interop/js-app.md (100%) rename src/{web => interop}/js-interop/js-util.md (100%) rename src/{web => interop}/js-interop/past-js-interop.md (91%) rename src/{web => interop}/js-interop/reference.md (98%) rename src/{web => interop}/js-interop/test-and-mock.md (100%) rename src/{web => interop}/js-interop/tutorials.md (84%) delete mode 100644 src/web/js-interop/js_util.md diff --git a/src/_data/side-nav.yml b/src/_data/side-nav.yml index 513a3e155d..90cdc89d01 100644 --- a/src/_data/side-nav.yml +++ b/src/_data/side-nav.yml @@ -247,7 +247,18 @@ - title: Java & Kotlin interop permalink: /interop/java-interop - title: JavaScript interop - permalink: /interop/js-interop + children: + - title: Overview + permalink: /interop/js-interop + - title: JS types + permalink: /interop/js-interop/reference + - title: Tutorials + permalink: /interop/js-interop/tutorials + - title: Past JS interop + permalink: /interop/js-interop/past-js-interop + - divider + - title: Web interop + permalink: /interop/js-interop/dom - title: Tools & techniques expanded: false diff --git a/src/interop/js-interop.md b/src/interop/js-interop.md deleted file mode 100644 index 226a9f8862..0000000000 --- a/src/interop/js-interop.md +++ /dev/null @@ -1,123 +0,0 @@ ---- -title: JavaScript interoperability -short-title: JS interop -description: "Use package:js to integrate JavaScript code into your Dart web app." ---- - -The [Dart web platform](/overview#web-platform) supports calling -JavaScript using the `js` package, -also known as _package:js_. - -For help using the `js` package, see the following: - -* Documentation for the `js` package: - * [pub.dev site page][js] - * [API reference][js-api] -* Packages that use the `js` package: - * [sass][] is an example of a more unusual use case: providing a - way for JavaScript code to call Dart code. - -[js]: {{site.pub-pkg}}/js -[js-api]: {{site.pub-api}}/js -[sass]: {{site.pub-pkg}}/sass - -## Next-generation JS interop preview - -{{site.alert.note}} - This interop feature is **experimental**, - and [in active development](https://github.com/dart-lang/sdk/issues/35084). -{{site.alert.end}} - -Dart's JS interop story is currently evolving. -Many of the features that enable future JS interop -are ready to experiment with as of Dart version 3.2. -These features support the existing production -and development web compilers, -as well as Dart's in-progress Wasm compiler ([`dart2wasm`][]). - -For a glimpse into the next generation of JS interop, -you can refactor your code to conform to -the new syntax and semantics now. -Doing so will likely not prevent the need to refactor again -once these features stabilize, as the features are still in development. -However, the features available for preview are much closer -to future JS interop than any pattern supported today. -So, there are a few reasons to try them out now: - -* New JS interop developers can learn and build with future JS interop, - so they won't have to unlearn obsolete patterns in a few months. -* Existing JS interop developers eager to experiment with - the latest features in JS interop - or with `dart2wasm` when it becomes available. -* Potentially ease transition of existing JS interop code - once migration becomes necessary. - -The following sections are the set of features -expected to work across compilers for JS interop. - -*Requirements:* - -* Dart SDK constraint: `>= 3.2` - -[`dart2wasm`]: https://github.com/dart-lang/sdk/blob/main/pkg/dart2wasm#running-dart2wasm - -### `dart:js_interop` - -The key feature of next-generation JS interop is static interop. -We recommend using static interop through [`dart:js_interop`][] -as the default choice for interoping with JavaScript. -It is more declarative and explicit, more likely to be optimized, -more likely to perform better, and required for `dart2wasm`. -Static interop addresses several gaps in the existing JS interop story: - -* **Missing features:** Static interop enables previously - unavailable features, like easily wrapping and transforming APIs, - renaming members, and static checking. - -* **Inconsistencies:** Static interop makes backends more consistent, - so development and production web compilers - won't behave as differently as before. - -* **Clarity:** Static interop takes a step towards making - JS interop more idiomatic in Dart, - and making the boundary between the two languages more visible. - For example, it enforces that JS classes are not meant to be mixed with Dart - (dynamic calls aren't allowed, - and JS interop types can't be implemented by a Dart class). - -You can implement static interop using -the `dart:js_interop` annotation [`@staticInterop`][]. -The set of features for future static interop currently includes: - -* `@staticInterop` interfaces - * External factory constructors with and without `@anonymous` - * External `static` class members - * External non-`static` extension members on a `@staticInterop` - class (fields, getters, setters, methods) - * Non-external extension members on a `@staticInterop` class -* Top-level external members -* [`@JSExport`][] for mocking and exports - -For examples that showcase how to use static interop, -check out the [implementation of `package:web`][package-web], -which provides bindings to browser APIs using static interop. - -[`@staticInterop`]: {{site.dart-api}}/dart-js_interop/staticInterop-constant.html -[`dart:js_interop`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/dart-js_interop-library.html -[`@JSExport`]: {{site.pub-pkg}}/dart-js_interop/JSExport-class.html -[package-web]: https://github.com/dart-lang/web - -### `dart:js_interop_unsafe` - -[`dart:js_interop_unsafe`][] provides low-level interop API -and is supported by the JS and `dart2wasm` backends. -`dart:js_interop_unsafe` can provide more flexibility, -for example, in potential, rare edge cases we haven't yet -accounted for where static interop is not expressive enough. - -However, it is not as ergonomic, and we do not plan -to optimize it in the same way as static interop. -As a result, we highly recommend using static interop over -`dart:js_interop_unsafe` whenever it's possible. - -[`dart:js_interop_unsafe`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop_unsafe/dart-js_interop_unsafe-library.html diff --git a/src/web/js-interop/dom.md b/src/interop/js-interop/dom.md similarity index 100% rename from src/web/js-interop/dom.md rename to src/interop/js-interop/dom.md diff --git a/src/web/js-interop/index.md b/src/interop/js-interop/index.md similarity index 93% rename from src/web/js-interop/index.md rename to src/interop/js-interop/index.md index 54c53b9bc6..b55091f924 100644 --- a/src/web/js-interop/index.md +++ b/src/interop/js-interop/index.md @@ -115,11 +115,11 @@ For information on other iterations of JS interop in Dart: [inline classes]: / -[static interop reference]: /web/js-interop/reference -[How to interop with DOM APIs]: /web/js-interop/dom -[How to interop with JavaScript libraries and apps]: /web/js-interop/js-app -[How to test and mock JavaScript interop in Dart]: /web/js-interop/test-and-mock +[static interop reference]: /js-interop/reference +[How to interop with DOM APIs]: /js-interop/dom +[How to interop with JavaScript libraries and apps]: /js-interop/js-app +[How to test and mock JavaScript interop in Dart]: /js-interop/test-and-mock [pub.dev site page]: / [API reference]: / -[`dart:js_util`]: /web/js-interop/js-util -[Past JS interop]: /web/js-interop/past-js-interop +[`dart:js_util`]: /js-interop/js-util +[Past JS interop]: /js-interop/past-js-interop diff --git a/src/web/js-interop/js-app.md b/src/interop/js-interop/js-app.md similarity index 100% rename from src/web/js-interop/js-app.md rename to src/interop/js-interop/js-app.md diff --git a/src/web/js-interop/js-util.md b/src/interop/js-interop/js-util.md similarity index 100% rename from src/web/js-interop/js-util.md rename to src/interop/js-interop/js-util.md diff --git a/src/web/js-interop/past-js-interop.md b/src/interop/js-interop/past-js-interop.md similarity index 91% rename from src/web/js-interop/past-js-interop.md rename to src/interop/js-interop/past-js-interop.md index 474d6ad979..d8460ffb2a 100644 --- a/src/web/js-interop/past-js-interop.md +++ b/src/interop/js-interop/past-js-interop.md @@ -16,7 +16,7 @@ and will continue to be supported and developed. If you are just starting out with JS interop in Dart, please start with the static interop library, [`js_interop`][]. -[static interop]: /web/js-interop +[static interop]: /js-interop [`js_interop`]: {{site.dart-api}}/js_interop ## `package:js` @@ -40,10 +40,10 @@ much more limited than static interop, and therefore cannot be fully sound. For the same reason, `package:js` can not interop with [DOM APIs][]. [**`package:js` API docs**]: {{site.pub-pkg}}/js -[`@JS`]: /web/js-interop/reference#js +[`@JS`]: /js-interop/reference#js [`@Anonymous`]: /web/js-interop/reference#others -[`@staticInterop`]: /web/js-interop/reference#staticinterop -[DOM APIs]: /web/js-interop/dom +[`@staticInterop`]: /js-interop/reference#staticinterop +[DOM APIs]: /js-interop/dom ## `dart:js` diff --git a/src/web/js-interop/reference.md b/src/interop/js-interop/reference.md similarity index 98% rename from src/web/js-interop/reference.md rename to src/interop/js-interop/reference.md index 911f10abd7..2de8fb0973 100644 --- a/src/web/js-interop/reference.md +++ b/src/interop/js-interop/reference.md @@ -140,7 +140,7 @@ is what makes static interop possible. The `@JS` annotation was also a member of one of Dart's past JS interop solutions, `package:js`. You can read more about that on the [Past JS interop][] page. -[Past JS interop]: /web/js-interop/past-js-interop +[Past JS interop]: /js-interop/past-js-interop ### `@staticInterop` diff --git a/src/web/js-interop/test-and-mock.md b/src/interop/js-interop/test-and-mock.md similarity index 100% rename from src/web/js-interop/test-and-mock.md rename to src/interop/js-interop/test-and-mock.md diff --git a/src/web/js-interop/tutorials.md b/src/interop/js-interop/tutorials.md similarity index 84% rename from src/web/js-interop/tutorials.md rename to src/interop/js-interop/tutorials.md index 74eef4c408..185e8529f1 100644 --- a/src/web/js-interop/tutorials.md +++ b/src/interop/js-interop/tutorials.md @@ -24,6 +24,6 @@ Exporting Dart objects with `@JSExport` creates a mock of the object at the JS l This tutorial will walk through both cases: simply exporting Dart objects to JS, and then using that same functionality to test JS interop code. -[How to interop with DOM APIs]: /web/js-interop/dom -[How to interop with JavaScript libraries and apps]: /web/js-interop/js-app -[How to test and mock JavaScript interop in Dart]: /web/js-interop/test-and-mock \ No newline at end of file +[How to interop with DOM APIs]: /js-interop/dom +[How to interop with JavaScript libraries and apps]: /js-interop/js-app +[How to test and mock JavaScript interop in Dart]: /js-interop/test-and-mock \ No newline at end of file diff --git a/src/web/js-interop/js_util.md b/src/web/js-interop/js_util.md deleted file mode 100644 index f65502fc56..0000000000 --- a/src/web/js-interop/js_util.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: About `dart:js_util` -description: placeholder description ---- - -// *I don't know if this needs to be a page or can just be on the "other" page* \ No newline at end of file From bc2347e8315b6f1bcc7be2476168d9c430e66ef1 Mon Sep 17 00:00:00 2001 From: Marya Belanger Date: Wed, 10 Jan 2024 17:02:04 -0800 Subject: [PATCH 04/29] update web/js-interop links to interop/js-interop --- src/interop/js-interop/index.md | 14 +++++++------- src/interop/js-interop/js-util.md | 2 +- src/interop/js-interop/past-js-interop.md | 10 +++++----- src/interop/js-interop/reference.md | 2 +- src/interop/js-interop/tutorials.md | 6 +++--- src/libraries/index.md | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/interop/js-interop/index.md b/src/interop/js-interop/index.md index b55091f924..af1161b31b 100644 --- a/src/interop/js-interop/index.md +++ b/src/interop/js-interop/index.md @@ -40,7 +40,7 @@ and expanding to new interop types Static interop addresses all of the shortcomings of Dart's previous JS interop solutions. Check out the [Features](#features) section for a summary of improvements. -[previous iterations of JS interop]: /web/js-interop/past-js-interop +[previous iterations of JS interop]: /interop/js-interop/past-js-interop ## Usage @@ -115,11 +115,11 @@ For information on other iterations of JS interop in Dart: [inline classes]: / -[static interop reference]: /js-interop/reference -[How to interop with DOM APIs]: /js-interop/dom -[How to interop with JavaScript libraries and apps]: /js-interop/js-app -[How to test and mock JavaScript interop in Dart]: /js-interop/test-and-mock +[static interop reference]: /interop/js-interop/reference +[How to interop with DOM APIs]: /interop/js-interop/dom +[How to interop with JavaScript libraries and apps]: /interop/js-interop/js-app +[How to test and mock JavaScript interop in Dart]: /interop/js-interop/test-and-mock [pub.dev site page]: / [API reference]: / -[`dart:js_util`]: /js-interop/js-util -[Past JS interop]: /js-interop/past-js-interop +[`dart:js_util`]: /interop/js-interop/js-util +[Past JS interop]: /interop/js-interop/past-js-interop diff --git a/src/interop/js-interop/js-util.md b/src/interop/js-interop/js-util.md index ccc902193b..fdf4891c4f 100644 --- a/src/interop/js-interop/js-util.md +++ b/src/interop/js-interop/js-util.md @@ -46,5 +46,5 @@ a use case that `js_interop` cannot address (and please [let us know][] if you encounter such a use case). [**`dart:js_util` API docs**]: {{site.dart-api}}/dart-js_util/dart-js_util-library.html -[`external`]: /web/js-interop/reference#external +[`external`]: /interop/js-interop/reference#external [let us know]: https://github.com/dart-lang/sdk/issues/new?assignees=&labels=web-js-interop&template=1_issue_template.md&title=Create+an+issue \ No newline at end of file diff --git a/src/interop/js-interop/past-js-interop.md b/src/interop/js-interop/past-js-interop.md index d8460ffb2a..192e810250 100644 --- a/src/interop/js-interop/past-js-interop.md +++ b/src/interop/js-interop/past-js-interop.md @@ -16,7 +16,7 @@ and will continue to be supported and developed. If you are just starting out with JS interop in Dart, please start with the static interop library, [`js_interop`][]. -[static interop]: /js-interop +[static interop]: /interop/js-interop [`js_interop`]: {{site.dart-api}}/js_interop ## `package:js` @@ -40,10 +40,10 @@ much more limited than static interop, and therefore cannot be fully sound. For the same reason, `package:js` can not interop with [DOM APIs][]. [**`package:js` API docs**]: {{site.pub-pkg}}/js -[`@JS`]: /js-interop/reference#js -[`@Anonymous`]: /web/js-interop/reference#others -[`@staticInterop`]: /js-interop/reference#staticinterop -[DOM APIs]: /js-interop/dom +[`@JS`]: /interop/js-interop/reference#js +[`@Anonymous`]: /interop/js-interop/reference#others +[`@staticInterop`]: /interop/js-interop/reference#staticinterop +[DOM APIs]: /interop/js-interop/dom ## `dart:js` diff --git a/src/interop/js-interop/reference.md b/src/interop/js-interop/reference.md index 2de8fb0973..6d40d1c903 100644 --- a/src/interop/js-interop/reference.md +++ b/src/interop/js-interop/reference.md @@ -140,7 +140,7 @@ is what makes static interop possible. The `@JS` annotation was also a member of one of Dart's past JS interop solutions, `package:js`. You can read more about that on the [Past JS interop][] page. -[Past JS interop]: /js-interop/past-js-interop +[Past JS interop]: /interop/js-interop/past-js-interop ### `@staticInterop` diff --git a/src/interop/js-interop/tutorials.md b/src/interop/js-interop/tutorials.md index 185e8529f1..ef5156a7e8 100644 --- a/src/interop/js-interop/tutorials.md +++ b/src/interop/js-interop/tutorials.md @@ -24,6 +24,6 @@ Exporting Dart objects with `@JSExport` creates a mock of the object at the JS l This tutorial will walk through both cases: simply exporting Dart objects to JS, and then using that same functionality to test JS interop code. -[How to interop with DOM APIs]: /js-interop/dom -[How to interop with JavaScript libraries and apps]: /js-interop/js-app -[How to test and mock JavaScript interop in Dart]: /js-interop/test-and-mock \ No newline at end of file +[How to interop with DOM APIs]: /interop/js-interop/dom +[How to interop with JavaScript libraries and apps]: /interop/js-interop/js-app +[How to test and mock JavaScript interop in Dart]: /interop/js-interop/test-and-mock \ No newline at end of file diff --git a/src/libraries/index.md b/src/libraries/index.md index 8c238fb412..2b7bc244e3 100644 --- a/src/libraries/index.md +++ b/src/libraries/index.md @@ -160,4 +160,4 @@ Misc --> [development JavaScript compiler]: /tools/webdev#serve [jit]: /overview#native-platform -[JavaScript interoperability]: /web/js-interop +[JavaScript interoperability]: /interop/js-interop From 7e629dd82bc3413bc54d5ca4bf47c1840be5f24d Mon Sep 17 00:00:00 2001 From: Marya Belanger Date: Wed, 10 Jan 2024 17:10:30 -0800 Subject: [PATCH 05/29] remove broken anchors --- src/guides/whats-new.md | 2 +- src/interop/js-interop/past-js-interop.md | 4 ++-- src/interop/js-interop/test-and-mock.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/guides/whats-new.md b/src/guides/whats-new.md index 5f24f2f689..6aa89b900e 100644 --- a/src/guides/whats-new.md +++ b/src/guides/whats-new.md @@ -223,7 +223,7 @@ we made the following changes: [source descriptor]: /tools/pub/cmd/pub-add#source-descriptor [SDK archive]: /get-dart/archive [glossary]: /resources/glossary -[JS static interop support]: /interop/js-interop#next-generation-js-interop-preview +[JS static interop support]: /interop/js-interop [analyzer plugins]: /tools/analysis#plugins ### Articles added to the Dart blog diff --git a/src/interop/js-interop/past-js-interop.md b/src/interop/js-interop/past-js-interop.md index 192e810250..a4ba623d65 100644 --- a/src/interop/js-interop/past-js-interop.md +++ b/src/interop/js-interop/past-js-interop.md @@ -41,8 +41,8 @@ For the same reason, `package:js` can not interop with [DOM APIs][]. [**`package:js` API docs**]: {{site.pub-pkg}}/js [`@JS`]: /interop/js-interop/reference#js -[`@Anonymous`]: /interop/js-interop/reference#others -[`@staticInterop`]: /interop/js-interop/reference#staticinterop +[`@Anonymous`]: /interop/js-interop/reference +[`@staticInterop`]: /interop/js-interop/referenc [DOM APIs]: /interop/js-interop/dom ## `dart:js` diff --git a/src/interop/js-interop/test-and-mock.md b/src/interop/js-interop/test-and-mock.md index bd0a864e26..7fe8b94570 100644 --- a/src/interop/js-interop/test-and-mock.md +++ b/src/interop/js-interop/test-and-mock.md @@ -14,7 +14,7 @@ description: placeholder description 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). +go to [Steps](#). ### Topic/concept 1 From 902703b72213a86554ce33921b4cf775ff6f814a Mon Sep 17 00:00:00 2001 From: Marya Belanger Date: Wed, 10 Jan 2024 17:14:15 -0800 Subject: [PATCH 06/29] typo in link --- src/interop/js-interop/past-js-interop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interop/js-interop/past-js-interop.md b/src/interop/js-interop/past-js-interop.md index a4ba623d65..569600a035 100644 --- a/src/interop/js-interop/past-js-interop.md +++ b/src/interop/js-interop/past-js-interop.md @@ -42,7 +42,7 @@ For the same reason, `package:js` can not interop with [DOM APIs][]. [**`package:js` API docs**]: {{site.pub-pkg}}/js [`@JS`]: /interop/js-interop/reference#js [`@Anonymous`]: /interop/js-interop/reference -[`@staticInterop`]: /interop/js-interop/referenc +[`@staticInterop`]: /interop/js-interop/reference [DOM APIs]: /interop/js-interop/dom ## `dart:js` From 3ce781fb8148ef0dcd7f9316b9fb149046127d91 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Fri, 12 Jan 2024 16:41:05 -0800 Subject: [PATCH 07/29] Add a section for JS types and reference it in the nav --- src/_data/side-nav.yml | 2 +- src/interop/js-interop/js-types.md | 189 +++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 src/interop/js-interop/js-types.md diff --git a/src/_data/side-nav.yml b/src/_data/side-nav.yml index 90cdc89d01..ce8bdd5d1d 100644 --- a/src/_data/side-nav.yml +++ b/src/_data/side-nav.yml @@ -251,7 +251,7 @@ - title: Overview permalink: /interop/js-interop - title: JS types - permalink: /interop/js-interop/reference + permalink: /interop/js-interop/js-types - title: Tutorials permalink: /interop/js-interop/tutorials - title: Past JS interop diff --git a/src/interop/js-interop/js-types.md b/src/interop/js-interop/js-types.md new file mode 100644 index 0000000000..1e23b77d67 --- /dev/null +++ b/src/interop/js-interop/js-types.md @@ -0,0 +1,189 @@ +--- +title: JS Types +description: Usage information about the core types in JS interop. +--- + +## Motivation + +Dart values and JS values belong to separate domains. When compiling to Wasm, +they belong in separate *runtimes* as well. As such, users should treat JS +values as foreign types. In order to provide types for JS values, we use the set +of JS types available in `dart:js_interop`. These are extension types prefixed +with `JS` and are called "JS types". + +Importantly, these types have compiler-specific representation types. This means +that their runtime type will differ based on whether code is compiled to Wasm or +JS. In order to interact with and examine these JS values, we use `external` +interop members. + +## Type Hierarchy + +JS types form a natural type hierarchy: + +- Top type: `JSAny`, which is any non-nullish JS value + - Primitives: `JSNumber`, `JSBoolean`, `JSString` + - `JSSymbol` + - `JSBigInt` + - `JSObject`, which is any JS object + - `JSFunction` + - `JSExportedDartFunction`, which represents a Dart callback that was + converted to a JS function + - `JSArray` + - `JSPromise` + - `JSDataView` + - `JSTypedArray` + - JS typed arrays e.g. `JSUint8Array` + - `JSBoxedDartObject`, which allows users to box and pass Dart values + opaquely within the same Dart runtime + +Note that these JS types are *also* interop types. As such, users can and should +use them as representation types of other interface types. Users will likely +want to use `JSObject` for this purpose. + +// TODO(srujzs): Add a link to the interop type section in the syntax. +// TODO(srujzs): Should we link the type to the definition? It might be a bit +// annoying to do everywhere a JS type is mentioned. + +## Conversions + +Since we have separate domains, we will likely want to *convert* values between +one domain to the other. For example, we may want to convert a Dart `String` +into a JS string, which is represented by the JS type JSString. To do this, we +supply a number of `extension` members on various Dart types that will do the +conversion for you, usually labeled as or prefixed with `toJS`. Similarly, we +can convert JS values to Dart types. We supply a number of members on various JS +types to enable this conversion, usually labeled as or prefixed with `toDart`. +They are often marked `external`, and their implementation is usually +platform-dependent. Note that not all JS types have a conversion, and not all +Dart types have a conversion. + +In general, the conversion table looks like the following: + +| JS type | Dart type | +| ----------------------------------- | ---------------------------------------- | +| `JSNumber`, `JSBoolean`, `JSString` | `num`, `int`, `double`, `bool`, `String` | +| `JSExportedFunction` | `JSFunction` | +| `JSArray` | `List` | +| `JSPromise` | `Future` | +| Typed arrays e.g. `JSUint8Array` | `dart:typed_data` | +| `JSBoxedDartObject` | Opaque Dart value | + +**Important**: Conversions may have different costs depending on the compiler. +Prefer to only convert when you need to. Furthermore, conversions may or may not +produce a new value. This doesn’t matter for immutable values like numbers, but +does matter for types like `List`s. A conversion to a `JSArray` may produce a +new value by copying or may not, so do not rely on modifications to the +`JSArray` affecting the `List`. Typed array conversions have a similar +limitation. Look at the specific conversion function for more details. + +## Requirements on `external` declarations and `Function.toJS` + +In order to ensure type safety and consistency, we place requirements on what +types can flow into and out of JS. Passing arbitrary Dart values into JS is not +allowed. Instead, we require users to use a compatible interop type like a JS +type or a primitive, which would then be implicitly converted by the compiler. +For example, these would be allowed: + +```dart +@JS() +external void usePrimitives(String a, int b, double c, num d, bool e); +``` +```dart +@JS() +external JSArray useJsTypes(JSObject _, JSString __); +``` +```dart +extension type InteropType(JSObject _) {} + +@JS() +external InteropType get interopType; +``` + +whereas these would return an error: + +```dart +@JS() +external Function get function; +``` + +```dart +@JS() +external set list(List _); +``` + +These same requirements exist when we use `Function.toJS` to make a Dart +function callable in JS. The values that flow into and out of this callback must +be a compatible interop type or a primitive. + +If you use a Dart primitive e.g. `String`, an implicit conversion happens in the +compiler to convert that value from a JS value to a Dart value. If performance +is critical and you don’t need to examine the contents of the string, then maybe +using `JSString` instead makes sense to avoid the conversion cost. + +## Compatibility/type checks and casts + +As mentioned above, the representation type of JS types may differ based on the +compiler. This affects runtime type-checking and casts. Therefore, almost always +avoid `is` checks where the value is an interop type or where the target type is +an interop type. Avoid casts between Dart types and interop types. In order to +type-check a JS value, use an interop member like `typeofEquals` or +`instanceOfString` that examines the JS value itself. + +Bad: + +```dart +void f(JSAny a) { + if (a is String) { … } +} +``` + +```dart +void f(JSObject o) { + if (o is JSObject) { … } +} +``` + +```dart +void f(JSString s) { + l as String; +} +``` + +Okay: + +```dart +void f(JSAny a) { + // Here we verify that `a` is a JS function, so the cast is okay. + if (a.typeofEquals('function')) { + a as JSFunction; + } +} +``` + +We may add lints to make runtime checks with JS interop types easier to avoid. + +## `null` vs `undefined` + +JS has both a `null` and an `undefined` value. This is in contrast with Dart, +which only has `null`. In order to make JS values more ergonomic to use, if an +interop member were to return either JS `null` or `undefined`, we map these +values to Dart `null`. Therefore a member like: + +```dart +@JS() +external JSObject? get value; +``` + +can be interpreted as returning a JS object, JS `null`, or `undefined`. + +**Important**: There is a subtle inconsistency with regards to `undefined` +between compiling to JS and Wasm. While compiling to JS treats `undefined` +values like Dart `null`, it doesn’t actually *change* the value itself. If you +an interop member returns `undefined` and you pass it back into JS, JS will see +`undefined` and not `null` when compiling to JS. When compiling to Wasm, this is +not the case, because we convert the value to Dart `null`, thereby losing +information on whether the original value was JS `null` or `undefined`. Avoid +writing code where this distinction matters by explicitly passing Dart `null` +instead to an interop member. Currently, there’s no platform-consistent way to +provide `undefined` to interop members or distinguish between JS `null` and +`undefined` values, but this will likely change in the future. \ No newline at end of file From 16cfb08489e6bf4d8ee964fcab95793533d33266 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Thu, 18 Jan 2024 15:16:39 -0800 Subject: [PATCH 08/29] Update JS types to address review comments --- src/interop/js-interop/js-types.md | 141 +++++++++++++++++------------ 1 file changed, 82 insertions(+), 59 deletions(-) diff --git a/src/interop/js-interop/js-types.md b/src/interop/js-interop/js-types.md index 1e23b77d67..452954df5e 100644 --- a/src/interop/js-interop/js-types.md +++ b/src/interop/js-interop/js-types.md @@ -1,22 +1,20 @@ --- -title: JS Types +title: JS types description: Usage information about the core types in JS interop. --- -## Motivation +Dart values and JS values belong to separate language domains. When compiling to +Wasm, they execute in separate *runtimes* as well. As such, you should treat JS +values as foreign types. To provide Dart types for JS values, +[`dart:js_interop`] exposes a set of [extension types] prefixed with `JS` called +"JS types". -Dart values and JS values belong to separate domains. When compiling to Wasm, -they belong in separate *runtimes* as well. As such, users should treat JS -values as foreign types. In order to provide types for JS values, we use the set -of JS types available in `dart:js_interop`. These are extension types prefixed -with `JS` and are called "JS types". +Importantly, these types have compiler-specific [representation types]. This +means that their runtime type will differ based on whether code is compiled to +Wasm or JS. In order to interact with and examine these JS values, you should +use [`external`] interop members. -Importantly, these types have compiler-specific representation types. This means -that their runtime type will differ based on whether code is compiled to Wasm or -JS. In order to interact with and examine these JS values, we use `external` -interop members. - -## Type Hierarchy +## Type hierarchy JS types form a natural type hierarchy: @@ -36,33 +34,32 @@ JS types form a natural type hierarchy: - `JSBoxedDartObject`, which allows users to box and pass Dart values opaquely within the same Dart runtime -Note that these JS types are *also* interop types. As such, users can and should -use them as representation types of other interface types. Users will likely -want to use `JSObject` for this purpose. +You can find the definition of each type in the [`dart:js_interop` API docs]. -// TODO(srujzs): Add a link to the interop type section in the syntax. -// TODO(srujzs): Should we link the type to the definition? It might be a bit -// annoying to do everywhere a JS type is mentioned. +{% comment %} +TODO (srujzs): Refer to this in the "interop type" section in syntax. +TODO (srujzs): Should we add a tree diagram instead for JS types? +{% endcomment %} ## Conversions -Since we have separate domains, we will likely want to *convert* values between -one domain to the other. For example, we may want to convert a Dart `String` -into a JS string, which is represented by the JS type JSString. To do this, we -supply a number of `extension` members on various Dart types that will do the -conversion for you, usually labeled as or prefixed with `toJS`. Similarly, we -can convert JS values to Dart types. We supply a number of members on various JS -types to enable this conversion, usually labeled as or prefixed with `toDart`. -They are often marked `external`, and their implementation is usually -platform-dependent. Note that not all JS types have a conversion, and not all -Dart types have a conversion. +Since there are two separate domains, you will likely want to *convert* values +between one domain to the other. For example, you may want to convert a Dart +`String` into a JS string, which is represented by the JS type `JSString`. To do +this, Dart supplies a number of `extension` members on various Dart types that +will do the conversion for you, usually labeled as or prefixed with `toJS`. +Similarly, you can convert JS values to Dart types. Dart supplies a number of +members on various JS types to enable this conversion, usually labeled as or +prefixed with `toDart`. They are often marked `external`, and their +implementation is usually platform-dependent. Note that not all JS types have a +conversion, and not all Dart types have a conversion. In general, the conversion table looks like the following: | JS type | Dart type | | ----------------------------------- | ---------------------------------------- | | `JSNumber`, `JSBoolean`, `JSString` | `num`, `int`, `double`, `bool`, `String` | -| `JSExportedFunction` | `JSFunction` | +| `JSExportedFunction` | `Function` | | `JSArray` | `List` | | `JSPromise` | `Future` | | Typed arrays e.g. `JSUint8Array` | `dart:typed_data` | @@ -73,25 +70,30 @@ Prefer to only convert when you need to. Furthermore, conversions may or may not produce a new value. This doesn’t matter for immutable values like numbers, but does matter for types like `List`s. A conversion to a `JSArray` may produce a new value by copying or may not, so do not rely on modifications to the -`JSArray` affecting the `List`. Typed array conversions have a similar +`JSArray` to affect the `List`. Typed array conversions have a similar limitation. Look at the specific conversion function for more details. ## Requirements on `external` declarations and `Function.toJS` -In order to ensure type safety and consistency, we place requirements on what -types can flow into and out of JS. Passing arbitrary Dart values into JS is not -allowed. Instead, we require users to use a compatible interop type like a JS -type or a primitive, which would then be implicitly converted by the compiler. -For example, these would be allowed: +In order to ensure type safety and consistency, the compiler places requirements +on what types can flow into and out of JS. Passing arbitrary Dart values into JS +is not allowed. Instead, the compiler requires users to use a compatible interop +type like a JS type or a primitive, which would then be implicitly converted by +the compiler. For example, these would be allowed: +{:.good} ```dart @JS() -external void usePrimitives(String a, int b, double c, num d, bool e); +external void primitives(String a, int b, double c, num d, bool e); ``` + +{:.good} ```dart @JS() -external JSArray useJsTypes(JSObject _, JSString __); +external JSArray jsTypes(JSObject _, JSString __); ``` + +{:.good} ```dart extension type InteropType(JSObject _) {} @@ -101,73 +103,79 @@ external InteropType get interopType; whereas these would return an error: +{:.bad} ```dart @JS() external Function get function; ``` +{:.bad} ```dart @JS() external set list(List _); ``` -These same requirements exist when we use `Function.toJS` to make a Dart +These same requirements exist when you use [`Function.toJS`] to make a Dart function callable in JS. The values that flow into and out of this callback must be a compatible interop type or a primitive. If you use a Dart primitive e.g. `String`, an implicit conversion happens in the compiler to convert that value from a JS value to a Dart value. If performance -is critical and you don’t need to examine the contents of the string, then maybe -using `JSString` instead makes sense to avoid the conversion cost. +is critical and you don’t need to examine the contents of the string, then using +`JSString` instead to avoid the conversion cost may make sense. -## Compatibility/type checks and casts +## Compatibility, type checks, and casts As mentioned above, the representation type of JS types may differ based on the compiler. This affects runtime type-checking and casts. Therefore, almost always avoid `is` checks where the value is an interop type or where the target type is an interop type. Avoid casts between Dart types and interop types. In order to -type-check a JS value, use an interop member like `typeofEquals` or -`instanceOfString` that examines the JS value itself. +type-check a JS value, use an interop member like [`typeofEquals`] or +[`instanceOfString`] that examines the JS value itself. Bad: +{:.bad} ```dart void f(JSAny a) { if (a is String) { … } } ``` +{:.bad} ```dart void f(JSObject o) { if (o is JSObject) { … } } ``` +{:.bad} ```dart void f(JSString s) { - l as String; + s as String; } ``` Okay: +{:.good} ```dart void f(JSAny a) { - // Here we verify that `a` is a JS function, so the cast is okay. + // Here `a` is verified to be a JS function, so the cast is okay. if (a.typeofEquals('function')) { a as JSFunction; } } ``` -We may add lints to make runtime checks with JS interop types easier to avoid. +Dart may add lints to make runtime checks with JS interop types easier to avoid. ## `null` vs `undefined` JS has both a `null` and an `undefined` value. This is in contrast with Dart, which only has `null`. In order to make JS values more ergonomic to use, if an -interop member were to return either JS `null` or `undefined`, we map these -values to Dart `null`. Therefore a member like: +interop member were to return either JS `null` or `undefined`, the compiler maps +these values to Dart `null`. Therefore a member like: ```dart @JS() @@ -177,13 +185,28 @@ external JSObject? get value; can be interpreted as returning a JS object, JS `null`, or `undefined`. **Important**: There is a subtle inconsistency with regards to `undefined` -between compiling to JS and Wasm. While compiling to JS treats `undefined` -values like Dart `null`, it doesn’t actually *change* the value itself. If you -an interop member returns `undefined` and you pass it back into JS, JS will see -`undefined` and not `null` when compiling to JS. When compiling to Wasm, this is -not the case, because we convert the value to Dart `null`, thereby losing -information on whether the original value was JS `null` or `undefined`. Avoid -writing code where this distinction matters by explicitly passing Dart `null` -instead to an interop member. Currently, there’s no platform-consistent way to -provide `undefined` to interop members or distinguish between JS `null` and -`undefined` values, but this will likely change in the future. \ No newline at end of file +between compiling to JS and Wasm. While compiling to JS *treats* `undefined` +values as if they were Dart `null`, it doesn’t actually *change* the value +itself. If an interop member returns `undefined` and you pass that value back +into JS, JS will see `undefined` and not `null` when compiling to JS. +However, when compiling to Wasm, this is not the case and the value will be +`null` in JS. This is because the compiler *converts* the value to Dart `null` +when compiling to Wasm, thereby losing information on whether the original value +was JS `null` or `undefined`. Avoid writing code where this distinction matters +by explicitly passing Dart `null` instead to an interop member. Currently, +there's no platform-consistent way to provide `undefined` to interop members or +distinguish between JS `null` and `undefined` values, but this will likely +change in the future. + +{% comment %} +TODO: add links (with stable) when ready: +{% endcomment %} + +[`dart:js_interop`]: https://api.dart.dev/dev/dart-js_interop/dart-js_interop-library.html +[extension types]: / +[representation types]: / +[`external`]: https://dart.dev/language/keywords +[`Function.toJS`]: https://api.dart.dev/dev/dart-js_interop/FunctionToJSExportedDartFunction/toJS.html +[`dart:js_interop` API docs]: https://api.dart.dev/dev/dart-js_interop/dart-js_interop-library.html#extension-types +[`typeofEquals`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/typeofEquals.html +[`instanceOfString`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/instanceOfString.html \ No newline at end of file From 4c442a1f04fe05d61901d3a73b1bcc9b69cb6f29 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Thu, 25 Jan 2024 15:21:20 -0800 Subject: [PATCH 09/29] Incorporate second round of JS types feedback --- src/interop/js-interop/js-types.md | 135 ++++++++++++++++++----------- 1 file changed, 83 insertions(+), 52 deletions(-) diff --git a/src/interop/js-interop/js-types.md b/src/interop/js-interop/js-types.md index 452954df5e..bdc0be1752 100644 --- a/src/interop/js-interop/js-types.md +++ b/src/interop/js-interop/js-types.md @@ -30,7 +30,7 @@ JS types form a natural type hierarchy: - `JSPromise` - `JSDataView` - `JSTypedArray` - - JS typed arrays e.g. `JSUint8Array` + - JS typed arrays like `JSUint8Array` - `JSBoxedDartObject`, which allows users to box and pass Dart values opaquely within the same Dart runtime @@ -43,35 +43,58 @@ TODO (srujzs): Should we add a tree diagram instead for JS types? ## Conversions -Since there are two separate domains, you will likely want to *convert* values -between one domain to the other. For example, you may want to convert a Dart -`String` into a JS string, which is represented by the JS type `JSString`. To do -this, Dart supplies a number of `extension` members on various Dart types that -will do the conversion for you, usually labeled as or prefixed with `toJS`. -Similarly, you can convert JS values to Dart types. Dart supplies a number of -members on various JS types to enable this conversion, usually labeled as or -prefixed with `toDart`. They are often marked `external`, and their -implementation is usually platform-dependent. Note that not all JS types have a -conversion, and not all Dart types have a conversion. +To use a value from one domain to another, you will likely want to *convert* the +value to the corresponding type of the other domain. For example, you might want +to convert a Dart `List` into a JS array of strings, which is +represented by the JS type `JSArray`, so that you can pass the array +to a JS interop API. -In general, the conversion table looks like the following: +Dart supplies a number of `extension` members on various Dart types and JS types +to convert the values between the domains for you. +Members that convert values from Dart to JS are usually labeled-as or +prefixed-with `toJS`: + +```dart +String str = 'hello world'; +JSString jsStr = str.toJS; +``` + +Members that convert values from JS to Dart are usually labeled-as or +prefixed-with `toDart`: + +```dart +JSNumber jsNum = ...; +int integer = jsNum.toDartInt; +``` + +These conversion functions are often marked `external`, but aren't interop +members. Their implementation is provided in the compiler instead. + +Not all JS types have a conversion, and not all Dart types have a conversion. +Generally, the conversion table looks like the following: + +
| JS type | Dart type | | ----------------------------------- | ---------------------------------------- | | `JSNumber`, `JSBoolean`, `JSString` | `num`, `int`, `double`, `bool`, `String` | | `JSExportedFunction` | `Function` | | `JSArray` | `List` | | `JSPromise` | `Future` | -| Typed arrays e.g. `JSUint8Array` | `dart:typed_data` | +| Typed arrays like `JSUint8Array` | `dart:typed_data` | | `JSBoxedDartObject` | Opaque Dart value | - -**Important**: Conversions may have different costs depending on the compiler. -Prefer to only convert when you need to. Furthermore, conversions may or may not -produce a new value. This doesn’t matter for immutable values like numbers, but -does matter for types like `List`s. A conversion to a `JSArray` may produce a -new value by copying or may not, so do not rely on modifications to the -`JSArray` to affect the `List`. Typed array conversions have a similar -limitation. Look at the specific conversion function for more details. +{:.table .table-striped} +
+ +{{site.alert.warn}} +Conversions might have different costs depending on the compiler, so prefer to +only convert values if you need to. Conversions also may or may not produce a +new value. This doesn’t matter for immutable values like numbers, but does +matter for types like `List`. A conversion to a `JSArray` may produce a new +value by copying or may not, so do not rely on modifications to the `JSArray` to +affect the `List`. Typed array conversions have a similar limitation. Look up +the specific conversion function for more details. +{{site.alert.end}} ## Requirements on `external` declarations and `Function.toJS` @@ -101,7 +124,7 @@ extension type InteropType(JSObject _) {} external InteropType get interopType; ``` -whereas these would return an error: +Whereas these would return an error: {:.bad} ```dart @@ -119,21 +142,18 @@ These same requirements exist when you use [`Function.toJS`] to make a Dart function callable in JS. The values that flow into and out of this callback must be a compatible interop type or a primitive. -If you use a Dart primitive e.g. `String`, an implicit conversion happens in the +If you use a Dart primitive like `String`, an implicit conversion happens in the compiler to convert that value from a JS value to a Dart value. If performance is critical and you don’t need to examine the contents of the string, then using -`JSString` instead to avoid the conversion cost may make sense. +`JSString` instead to avoid the conversion cost might make sense like in the +second example. ## Compatibility, type checks, and casts -As mentioned above, the representation type of JS types may differ based on the -compiler. This affects runtime type-checking and casts. Therefore, almost always -avoid `is` checks where the value is an interop type or where the target type is -an interop type. Avoid casts between Dart types and interop types. In order to -type-check a JS value, use an interop member like [`typeofEquals`] or -[`instanceOfString`] that examines the JS value itself. - -Bad: +The representation type of JS types may differ based on the compiler. This +affects runtime type-checking and casts. Therefore, almost always avoid `is` +checks where the value is an interop type or where the target type is an interop +type: {:.bad} ```dart @@ -141,7 +161,6 @@ void f(JSAny a) { if (a is String) { … } } ``` - {:.bad} ```dart void f(JSObject o) { @@ -149,6 +168,8 @@ void f(JSObject o) { } ``` +Also, avoid casts between Dart types and interop types: + {:.bad} ```dart void f(JSString s) { @@ -156,7 +177,8 @@ void f(JSString s) { } ``` -Okay: +To type-check a JS value, use an interop member like [`typeofEquals`] or +[`instanceOfString`] that examines the JS value itself: {:.good} ```dart @@ -168,35 +190,42 @@ void f(JSAny a) { } ``` -Dart may add lints to make runtime checks with JS interop types easier to avoid. +{% comment %} +TODO: Add a link to and an example using `isA` once it's in a dev release. Users +should prefer that method if it's available. +{% endcomment %} + +Dart might add lints to make runtime checks with JS interop types easier to +avoid. See issue [#4841] for more details. ## `null` vs `undefined` JS has both a `null` and an `undefined` value. This is in contrast with Dart, which only has `null`. In order to make JS values more ergonomic to use, if an interop member were to return either JS `null` or `undefined`, the compiler maps -these values to Dart `null`. Therefore a member like: +these values to Dart `null`. Therefore a member like `value` in the following example +can be interpreted as returning a JS object, JS `null`, or `undefined`: ```dart @JS() external JSObject? get value; ``` -can be interpreted as returning a JS object, JS `null`, or `undefined`. - -**Important**: There is a subtle inconsistency with regards to `undefined` -between compiling to JS and Wasm. While compiling to JS *treats* `undefined` -values as if they were Dart `null`, it doesn’t actually *change* the value -itself. If an interop member returns `undefined` and you pass that value back -into JS, JS will see `undefined` and not `null` when compiling to JS. -However, when compiling to Wasm, this is not the case and the value will be -`null` in JS. This is because the compiler *converts* the value to Dart `null` -when compiling to Wasm, thereby losing information on whether the original value -was JS `null` or `undefined`. Avoid writing code where this distinction matters -by explicitly passing Dart `null` instead to an interop member. Currently, -there's no platform-consistent way to provide `undefined` to interop members or -distinguish between JS `null` and `undefined` values, but this will likely -change in the future. +{{site.alert.warn}} +There is a subtle inconsistency with regards to `undefined` between compiling to +JS and Wasm. While compiling to JS *treats* `undefined` values as if they were +Dart `null`, it doesn’t actually *change* the value itself. If an interop member +returns `undefined` and you pass that value back into JS, JS will see +`undefined`, *not* `null`, when compiling to JS. However, when compiling to +Wasm, this is not the case and the value will be `null` in JS. This is because +the compiler implicitly *converts* the value to Dart `null` when compiling to +Wasm, thereby losing information on whether the original value was JS `null` or +`undefined`. Avoid writing code where this distinction matters by explicitly +passing Dart `null` instead to an interop member. Currently, there's no +platform-consistent way to provide `undefined` to interop members or distinguish +between JS `null` and `undefined` values, but this will likely change in the +future. See [#54025] for more details. +{{site.alert.end}} {% comment %} TODO: add links (with stable) when ready: @@ -209,4 +238,6 @@ TODO: add links (with stable) when ready: [`Function.toJS`]: https://api.dart.dev/dev/dart-js_interop/FunctionToJSExportedDartFunction/toJS.html [`dart:js_interop` API docs]: https://api.dart.dev/dev/dart-js_interop/dart-js_interop-library.html#extension-types [`typeofEquals`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/typeofEquals.html -[`instanceOfString`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/instanceOfString.html \ No newline at end of file +[`instanceOfString`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/instanceOfString.html +[#4841]: https://github.com/dart-lang/linter/issues/4841 +[#54025]: https://github.com/dart-lang/sdk/issues/54025 \ No newline at end of file From 8c2b89f8f2e3c2b1145de521768516b9c8ca11f8 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Fri, 26 Jan 2024 15:02:23 -0800 Subject: [PATCH 10/29] Add docs on interop usage --- src/interop/js-interop/js-types.md | 1 - src/interop/js-interop/usage.md | 368 +++++++++++++++++++++++++++++ 2 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 src/interop/js-interop/usage.md diff --git a/src/interop/js-interop/js-types.md b/src/interop/js-interop/js-types.md index bdc0be1752..296de1dad6 100644 --- a/src/interop/js-interop/js-types.md +++ b/src/interop/js-interop/js-types.md @@ -37,7 +37,6 @@ JS types form a natural type hierarchy: You can find the definition of each type in the [`dart:js_interop` API docs]. {% comment %} -TODO (srujzs): Refer to this in the "interop type" section in syntax. TODO (srujzs): Should we add a tree diagram instead for JS types? {% endcomment %} diff --git a/src/interop/js-interop/usage.md b/src/interop/js-interop/usage.md new file mode 100644 index 0000000000..fc1fd5f0a0 --- /dev/null +++ b/src/interop/js-interop/usage.md @@ -0,0 +1,368 @@ +--- +title: Usage +description: How to declare and use JS interop members. +--- + +JS interop works by looking up properties in the [global JS scope]. In order to +interact with these properties, you use [`external`] interop members. In order +to provide types for these properties, you use interop types. + +## Interop types + +When interacting with a JS value, you need to provide a Dart type for it. In +order to do this, you either use or declare an interop type. Dart provides a set +of core interop types called ["JS types"] that you can use to add types to your +interop members. You can also declare an interop type using [extension types] by +making its representation type another interop type. "JS types" refer to the +interop types provided by Dart, and while all JS types are interop types, not +all interop types are JS types. For example: + +```dart +extension type Console(JSObject _) implements JSObject {} +``` + +Both `Console` and `JSObject` are interop types, but only `JSObject` is a JS +type. + +You can also use `Console` as a representation type of an interop type as it is +an interop type: + +```dart +extension type Console2(Console _) implements Console {} +``` + +Because you're using extension types, both `Console` and `Console2` are still +`JSObject`s. You're just providing a Dart interface to view the `JSObject` +differently. There is no guarantee that they are a specific type of JS object. + +Generally, you will likely use `JSObject` as the representation type for interop +types you declare because you're likely interacting with JS objects who don't +have a corresponding JS type like [`Window`]. + +Interop types should also generally implement their representation type so that +they can be subtypes of the representation type. + +## Interop members + +In order to get a JS value that you can provide a type for, you'll want to use +an `external` interop member. These members might take in arguments and return a +type. The types that can be used on these members have [restrictions]. There are +various ways you can declare interop members. + +### Top-level interop members + +```dart +@JS() +external Console get console; + +@JS() +external set name(String value); + +@JS() +external void close(); +``` + +Here, `console`, `name`, and `close` are top-level interop members. When +`console` is called, the value in the `console` property in the global namespace +is retrieved and casted to `Console`. When `name` is set, the property `name` in +the global namespace is set to the given value. When `close` is called, the +function `close` in the global namespace is called with no arguments. You can +declare top-level interop getters, setters, methods, and fields, which are just +pairs of getters and setters. + +### Interop type members + +```dart +extension type Array._(JSObject _) implements JSObject { + external Array(); + external factory Array.withLength(int length); + + external static bool isArray(JSObject o); + + external int length; + external JSAny? at(int index); + + external operator [](int index); + external operator []=(int index, JSAny? any); + + bool get isZeroLength => length == 0; +} +``` + +Here we are using `Array` to interop with JS' [`Array`] type. + +Within an interop type, you can declare several different types of `external` +interop members: + +- Constructors. When called, these members construct a new JS object whose + constructor is defined by the name of the extension type using `new`. For + example, calling `Array()` in Dart will generate a JS invocation that looks + like `new Array()`. Similarly, calling `Array.withLength(10)` will generate a + JS invocation that looks like `new Array(10)`. Note that the JS invocations of + all `external` interop constructors follow the same semantics, regardless of + whether they're given a name in Dart or if they are a factory. + +- `static` members. Like constructors, these members use the name of the + extension type to generate the JS code. For example, calling `Array.isArray()` + will generate a JS invocation that looks like `Array.isArray()`. Like + top-levels, you can declare `static` methods, getters, setters, and fields. + +- Instance members. Like with other Dart types, these members require an + instance in order to be used. These members get, set, or invoke properties on + the instance. For example: + + ```dart + final array = Array(); + array.length = 10; + final length = array.length; + for (final i = 0; i <= 10; i++) { + array[i] = i.toJS; + assert(array[i] == array.at(i)); + } + ``` + + The call to `array.length` gets the value of the `length` property of `array`. + The call to `array.length = 10` sets the value of the `length` property. The + call to `array.at(i)` calls the function in the `at` property of `array` with + the given arguments and returns the value of that call. Like top-levels and + `static` members, you can declare instance methods, getters, setters, and + fields. + +- Operators. There are only two `external` interop operators allowed in interop + types: `[]` and `[]=`. These are instance members that match the semantics of + JS' [property accessors]. In the above example, `array[i]` gets the value in + the `i`th slot of `array`, and `array[i]` sets the value in that slot to + `i.toJS`. We expose other JS operators through [utility functions] in + `dart:js_interop`. + +In the [JS types] section, you'll see that there exists a predefined JS type for +`Array` called `JSArray`. Here, we're writing another interop type for the same +JS type and just *viewing* it differently, so there is no conflict. If we +wanted, we could have made the representation type `JSArray`: + +```dart +extension type Array._(JSArray _) implements JSArray {} +``` + +Lastly, like any other extension type, you're allowed to declare any +non-`external` members in the interop type. `isZeroLength` is one such example. + +#### Object literal constructors + +It is useful sometimes to create a JS [object literal] that simply contains a +a number of properties and their values. In order to do this, we allow you to +use a constructor with only named arguments: + +```dart +import ‘dart:js_interop’; + +extension type Options._(JSObject o) { + external Options({int a, int b}); + external int get a; + external int get b; +} +``` + +A call to `Options(a: 0, b: 1)` will result in a JS invocation of +`{a: 0, b: 1}`. You can get or set these values through `external` instance +members. + +### Extension members on interop types + +You can also write `external` members in extensions of interop types. For +example: + +```dart +extension on Array { + external int push(JSAny? any); +} +``` + +The semantics of calling `push` are identical to what it would have been if it +was in the definition of `Array` instead. The only `external` members you can +write in such an extension are instance members and the allowed operators. Like +with interop types, you can write any non-`external` members in the extension. +These extensions are useful for when an interop type doesn't expose the +`external` member you need and you don't want to create a new interop type. + +### Parameters + +`external` interop methods can only contain positional and optional arguments. +The one exception is object literal constructors, where they can contain *only* +named arguments. + +Unlike with non-`external` methods, optional arguments do not get replaced with +their default value, but are instead omitted. For example: + +```dart +external int push(JSAny? any, [JSAny? any2]); +``` + +Calling `array.push(0.toJS)` in Dart will result in a JS invocation of +`array.push(0.toJS)` and *not* `array.push(0.toJS, null)`. If you declare a +parameter with an explicit default value, you will get a warning that the value +will be ignored. + +## `@JS()` + +It is sometimes useful to refer to a JS property with a different name than the +one written. For example, if you want to write two `external` APIs that point to +the same JS property, you’d need to write a different name for at least one of +them. In order to do this, you can use the [`@JS()`] annotation with a constant +string value. For example: + +```dart +extension type Array._(JSArray _) implements JSArray { + external int push(JSNumber number); + @JS('push') + external int pushString(JSString string); +} +``` + +Calling either `push` or `pushString` will result in JS code that uses `push`. + +You can also rename interop types: + +```dart +@JS('Date') +extension type JSDate._(JSObject _) implements JSObject { + external JSDate(); + + external static int now(); +} +``` + +Calling `JSDate()` will result in a JS invocation of `new Date()`. Similarly, +calling `JSDate.now()` will result in a JS invocation of `Date.now()`. + +Furthermore, you can namespace an entire library, which will add a prefix to all +interop top-level members, interop types, and `static` interop members within +those types: + +```dart +@JS('library1') +library; + +import 'dart:js_interop'; + +@JS() +external void method(); + +extension type JSType(JSObject _) implements JSObject { + external JSType(); + + external static int get staticMember; +} +``` + +Calling `method()` will result in a JS invocation of `library1.method()`, +calling `JSType()` will result in a JS invocation of `new library1.JSType()`, +and calling `JSType.staticMember` will result in a JS invocation of +`library1.JSType.staticMember`. + +Unlike interop members and interop types, we only ever add a library name in the +JS invocation if you provide a non-empty value in the `@JS()` annotation on the +library. + +```dart +library interop_library; + +import 'dart:js_interop'; + +@JS() +external void method(); +``` + +Calling `method()` will result in a JS invocation of `method()` and *not* +`interop_library.method()`. + +You can also write multiple namespaces delimited by a '.' for libraries, +top-level members, and interop types: + +```dart +@JS('library1.library2') +library; + +import 'dart:js_interop'; + +@JS('library3.method') +external void method(); + +@JS('library3.JSType') +extension type JSType(JSObject _) implements JSObject { + external JSType(); +} +``` + +Calling `method()` will result in a JS invocation of +`library1.library2.library3.method()`, calling `JSType()` will result in a JS +invocation of `new library1.library2.library3.JSType()`, and so forth. + +You can't use `@JS()` annotations with '.' in the value on interop type members +or extension members of interop types, however. + +If there is no value provided to `@JS()` or the value is empty, no renaming will +occur. + +`@JS()` also tells the compiler that a member or type is intended to be treated +as a JS interop member or type. It is required (with or without a value) for all +top-level members to distinguish them from other `external` top-level members, +but can often be elided on and within interop types and on extension members as +the compiler can tell from the representation type and on-type. + +## `dart:js_interop` and `dart:js_interop_unsafe` + +[`dart:js_interop`] contains all the necessary members you should need, +including `@JS`, JS types, conversion functions, and various utility functions. +Utility functions include: + +- [`globalContext`], which represents the global namespace that the compilers + use to generate JS invocations. +- [Helpers to type-check JS values] +- JS operators +- [`dartify`] and [`jsify`], which type-check and auto-convert certain JS values + to Dart values and vice versa. + +and more. More utilities might be added to this library in the future. + +[`dart:js_interop_unsafe`] contains members that allow you to look up properties +dynamically. For example: + +```dart +JSFunction f = console['log']; +``` + +Instead of declaring an interop member named `log`, we're instead using a string +to represent the property. `dart:js_interop_unsafe` provides functionality to +dynamically get, set, and call properties. + +{{site.alert.warn}} +Avoid using `dart:js_interop_unsafe` if possible. It makes security compliance +more difficult to guarantee and may lead to violations. +{{site.alert.end}} + +{% comment %} +TODO: add links (with stable) when ready: +TODO: How do you link to a subsection of another section? +{% endcomment %} + +[global JS scope]: https://developer.mozilla.org/en-US/docs/Glossary/Global_scope +[`external`]: https://dart.dev/language/keywords +["JS types"]: /interop/js-interop/js-types +[extension types]: / +[`Window`]: https://developer.mozilla.org/en-US/docs/Web/API/Window +[restrictions]: / +[`Date`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date +[property accessors]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#bracket_notation +[utility functions]: https://api.dart.dev/dev/dart-js_interop/JSAnyOperatorExtension.html +[JS types]: /interop/js-interop/js-types +[`JSArray`]: https://api.dart.dev/dev/dart-js_interop/JSArray-extension-type.html +[object literal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer +[`@JS()`]: https://api.dart.dev/dev/dart-js_interop/JS-class.html +[`dart:js_interop`]: https://api.dart.dev/dev/dart-js_interop +[`globalContext`]: https://api.dart.dev/dev/dart-js_interop/globalContext.html +[Helpers to type-check JS values]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension.html +[`dartify`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/dartify.html +[`jsify`]: https://api.dart.dev/dev/dart-js_interop/NullableObjectUtilExtension.html +[`dart:js_interop_unsafe`]: https://api.dart.dev/dev/dart-js_interop_unsafe + From e04e2d1b39b6ccba979336be298262e0ea9407e0 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 29 Jan 2024 11:45:06 -0800 Subject: [PATCH 11/29] Update past JS interop section Reorganizes the section to be chronological and notes differences and similarities between old interop and current interop. Also mentions that these are legacy and unsupported for Wasm. --- src/interop/js-interop/past-js-interop.md | 157 +++++++++++++++------- 1 file changed, 109 insertions(+), 48 deletions(-) diff --git a/src/interop/js-interop/past-js-interop.md b/src/interop/js-interop/past-js-interop.md index 569600a035..badca37638 100644 --- a/src/interop/js-interop/past-js-interop.md +++ b/src/interop/js-interop/past-js-interop.md @@ -1,59 +1,120 @@ --- title: Past JS interop -description: Archive of past JS interop implementations +description: Archive of past JS interop implementations. --- -This page addresses previous iterations of JS interop for Dart: -* `package:js` -* `dart:js` +This page addresses previous iterations of JS interop for Dart that are +considered legacy. They are not necessarily deprecated, but might be in the +future. Therefore, prefer using [`dart:js_interop`] going forwards and migrate +usages of old interop libraries when possible. While [`dart:html`] and other web +libraries are closely related, they're covered in the [`package:web`] page. -**We do not recommend using any JS interop solution other than [static interop][].** - -Each of these tools still exist and are usable. -However, the static interop model -is more performant, provides more capabilities, -and will continue to be supported and developed. -If you are just starting out with JS interop in Dart, -please start with the static interop library, [`js_interop`][]. - -[static interop]: /interop/js-interop -[`js_interop`]: {{site.dart-api}}/js_interop - -## `package:js` - -// *This section probably doesn't make any sense* - -[**`package:js` API docs**] - -**We will not continue to support `package:js` alongside static interop.** - -The `package:js` library can represent objects in different ways with its -class type annotations: - -* [`@JS`] -* [`@anonymous`] -* [`@staticInterop`] - -Because `package:js` supports dynamic invocations of external members (the -opposite of static interop), its static type checking capabilities are -much more limited than static interop, and therefore cannot be fully sound. -For the same reason, `package:js` can not interop with [DOM APIs][]. - -[**`package:js` API docs**]: {{site.pub-pkg}}/js -[`@JS`]: /interop/js-interop/reference#js -[`@Anonymous`]: /interop/js-interop/reference -[`@staticInterop`]: /interop/js-interop/reference -[DOM APIs]: /interop/js-interop/dom +{{site.alert.warn}} +None of these legacy interop libraries are supported when compiling to Wasm. +{{site.alert.end}} ## `dart:js` -[**`dart:js` API docs**] +[`dart:js`] exposed a concrete [`class wrapper`] to interop with JS objects. +This wrapper contained methods to dynamically get, set, and call properties on +the wrapped JS object. It was less performant to use due to the use of a class +wrapper and ergonomically more difficult to use as you couldn't declare interop +members. Many of the functionalities exposed in `dart:js` like [`allowInterop`] +were later re-exposed through other interop libraries. -**We will not continue to support `dart:js` alongside static interop.** +This library has been legacy ever since `package:js` and `dart:js_util` were +released. It is likely the first to be deprecated. -The `dart:js` library is a low-level API for non-static interop with JavaScript. -It's wrapper-based model requires much more overhead, -and is much more expensive and slow, -than static interop's zero-cost wrapper model. +## `package:js` -[**`dart:js` API docs**]: {{site.dart-api}}/dart-js/dart-js-library.html \ No newline at end of file +[`package:js`] introduced functionality to declare interop types and members. +It allowed users to write interop classes instead of interop extension types. At +runtime, these classes were erased to a type that is similar to +`dart:js_interop`'s [`JSObject`]. + +```dart +@JS() +class JSType {} +``` + +Users who have used this package will find the syntax and semantics of +`dart:js_interop` familiar. You may be able to migrate to `dart:js_interop` by +replacing the class definition with an extension type and have it work in many +cases. + +There are significant differences, however: + +- `package:js` types could not be used to interop with browser APIs. +- `package:js` allowed dynamic dispatch. This meant that if you casted the + `package:js` type to `dynamic` and called an interop member on it, it would + forward to the right member. This is no longer possible with + `dart:js_interop`. +- `package:js`' [`@JS`] has different soundness guarantees. `external` members + would not have their return types checked. This is also why there's a + [separate `@JS`] annotation in `dart:js_interop` that users should use + instead. +- `package:js` types could not rename instance members or have non-`external` + members. +- `package:js` types could subtype and be a supertype of non-interop classes. + This was often used for mocks. +- [`@anonymous`] types were a way to declare an interop type with an object + literal constructor. `dart:js_interop` does not use this annotation and treats + all `external` named-argument constructors as object literal constructors. + +### `@staticInterop` + +Along with `@JS` and `@anonymous`, `package:js` later exposed +[`@staticInterop`], which was a prototype of interop extension types. + +`@staticInterop` types were implicitly erased to `JSObject`. It required users +to declare all instance members in extensions so that only static semantics +could be used, and had stronger soundness guarantees. Users could use it to +interact with browser APIs, and it also allowed things like renaming and +non-`external` members. Like interop extension types, it didn't have support for +dynamic dispatch. + +`@staticInterop` classes can almost always be migrated to an interop extension +type by just changing the class to an extension type and removing the +annotations. + +`dart:js_interop` exposed `@staticInterop` (and `@anonymous`, but only if +`@staticInterop` is also used) to support static interop semantics until +extension types were added to the language. All such types should now be +migrated to extension types. + +## `dart:js_util` + +[`dart:js_util`] supplied a number of utility functions that could not be +declared in an interop type or were necessary for values to be passed back and +forth. This included members like: + +- `allowInterop` (which is now [`Function.toJS`]) +- `getProperty`/`setProperty`/`callMethod`/`callConstructor` (which are now in + [`dart:js_interop_unsafe`]) +- Various JS operators +- Type-checking helpers +- Mocking support + +and more. `dart:js_interop` and `dart:js_interop_unsafe` contain these helpers +now with possibly alternate syntax. + +{% comment %} +TODO: add links (with stable) when ready: +TODO: Link to `package:web` section +{% endcomment %} + +[`dart:js_interop`]: https://api.dart.dev/dev/dart-js_interop +[`dart:html`]: https://api.dart.dev/dev/dart-html +[`package:web`]: / +[`dart:js`]: https://api.dart.dev/dev/dart-js +[`class wrapper`]: https://api.dart.dev/dev/dart-js/JsObject-class.html +[`allowInterop`]: https://api.dart.dev/dev/dart-js_util/allowInterop.html +[`package:js`]: https://pub.dev/packages/js +[`JSObject`]: https://api.dart.dev/dev/dart-js_interop/JSObject-extension-type.html +[`@JS`]: https://github.com/dart-lang/sdk/blob/main/sdk/lib/js/_js_annotations.dart#L11 +[separate `@JS`]: https://api.dart.dev/dev/dart-js_interop/JS-class.html +[`@anonymous`]: https://github.com/dart-lang/sdk/blob/main/sdk/lib/js/_js_annotations.dart#L40 +[`@staticInterop`]: https://github.com/dart-lang/sdk/blob/main/sdk/lib/js/_js_annotations.dart#L48 +[`dart:js_util`]: https://api.dart.dev/dev/dart-js_util +[`Function.toJS`]: https://api.dart.dev/dev/dart-js_interop/FunctionToJSExportedDartFunction/toJS.html +[`dart:js_interop_unsafe`]: https://api.dart.dev/dev/dart-js_interop_unsafe From 992fd0fad8b8bec90dc09b4a9e7231213f3d431b Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Fri, 2 Feb 2024 14:26:26 -0800 Subject: [PATCH 12/29] Review feedback on JS types --- src/interop/js-interop/js-types.md | 46 ++++++++++++++---------------- src/interop/js-interop/usage.md | 2 +- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/interop/js-interop/js-types.md b/src/interop/js-interop/js-types.md index 296de1dad6..596347dd2c 100644 --- a/src/interop/js-interop/js-types.md +++ b/src/interop/js-interop/js-types.md @@ -6,13 +6,15 @@ description: Usage information about the core types in JS interop. Dart values and JS values belong to separate language domains. When compiling to Wasm, they execute in separate *runtimes* as well. As such, you should treat JS values as foreign types. To provide Dart types for JS values, -[`dart:js_interop`] exposes a set of [extension types] prefixed with `JS` called -"JS types". +[`dart:js_interop`] exposes a set of types prefixed with `JS` called "JS types". +These types are used to distinguish between Dart values and JS values at +compile-time. -Importantly, these types have compiler-specific [representation types]. This -means that their runtime type will differ based on whether code is compiled to -Wasm or JS. In order to interact with and examine these JS values, you should -use [`external`] interop members. +Importantly, these types are reified differently based on whether you compile to +Wasm or JS. This means that their runtime type will differ, and therefore you +[can't use `is` checks and `as` casts](#compatibility-type-checks-and-casts). +In order to interact with and examine these JS values, you should use +[`external`] interop members or [conversions](#conversions). ## Type hierarchy @@ -48,28 +50,23 @@ to convert a Dart `List` into a JS array of strings, which is represented by the JS type `JSArray`, so that you can pass the array to a JS interop API. -Dart supplies a number of `extension` members on various Dart types and JS types +Dart supplies a number of conversion members on various Dart types and JS types to convert the values between the domains for you. -Members that convert values from Dart to JS are usually labeled-as or -prefixed-with `toJS`: +Members that convert values from Dart to JS usually start with `toJS`: ```dart String str = 'hello world'; JSString jsStr = str.toJS; ``` -Members that convert values from JS to Dart are usually labeled-as or -prefixed-with `toDart`: +Members that convert values from JS to Dart usually start with `toDart`: ```dart JSNumber jsNum = ...; int integer = jsNum.toDartInt; ``` -These conversion functions are often marked `external`, but aren't interop -members. Their implementation is provided in the compiler instead. - Not all JS types have a conversion, and not all Dart types have a conversion. Generally, the conversion table looks like the following: @@ -149,10 +146,9 @@ second example. ## Compatibility, type checks, and casts -The representation type of JS types may differ based on the compiler. This -affects runtime type-checking and casts. Therefore, almost always avoid `is` -checks where the value is an interop type or where the target type is an interop -type: +The runtime type of JS types may differ based on the compiler. This affects +runtime type-checking and casts. Therefore, almost always avoid `is` checks +where the value is an interop type or where the target type is an interop type: {:.bad} ```dart @@ -160,10 +156,11 @@ void f(JSAny a) { if (a is String) { … } } ``` + {:.bad} ```dart -void f(JSObject o) { - if (o is JSObject) { … } +void f(JSAny a) { + if (a is JSObject) { … } } ``` @@ -202,14 +199,17 @@ avoid. See issue [#4841] for more details. JS has both a `null` and an `undefined` value. This is in contrast with Dart, which only has `null`. In order to make JS values more ergonomic to use, if an interop member were to return either JS `null` or `undefined`, the compiler maps -these values to Dart `null`. Therefore a member like `value` in the following example -can be interpreted as returning a JS object, JS `null`, or `undefined`: +these values to Dart `null`. Therefore a member like `value` in the following +example can be interpreted as returning a JS object, JS `null`, or `undefined`: ```dart @JS() external JSObject? get value; ``` +If the return type was not declared as nullable, then the compiler will throw an +error if the value returned was JS `null` or `undefined` to ensure soundness. + {{site.alert.warn}} There is a subtle inconsistency with regards to `undefined` between compiling to JS and Wasm. While compiling to JS *treats* `undefined` values as if they were @@ -231,8 +231,6 @@ TODO: add links (with stable) when ready: {% endcomment %} [`dart:js_interop`]: https://api.dart.dev/dev/dart-js_interop/dart-js_interop-library.html -[extension types]: / -[representation types]: / [`external`]: https://dart.dev/language/keywords [`Function.toJS`]: https://api.dart.dev/dev/dart-js_interop/FunctionToJSExportedDartFunction/toJS.html [`dart:js_interop` API docs]: https://api.dart.dev/dev/dart-js_interop/dart-js_interop-library.html#extension-types diff --git a/src/interop/js-interop/usage.md b/src/interop/js-interop/usage.md index fc1fd5f0a0..7022b11b85 100644 --- a/src/interop/js-interop/usage.md +++ b/src/interop/js-interop/usage.md @@ -351,7 +351,7 @@ TODO: How do you link to a subsection of another section? ["JS types"]: /interop/js-interop/js-types [extension types]: / [`Window`]: https://developer.mozilla.org/en-US/docs/Web/API/Window -[restrictions]: / +[restrictions]: /interop/js-interop/js-types.md#restrictions [`Date`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date [property accessors]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#bracket_notation [utility functions]: https://api.dart.dev/dev/dart-js_interop/JSAnyOperatorExtension.html From 6733b04436bbd9d85929a2586166de5b43bd2292 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 5 Feb 2024 09:57:25 -0800 Subject: [PATCH 13/29] Change "compiler" to "program" in JS types section --- src/interop/js-interop/js-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interop/js-interop/js-types.md b/src/interop/js-interop/js-types.md index 596347dd2c..38bb2930b4 100644 --- a/src/interop/js-interop/js-types.md +++ b/src/interop/js-interop/js-types.md @@ -207,7 +207,7 @@ example can be interpreted as returning a JS object, JS `null`, or `undefined`: external JSObject? get value; ``` -If the return type was not declared as nullable, then the compiler will throw an +If the return type was not declared as nullable, then the program will throw an error if the value returned was JS `null` or `undefined` to ensure soundness. {{site.alert.warn}} From 0e7f70a8e64620248b8cca02717a5f0f5db72705 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Tue, 6 Feb 2024 20:06:08 -0800 Subject: [PATCH 14/29] Review feedback of past JS interop --- src/interop/js-interop/past-js-interop.md | 61 ++++++++++++----------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/src/interop/js-interop/past-js-interop.md b/src/interop/js-interop/past-js-interop.md index badca37638..78959158ef 100644 --- a/src/interop/js-interop/past-js-interop.md +++ b/src/interop/js-interop/past-js-interop.md @@ -3,27 +3,28 @@ title: Past JS interop description: Archive of past JS interop implementations. --- +{{site.alert.warn}} +None of these legacy interop libraries are supported when compiling to Wasm. +{{site.alert.end}} + This page addresses previous iterations of JS interop for Dart that are -considered legacy. They are not necessarily deprecated, but might be in the +considered legacy. They are not deprecated yet, but will likely be in the future. Therefore, prefer using [`dart:js_interop`] going forwards and migrate usages of old interop libraries when possible. While [`dart:html`] and other web libraries are closely related, they're covered in the [`package:web`] page. -{{site.alert.warn}} -None of these legacy interop libraries are supported when compiling to Wasm. -{{site.alert.end}} - ## `dart:js` -[`dart:js`] exposed a concrete [`class wrapper`] to interop with JS objects. -This wrapper contained methods to dynamically get, set, and call properties on -the wrapped JS object. It was less performant to use due to the use of a class -wrapper and ergonomically more difficult to use as you couldn't declare interop -members. Many of the functionalities exposed in `dart:js` like [`allowInterop`] +[`dart:js`] exposed a concrete [`object wrapper`] to interop with JS objects. +This wrapper contained String-based methods to dynamically get, set, and call +properties on the wrapped JS object. It was less performant due to wrapping +costs and ergonomically more difficult to use. For example, you did not get +code-completion as you couldn't declare interop members and instead relied on +Strings. Many of the functionalities exposed in `dart:js` like [`allowInterop`] were later re-exposed through other interop libraries. This library has been legacy ever since `package:js` and `dart:js_util` were -released. It is likely the first to be deprecated. +released. It will likely be the first to be deprecated. ## `package:js` @@ -37,34 +38,35 @@ runtime, these classes were erased to a type that is similar to class JSType {} ``` -Users who have used this package will find the syntax and semantics of -`dart:js_interop` familiar. You may be able to migrate to `dart:js_interop` by -replacing the class definition with an extension type and have it work in many -cases. +Users of `package:js` will find the syntax and semantics of `dart:js_interop` +familiar. You may be able to migrate to `dart:js_interop` by replacing the class +definition with an extension type and have it work in many cases. There are significant differences, however: - `package:js` types could not be used to interop with browser APIs. + `dart:js_interop` types can. - `package:js` allowed dynamic dispatch. This meant that if you casted the `package:js` type to `dynamic` and called an interop member on it, it would forward to the right member. This is no longer possible with `dart:js_interop`. -- `package:js`' [`@JS`] has different soundness guarantees. `external` members - would not have their return types checked. This is also why there's a - [separate `@JS`] annotation in `dart:js_interop` that users should use - instead. +- `package:js`' [`@JS`] has no soundness guarantees as return types of + `external` members were not checked. `dart:js_interop` is sound. - `package:js` types could not rename instance members or have non-`external` members. - `package:js` types could subtype and be a supertype of non-interop classes. - This was often used for mocks. + This was often used for mocks. With `dart:js_interop`, mocking is done by + substituting the JS object instead. See the [tutorial on mocking]. - [`@anonymous`] types were a way to declare an interop type with an object - literal constructor. `dart:js_interop` does not use this annotation and treats - all `external` named-argument constructors as object literal constructors. + literal constructor. `dart:js_interop` doesn't distinguish types this way and + any `external` named-argument constructor is an object literal constructor. ### `@staticInterop` Along with `@JS` and `@anonymous`, `package:js` later exposed -[`@staticInterop`], which was a prototype of interop extension types. +[`@staticInterop`], which was a prototype of interop extension types. It is as +expressive and restrictive as `dart:js_interop` and was meant to be a +transitory syntax until extension types were available. `@staticInterop` types were implicitly erased to `JSObject`. It required users to declare all instance members in extensions so that only static semantics @@ -85,8 +87,8 @@ migrated to extension types. ## `dart:js_util` [`dart:js_util`] supplied a number of utility functions that could not be -declared in an interop type or were necessary for values to be passed back and -forth. This included members like: +declared in an `package:js` type or were necessary for values to be passed back +and forth. This included members like: - `allowInterop` (which is now [`Function.toJS`]) - `getProperty`/`setProperty`/`callMethod`/`callConstructor` (which are now in @@ -94,9 +96,10 @@ forth. This included members like: - Various JS operators - Type-checking helpers - Mocking support +- And more. -and more. `dart:js_interop` and `dart:js_interop_unsafe` contain these helpers -now with possibly alternate syntax. +`dart:js_interop` and `dart:js_interop_unsafe` contain these helpers now with +possibly alternate syntax. {% comment %} TODO: add links (with stable) when ready: @@ -107,12 +110,12 @@ TODO: Link to `package:web` section [`dart:html`]: https://api.dart.dev/dev/dart-html [`package:web`]: / [`dart:js`]: https://api.dart.dev/dev/dart-js -[`class wrapper`]: https://api.dart.dev/dev/dart-js/JsObject-class.html +[`object wrapper`]: https://api.dart.dev/dev/dart-js/JsObject-class.html [`allowInterop`]: https://api.dart.dev/dev/dart-js_util/allowInterop.html [`package:js`]: https://pub.dev/packages/js [`JSObject`]: https://api.dart.dev/dev/dart-js_interop/JSObject-extension-type.html [`@JS`]: https://github.com/dart-lang/sdk/blob/main/sdk/lib/js/_js_annotations.dart#L11 -[separate `@JS`]: https://api.dart.dev/dev/dart-js_interop/JS-class.html +[tutorial for mocking]: /interop/js-interop/test-and-mock [`@anonymous`]: https://github.com/dart-lang/sdk/blob/main/sdk/lib/js/_js_annotations.dart#L40 [`@staticInterop`]: https://github.com/dart-lang/sdk/blob/main/sdk/lib/js/_js_annotations.dart#L48 [`dart:js_util`]: https://api.dart.dev/dev/dart-js_util From b811f26f931f97819e6d5ea78da513c36ffc052e Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Fri, 9 Feb 2024 13:55:37 -0800 Subject: [PATCH 15/29] Handle review feedback on usage --- src/interop/js-interop/js-types.md | 10 +- src/interop/js-interop/past-js-interop.md | 18 +- src/interop/js-interop/usage.md | 378 +++++++++++++--------- 3 files changed, 246 insertions(+), 160 deletions(-) diff --git a/src/interop/js-interop/js-types.md b/src/interop/js-interop/js-types.md index 38bb2930b4..b18c337987 100644 --- a/src/interop/js-interop/js-types.md +++ b/src/interop/js-interop/js-types.md @@ -230,11 +230,11 @@ future. See [#54025] for more details. TODO: add links (with stable) when ready: {% endcomment %} -[`dart:js_interop`]: https://api.dart.dev/dev/dart-js_interop/dart-js_interop-library.html +[`dart:js_interop`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/dart-js_interop-library.html [`external`]: https://dart.dev/language/keywords -[`Function.toJS`]: https://api.dart.dev/dev/dart-js_interop/FunctionToJSExportedDartFunction/toJS.html -[`dart:js_interop` API docs]: https://api.dart.dev/dev/dart-js_interop/dart-js_interop-library.html#extension-types -[`typeofEquals`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/typeofEquals.html -[`instanceOfString`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/instanceOfString.html +[`Function.toJS`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/FunctionToJSExportedDartFunction/toJS.html +[`dart:js_interop` API docs]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/dart-js_interop-library.html#extension-types +[`typeofEquals`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/JSAnyUtilityExtension/typeofEquals.html +[`instanceOfString`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/JSAnyUtilityExtension/instanceOfString.html [#4841]: https://github.com/dart-lang/linter/issues/4841 [#54025]: https://github.com/dart-lang/sdk/issues/54025 \ No newline at end of file diff --git a/src/interop/js-interop/past-js-interop.md b/src/interop/js-interop/past-js-interop.md index 78959158ef..1978b615f0 100644 --- a/src/interop/js-interop/past-js-interop.md +++ b/src/interop/js-interop/past-js-interop.md @@ -106,18 +106,18 @@ TODO: add links (with stable) when ready: TODO: Link to `package:web` section {% endcomment %} -[`dart:js_interop`]: https://api.dart.dev/dev/dart-js_interop -[`dart:html`]: https://api.dart.dev/dev/dart-html +[`dart:js_interop`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop +[`dart:html`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-html [`package:web`]: / -[`dart:js`]: https://api.dart.dev/dev/dart-js -[`object wrapper`]: https://api.dart.dev/dev/dart-js/JsObject-class.html -[`allowInterop`]: https://api.dart.dev/dev/dart-js_util/allowInterop.html +[`dart:js`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js +[`object wrapper`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js/JsObject-class.html +[`allowInterop`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_util/allowInterop.html [`package:js`]: https://pub.dev/packages/js -[`JSObject`]: https://api.dart.dev/dev/dart-js_interop/JSObject-extension-type.html +[`JSObject`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/JSObject-extension-type.html [`@JS`]: https://github.com/dart-lang/sdk/blob/main/sdk/lib/js/_js_annotations.dart#L11 [tutorial for mocking]: /interop/js-interop/test-and-mock [`@anonymous`]: https://github.com/dart-lang/sdk/blob/main/sdk/lib/js/_js_annotations.dart#L40 [`@staticInterop`]: https://github.com/dart-lang/sdk/blob/main/sdk/lib/js/_js_annotations.dart#L48 -[`dart:js_util`]: https://api.dart.dev/dev/dart-js_util -[`Function.toJS`]: https://api.dart.dev/dev/dart-js_interop/FunctionToJSExportedDartFunction/toJS.html -[`dart:js_interop_unsafe`]: https://api.dart.dev/dev/dart-js_interop_unsafe +[`dart:js_util`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_util +[`Function.toJS`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/FunctionToJSExportedDartFunction/toJS.html +[`dart:js_interop_unsafe`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop_unsafe diff --git a/src/interop/js-interop/usage.md b/src/interop/js-interop/usage.md index 7022b11b85..6b4fadd4da 100644 --- a/src/interop/js-interop/usage.md +++ b/src/interop/js-interop/usage.md @@ -3,173 +3,246 @@ title: Usage description: How to declare and use JS interop members. --- -JS interop works by looking up properties in the [global JS scope]. In order to -interact with these properties, you use [`external`] interop members. In order -to provide types for these properties, you use interop types. +JS interop provides the mechanisms to interact with JavaScript APIs from Dart. +It allows you to invoke these APIs and interact with the values that you get +from them using an explicit, idiomatic syntax. + +Typically, you access a JavaScript API by making it available somewhere within +the [global JS scope]. To call and receive JS values from this API, you use +[`external` interop members](#interop-members). In order to construct and +provide types for JS values, you use and declare [interop types] +(#interop-types), which also contain interop members. To pass Dart values like +`List`s or `Function` to interop members or convert from JS values to Dart +values, you will use [conversion functions] unless the interop member is +[declared with a primitive]. ## Interop types -When interacting with a JS value, you need to provide a Dart type for it. In -order to do this, you either use or declare an interop type. Dart provides a set -of core interop types called ["JS types"] that you can use to add types to your -interop members. You can also declare an interop type using [extension types] by -making its representation type another interop type. "JS types" refer to the -interop types provided by Dart, and while all JS types are interop types, not -all interop types are JS types. For example: +When interacting with a JS value, you need to provide a Dart type for it. You +can do this by either using or declaring an interop type. Interop types are +either a ["JS type"] provided by Dart or an [extension type] wrapping an interop +type. Interop types allow you to provide an interface for a JS value and lets +you declare interop APIs for its members. They are also used in the signature +of other interop APIs. ```dart -extension type Console(JSObject _) implements JSObject {} +extension type Window(JSObject _) implements JSObject {} ``` -Both `Console` and `JSObject` are interop types, but only `JSObject` is a JS -type. +`Window` here is an interop type for an arbitrary `JSObject`. There is no +runtime guarantee that `Window` is actually a JS [`Window`]. See +[the extension types documentation] for more info on the runtime semantics of +extension types. There also is no conflict with any other interop interface +that is defined for the same value. If you do want to check that `Window` is +actually a JS `Window`, you can [check the type of the JS value through +interop]. -You can also use `Console` as a representation type of an interop type as it is -an interop type: +You can also declare your own interop type for the JS types Dart provides by +wrapping them or an interop type they implement with no conflict: ```dart -extension type Console2(Console _) implements Console {} +extension type Array._(JSArray _) implements JSArray { + external Array(); +} ``` -Because you're using extension types, both `Console` and `Console2` are still -`JSObject`s. You're just providing a Dart interface to view the `JSObject` -differently. There is no guarantee that they are a specific type of JS object. +In most cases, you will likely declare an interop type using `JSObject` as the +[representation type] because you're likely interacting with JS objects which +don't have an interop type provided by Dart. -Generally, you will likely use `JSObject` as the representation type for interop -types you declare because you're likely interacting with JS objects who don't -have a corresponding JS type like [`Window`]. - -Interop types should also generally implement their representation type so that -they can be subtypes of the representation type. +Interop types should also generally [implement] their representation type so +that they can be used where the representation type is expected, like in +[`package:web`]. ## Interop members -In order to get a JS value that you can provide a type for, you'll want to use -an `external` interop member. These members might take in arguments and return a -type. The types that can be used on these members have [restrictions]. There are -various ways you can declare interop members. +[`external`] interop members provide an idiomatic syntax for JS members. They +allow you to write a Dart type signature for any arguments and return value. +The types that can be used in the signature of these members have +[restrictions]. The JS API they correspond to is determined by a combination of +where they're declared, their name, what kind of Dart member they are, and any +[renames](#js). ### Top-level interop members +Given the following JS members: + +```JS +globalThis.name = 'global'; +globalThis.isNameEmpty = function() { + return globalThis.name.length == 0; +} +``` + +You can write interop members for them like so: + ```dart @JS() -external Console get console; +external String get name; @JS() external set name(String value); @JS() -external void close(); +external bool isNameEmpty(); ``` -Here, `console`, `name`, and `close` are top-level interop members. When -`console` is called, the value in the `console` property in the global namespace -is retrieved and casted to `Console`. When `name` is set, the property `name` in -the global namespace is set to the given value. When `close` is called, the -function `close` in the global namespace is called with no arguments. You can -declare top-level interop getters, setters, methods, and fields, which are just -pairs of getters and setters. +Here, there exists a property `name` and a function `isNameEmpty` that are +exposed in the global scope. To access them, you use top-level interop members. +To get and set `name`, you declare and use an interop getter and setter with the +same name. To use `isNameEmpty`, you declare and call an interop function with +the same name. You can declare top-level interop getters, setters, methods, and +fields, which is the same as a getter and setter pair. + +Top-level interop members must be declared with a [`@JS()`](#js) annotation to +distinguish them from other `external` top-level members. ### Interop type members -```dart -extension type Array._(JSObject _) implements JSObject { - external Array(); - external factory Array.withLength(int length); +Given a JS interface like the following: + +```JS +class Time { + constructor(hours, minutes) { + this._hours = Math.abs(hours) % 24; + this._minutes = arguments.length == 1 ? 0 : Math.abs(minutes) % 60; + } + + static dinnerTime = new Time(18, 0); + + static getTimeDifference(t1, t2) { + return new Time(t1.hours - t2.hours, t1.minutes - t2.minutes); + } - external static bool isArray(JSObject o); + get hours() { + return this._hours; + } - external int length; - external JSAny? at(int index); + set hours(value) { + this._hours = Math.abs(value) % 24; + } - external operator [](int index); - external operator []=(int index, JSAny? any); + get minutes() { + return this._minutes; + } - bool get isZeroLength => length == 0; + set minutes(value) { + this._minutes = Math.abs(value) % 60; + } + + isDinnerTime() { + return this.hours == Time.dinnerTime.hours && this.minutes == Time.dinnerTime.minutes; + } } +// Need to expose the type to the global scope. +globalThis.Time = Time; ``` -Here we are using `Array` to interop with JS' [`Array`] type. +You can write an interop interface for it like so: -Within an interop type, you can declare several different types of `external` -interop members: +```dart +extension type Time._(JSObject _) implements JSObject { + external Time(int hours, int minutes); + external factory Time.onlyHours(int hours); -- Constructors. When called, these members construct a new JS object whose - constructor is defined by the name of the extension type using `new`. For - example, calling `Array()` in Dart will generate a JS invocation that looks - like `new Array()`. Similarly, calling `Array.withLength(10)` will generate a - JS invocation that looks like `new Array(10)`. Note that the JS invocations of - all `external` interop constructors follow the same semantics, regardless of - whether they're given a name in Dart or if they are a factory. + external static Time dinnerTime; + external static Time getTimeDifference(Time t1, Time t2); -- `static` members. Like constructors, these members use the name of the - extension type to generate the JS code. For example, calling `Array.isArray()` - will generate a JS invocation that looks like `Array.isArray()`. Like - top-levels, you can declare `static` methods, getters, setters, and fields. + external int hours; + external int minutes; + external bool isDinnerTime(); -- Instance members. Like with other Dart types, these members require an + bool isMidnight() => hours == 0 && minutes == 0; +} +``` + +Within an interop type, you can declare several different types of +`external` interop members: + +- **Constructors**. When called, constructors with only positional parameters + construct a new JS object whose constructor is defined by the name of the + extension type using `new`. For example, calling `Time(0, 0)` in Dart will + generate a JS invocation that looks like `new Time(0, 0)`. Similarly, calling + `Time.onlyHours(0)` will generate a JS invocation that looks like + `new Time(0)`. Note that the JS invocations of the two constructors follow the + same semantics, regardless of whether they're given a name in Dart or if they + are a factory. + + - **Object literal constructors**. It is useful sometimes to create a JS + [object literal] that simply contains a number of properties and their + values. In order to do this, you declare a constructor with only named + parameters, as you're creating an object that is defined by the names of + its properties: + + ```dart + import ‘dart:js_interop’; + + extension type Options._(JSObject o) { + external Options({int a, int b}); + external int get a; + external int get b; + } + ``` + + A call to `Options(a: 0, b: 1)` will result in creating the JS object + `{a: 0, b: 1}`. The object is defined by the invocation, so calling + `Options(a: 0)` would result in `{a: 0}`. You can get or set the properties + of the resulting object through `external` instance members. + + {{site.alert.warn}} + There's a bug that currently requires object literal constructors to have an + [`@JS`](#js) annotation on the library, empty or otherwise. See [#54801] for + more details. + {{site.alert.end}} + +- **`static` members**. Like constructors, these members use the name of the + extension type to generate the JS code. For example, calling + `Time.getTimeDifference(t1, t2)` will generate a JS invocation that looks like + `Time.getTimeDifference(t1, t2)`. Similarly, calling `Time.dinnerTime` will + result in a JS invocation that looks like `Time.dinnerTime`. Like top-levels, + you can declare `static` methods, getters, setters, and fields. + +- **Instance members**. Like with other Dart types, these members require an instance in order to be used. These members get, set, or invoke properties on the instance. For example: ```dart - final array = Array(); - array.length = 10; - final length = array.length; - for (final i = 0; i <= 10; i++) { - array[i] = i.toJS; - assert(array[i] == array.at(i)); - } + final time = Time(0, 0); + print(time.isDinnerTime()); // false + final dinnerTime = Time.dinnerTime; + time.hours = dinnerTime.hours; + time.minutes = dinnerTime.minutes; + print(time.isDinnerTime()); // true ``` - The call to `array.length` gets the value of the `length` property of `array`. - The call to `array.length = 10` sets the value of the `length` property. The - call to `array.at(i)` calls the function in the `at` property of `array` with - the given arguments and returns the value of that call. Like top-levels and - `static` members, you can declare instance methods, getters, setters, and - fields. - -- Operators. There are only two `external` interop operators allowed in interop - types: `[]` and `[]=`. These are instance members that match the semantics of - JS' [property accessors]. In the above example, `array[i]` gets the value in - the `i`th slot of `array`, and `array[i]` sets the value in that slot to - `i.toJS`. We expose other JS operators through [utility functions] in - `dart:js_interop`. - -In the [JS types] section, you'll see that there exists a predefined JS type for -`Array` called `JSArray`. Here, we're writing another interop type for the same -JS type and just *viewing* it differently, so there is no conflict. If we -wanted, we could have made the representation type `JSArray`: - -```dart -extension type Array._(JSArray _) implements JSArray {} -``` - -Lastly, like any other extension type, you're allowed to declare any -non-`external` members in the interop type. `isZeroLength` is one such example. - -#### Object literal constructors + The call to `dinnerTime.hours` gets the value of the `hours` property of + `dinnerTime`. Similarly, the call to `time.minutes=` sets the value of the + `minutes` property of time. The call to `time.isDinnerTime()` calls the + function in the `isDinnerTime` property of `time` and returns the value. + Like top-levels and `static` members, you can declare instance methods, + getters, setters, and fields. -It is useful sometimes to create a JS [object literal] that simply contains a -a number of properties and their values. In order to do this, we allow you to -use a constructor with only named arguments: +- **Operators**. There are only two `external` interop operators allowed in + interop types: `[]` and `[]=`. These are instance members that match the + semantics of JS' [property accessors]. For example, you can declare them like: -```dart -import ‘dart:js_interop’; + ```dart + extension type Array(JSArray _) implements JSArray { + external JSNumber operator [](int index); + external void operator []=(int index, JSNumber value); + } + ``` -extension type Options._(JSObject o) { - external Options({int a, int b}); - external int get a; - external int get b; -} -``` + Calling `array[i]` gets the value in the `i`th slot of `array`, and + `array[i]=` sets the value in that slot to `i.toJS`. Other JS operators are + exposed through [utility functions] in `dart:js_interop`. -A call to `Options(a: 0, b: 1)` will result in a JS invocation of -`{a: 0, b: 1}`. You can get or set these values through `external` instance -members. +Lastly, like any other extension type, you're allowed to declare any +[non-`external` members] in the interop type. `isMidnight` is one such example. ### Extension members on interop types -You can also write `external` members in extensions of interop types. For +You can also write `external` members in [extensions] of interop types. For example: ```dart @@ -188,8 +261,8 @@ These extensions are useful for when an interop type doesn't expose the ### Parameters `external` interop methods can only contain positional and optional arguments. -The one exception is object literal constructors, where they can contain *only* -named arguments. +This is because JS members only take positional arguments. The one exception is +object literal constructors, where they can contain only named arguments. Unlike with non-`external` methods, optional arguments do not get replaced with their default value, but are instead omitted. For example: @@ -199,17 +272,20 @@ external int push(JSAny? any, [JSAny? any2]); ``` Calling `array.push(0.toJS)` in Dart will result in a JS invocation of -`array.push(0.toJS)` and *not* `array.push(0.toJS, null)`. If you declare a -parameter with an explicit default value, you will get a warning that the value -will be ignored. +`array.push(0.toJS)` and *not* `array.push(0.toJS, null)`. This allows users to +not have to write multiple interop members for the same JS API to avoid passing +in `null`s. If you declare a parameter with an explicit default value, you will +get a warning that the value will be ignored. ## `@JS()` It is sometimes useful to refer to a JS property with a different name than the one written. For example, if you want to write two `external` APIs that point to the same JS property, you’d need to write a different name for at least one of -them. In order to do this, you can use the [`@JS()`] annotation with a constant -string value. For example: +them. Similarly, if you want to define multiple interop types that refer to the +same JS interface, you need to rename at least one of them. Another example is +if the JS name is unusable in Dart e.g. `$a`. In order to do this, you can use +the [`@JS()`] annotation with a constant string value. For example: ```dart extension type Array._(JSArray _) implements JSArray { @@ -237,7 +313,8 @@ calling `JSDate.now()` will result in a JS invocation of `Date.now()`. Furthermore, you can namespace an entire library, which will add a prefix to all interop top-level members, interop types, and `static` interop members within -those types: +those types. This is useful if you want to avoid adding too many members to the +global JS scope. ```dart @JS('library1') @@ -260,9 +337,9 @@ calling `JSType()` will result in a JS invocation of `new library1.JSType()`, and calling `JSType.staticMember` will result in a JS invocation of `library1.JSType.staticMember`. -Unlike interop members and interop types, we only ever add a library name in the -JS invocation if you provide a non-empty value in the `@JS()` annotation on the -library. +Unlike interop members and interop types, Dart only ever adds a library name in +the JS invocation if you provide a non-empty value in the `@JS()` annotation on +the library. It does not use the Dart name of the library as the default. ```dart library interop_library; @@ -273,10 +350,10 @@ import 'dart:js_interop'; external void method(); ``` -Calling `method()` will result in a JS invocation of `method()` and *not* +Calling `method()` will result in a JS invocation of `method()` and not `interop_library.method()`. -You can also write multiple namespaces delimited by a '.' for libraries, +You can also write multiple namespaces delimited by a `.` for libraries, top-level members, and interop types: ```dart @@ -298,7 +375,7 @@ Calling `method()` will result in a JS invocation of `library1.library2.library3.method()`, calling `JSType()` will result in a JS invocation of `new library1.library2.library3.JSType()`, and so forth. -You can't use `@JS()` annotations with '.' in the value on interop type members +You can't use `@JS()` annotations with `.` in the value on interop type members or extension members of interop types, however. If there is no value provided to `@JS()` or the value is empty, no renaming will @@ -318,12 +395,14 @@ Utility functions include: - [`globalContext`], which represents the global namespace that the compilers use to generate JS invocations. -- [Helpers to type-check JS values] +- [Helpers to inspect the type of JS values] - JS operators -- [`dartify`] and [`jsify`], which type-check and auto-convert certain JS values - to Dart values and vice versa. +- [`dartify`] and [`jsify`], which check the type of certain JS values and + convert them to Dart values and vice versa. Prefer using the specific + conversion when you know the type of the JS value. +- [`importModule`] to import modules dynamically as `JSObject`s. -and more. More utilities might be added to this library in the future. +More utilities might be added to this library in the future. [`dart:js_interop_unsafe`] contains members that allow you to look up properties dynamically. For example: @@ -347,22 +426,29 @@ TODO: How do you link to a subsection of another section? {% endcomment %} [global JS scope]: https://developer.mozilla.org/en-US/docs/Glossary/Global_scope -[`external`]: https://dart.dev/language/keywords -["JS types"]: /interop/js-interop/js-types -[extension types]: / +[conversion functions]: /interop/js-interop/js-types.md#conversions +[declared with a primitive]: /interop/js-interop/js-types.md#restrictions +["JS type"]: /interop/js-interop/js-types +[extension type]: /language/extension-types [`Window`]: https://developer.mozilla.org/en-US/docs/Web/API/Window +[the extension types documentation]: /language/extension-types#type-considerations +[check the type of the JS value through interop]: /interop/js-interop/js-types.md#compatibility-type-checks-and-casts +[representation type]: /language/extension-types#declaration +[implement]: /language/extension-types#implements +[`package:web`]: / +[`external`]: /language/functions#external [restrictions]: /interop/js-interop/js-types.md#restrictions -[`Date`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date -[property accessors]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#bracket_notation -[utility functions]: https://api.dart.dev/dev/dart-js_interop/JSAnyOperatorExtension.html -[JS types]: /interop/js-interop/js-types -[`JSArray`]: https://api.dart.dev/dev/dart-js_interop/JSArray-extension-type.html [object literal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer -[`@JS()`]: https://api.dart.dev/dev/dart-js_interop/JS-class.html -[`dart:js_interop`]: https://api.dart.dev/dev/dart-js_interop -[`globalContext`]: https://api.dart.dev/dev/dart-js_interop/globalContext.html -[Helpers to type-check JS values]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension.html -[`dartify`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/dartify.html -[`jsify`]: https://api.dart.dev/dev/dart-js_interop/NullableObjectUtilExtension.html -[`dart:js_interop_unsafe`]: https://api.dart.dev/dev/dart-js_interop_unsafe - +[#54801]: https://github.com/dart-lang/sdk/issues/54801 +[property accessors]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#bracket_notation +[utility functions]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/JSAnyOperatorExtension.html +[non-`external` members]: /language/extension-types#members +[extensions]: /language/extension-methods +[`@JS()`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/JS-class.html +[`dart:js_interop`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop +[`globalContext`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/globalContext.html +[Helpers to inspect the type of JS values]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/JSAnyUtilityExtension.html +[`dartify`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/JSAnyUtilityExtension/dartify.html +[`jsify`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/NullableObjectUtilExtension.html +[`importModule`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/importModule.html +[`dart:js_interop_unsafe`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop_unsafe From 1b3ca1c1dd57a2406ccaea0ff06cdd44028b9e76 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 12 Feb 2024 14:21:46 -0800 Subject: [PATCH 16/29] Update index, overview, and mocking tutorial Also addresses some review comments and removes old pages that are irrelevant now. --- src/_data/side-nav.yml | 2 + src/content/interop/js-interop/dom.md | 34 ---- src/content/interop/js-interop/index.md | 135 +++------------ src/content/interop/js-interop/js-app.md | 34 ---- src/content/interop/js-interop/js-types.md | 34 +++- src/content/interop/js-interop/js-util.md | 50 ------ src/content/interop/js-interop/mock.md | 120 +++++++++++++ src/content/interop/js-interop/reference.md | 162 ------------------ .../interop/js-interop/test-and-mock.md | 41 ----- src/content/interop/js-interop/tutorials.md | 27 ++- src/content/interop/js-interop/usage.md | 18 +- 11 files changed, 193 insertions(+), 464 deletions(-) delete mode 100644 src/content/interop/js-interop/dom.md delete mode 100644 src/content/interop/js-interop/js-app.md delete mode 100644 src/content/interop/js-interop/js-util.md create mode 100644 src/content/interop/js-interop/mock.md delete mode 100644 src/content/interop/js-interop/reference.md delete mode 100644 src/content/interop/js-interop/test-and-mock.md diff --git a/src/_data/side-nav.yml b/src/_data/side-nav.yml index 74cddbbd66..adf6c8c815 100644 --- a/src/_data/side-nav.yml +++ b/src/_data/side-nav.yml @@ -253,6 +253,8 @@ - title: Overview permalink: /interop/js-interop match-page-url-exactly: true + - title: Usage + permalink: /interop/js-interop/usage - title: JS types permalink: /interop/js-interop/js-types - title: Tutorials 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/index.md b/src/content/interop/js-interop/index.md index ec95e27d2e..079ae2d2cd 100644 --- a/src/content/interop/js-interop/index.md +++ b/src/content/interop/js-interop/index.md @@ -5,125 +5,40 @@ description: Integrate JavaScript code into your Dart web app. --- The [Dart web platform](/overview#web-platform) supports communication with -JavaScript apps and libraries, and browser DOM APIs, using the `js_interop` -package, also known as `dart:js_interop`. -Web developers can benefit from using external JS libraries in their Dart code, without -having to rewrite anything in Dart. +JavaScript apps and libraries, as well as browser APIs, using `dart:js_interop`. -## Static interop +Web developers can benefit from using external JS libraries in their Dart code, +without having to rewrite anything in Dart. -The principal model of JS interop in Dart is **static interop**. -Static interop means interop that requires staticly typing external members, -rather than allowing dynamic invocations. -This enables [features](#features) like better performance, type soundness, and more. +## Why use JS interop? -Performant static interop model is made possible by **[inline classes][]**. -Inline classes are a special class type that wrap an existing type into a new static type, -without the overhead of a traditional class, enabling zero cost wrapping. +{% comment %} +TODO: Should we have a paragraph here explaining the benefits of using interop? +{% endcomment %} -### Why static interop? +## Overview -Static interop represents the desire to decouple Dart's "core" from the platform -where it's embedded -(*maybe definition of "embedded" or examples here, like the browser, Flutter, etc.?*). +For information on how to write and use JavaScript interop: + * [Usage reference] + * [JS types reference] -Our [previous iterations of JS interop][] provided the capabillity -to access the embedder, but were not primed to handle that decoupling. -Too much of the process was entwined into the Dart side, -like writing all the bindings to the browser -(*not sure if I understood that note correctly*). -They also had limitations around using the DOM, -typing (which is a cornerstone of Dart), -and expanding to new interop types -(*this is me trying to refer to wasm without actually saying, idk if "interop types" is the right term*). - -Static interop addresses all of the shortcomings of Dart's previous JS interop solutions. -Check out the [Features](#features) section for a summary of improvements. - -[previous iterations of JS interop]: /interop/js-interop/past-js-interop - -## Usage - -The following example implements inline class-based, static, JS interop in Dart, -using its key members and syntaxes: - -```dart -@JS() -library; -// library names aren't cool anymore... - -import 'dart:js_interop'; - -inline class SomeThing { - @JS('JSON.stringify') - external String stringify(Object obj); -} -// idk where `inline` fits into this but basically just show the key components as briefly as possible -``` - -// *Below the example, go through the steps line by line **and why**:* - -1. Append the `@JS` annotation to a `library` directive *so that....* - -2. Import `dart:js_interop`, which provides most of the tools needed for static interop, -including annotations, the representation type for inline classes, *... etc.* - -3. Create an `inline` class because it *allows you to...*, -which is the core of static interop. - -4. Use the `@JS` annotation to call a main-spaced function, or top level external member, from your JS app - - // *(or, call the external member from outside an inline class if you don't care about types?* -*idk if that's worth mentioning)* - -5. Use the `external` keyword to.... *(allow external and non-external members to communicate? idk)* - -6. *show external and non-external members interacting if that's not already shown?* - -## Features - -Inline class-based static interop enable the following features: - -
- -| | | -|------|-------| -| **Rename external members** | Complex JS member names can be re-written and represented in Dart | -| **Interact external and non-external members** | Process external calls, but "keep it on the type itself" (*idk what that means?*), without writing a separate function | -| **Zero cost wrapping** | Inline classes require virtually no overhead for re-typing external members as static types. (*idk*) | -| **Static error checking** | Since external members must be statically typed, static error checking on types and other parts of the interop is possible (partially sound, more work coming) | -| **Interop with DOM types** | *You can interop with DOM types because...?* | -| **Compatibility with Wasm** | Disallowing generative constructors from the model makes `js_interop` compatible with Wasm | - -{:.table .table-striped .nowrap} -
- - -## Up next - -For a complete summary of static JS interop concepts, members, and syntaxes: -* The [static interop reference][]. +For information on interacting with web APIs: + * [`package:web` and migration] For tutorials and help: -* [How to interop with DOM APIs][] -* [How to interop with JavaScript libraries and apps][] -* [How to test and mock JavaScript interop in Dart][] - -For additonal types of documentation on `js_interop`: - * [pub.dev site page][] - * [API reference][] + * [How to mock JavaScript interop objects] -For information on other iterations of JS interop in Dart: - * [`dart:js_util`][] - * [Past JS interop][] +For information on previous JavaScript interop libraries: + * [Past JS interop] +For additional documentation on JavaScript interop: + * [`dart:js_interop` API reference] + * [`dart:js_interop_unsafe` API reference] -[inline classes]: / -[static interop reference]: /interop/js-interop/reference -[How to interop with DOM APIs]: /interop/js-interop/dom -[How to interop with JavaScript libraries and apps]: /interop/js-interop/js-app -[How to test and mock JavaScript interop in Dart]: /interop/js-interop/test-and-mock -[pub.dev site page]: / -[API reference]: / -[`dart:js_util`]: /interop/js-interop/js-util +[Usage reference]: /interop/js-interop/usage +[JS types reference]: /interop/js-interop/js-types +[`package:web` and migration]: / +[How to mock JavaScript interop objects]: /interop/js-interop/mock [Past JS interop]: /interop/js-interop/past-js-interop +[`dart:js_interop` API reference]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop +[`dart:js_interop_unsafe` API reference]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop_unsafe diff --git a/src/content/interop/js-interop/js-app.md b/src/content/interop/js-interop/js-app.md deleted file mode 100644 index bb7ce58fe9..0000000000 --- a/src/content/interop/js-interop/js-app.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: How to interop with JavaScript libraries and apps -description: placeholder description ---- - -// *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/js-types.md b/src/content/interop/js-interop/js-types.md index 349481ac00..1914fd42d1 100644 --- a/src/content/interop/js-interop/js-types.md +++ b/src/content/interop/js-interop/js-types.md @@ -78,20 +78,23 @@ Generally, the conversion table looks like the following: | `JSExportedFunction` | `Function` | | `JSArray` | `List` | | `JSPromise` | `Future` | -| Typed arrays like `JSUint8Array` | `dart:typed_data` | +| Typed arrays like `JSUint8Array` | Typed arrays from `dart:typed_data` | | `JSBoxedDartObject` | Opaque Dart value | {:.table .table-striped} :::warning -Conversions might have different costs depending on the compiler, so prefer to -only convert values if you need to. Conversions also may or may not produce a -new value. This doesn’t matter for immutable values like numbers, but does -matter for types like `List`. A conversion to a `JSArray` may produce a new -value by copying or may not, so do not rely on modifications to the `JSArray` to -affect the `List`. Typed array conversions have a similar limitation. Look up -the specific conversion function for more details. +There can be inconsistencies in both performance and semantics for conversions +when compiling to JavaScript vs Wasm. Conversions might have different costs +depending on the compiler, so prefer to only convert values if you need to. +Conversions also may or may not produce a new value. This doesn’t matter for +immutable values like numbers, but does matter for types like `List`. Depending +on the implementation, a conversion to `JSArray` may return a reference, a +proxy, or a clone of the original list. To avoid this, do not rely on any +relation between the `List` and `JSArray` and only rely on their contents being +the same. Typed array conversions have a similar limitation. Look up the +specific conversion function for more details. ::: ## Requirements on `external` declarations and `Function.toJS` @@ -179,6 +182,20 @@ void f(JSAny a) { } ``` +From Dart 3.4 onwards, you can use the [`isA`] helper function to check whether +a value is any interop type: + +```dart tag=good +void f(JSAny a) { + if (a.isA()) {} // `typeofEquals('string')` + if (a.isA()) {} // `instanceOfString('Array')` + if (a.isA()) {} // `instanceOfString('CustomInteropType')` +} +``` + +Depending on the type parameter, it'll transform the call into the appropriate +type-check for that type. + {% comment %} TODO: Add a link to and an example using `isA` once it's in a dev release. Users should prefer that method if it's available. @@ -229,5 +246,6 @@ TODO: add links (with stable) when ready: [`dart:js_interop` API docs]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/dart-js_interop-library.html#extension-types [`typeofEquals`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JSAnyUtilityExtension/typeofEquals.html [`instanceOfString`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JSAnyUtilityExtension/instanceOfString.html +[`isA`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JSAnyUtilityExtension/isA.html [#4841]: https://github.com/dart-lang/linter/issues/4841 [#54025]: https://github.com/dart-lang/sdk/issues/54025 \ No newline at end of file diff --git a/src/content/interop/js-interop/js-util.md b/src/content/interop/js-interop/js-util.md deleted file mode 100644 index fdf4891c4f..0000000000 --- a/src/content/interop/js-interop/js-util.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: The dart:js_util library -short-title: dart:js_util -description: Overview of the utility library for JS interop ---- - -[**`dart:js_util` API docs**][] - -**We will continue to support `dart:js_util` alongside static interop.** - -The `dart:js_util` library, or just `js_util`, is a low-level utility library -for performing JS interop. Because `js_util` is so low-level, -it could potentially be able to provide more flexibility than static interop, -for example, in rare edge cases where `js_interop` is not expressive enough. -This is an exception to the rule; -**please always use static, inline-class based interop by default**. - -The `js_util` library is supported by the JS and `dart2wasm` backends. -It is slower and less ergonomic than `js_interop`. - -The best example of the difference in ergonomics between `js_interop` and -`js_util` is calling equivalent [`external`][] methods. -Each interop solution generates JavaScript code upon calling an `external` method: - -```dart -// js_util external call: -... - -// javascript generated: -... -``` - -The JavaScript code `external` generates for `js_util` is very verbose, -compared to the efficient, compact generation for `js_interop`: - -```dart -// js_interop external call: -... - -// javascript generated: -... -``` - -For optimal JS interop, only use `js_util` over static interop if you encounter -a use case that `js_interop` cannot address -(and please [let us know][] if you encounter such a use case). - -[**`dart:js_util` API docs**]: {{site.dart-api}}/dart-js_util/dart-js_util-library.html -[`external`]: /interop/js-interop/reference#external -[let us know]: https://github.com/dart-lang/sdk/issues/new?assignees=&labels=web-js-interop&template=1_issue_template.md&title=Create+an+issue \ No newline at end of file diff --git a/src/content/interop/js-interop/mock.md b/src/content/interop/js-interop/mock.md new file mode 100644 index 0000000000..226ab44aed --- /dev/null +++ b/src/content/interop/js-interop/mock.md @@ -0,0 +1,120 @@ +--- +title: How to mock JavaScript interop objects. +--- + +{{site.why.learn}} + * In this tutorial, you'll learn how to mock JS objects so that you can test + interop instance members without having to use a real implementation. +{{site.why.end}} + +## Background and motivation + +Mocking classes in Dart is usually done through overriding instance members. +However, since [extension types] are used to declare interop types, all +extension type members are dispatched statically and therefore overriding can't +be used. This [limitation is true for extension members] as well, and therefore +instance extension type or extension members can't be mocked. + +While this applies to any non-`external` extension type member, `external` +interop members are special as they invoke members on a JS value. For example: + +```dart +extension type Date(JSObject _) implements JSObject { + external int getDay(); +} +``` + +As discussed in the [Usage] section, calling `getDay()` will result in calling +`getDay()` on the JS object. Therefore, by using a different `JSObject`, a +different *implementation* of `getDay` can be called. + +In order to do this, there should be some mechanism of creating a JS object that +has a property `getDay` which when called, calls the Dart function. A simple way +is to create a JS object and set the property `getDay` to a converted callback +e.g. + +```dart +final date = Date(JSObject()); +date['getDay'] = (() => 0).toJS; +``` + +While this works, this is prone to error and doesn't scale well when you are +using many interop members. It also doesn't handle getters or setters properly. +Instead, you should use a combination of [`createJSInteropWrapper`] and +[`@JSExport`] to declare a type that provides an implementation for all the +`external` instance members. + +## Mocking example + +```dart +import 'dart:js_interop'; + +import 'package:expect/minitest.dart'; + +// The Dart class must have `@JSExport` on it or one of its instance members. +@JSExport() +class FakeCounter { + int value = 0; + @JSExport('increment') + void renamedIncrement() { + value++; + } + void decrement() { + value--; + } +} + +extension type Counter(JSObject _) implements JSObject { + external int value; + external void increment(); + void decrement() { + value -= 2; + } +} + +void main() { + var fakeCounter = FakeCounter(); + // Returns a JS object whose properties call the relevant instance members in + // `fakeCounter`. + var counter = createJSInteropWrapper(fakeCounter) as Counter; + expect(counter.value, 0); + // Renamed member in the fake gets called. + counter.increment(); + expect(counter.value, 1); + expect(fakeCounter.value, 1); // Dart object gets modified + fakeCounter.value = 0; + expect(counter.value, 0); // Changes in Dart object affect the exported object + counter.decrement(); + // Because `Counter.decrement` is non-`external`, we never called + // `FakeCounter.decrement`. + expect(counter.value, -2); +} +``` + +## [`@JSExport`] and [`createJSInteropWrapper`] + +`@JSExport` allows you to declare a class that can be used in +`createJSInteropWrapper`. `createJSInteropWrapper` will create an object literal +that maps each of the class' instance member names (or renames) to a JS callback +that triggers the instance's class member when called. In the above example, +getting and setting `counter.value` gets and sets `fakeCounter.value`. + +You can specify only some members of a class to be exported by omitting the +annotation from the class and instead only annotate the specific members. You +can see more specifics on more specialized exporting (including inheritance) in +the documentation of [`@JSExport`]. + +Note that this mechanism isn't specific to testing only. You can use this to +provide a JS interface for an arbitrary Dart object, allowing you to essentially +*export* Dart objects to JS. + +{% comment %} +TODO: Add more documentation in the dartdoc for `@JSExport`. +TODO: Should we add a section on general testing? We can't really mock +non-instance members unless the user explicitly replaces the real API in JS. +{% endcomment %} + +[Usage]: /interop/js-interop/usage +[`createJSInteropWrapper`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/createJSInteropWrapper.html +[`@JSExport`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/JSExport-class.html +[limitation is true for extension members]: https://github.com/dart-lang/mockito/blob/master/FAQ.md#how-do-i-mock-an-extension-method \ No newline at end of file diff --git a/src/content/interop/js-interop/reference.md b/src/content/interop/js-interop/reference.md deleted file mode 100644 index 6d40d1c903..0000000000 --- a/src/content/interop/js-interop/reference.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -title: JS interop reference -description: A glossary of key terms and concepts in JS interop. ---- - -## Members - -// *I'm not sure if members, syntax, etc are interchangeable or different categories,* -*so rearrange / add more headings as needed* - -### Inline classes - -[Inline classes][] are a language feature independent of JS interop and static interop. -They are a special kind of class that wraps an existing type into a new static type, without the overhead of a traditional class. - -In the context of JS interop and static interop, -inline classes are an entirely static type representation of a JavaScript object. -They consume no additional memory, as opposed to using the representation type directly. -This zero-cost wrapper capability makes static interop possible. - -Inline classes can call *non-static* external fields, getters, setters, -and methods, as well as *static* external getters, setters, and methods. - -// *You should always wrap external types with inline classes?* - - -#### Usage: - -```dart -// - -// - -// -``` - -[Inline classes]: / - -// *TO DO: link to inline class page* - -### `external` - -The [`external` keyword][] calls a method from an external source. -It is not exclusive to JS interop or static interop. -To specify external *JavaScript* members, -you need to use the `@JS` annotation with `external`. - -#### Usage: - -```dart -// - -// - -// -``` - -[`external` keyword]: https://spec.dart.dev/DartLangSpecDraft.pdf#External%20Functions - -### Top-level external members - -Communication with top-level external members is a core feature of static interop, -though not exclusive to it. We have improved it to be more sound for static interop. - -You can call a main-spaced function from a JavaScript app -without concern for type using the [`@JS` annotation](#js). - -// *Why/when will users want to use this/ not be concerned with type?* - -#### Usage: - -```dart -// -@JS(‘...’) - -// - -// -``` - -### External factories - -External factories are not exclusive to static interop, -but are an importatnt part of the interface. -You can use a factory to instantiate a JavaScript object. - -// *You will want to do this because/when...* - -#### Usage: - -```dart -// - -// - -// -``` - -### Extensions with external members - -// *Does this go under external, or extensions, or...?* - -Extensions extend types. When you have an external class that you don't own, -and you don't want to add members to it, you can simply extend it. -Just write an extension and add members. - -// *More detail on when/why you might want to use this maybe?* - -#### Usage: - -```dart -// - -// - -// -``` - -## Annotations - -### `@JS` - -The `@JS` annotation belongs to the `dart:js_interop` library. -It specifies that you're using JavaScript interop, -as opposed to any other kind of interop. -It provides the bindings between a JavaScript API and your Dart API. -The use of `@JS` interop, combined with [inline classes](#inline-classes), -is what makes static interop possible. - -#### Usage: - -```dart -// append to library directive - -// call main-spaced JS function when you're not concerned with type (if that's a feature? idk) - -// specify `external` declaration is JS -``` - -The `@JS` annotation was also a member of one of Dart's past JS interop solutions, -`package:js`. You can read more about that on the [Past JS interop][] page. - -[Past JS interop]: /interop/js-interop/past-js-interop - -### `@staticInterop` - -The `@staticInterop` annotation of `package:js` was an intermeditate static interop -solution before developing the complete, inline-class based, static interop model. -Inline classes and the `@JS` annotations replace the functionality of `@staticInterop`. - -#### Usage: - -```dart -// using @staticInterop -... - -// same example using @JS + inline classes -... -``` - - - diff --git a/src/content/interop/js-interop/test-and-mock.md b/src/content/interop/js-interop/test-and-mock.md deleted file mode 100644 index 973f241b4d..0000000000 --- a/src/content/interop/js-interop/test-and-mock.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: How to test and mock JavaScript interop in Dart -description: placeholder description ---- - -// *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](#). - -### Topic/concept 1 - -### Topic/concept..n - -## Export steps - -// *make sure to link to this content: https://pub.dev/packages/js/versions/0.6.6#jsexport-and-js_utilcreatedartexport* - -### Actionable step 1 - -Step subsections should be actionalble, like "Build...", "Retrieve...", "Configure...", etc. - -### Actionable step...n - -## Test/mock steps - -// *make sure to link to this content: https://pub.dev/packages/js/versions/0.6.6#js_utilcreatestaticinteropmock - -### Actionable step 1 - -... - -### 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/tutorials.md b/src/content/interop/js-interop/tutorials.md index ef5156a7e8..86eee1cfc6 100644 --- a/src/content/interop/js-interop/tutorials.md +++ b/src/content/interop/js-interop/tutorials.md @@ -5,25 +5,18 @@ description: Tutorials for common JavaScript interop use cases in Dart. ## Tutorials -### [How to interop with DOM APIs][] - -The browser exposes a number of DOM APIs accessible to Dart through the `dart:js_interop` library. -DOM APIs are the most common set of APIs Dart users will want to expose or interact with while using JS interop. This tutorial shows how to access these APIs and some of the common reasons you may want to use them. - -### [How to interop with JavaScript libraries and apps][] - -Have you found a JS library or app that does exactly what you want to do in your Dart code, but it's way too complex to rewrite in Dart? - -This tutorial will show you how to incorporate methods from an existing JS library. -It will also discuss build and serving options. - ### [How to test and mock JavaScript interop in Dart][] -Exporting Dart objects with `@JSExport` creates a mock of the object at the JS level, which essentially allows you to test your Dart JS interop code. +Exporting Dart objects with [`@JSExport`] creates a mock of the object at the JS +level, which essentially allows you to test your Dart JS interop code. This tutorial will walk through both cases: simply exporting Dart objects to JS, -and then using that same functionality to test JS interop code. +and then using that same functionality to test JS interop code. + +{% comment %} +TODO: add a section on how to bundle a JS and Dart app for interop +TODO: maybe add a section on conversions +{% endcomment %} -[How to interop with DOM APIs]: /interop/js-interop/dom -[How to interop with JavaScript libraries and apps]: /interop/js-interop/js-app -[How to test and mock JavaScript interop in Dart]: /interop/js-interop/test-and-mock \ No newline at end of file +[How to test and mock JavaScript interop in Dart]: /interop/js-interop/test-and-mock +[`@JSExport`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/JSExport-class.html \ No newline at end of file diff --git a/src/content/interop/js-interop/usage.md b/src/content/interop/js-interop/usage.md index ba58ff6c23..ecb07672fe 100644 --- a/src/content/interop/js-interop/usage.md +++ b/src/content/interop/js-interop/usage.md @@ -95,7 +95,8 @@ the same name. You can declare top-level interop getters, setters, methods, and fields, which is the same as a getter and setter pair. Top-level interop members must be declared with a [`@JS()`](#js) annotation to -distinguish them from other `external` top-level members. +distinguish them from other `external` top-level members, like those that can be +written using `dart:ffi`. ### Interop type members @@ -234,8 +235,8 @@ Within an interop type, you can declare several different types of ``` Calling `array[i]` gets the value in the `i`th slot of `array`, and - `array[i]=` sets the value in that slot to `i.toJS`. Other JS operators are - exposed through [utility functions] in `dart:js_interop`. + `array[i] = i.toJS` sets the value in that slot to `i.toJS`. Other JS + operators are exposed through [utility functions] in `dart:js_interop`. Lastly, like any other extension type, you're allowed to declare any [non-`external` members] in the interop type. `isMidnight` is one such example. @@ -252,11 +253,12 @@ extension on Array { ``` The semantics of calling `push` are identical to what it would have been if it -was in the definition of `Array` instead. The only `external` members you can -write in such an extension are instance members and the allowed operators. Like -with interop types, you can write any non-`external` members in the extension. -These extensions are useful for when an interop type doesn't expose the -`external` member you need and you don't want to create a new interop type. +was in the definition of `Array` instead. Extensions can have `external` +instance members and operators, but cannot have `external` `static` members or +constructors. Like with interop types, you can write any non-`external` members +in the extension. These extensions are useful for when an interop type doesn't +expose the `external` member you need and you don't want to create a new interop +type. ### Parameters From ab0fc8d212a938cca768adaa6acae32720f1afdb Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 12 Feb 2024 14:25:10 -0800 Subject: [PATCH 17/29] Add back dom.md Needed for the site index for now. --- src/content/interop/js-interop/dom.md | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/content/interop/js-interop/dom.md diff --git a/src/content/interop/js-interop/dom.md b/src/content/interop/js-interop/dom.md new file mode 100644 index 0000000000..5cdb7b30a3 --- /dev/null +++ b/src/content/interop/js-interop/dom.md @@ -0,0 +1,34 @@ +--- +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 From 95a885ad4bf8027e82a76b48f1f43b15b3e574ad Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 12 Feb 2024 14:37:47 -0800 Subject: [PATCH 18/29] Fix links in sections --- .../interop/js-interop/past-js-interop.md | 4 ++-- src/content/interop/js-interop/tutorials.md | 12 ++++------ src/content/interop/js-interop/usage.md | 24 +++++++++---------- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/content/interop/js-interop/past-js-interop.md b/src/content/interop/js-interop/past-js-interop.md index f21f4eda79..f13fff04ab 100644 --- a/src/content/interop/js-interop/past-js-interop.md +++ b/src/content/interop/js-interop/past-js-interop.md @@ -13,7 +13,7 @@ future. Therefore, prefer using [`dart:js_interop`] going forwards and migrate usages of old interop libraries when possible. While [`dart:html`] and other web libraries are closely related, they're covered in the [`package:web`] page. -## `dart:js` +## `dart:js` [`dart:js`] exposed a concrete [`object wrapper`] to interop with JS objects. This wrapper contained String-based methods to dynamically get, set, and call @@ -115,7 +115,7 @@ TODO: Link to `package:web` section [`package:js`]: https://pub.dev/packages/js [`JSObject`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JSObject-extension-type.html [`@JS`]: https://github.com/dart-lang/sdk/blob/main/sdk/lib/js/_js_annotations.dart#L11 -[tutorial for mocking]: /interop/js-interop/test-and-mock +[tutorial on mocking]: /interop/js-interop/mock [`@anonymous`]: https://github.com/dart-lang/sdk/blob/main/sdk/lib/js/_js_annotations.dart#L40 [`@staticInterop`]: https://github.com/dart-lang/sdk/blob/main/sdk/lib/js/_js_annotations.dart#L48 [`dart:js_util`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_util diff --git a/src/content/interop/js-interop/tutorials.md b/src/content/interop/js-interop/tutorials.md index 86eee1cfc6..693de43872 100644 --- a/src/content/interop/js-interop/tutorials.md +++ b/src/content/interop/js-interop/tutorials.md @@ -5,18 +5,14 @@ description: Tutorials for common JavaScript interop use cases in Dart. ## Tutorials -### [How to test and mock JavaScript interop in Dart][] +### [How to mock JavaScript interop in Dart][] -Exporting Dart objects with [`@JSExport`] creates a mock of the object at the JS -level, which essentially allows you to test your Dart JS interop code. - -This tutorial will walk through both cases: simply exporting Dart objects to JS, -and then using that same functionality to test JS interop code. +This tutorial will walk through how you can use Dart classes to mock the +`external` instance members of an interop type. {% comment %} TODO: add a section on how to bundle a JS and Dart app for interop TODO: maybe add a section on conversions {% endcomment %} -[How to test and mock JavaScript interop in Dart]: /interop/js-interop/test-and-mock -[`@JSExport`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/JSExport-class.html \ No newline at end of file +[How to mock JavaScript interop in Dart]: /interop/js-interop/mock \ No newline at end of file diff --git a/src/content/interop/js-interop/usage.md b/src/content/interop/js-interop/usage.md index ecb07672fe..1123bad352 100644 --- a/src/content/interop/js-interop/usage.md +++ b/src/content/interop/js-interop/usage.md @@ -423,29 +423,28 @@ more difficult to guarantee and may lead to violations. ::: {% comment %} -TODO: add links (with stable) when ready: -TODO: How do you link to a subsection of another section? -{% endcomment %} +TODO: Add links when ready -[global JS scope]: https://developer.mozilla.org/en-US/docs/Glossary/Global_scope -[conversion functions]: /interop/js-interop/js-types.md#conversions -[declared with a primitive]: /interop/js-interop/js-types.md#restrictions -["JS type"]: /interop/js-interop/js-types [extension type]: /language/extension-types -[`Window`]: https://developer.mozilla.org/en-US/docs/Web/API/Window [the extension types documentation]: /language/extension-types#type-considerations -[check the type of the JS value through interop]: /interop/js-interop/js-types.md#compatibility-type-checks-and-casts [representation type]: /language/extension-types#declaration [implement]: /language/extension-types#implements +[non-`external` members]: /language/extension-types#members +{% endcomment %} + +[global JS scope]: https://developer.mozilla.org/en-US/docs/Glossary/Global_scope +[conversion functions]: /interop/js-interop/js-types#conversions +[declared with a primitive]: /interop/js-interop/js-types#restrictions +["JS type"]: /interop/js-interop/js-types +[`Window`]: https://developer.mozilla.org/en-US/docs/Web/API/Window +[check the type of the JS value through interop]: /interop/js-interop/js-types#compatibility-type-checks-and-casts [`package:web`]: / [`external`]: /language/functions#external -[restrictions]: /interop/js-interop/js-types.md#restrictions +[restrictions]: /interop/js-interop/js-types#restrictions [object literal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer [#54801]: https://github.com/dart-lang/sdk/issues/54801 [property accessors]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#bracket_notation [utility functions]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JSAnyOperatorExtension.html -[non-`external` members]: /language/extension-types#members -[extensions]: /language/extension-methods [`@JS()`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JS-class.html [`dart:js_interop`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop [`globalContext`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/globalContext.html @@ -454,3 +453,4 @@ TODO: How do you link to a subsection of another section? [`jsify`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/NullableObjectUtilExtension.html [`importModule`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/importModule.html [`dart:js_interop_unsafe`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop_unsafe +[extensions]: /language/extension-methods From ee822fd52433390146ebba8bc3c66d286fd72519 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 12 Feb 2024 14:40:33 -0800 Subject: [PATCH 19/29] Fix links to restrictions --- src/content/interop/js-interop/usage.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/interop/js-interop/usage.md b/src/content/interop/js-interop/usage.md index 1123bad352..6e89070d87 100644 --- a/src/content/interop/js-interop/usage.md +++ b/src/content/interop/js-interop/usage.md @@ -434,13 +434,13 @@ TODO: Add links when ready [global JS scope]: https://developer.mozilla.org/en-US/docs/Glossary/Global_scope [conversion functions]: /interop/js-interop/js-types#conversions -[declared with a primitive]: /interop/js-interop/js-types#restrictions +[declared with a primitive]: /interop/js-interop/js-types#requirements-on-external-declarations-and-functiontojs ["JS type"]: /interop/js-interop/js-types [`Window`]: https://developer.mozilla.org/en-US/docs/Web/API/Window [check the type of the JS value through interop]: /interop/js-interop/js-types#compatibility-type-checks-and-casts [`package:web`]: / [`external`]: /language/functions#external -[restrictions]: /interop/js-interop/js-types#restrictions +[restrictions]: /interop/js-interop/js-types#requirements-on-external-declarations-and-functiontojs [object literal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer [#54801]: https://github.com/dart-lang/sdk/issues/54801 [property accessors]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#bracket_notation From 51ab02f47640526cbd71398b2c025ae00fcb1863 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 12 Feb 2024 14:46:30 -0800 Subject: [PATCH 20/29] Added missing hyphen in link to restrictions in JS types section --- src/content/interop/js-interop/usage.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/interop/js-interop/usage.md b/src/content/interop/js-interop/usage.md index 6e89070d87..e8261fe64d 100644 --- a/src/content/interop/js-interop/usage.md +++ b/src/content/interop/js-interop/usage.md @@ -434,13 +434,13 @@ TODO: Add links when ready [global JS scope]: https://developer.mozilla.org/en-US/docs/Glossary/Global_scope [conversion functions]: /interop/js-interop/js-types#conversions -[declared with a primitive]: /interop/js-interop/js-types#requirements-on-external-declarations-and-functiontojs +[declared with a primitive]: /interop/js-interop/js-types#requirements-on-external-declarations-and-function-tojs ["JS type"]: /interop/js-interop/js-types [`Window`]: https://developer.mozilla.org/en-US/docs/Web/API/Window [check the type of the JS value through interop]: /interop/js-interop/js-types#compatibility-type-checks-and-casts [`package:web`]: / [`external`]: /language/functions#external -[restrictions]: /interop/js-interop/js-types#requirements-on-external-declarations-and-functiontojs +[restrictions]: /interop/js-interop/js-types#requirements-on-external-declarations-and-function-tojs [object literal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer [#54801]: https://github.com/dart-lang/sdk/issues/54801 [property accessors]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#bracket_notation From 83a613f40ada12097293c20f45cdec2ee1cf6012 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 12 Feb 2024 15:59:27 -0800 Subject: [PATCH 21/29] Cleanup wording to be clearer --- src/content/interop/js-interop/js-types.md | 18 ++-- src/content/interop/js-interop/mock.md | 27 +++--- src/content/interop/js-interop/usage.md | 96 +++++++++++----------- 3 files changed, 76 insertions(+), 65 deletions(-) diff --git a/src/content/interop/js-interop/js-types.md b/src/content/interop/js-interop/js-types.md index 1914fd42d1..87f5f1da16 100644 --- a/src/content/interop/js-interop/js-types.md +++ b/src/content/interop/js-interop/js-types.md @@ -45,7 +45,7 @@ TODO (srujzs): Should we add a tree diagram instead for JS types? ## Conversions To use a value from one domain to another, you will likely want to *convert* the -value to the corresponding type of the other domain. For example, you might want +value to the corresponding type of the other domain. For example, you may want to convert a Dart `List` into a JS array of strings, which is represented by the JS type `JSArray`, so that you can pass the array to a JS interop API. @@ -75,10 +75,10 @@ Generally, the conversion table looks like the following: | JS type | Dart type | |-------------------------------------|------------------------------------------| | `JSNumber`, `JSBoolean`, `JSString` | `num`, `int`, `double`, `bool`, `String` | -| `JSExportedFunction` | `Function` | +| `JSExportedDartFunction` | `Function` | | `JSArray` | `List` | | `JSPromise` | `Future` | -| Typed arrays like `JSUint8Array` | Typed arrays from `dart:typed_data` | +| Typed arrays like `JSUint8Array` | Typed lists from `dart:typed_data` | | `JSBoxedDartObject` | Opaque Dart value | {:.table .table-striped} @@ -86,7 +86,7 @@ Generally, the conversion table looks like the following: :::warning There can be inconsistencies in both performance and semantics for conversions -when compiling to JavaScript vs Wasm. Conversions might have different costs +when compiling to JavaScript vs Wasm. Conversions may have different costs depending on the compiler, so prefer to only convert values if you need to. Conversions also may or may not produce a new value. This doesn’t matter for immutable values like numbers, but does matter for types like `List`. Depending @@ -102,8 +102,8 @@ specific conversion function for more details. In order to ensure type safety and consistency, the compiler places requirements on what types can flow into and out of JS. Passing arbitrary Dart values into JS is not allowed. Instead, the compiler requires users to use a compatible interop -type like a JS type or a primitive, which would then be implicitly converted by -the compiler. For example, these would be allowed: +type or a primitive, which would then be implicitly converted by the compiler. +For example, these would be allowed: ```dart tag=good @JS() @@ -141,7 +141,7 @@ be a compatible interop type or a primitive. If you use a Dart primitive like `String`, an implicit conversion happens in the compiler to convert that value from a JS value to a Dart value. If performance is critical and you don’t need to examine the contents of the string, then using -`JSString` instead to avoid the conversion cost might make sense like in the +`JSString` instead to avoid the conversion cost may make sense like in the second example. ## Compatibility, type checks, and casts @@ -201,8 +201,8 @@ TODO: Add a link to and an example using `isA` once it's in a dev release. Users should prefer that method if it's available. {% endcomment %} -Dart might add lints to make runtime checks with JS interop types easier to -avoid. See issue [#4841] for more details. +Dart may add lints to make runtime checks with JS interop types easier to avoid. +See issue [#4841] for more details. ## `null` vs `undefined` diff --git a/src/content/interop/js-interop/mock.md b/src/content/interop/js-interop/mock.md index 226ab44aed..9b4cc18f41 100644 --- a/src/content/interop/js-interop/mock.md +++ b/src/content/interop/js-interop/mock.md @@ -1,5 +1,5 @@ --- -title: How to mock JavaScript interop objects. +title: How to mock JavaScript interop objects --- {{site.why.learn}} @@ -16,7 +16,7 @@ be used. This [limitation is true for extension members] as well, and therefore instance extension type or extension members can't be mocked. While this applies to any non-`external` extension type member, `external` -interop members are special as they invoke members on a JS value. For example: +interop members are special as they invoke members on a JS value. ```dart extension type Date(JSObject _) implements JSObject { @@ -29,7 +29,7 @@ As discussed in the [Usage] section, calling `getDay()` will result in calling different *implementation* of `getDay` can be called. In order to do this, there should be some mechanism of creating a JS object that -has a property `getDay` which when called, calls the Dart function. A simple way +has a property `getDay` which when called, calls a Dart function. A simple way is to create a JS object and set the property `getDay` to a converted callback e.g. @@ -51,7 +51,8 @@ import 'dart:js_interop'; import 'package:expect/minitest.dart'; -// The Dart class must have `@JSExport` on it or one of its instance members. +// The Dart class must have `@JSExport` on it or at least one of its instance +// members. @JSExport() class FakeCounter { int value = 0; @@ -77,13 +78,16 @@ void main() { // Returns a JS object whose properties call the relevant instance members in // `fakeCounter`. var counter = createJSInteropWrapper(fakeCounter) as Counter; + // Calls `FakeCounter.value`. expect(counter.value, 0); - // Renamed member in the fake gets called. + // `FakeCounter.renamedIncrement` is renamed to `increment`, so it gets + // called. counter.increment(); expect(counter.value, 1); - expect(fakeCounter.value, 1); // Dart object gets modified + expect(fakeCounter.value, 1); + // Changes in the fake affect the wrapper and vice-versa. fakeCounter.value = 0; - expect(counter.value, 0); // Changes in Dart object affect the exported object + expect(counter.value, 0); counter.decrement(); // Because `Counter.decrement` is non-`external`, we never called // `FakeCounter.decrement`. @@ -96,8 +100,8 @@ void main() { `@JSExport` allows you to declare a class that can be used in `createJSInteropWrapper`. `createJSInteropWrapper` will create an object literal that maps each of the class' instance member names (or renames) to a JS callback -that triggers the instance's class member when called. In the above example, -getting and setting `counter.value` gets and sets `fakeCounter.value`. +that triggers the instance member when called. In the above example, getting and +setting `counter.value` gets and sets `fakeCounter.value`. You can specify only some members of a class to be exported by omitting the annotation from the class and instead only annotate the specific members. You @@ -106,12 +110,15 @@ the documentation of [`@JSExport`]. Note that this mechanism isn't specific to testing only. You can use this to provide a JS interface for an arbitrary Dart object, allowing you to essentially -*export* Dart objects to JS. +*export* Dart objects to JS with a predefined interface. {% comment %} TODO: Add more documentation in the dartdoc for `@JSExport`. TODO: Should we add a section on general testing? We can't really mock non-instance members unless the user explicitly replaces the real API in JS. + +TODO: Expose this once the link works: +[extension types]: /language/extension-types {% endcomment %} [Usage]: /interop/js-interop/usage diff --git a/src/content/interop/js-interop/usage.md b/src/content/interop/js-interop/usage.md index e8261fe64d..5c4fe0a613 100644 --- a/src/content/interop/js-interop/usage.md +++ b/src/content/interop/js-interop/usage.md @@ -10,35 +10,35 @@ from them using an explicit, idiomatic syntax. Typically, you access a JavaScript API by making it available somewhere within the [global JS scope]. To call and receive JS values from this API, you use [`external` interop members](#interop-members). In order to construct and -provide types for JS values, you use and declare [interop types] -(#interop-types), which also contain interop members. To pass Dart values like -`List`s or `Function` to interop members or convert from JS values to Dart -values, you will use [conversion functions] unless the interop member is -[declared with a primitive]. +provide types for JS values, you use and declare +[interop types](#interop-types), which also contain interop members. To pass +Dart values like `List`s or `Function` to interop members or convert from JS +values to Dart values, you use [conversion functions] unless the interop member +[contains a primitive type]. ## Interop types When interacting with a JS value, you need to provide a Dart type for it. You can do this by either using or declaring an interop type. Interop types are either a ["JS type"] provided by Dart or an [extension type] wrapping an interop -type. Interop types allow you to provide an interface for a JS value and lets -you declare interop APIs for its members. They are also used in the signature -of other interop APIs. +type. + +Interop types allow you to provide an interface for a JS value and lets you +declare interop APIs for its members. They are also used in the signature of +other interop APIs. ```dart extension type Window(JSObject _) implements JSObject {} ``` -`Window` here is an interop type for an arbitrary `JSObject`. There is no -runtime guarantee that `Window` is actually a JS [`Window`]. See -[the extension types documentation] for more info on the runtime semantics of -extension types. There also is no conflict with any other interop interface -that is defined for the same value. If you do want to check that `Window` is -actually a JS `Window`, you can [check the type of the JS value through -interop]. +`Window` is an interop type for an arbitrary `JSObject`. There is no [runtime +guarantee] that `Window` is actually a JS [`Window`]. There also is no conflict +with any other interop interface that is defined for the same value. If you want +to check that `Window` is actually a JS `Window`, you can +[check the type of the JS value through interop]. You can also declare your own interop type for the JS types Dart provides by -wrapping them or an interop type they implement with no conflict: +wrapping them: ```dart extension type Array._(JSArray _) implements JSArray { @@ -51,16 +51,16 @@ In most cases, you will likely declare an interop type using `JSObject` as the don't have an interop type provided by Dart. Interop types should also generally [implement] their representation type so -that they can be used where the representation type is expected, like in -[`package:web`]. +that they can be used where the representation type is expected, like in many +APIs in [`package:web`]. ## Interop members [`external`] interop members provide an idiomatic syntax for JS members. They -allow you to write a Dart type signature for any arguments and return value. -The types that can be used in the signature of these members have -[restrictions]. The JS API they correspond to is determined by a combination of -where they're declared, their name, what kind of Dart member they are, and any +allow you to write a Dart type signature for its arguments and return value. The +types that can be written in the signature of these members have [restrictions]. +The JS API the interop member corresponds to is determined by a combination of +where it's declared, its name, what kind of Dart member it is, and any [renames](#js). ### Top-level interop members @@ -92,7 +92,7 @@ exposed in the global scope. To access them, you use top-level interop members. To get and set `name`, you declare and use an interop getter and setter with the same name. To use `isNameEmpty`, you declare and call an interop function with the same name. You can declare top-level interop getters, setters, methods, and -fields, which is the same as a getter and setter pair. +fields. Interop fields are equivalent to getter and setter pairs. Top-level interop members must be declared with a [`@JS()`](#js) annotation to distinguish them from other `external` top-level members, like those that can be @@ -161,23 +161,20 @@ Within an interop type, you can declare several different types of `external` interop members: - **Constructors**. When called, constructors with only positional parameters - construct a new JS object whose constructor is defined by the name of the + create a new JS object whose constructor is defined by the name of the extension type using `new`. For example, calling `Time(0, 0)` in Dart will generate a JS invocation that looks like `new Time(0, 0)`. Similarly, calling `Time.onlyHours(0)` will generate a JS invocation that looks like `new Time(0)`. Note that the JS invocations of the two constructors follow the - same semantics, regardless of whether they're given a name in Dart or if they - are a factory. + same semantics, regardless of whether they're given a Dart name or if they are + a factory. - **Object literal constructors**. It is useful sometimes to create a JS [object literal] that simply contains a number of properties and their values. In order to do this, you declare a constructor with only named - parameters, as you're creating an object that is defined by the names of - its properties: + parameters, where the names of the parameters will be the property names: ```dart - import ‘dart:js_interop’; - extension type Options._(JSObject o) { external Options({int a, int b}); external int get a; @@ -186,14 +183,13 @@ Within an interop type, you can declare several different types of ``` A call to `Options(a: 0, b: 1)` will result in creating the JS object - `{a: 0, b: 1}`. The object is defined by the invocation, so calling - `Options(a: 0)` would result in `{a: 0}`. You can get or set the properties - of the resulting object through `external` instance members. + `{a: 0, b: 1}`. The object is defined by the invocation arguments, so + calling `Options(a: 0)` would result in `{a: 0}`. You can get or set the + properties of the object through `external` instance members. :::warning There's a bug that currently requires object literal constructors to have an - [`@JS`](#js) annotation on the library, empty or otherwise. See [#54801] for - more details. + [`@JS`](#js) annotation on the library. See [#54801] for more details. ::: - **`static` members**. Like constructors, these members use the name of the @@ -286,8 +282,10 @@ one written. For example, if you want to write two `external` APIs that point to the same JS property, you’d need to write a different name for at least one of them. Similarly, if you want to define multiple interop types that refer to the same JS interface, you need to rename at least one of them. Another example is -if the JS name is unusable in Dart e.g. `$a`. In order to do this, you can use -the [`@JS()`] annotation with a constant string value. For example: +if the JS name cannot be written in Dart e.g. `$a`. + +In order to do this, you can use the [`@JS()`] annotation with a constant +string value. For example: ```dart extension type Array._(JSArray _) implements JSArray { @@ -387,7 +385,8 @@ occur. as a JS interop member or type. It is required (with or without a value) for all top-level members to distinguish them from other `external` top-level members, but can often be elided on and within interop types and on extension members as -the compiler can tell from the representation type and on-type. +the compiler can tell it is a JS interop type from the representation type and +on-type. ## `dart:js_interop` and `dart:js_interop_unsafe` @@ -395,16 +394,18 @@ the compiler can tell from the representation type and on-type. including `@JS`, JS types, conversion functions, and various utility functions. Utility functions include: -- [`globalContext`], which represents the global namespace that the compilers - use to generate JS invocations. +- [`globalContext`], which represents the global scope that the compilers use to + find interop members and types. - [Helpers to inspect the type of JS values] - JS operators - [`dartify`] and [`jsify`], which check the type of certain JS values and convert them to Dart values and vice versa. Prefer using the specific - conversion when you know the type of the JS value. -- [`importModule`] to import modules dynamically as `JSObject`s. + conversion when you know the type of the JS value, as the extra type-checking + may be expensive. +- [`importModule`], which allows you to import modules dynamically as + `JSObject`s. -More utilities might be added to this library in the future. +More utilities may be added to this library in the future. [`dart:js_interop_unsafe`] contains members that allow you to look up properties dynamically. For example: @@ -419,22 +420,25 @@ dynamically get, set, and call properties. :::tip Avoid using `dart:js_interop_unsafe` if possible. It makes security compliance -more difficult to guarantee and may lead to violations. +more difficult to guarantee and may lead to violations, which is why it can be +"unsafe". ::: {% comment %} TODO: Add links when ready [extension type]: /language/extension-types -[the extension types documentation]: /language/extension-types#type-considerations +[runtime guarantee]: /language/extension-types#type-considerations [representation type]: /language/extension-types#declaration [implement]: /language/extension-types#implements [non-`external` members]: /language/extension-types#members + +TODO: Some of these are not available on stable. How do we link to dev? {% endcomment %} [global JS scope]: https://developer.mozilla.org/en-US/docs/Glossary/Global_scope [conversion functions]: /interop/js-interop/js-types#conversions -[declared with a primitive]: /interop/js-interop/js-types#requirements-on-external-declarations-and-function-tojs +[contains a primitive type]: /interop/js-interop/js-types#requirements-on-external-declarations-and-function-tojs ["JS type"]: /interop/js-interop/js-types [`Window`]: https://developer.mozilla.org/en-US/docs/Web/API/Window [check the type of the JS value through interop]: /interop/js-interop/js-types#compatibility-type-checks-and-casts From 1510117f9ddb83fd714bebc91c5ef1c1889097f8 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Wed, 14 Feb 2024 17:37:02 -0800 Subject: [PATCH 22/29] Link to extension types sections Also cleans up some TODOs. --- src/content/interop/js-interop/index.md | 2 +- src/content/interop/js-interop/mock.md | 7 ++----- src/content/interop/js-interop/past-js-interop.md | 2 +- src/content/interop/js-interop/usage.md | 13 +++++-------- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/content/interop/js-interop/index.md b/src/content/interop/js-interop/index.md index 079ae2d2cd..6aa74a8cef 100644 --- a/src/content/interop/js-interop/index.md +++ b/src/content/interop/js-interop/index.md @@ -37,7 +37,7 @@ For additional documentation on JavaScript interop: [Usage reference]: /interop/js-interop/usage [JS types reference]: /interop/js-interop/js-types -[`package:web` and migration]: / +[`package:web` and migration]: /interop/js-interop/package-web [How to mock JavaScript interop objects]: /interop/js-interop/mock [Past JS interop]: /interop/js-interop/past-js-interop [`dart:js_interop` API reference]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop diff --git a/src/content/interop/js-interop/mock.md b/src/content/interop/js-interop/mock.md index 9b4cc18f41..5b9e4689f5 100644 --- a/src/content/interop/js-interop/mock.md +++ b/src/content/interop/js-interop/mock.md @@ -113,15 +113,12 @@ provide a JS interface for an arbitrary Dart object, allowing you to essentially *export* Dart objects to JS with a predefined interface. {% comment %} -TODO: Add more documentation in the dartdoc for `@JSExport`. TODO: Should we add a section on general testing? We can't really mock non-instance members unless the user explicitly replaces the real API in JS. - -TODO: Expose this once the link works: -[extension types]: /language/extension-types {% endcomment %} [Usage]: /interop/js-interop/usage [`createJSInteropWrapper`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/createJSInteropWrapper.html [`@JSExport`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/JSExport-class.html -[limitation is true for extension members]: https://github.com/dart-lang/mockito/blob/master/FAQ.md#how-do-i-mock-an-extension-method \ No newline at end of file +[limitation is true for extension members]: https://github.com/dart-lang/mockito/blob/master/FAQ.md#how-do-i-mock-an-extension-method +[extension types]: /language/extension-types diff --git a/src/content/interop/js-interop/past-js-interop.md b/src/content/interop/js-interop/past-js-interop.md index f13fff04ab..20c7d9f504 100644 --- a/src/content/interop/js-interop/past-js-interop.md +++ b/src/content/interop/js-interop/past-js-interop.md @@ -108,7 +108,7 @@ TODO: Link to `package:web` section [`dart:js_interop`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop [`dart:html`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-html -[`package:web`]: / +[`package:web`]: /interop/js-interop/package-web [`dart:js`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js [`object wrapper`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js/JsObject-class.html [`allowInterop`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_util/allowInterop.html diff --git a/src/content/interop/js-interop/usage.md b/src/content/interop/js-interop/usage.md index 5c4fe0a613..77adf401dd 100644 --- a/src/content/interop/js-interop/usage.md +++ b/src/content/interop/js-interop/usage.md @@ -425,14 +425,6 @@ more difficult to guarantee and may lead to violations, which is why it can be ::: {% comment %} -TODO: Add links when ready - -[extension type]: /language/extension-types -[runtime guarantee]: /language/extension-types#type-considerations -[representation type]: /language/extension-types#declaration -[implement]: /language/extension-types#implements -[non-`external` members]: /language/extension-types#members - TODO: Some of these are not available on stable. How do we link to dev? {% endcomment %} @@ -458,3 +450,8 @@ TODO: Some of these are not available on stable. How do we link to dev? [`importModule`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/importModule.html [`dart:js_interop_unsafe`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop_unsafe [extensions]: /language/extension-methods +[extension type]: /language/extension-types +[runtime guarantee]: /language/extension-types#type-considerations +[representation type]: /language/extension-types#declaration +[implement]: /language/extension-types#implements +[non-`external` members]: /language/extension-types#members From 3f52a4b2b9e0fd8d31afe18e9c2d26a289235711 Mon Sep 17 00:00:00 2001 From: Marya <111139605+MaryaBelanger@users.noreply.github.com> Date: Thu, 15 Feb 2024 08:06:08 -0800 Subject: [PATCH 23/29] `package:web` migration page (#5524) Co-authored-by: Parker Lougheed Co-authored-by: Srujan Gaddam Co-authored-by: sigmundch --- examples/create_libraries/lib/hw_mp.dart | 2 +- firebase.json | 2 +- src/_data/side-nav.yml | 2 +- .../guides/libraries/create-packages.md | 10 +- src/content/interop/js-interop/dom.md | 34 -- src/content/interop/js-interop/package-web.md | 306 ++++++++++++++++++ src/content/libraries/dart-html.md | 10 + 7 files changed, 324 insertions(+), 42 deletions(-) delete mode 100644 src/content/interop/js-interop/dom.md create mode 100644 src/content/interop/js-interop/package-web.md 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*, From 481f0e7830341e99b87d3d461842f2af68a16c39 Mon Sep 17 00:00:00 2001 From: Marya Belanger Date: Thu, 15 Feb 2024 08:22:00 -0800 Subject: [PATCH 24/29] fix some broken links, hide why section in index --- src/content/interop/js-interop/index.md | 2 +- src/content/interop/js-interop/js-types.md | 2 +- src/content/interop/js-interop/package-web.md | 2 +- src/content/libraries/dart-html.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/content/interop/js-interop/index.md b/src/content/interop/js-interop/index.md index 6aa74a8cef..1dce3020fc 100644 --- a/src/content/interop/js-interop/index.md +++ b/src/content/interop/js-interop/index.md @@ -10,9 +10,9 @@ JavaScript apps and libraries, as well as browser APIs, using `dart:js_interop`. Web developers can benefit from using external JS libraries in their Dart code, without having to rewrite anything in Dart. +{% comment %} ## Why use JS interop? -{% comment %} TODO: Should we have a paragraph here explaining the benefits of using interop? {% endcomment %} diff --git a/src/content/interop/js-interop/js-types.md b/src/content/interop/js-interop/js-types.md index 87f5f1da16..1e3d97951d 100644 --- a/src/content/interop/js-interop/js-types.md +++ b/src/content/interop/js-interop/js-types.md @@ -246,6 +246,6 @@ TODO: add links (with stable) when ready: [`dart:js_interop` API docs]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/dart-js_interop-library.html#extension-types [`typeofEquals`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JSAnyUtilityExtension/typeofEquals.html [`instanceOfString`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JSAnyUtilityExtension/instanceOfString.html -[`isA`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JSAnyUtilityExtension/isA.html +[`isA`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/isA.html [#4841]: https://github.com/dart-lang/linter/issues/4841 [#54025]: https://github.com/dart-lang/sdk/issues/54025 \ 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 index 1dd357ebda..68bbc0640a 100644 --- a/src/content/interop/js-interop/package-web.md +++ b/src/content/interop/js-interop/package-web.md @@ -66,7 +66,7 @@ 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), +[type tests](#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`. diff --git a/src/content/libraries/dart-html.md b/src/content/libraries/dart-html.md index f87617fa69..c941226323 100644 --- a/src/content/libraries/dart-html.md +++ b/src/content/libraries/dart-html.md @@ -37,7 +37,7 @@ import 'dart:html'; ``` [`package:web`]: {{site.pub-pkg}}/web -[Migrate to package:web]: /interop/js-interop/package-web#migrating-from-darthtml +[Migrate to package:web]: /interop/js-interop/package-web ### Manipulating the DOM From 01c64bc33ec01eef569cc2b22936ce3cfbf7ce91 Mon Sep 17 00:00:00 2001 From: Marya Belanger Date: Thu, 15 Feb 2024 08:41:03 -0800 Subject: [PATCH 25/29] go link, fix index api links, remove old learn directive --- firebase.json | 2 +- src/content/interop/js-interop/index.md | 4 ++-- src/content/interop/js-interop/mock.md | 6 ++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/firebase.json b/firebase.json index a0561b4b5d..f718a46920 100644 --- a/firebase.json +++ b/firebase.json @@ -172,7 +172,7 @@ { "source": "/go/non-promo-this", "destination": "/tools/non-promotion-reasons#this", "type": 301 }, { "source": "/go/non-promo-write", "destination": "/tools/non-promotion-reasons#write", "type": 301 }, - { "source": "/go/next-gen-js-interop", "destination": "/interop/js-interop#next-generation-js-interop-preview", "type": 301 }, + { "source": "/go/next-gen-js-interop", "destination": "/interop/js-interop", "type": 301 }, { "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 }, diff --git a/src/content/interop/js-interop/index.md b/src/content/interop/js-interop/index.md index 1dce3020fc..5a0ccfe3f3 100644 --- a/src/content/interop/js-interop/index.md +++ b/src/content/interop/js-interop/index.md @@ -40,5 +40,5 @@ For additional documentation on JavaScript interop: [`package:web` and migration]: /interop/js-interop/package-web [How to mock JavaScript interop objects]: /interop/js-interop/mock [Past JS interop]: /interop/js-interop/past-js-interop -[`dart:js_interop` API reference]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop -[`dart:js_interop_unsafe` API reference]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop_unsafe +[`dart:js_interop` API reference]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/dart-js_interop-library.html +[`dart:js_interop_unsafe` API reference]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop_unsafe/dart-js_interop_unsafe-library.html diff --git a/src/content/interop/js-interop/mock.md b/src/content/interop/js-interop/mock.md index 5b9e4689f5..1c502a247d 100644 --- a/src/content/interop/js-interop/mock.md +++ b/src/content/interop/js-interop/mock.md @@ -2,10 +2,8 @@ title: How to mock JavaScript interop objects --- -{{site.why.learn}} - * In this tutorial, you'll learn how to mock JS objects so that you can test - interop instance members without having to use a real implementation. -{{site.why.end}} +In this tutorial, you'll learn how to mock JS objects so that you can test +interop instance members without having to use a real implementation. ## Background and motivation From efb81a2610177c1e437b1d3aa6769fecac686e25 Mon Sep 17 00:00:00 2001 From: Marya Belanger Date: Thu, 15 Feb 2024 08:56:26 -0800 Subject: [PATCH 26/29] more minor link fixes --- src/content/interop/js-interop/usage.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/interop/js-interop/usage.md b/src/content/interop/js-interop/usage.md index 77adf401dd..f4644daf3b 100644 --- a/src/content/interop/js-interop/usage.md +++ b/src/content/interop/js-interop/usage.md @@ -434,7 +434,7 @@ TODO: Some of these are not available on stable. How do we link to dev? ["JS type"]: /interop/js-interop/js-types [`Window`]: https://developer.mozilla.org/en-US/docs/Web/API/Window [check the type of the JS value through interop]: /interop/js-interop/js-types#compatibility-type-checks-and-casts -[`package:web`]: / +[`package:web`]: {{site.pub-pkg}}/web [`external`]: /language/functions#external [restrictions]: /interop/js-interop/js-types#requirements-on-external-declarations-and-function-tojs [object literal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer @@ -446,7 +446,7 @@ TODO: Some of these are not available on stable. How do we link to dev? [`globalContext`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/globalContext.html [Helpers to inspect the type of JS values]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JSAnyUtilityExtension.html [`dartify`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JSAnyUtilityExtension/dartify.html -[`jsify`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/NullableObjectUtilExtension.html +[`jsify`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/NullableObjectUtilExtension/jsify.html [`importModule`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/importModule.html [`dart:js_interop_unsafe`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop_unsafe [extensions]: /language/extension-methods From 202b62655b3379a63f478fd49db22305313d30e4 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Thu, 15 Feb 2024 09:02:57 -0800 Subject: [PATCH 27/29] Move dart:js_interop and dart:js_interop_unsafe links to dev for latest documentation --- src/content/interop/js-interop/index.md | 4 ++-- src/content/interop/js-interop/js-types.md | 10 +++++----- src/content/interop/js-interop/mock.md | 4 ++-- src/content/interop/js-interop/package-web.md | 4 ++-- .../interop/js-interop/past-js-interop.md | 8 ++++---- src/content/interop/js-interop/usage.md | 18 +++++++++--------- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/content/interop/js-interop/index.md b/src/content/interop/js-interop/index.md index 5a0ccfe3f3..bc8115ec6d 100644 --- a/src/content/interop/js-interop/index.md +++ b/src/content/interop/js-interop/index.md @@ -40,5 +40,5 @@ For additional documentation on JavaScript interop: [`package:web` and migration]: /interop/js-interop/package-web [How to mock JavaScript interop objects]: /interop/js-interop/mock [Past JS interop]: /interop/js-interop/past-js-interop -[`dart:js_interop` API reference]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/dart-js_interop-library.html -[`dart:js_interop_unsafe` API reference]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop_unsafe/dart-js_interop_unsafe-library.html +[`dart:js_interop` API reference]: https://api.dart.dev/dev/dart-js_interop/dart-js_interop-library.html +[`dart:js_interop_unsafe` API reference]: https://api.dart.dev/dev/dart-js_interop_unsafe/dart-js_interop_unsafe-library.html diff --git a/src/content/interop/js-interop/js-types.md b/src/content/interop/js-interop/js-types.md index 1e3d97951d..d47760cd57 100644 --- a/src/content/interop/js-interop/js-types.md +++ b/src/content/interop/js-interop/js-types.md @@ -240,12 +240,12 @@ future. See [#54025] for more details. TODO: add links (with stable) when ready: {% endcomment %} -[`dart:js_interop`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/dart-js_interop-library.html +[`dart:js_interop`]: https://api.dart.dev/dev/dart-js_interop/dart-js_interop-library.html [`external`]: https://dart.dev/language/keywords -[`Function.toJS`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/FunctionToJSExportedDartFunction/toJS.html -[`dart:js_interop` API docs]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/dart-js_interop-library.html#extension-types -[`typeofEquals`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JSAnyUtilityExtension/typeofEquals.html -[`instanceOfString`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JSAnyUtilityExtension/instanceOfString.html +[`Function.toJS`]: https://api.dart.dev/dev/dart-js_interop/FunctionToJSExportedDartFunction/toJS.html +[`dart:js_interop` API docs]: https://api.dart.dev/dev/dart-js_interop/dart-js_interop-library.html#extension-types +[`typeofEquals`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/typeofEquals.html +[`instanceOfString`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/instanceOfString.html [`isA`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/isA.html [#4841]: https://github.com/dart-lang/linter/issues/4841 [#54025]: https://github.com/dart-lang/sdk/issues/54025 \ No newline at end of file diff --git a/src/content/interop/js-interop/mock.md b/src/content/interop/js-interop/mock.md index 1c502a247d..4976f443c5 100644 --- a/src/content/interop/js-interop/mock.md +++ b/src/content/interop/js-interop/mock.md @@ -116,7 +116,7 @@ non-instance members unless the user explicitly replaces the real API in JS. {% endcomment %} [Usage]: /interop/js-interop/usage -[`createJSInteropWrapper`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/createJSInteropWrapper.html -[`@JSExport`]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-js_interop/JSExport-class.html +[`createJSInteropWrapper`]: https://api.dart.dev/dev/dart-js_interop/createJSInteropWrapper.html +[`@JSExport`]: https://api.dart.dev/dev/dart-js_interop/JSExport-class.html [limitation is true for extension members]: https://github.com/dart-lang/mockito/blob/master/FAQ.md#how-do-i-mock-an-extension-method [extension types]: /language/extension-types diff --git a/src/content/interop/js-interop/package-web.md b/src/content/interop/js-interop/package-web.md index 68bbc0640a..bfc8a2791a 100644 --- a/src/content/interop/js-interop/package-web.md +++ b/src/content/interop/js-interop/package-web.md @@ -283,8 +283,8 @@ Do we have any other package migrations to show off here? [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 +[`dart:js_interop`]: https://api.dart.dev/dev/dart-js_interop/dart-js_interop-library.html +[`dart:js_interop_unsafe`]: https://api.dart.dev/dev/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 diff --git a/src/content/interop/js-interop/past-js-interop.md b/src/content/interop/js-interop/past-js-interop.md index 20c7d9f504..f1980a87a1 100644 --- a/src/content/interop/js-interop/past-js-interop.md +++ b/src/content/interop/js-interop/past-js-interop.md @@ -106,18 +106,18 @@ TODO: add links (with stable) when ready: TODO: Link to `package:web` section {% endcomment %} -[`dart:js_interop`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop +[`dart:js_interop`]: https://api.dart.dev/dev/dart-js_interop [`dart:html`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-html [`package:web`]: /interop/js-interop/package-web [`dart:js`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js [`object wrapper`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js/JsObject-class.html [`allowInterop`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_util/allowInterop.html [`package:js`]: https://pub.dev/packages/js -[`JSObject`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JSObject-extension-type.html +[`JSObject`]: https://api.dart.dev/dev/dart-js_interop/JSObject-extension-type.html [`@JS`]: https://github.com/dart-lang/sdk/blob/main/sdk/lib/js/_js_annotations.dart#L11 [tutorial on mocking]: /interop/js-interop/mock [`@anonymous`]: https://github.com/dart-lang/sdk/blob/main/sdk/lib/js/_js_annotations.dart#L40 [`@staticInterop`]: https://github.com/dart-lang/sdk/blob/main/sdk/lib/js/_js_annotations.dart#L48 [`dart:js_util`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_util -[`Function.toJS`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/FunctionToJSExportedDartFunction/toJS.html -[`dart:js_interop_unsafe`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop_unsafe +[`Function.toJS`]: https://api.dart.dev/dev/dart-js_interop/FunctionToJSExportedDartFunction/toJS.html +[`dart:js_interop_unsafe`]: https://api.dart.dev/dev/dart-js_interop_unsafe diff --git a/src/content/interop/js-interop/usage.md b/src/content/interop/js-interop/usage.md index f4644daf3b..8f46db3c89 100644 --- a/src/content/interop/js-interop/usage.md +++ b/src/content/interop/js-interop/usage.md @@ -440,15 +440,15 @@ TODO: Some of these are not available on stable. How do we link to dev? [object literal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer [#54801]: https://github.com/dart-lang/sdk/issues/54801 [property accessors]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#bracket_notation -[utility functions]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JSAnyOperatorExtension.html -[`@JS()`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JS-class.html -[`dart:js_interop`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop -[`globalContext`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/globalContext.html -[Helpers to inspect the type of JS values]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JSAnyUtilityExtension.html -[`dartify`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/JSAnyUtilityExtension/dartify.html -[`jsify`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/NullableObjectUtilExtension/jsify.html -[`importModule`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop/importModule.html -[`dart:js_interop_unsafe`]: {{site.dart-api}}/{{site.sdkInfo.channel}}/dart-js_interop_unsafe +[utility functions]: https://api.dart.dev/dev/dart-js_interop/JSAnyOperatorExtension.html +[`@JS()`]: https://api.dart.dev/dev/dart-js_interop/JS-class.html +[`dart:js_interop`]: https://api.dart.dev/dev/dart-js_interop +[`globalContext`]: https://api.dart.dev/dev/dart-js_interop/globalContext.html +[Helpers to inspect the type of JS values]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension.html +[`dartify`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/dartify.html +[`jsify`]: https://api.dart.dev/dev/dart-js_interop/NullableObjectUtilExtension/jsify.html +[`importModule`]: https://api.dart.dev/dev/dart-js_interop/importModule.html +[`dart:js_interop_unsafe`]: https://api.dart.dev/dev/dart-js_interop_unsafe [extensions]: /language/extension-methods [extension type]: /language/extension-types [runtime guarantee]: /language/extension-types#type-considerations From 33f8d05479a12af8b5191862103dc0160f5f9162 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Thu, 15 Feb 2024 09:41:56 -0800 Subject: [PATCH 28/29] Small fixes to code snippets and external functions link --- src/content/interop/js-interop/js-types.md | 4 ++-- src/content/interop/js-interop/usage.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/content/interop/js-interop/js-types.md b/src/content/interop/js-interop/js-types.md index d47760cd57..902e8196e4 100644 --- a/src/content/interop/js-interop/js-types.md +++ b/src/content/interop/js-interop/js-types.md @@ -116,7 +116,7 @@ external JSArray jsTypes(JSObject _, JSString __); ``` ```dart tag=good -extension type InteropType(JSObject _) {} +extension type InteropType(JSObject _) implements JSObject {} @JS() external InteropType get interopType; @@ -241,7 +241,7 @@ TODO: add links (with stable) when ready: {% endcomment %} [`dart:js_interop`]: https://api.dart.dev/dev/dart-js_interop/dart-js_interop-library.html -[`external`]: https://dart.dev/language/keywords +[`external`]: https://dart.dev/language/functions#external [`Function.toJS`]: https://api.dart.dev/dev/dart-js_interop/FunctionToJSExportedDartFunction/toJS.html [`dart:js_interop` API docs]: https://api.dart.dev/dev/dart-js_interop/dart-js_interop-library.html#extension-types [`typeofEquals`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/typeofEquals.html diff --git a/src/content/interop/js-interop/usage.md b/src/content/interop/js-interop/usage.md index 8f46db3c89..1c87fb3b92 100644 --- a/src/content/interop/js-interop/usage.md +++ b/src/content/interop/js-interop/usage.md @@ -175,7 +175,7 @@ Within an interop type, you can declare several different types of parameters, where the names of the parameters will be the property names: ```dart - extension type Options._(JSObject o) { + extension type Options._(JSObject o) implements JSObject { external Options({int a, int b}); external int get a; external int get b; @@ -325,7 +325,7 @@ import 'dart:js_interop'; @JS() external void method(); -extension type JSType(JSObject _) implements JSObject { +extension type JSType._(JSObject _) implements JSObject { external JSType(); external static int get staticMember; @@ -366,7 +366,7 @@ import 'dart:js_interop'; external void method(); @JS('library3.JSType') -extension type JSType(JSObject _) implements JSObject { +extension type JSType._(JSObject _) implements JSObject { external JSType(); } ``` From 7e1eb3fab71408a4a624d6293df84cff654d6bb4 Mon Sep 17 00:00:00 2001 From: Marya Belanger Date: Thu, 15 Feb 2024 10:32:48 -0800 Subject: [PATCH 29/29] minor fixes --- src/content/interop/js-interop/js-types.md | 20 ++++++++++++-------- src/content/interop/js-interop/usage.md | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/content/interop/js-interop/js-types.md b/src/content/interop/js-interop/js-types.md index 902e8196e4..2d89deb661 100644 --- a/src/content/interop/js-interop/js-types.md +++ b/src/content/interop/js-interop/js-types.md @@ -85,9 +85,10 @@ Generally, the conversion table looks like the following: :::warning -There can be inconsistencies in both performance and semantics for conversions -when compiling to JavaScript vs Wasm. Conversions may have different costs +Compiling to JavaScript vs Wasm can introduce inconsistencies in both +performance and semantics for conversions. Conversions may have different costs depending on the compiler, so prefer to only convert values if you need to. + Conversions also may or may not produce a new value. This doesn’t matter for immutable values like numbers, but does matter for types like `List`. Depending on the implementation, a conversion to `JSArray` may return a reference, a @@ -225,15 +226,18 @@ There is a subtle inconsistency with regards to `undefined` between compiling to JS and Wasm. While compiling to JS *treats* `undefined` values as if they were Dart `null`, it doesn’t actually *change* the value itself. If an interop member returns `undefined` and you pass that value back into JS, JS will see -`undefined`, *not* `null`, when compiling to JS. However, when compiling to -Wasm, this is not the case and the value will be `null` in JS. This is because +`undefined`, *not* `null`, when compiling to JS. + +However, when compiling to Wasm, this is not the case, +and the value will be `null` in JS. This is because the compiler implicitly *converts* the value to Dart `null` when compiling to Wasm, thereby losing information on whether the original value was JS `null` or `undefined`. Avoid writing code where this distinction matters by explicitly -passing Dart `null` instead to an interop member. Currently, there's no -platform-consistent way to provide `undefined` to interop members or distinguish -between JS `null` and `undefined` values, but this will likely change in the -future. See [#54025] for more details. +passing Dart `null` instead to an interop member. + +Currently, there's no platform-consistent way to provide `undefined` +to interop members or distinguish between JS `null` and `undefined` values, +but this will likely change in the future. See [#54025] for more details. ::: {% comment %} diff --git a/src/content/interop/js-interop/usage.md b/src/content/interop/js-interop/usage.md index 1c87fb3b92..88b1c3ff37 100644 --- a/src/content/interop/js-interop/usage.md +++ b/src/content/interop/js-interop/usage.md @@ -32,7 +32,7 @@ extension type Window(JSObject _) implements JSObject {} ``` `Window` is an interop type for an arbitrary `JSObject`. There is no [runtime -guarantee] that `Window` is actually a JS [`Window`]. There also is no conflict +guarantee][] that `Window` is actually a JS [`Window`]. There also is no conflict with any other interop interface that is defined for the same value. If you want to check that `Window` is actually a JS `Window`, you can [check the type of the JS value through interop].