Skip to content

Commit 8c2b89f

Browse files
committed
Add docs on interop usage
1 parent 4c442a1 commit 8c2b89f

File tree

2 files changed

+368
-1
lines changed

2 files changed

+368
-1
lines changed

src/interop/js-interop/js-types.md

-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ JS types form a natural type hierarchy:
3737
You can find the definition of each type in the [`dart:js_interop` API docs].
3838

3939
{% comment %}
40-
TODO (srujzs): Refer to this in the "interop type" section in syntax.
4140
TODO (srujzs): Should we add a tree diagram instead for JS types?
4241
{% endcomment %}
4342

src/interop/js-interop/usage.md

+368
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
1+
---
2+
title: Usage
3+
description: How to declare and use JS interop members.
4+
---
5+
6+
JS interop works by looking up properties in the [global JS scope]. In order to
7+
interact with these properties, you use [`external`] interop members. In order
8+
to provide types for these properties, you use interop types.
9+
10+
## Interop types
11+
12+
When interacting with a JS value, you need to provide a Dart type for it. In
13+
order to do this, you either use or declare an interop type. Dart provides a set
14+
of core interop types called ["JS types"] that you can use to add types to your
15+
interop members. You can also declare an interop type using [extension types] by
16+
making its representation type another interop type. "JS types" refer to the
17+
interop types provided by Dart, and while all JS types are interop types, not
18+
all interop types are JS types. For example:
19+
20+
```dart
21+
extension type Console(JSObject _) implements JSObject {}
22+
```
23+
24+
Both `Console` and `JSObject` are interop types, but only `JSObject` is a JS
25+
type.
26+
27+
You can also use `Console` as a representation type of an interop type as it is
28+
an interop type:
29+
30+
```dart
31+
extension type Console2(Console _) implements Console {}
32+
```
33+
34+
Because you're using extension types, both `Console` and `Console2` are still
35+
`JSObject`s. You're just providing a Dart interface to view the `JSObject`
36+
differently. There is no guarantee that they are a specific type of JS object.
37+
38+
Generally, you will likely use `JSObject` as the representation type for interop
39+
types you declare because you're likely interacting with JS objects who don't
40+
have a corresponding JS type like [`Window`].
41+
42+
Interop types should also generally implement their representation type so that
43+
they can be subtypes of the representation type.
44+
45+
## Interop members
46+
47+
In order to get a JS value that you can provide a type for, you'll want to use
48+
an `external` interop member. These members might take in arguments and return a
49+
type. The types that can be used on these members have [restrictions]. There are
50+
various ways you can declare interop members.
51+
52+
### Top-level interop members
53+
54+
```dart
55+
@JS()
56+
external Console get console;
57+
58+
@JS()
59+
external set name(String value);
60+
61+
@JS()
62+
external void close();
63+
```
64+
65+
Here, `console`, `name`, and `close` are top-level interop members. When
66+
`console` is called, the value in the `console` property in the global namespace
67+
is retrieved and casted to `Console`. When `name` is set, the property `name` in
68+
the global namespace is set to the given value. When `close` is called, the
69+
function `close` in the global namespace is called with no arguments. You can
70+
declare top-level interop getters, setters, methods, and fields, which are just
71+
pairs of getters and setters.
72+
73+
### Interop type members
74+
75+
```dart
76+
extension type Array._(JSObject _) implements JSObject {
77+
external Array();
78+
external factory Array.withLength(int length);
79+
80+
external static bool isArray(JSObject o);
81+
82+
external int length;
83+
external JSAny? at(int index);
84+
85+
external operator [](int index);
86+
external operator []=(int index, JSAny? any);
87+
88+
bool get isZeroLength => length == 0;
89+
}
90+
```
91+
92+
Here we are using `Array` to interop with JS' [`Array`] type.
93+
94+
Within an interop type, you can declare several different types of `external`
95+
interop members:
96+
97+
- Constructors. When called, these members construct a new JS object whose
98+
constructor is defined by the name of the extension type using `new`. For
99+
example, calling `Array()` in Dart will generate a JS invocation that looks
100+
like `new Array()`. Similarly, calling `Array.withLength(10)` will generate a
101+
JS invocation that looks like `new Array(10)`. Note that the JS invocations of
102+
all `external` interop constructors follow the same semantics, regardless of
103+
whether they're given a name in Dart or if they are a factory.
104+
105+
- `static` members. Like constructors, these members use the name of the
106+
extension type to generate the JS code. For example, calling `Array.isArray()`
107+
will generate a JS invocation that looks like `Array.isArray()`. Like
108+
top-levels, you can declare `static` methods, getters, setters, and fields.
109+
110+
- Instance members. Like with other Dart types, these members require an
111+
instance in order to be used. These members get, set, or invoke properties on
112+
the instance. For example:
113+
114+
```dart
115+
final array = Array();
116+
array.length = 10;
117+
final length = array.length;
118+
for (final i = 0; i <= 10; i++) {
119+
array[i] = i.toJS;
120+
assert(array[i] == array.at(i));
121+
}
122+
```
123+
124+
The call to `array.length` gets the value of the `length` property of `array`.
125+
The call to `array.length = 10` sets the value of the `length` property. The
126+
call to `array.at(i)` calls the function in the `at` property of `array` with
127+
the given arguments and returns the value of that call. Like top-levels and
128+
`static` members, you can declare instance methods, getters, setters, and
129+
fields.
130+
131+
- Operators. There are only two `external` interop operators allowed in interop
132+
types: `[]` and `[]=`. These are instance members that match the semantics of
133+
JS' [property accessors]. In the above example, `array[i]` gets the value in
134+
the `i`th slot of `array`, and `array[i]` sets the value in that slot to
135+
`i.toJS`. We expose other JS operators through [utility functions] in
136+
`dart:js_interop`.
137+
138+
In the [JS types] section, you'll see that there exists a predefined JS type for
139+
`Array` called `JSArray`. Here, we're writing another interop type for the same
140+
JS type and just *viewing* it differently, so there is no conflict. If we
141+
wanted, we could have made the representation type `JSArray`:
142+
143+
```dart
144+
extension type Array._(JSArray<JSAny?> _) implements JSArray<JSAny?> {}
145+
```
146+
147+
Lastly, like any other extension type, you're allowed to declare any
148+
non-`external` members in the interop type. `isZeroLength` is one such example.
149+
150+
#### Object literal constructors
151+
152+
It is useful sometimes to create a JS [object literal] that simply contains a
153+
a number of properties and their values. In order to do this, we allow you to
154+
use a constructor with only named arguments:
155+
156+
```dart
157+
import ‘dart:js_interop’;
158+
159+
extension type Options._(JSObject o) {
160+
external Options({int a, int b});
161+
external int get a;
162+
external int get b;
163+
}
164+
```
165+
166+
A call to `Options(a: 0, b: 1)` will result in a JS invocation of
167+
`{a: 0, b: 1}`. You can get or set these values through `external` instance
168+
members.
169+
170+
### Extension members on interop types
171+
172+
You can also write `external` members in extensions of interop types. For
173+
example:
174+
175+
```dart
176+
extension on Array {
177+
external int push(JSAny? any);
178+
}
179+
```
180+
181+
The semantics of calling `push` are identical to what it would have been if it
182+
was in the definition of `Array` instead. The only `external` members you can
183+
write in such an extension are instance members and the allowed operators. Like
184+
with interop types, you can write any non-`external` members in the extension.
185+
These extensions are useful for when an interop type doesn't expose the
186+
`external` member you need and you don't want to create a new interop type.
187+
188+
### Parameters
189+
190+
`external` interop methods can only contain positional and optional arguments.
191+
The one exception is object literal constructors, where they can contain *only*
192+
named arguments.
193+
194+
Unlike with non-`external` methods, optional arguments do not get replaced with
195+
their default value, but are instead omitted. For example:
196+
197+
```dart
198+
external int push(JSAny? any, [JSAny? any2]);
199+
```
200+
201+
Calling `array.push(0.toJS)` in Dart will result in a JS invocation of
202+
`array.push(0.toJS)` and *not* `array.push(0.toJS, null)`. If you declare a
203+
parameter with an explicit default value, you will get a warning that the value
204+
will be ignored.
205+
206+
## `@JS()`
207+
208+
It is sometimes useful to refer to a JS property with a different name than the
209+
one written. For example, if you want to write two `external` APIs that point to
210+
the same JS property, you’d need to write a different name for at least one of
211+
them. In order to do this, you can use the [`@JS()`] annotation with a constant
212+
string value. For example:
213+
214+
```dart
215+
extension type Array._(JSArray<JSAny?> _) implements JSArray<JSAny?> {
216+
external int push(JSNumber number);
217+
@JS('push')
218+
external int pushString(JSString string);
219+
}
220+
```
221+
222+
Calling either `push` or `pushString` will result in JS code that uses `push`.
223+
224+
You can also rename interop types:
225+
226+
```dart
227+
@JS('Date')
228+
extension type JSDate._(JSObject _) implements JSObject {
229+
external JSDate();
230+
231+
external static int now();
232+
}
233+
```
234+
235+
Calling `JSDate()` will result in a JS invocation of `new Date()`. Similarly,
236+
calling `JSDate.now()` will result in a JS invocation of `Date.now()`.
237+
238+
Furthermore, you can namespace an entire library, which will add a prefix to all
239+
interop top-level members, interop types, and `static` interop members within
240+
those types:
241+
242+
```dart
243+
@JS('library1')
244+
library;
245+
246+
import 'dart:js_interop';
247+
248+
@JS()
249+
external void method();
250+
251+
extension type JSType(JSObject _) implements JSObject {
252+
external JSType();
253+
254+
external static int get staticMember;
255+
}
256+
```
257+
258+
Calling `method()` will result in a JS invocation of `library1.method()`,
259+
calling `JSType()` will result in a JS invocation of `new library1.JSType()`,
260+
and calling `JSType.staticMember` will result in a JS invocation of
261+
`library1.JSType.staticMember`.
262+
263+
Unlike interop members and interop types, we only ever add a library name in the
264+
JS invocation if you provide a non-empty value in the `@JS()` annotation on the
265+
library.
266+
267+
```dart
268+
library interop_library;
269+
270+
import 'dart:js_interop';
271+
272+
@JS()
273+
external void method();
274+
```
275+
276+
Calling `method()` will result in a JS invocation of `method()` and *not*
277+
`interop_library.method()`.
278+
279+
You can also write multiple namespaces delimited by a '.' for libraries,
280+
top-level members, and interop types:
281+
282+
```dart
283+
@JS('library1.library2')
284+
library;
285+
286+
import 'dart:js_interop';
287+
288+
@JS('library3.method')
289+
external void method();
290+
291+
@JS('library3.JSType')
292+
extension type JSType(JSObject _) implements JSObject {
293+
external JSType();
294+
}
295+
```
296+
297+
Calling `method()` will result in a JS invocation of
298+
`library1.library2.library3.method()`, calling `JSType()` will result in a JS
299+
invocation of `new library1.library2.library3.JSType()`, and so forth.
300+
301+
You can't use `@JS()` annotations with '.' in the value on interop type members
302+
or extension members of interop types, however.
303+
304+
If there is no value provided to `@JS()` or the value is empty, no renaming will
305+
occur.
306+
307+
`@JS()` also tells the compiler that a member or type is intended to be treated
308+
as a JS interop member or type. It is required (with or without a value) for all
309+
top-level members to distinguish them from other `external` top-level members,
310+
but can often be elided on and within interop types and on extension members as
311+
the compiler can tell from the representation type and on-type.
312+
313+
## `dart:js_interop` and `dart:js_interop_unsafe`
314+
315+
[`dart:js_interop`] contains all the necessary members you should need,
316+
including `@JS`, JS types, conversion functions, and various utility functions.
317+
Utility functions include:
318+
319+
- [`globalContext`], which represents the global namespace that the compilers
320+
use to generate JS invocations.
321+
- [Helpers to type-check JS values]
322+
- JS operators
323+
- [`dartify`] and [`jsify`], which type-check and auto-convert certain JS values
324+
to Dart values and vice versa.
325+
326+
and more. More utilities might be added to this library in the future.
327+
328+
[`dart:js_interop_unsafe`] contains members that allow you to look up properties
329+
dynamically. For example:
330+
331+
```dart
332+
JSFunction f = console['log'];
333+
```
334+
335+
Instead of declaring an interop member named `log`, we're instead using a string
336+
to represent the property. `dart:js_interop_unsafe` provides functionality to
337+
dynamically get, set, and call properties.
338+
339+
{{site.alert.warn}}
340+
Avoid using `dart:js_interop_unsafe` if possible. It makes security compliance
341+
more difficult to guarantee and may lead to violations.
342+
{{site.alert.end}}
343+
344+
{% comment %}
345+
TODO: add links (with stable) when ready:
346+
TODO: How do you link to a subsection of another section?
347+
{% endcomment %}
348+
349+
[global JS scope]: https://developer.mozilla.org/en-US/docs/Glossary/Global_scope
350+
[`external`]: https://dart.dev/language/keywords
351+
["JS types"]: /interop/js-interop/js-types
352+
[extension types]: /
353+
[`Window`]: https://developer.mozilla.org/en-US/docs/Web/API/Window
354+
[restrictions]: /
355+
[`Date`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date
356+
[property accessors]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#bracket_notation
357+
[utility functions]: https://api.dart.dev/dev/dart-js_interop/JSAnyOperatorExtension.html
358+
[JS types]: /interop/js-interop/js-types
359+
[`JSArray`]: https://api.dart.dev/dev/dart-js_interop/JSArray-extension-type.html
360+
[object literal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer
361+
[`@JS()`]: https://api.dart.dev/dev/dart-js_interop/JS-class.html
362+
[`dart:js_interop`]: https://api.dart.dev/dev/dart-js_interop
363+
[`globalContext`]: https://api.dart.dev/dev/dart-js_interop/globalContext.html
364+
[Helpers to type-check JS values]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension.html
365+
[`dartify`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/dartify.html
366+
[`jsify`]: https://api.dart.dev/dev/dart-js_interop/NullableObjectUtilExtension.html
367+
[`dart:js_interop_unsafe`]: https://api.dart.dev/dev/dart-js_interop_unsafe
368+

0 commit comments

Comments
 (0)