[pkg:js] Update CHANGELOG, README, and WORKAROUNDS

Updates documentation now that static interop features are
available. This should be the last component before the new
`package:js` version is published.

Change-Id: I9ddb494a5723036ca699bdcf10a000f7670cdfd8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/224401
Reviewed-by: Riley Porter <rileyporter@google.com>
Commit-Queue: Srujan Gaddam <srujzs@google.com>
This commit is contained in:
Srujan Gaddam 2021-12-16 03:11:57 +00:00 committed by Commit Bot
parent a9b33cfcd2
commit 7701bfb3e9
3 changed files with 156 additions and 14 deletions

View file

@ -1,3 +1,7 @@
## 0.6.4
* Includes `@staticInterop` to allow interop with native types from `dart:html`.
## 0.6.3
* Stable release for null safety.

View file

@ -131,6 +131,48 @@ void main() {
}
```
## Interop with native types using `@staticInterop`
Previously, you could not use `@JS()` or `@anonymous` types to interface with
native types that were reserved within `dart:html` e.g. `Window`.
Using `@staticInterop` will now let you do so. However, it requires that there
be no instance members within the class (constructors are still allowed). You
can use static extension methods instead to declare these members. For example:
```dart
@JS()
library static_interop;
import 'dart:html' as html;
import 'package:js/js.dart';
@JS()
@staticInterop
class JSWindow {}
extension JSWindowExtension on JSWindow {
external String get name;
String get nameAllCaps => name.toUpperCase();
}
void main() {
var jsWindow = html.window as JSWindow;
print(jsWindow.name.toUpperCase() == jsWindow.nameAllCaps);
}
```
Note that in the above you can have both `external` and non-`external` members
in the extension. You can have `external` variables, getters/setters, and
methods within a static extension currently. These `external` members are
lowered to their respective `js_util` calls under the hood. For example, the
`external` `name` getter is equivalent to `js_util.getProperty(this, 'name')`.
In general, it's advised to use `@staticInterop` wherever you can over using
just `@JS()`. There will be fewer surprises and it's aligned with the statically
typed future planned for JS interop.
## Reporting issues
Please file bugs and feature requests on the [SDK issue tracker][issues].

View file

@ -19,13 +19,45 @@ platform libraries.
As mentioned above, there exists stale interfaces. While some of these may be
fixed in the source code, many might not.
In order to circumvent this, you can use the `js_util` library, like
`getProperty`, `setProperty`, `callMethod`, and `callConstructor`.
In order to work around this, you can use the annotation `@staticInterop` from
`package:js`.
Lets look at an example. `FileReader` is a `dart:html` interface that is
missing the API `readAsBinaryString` ([#42834][]). We can work around this by
doing something like the following:
```dart
@JS()
library workarounds;
import 'dart:html';
import 'package:async_helper/async_minitest.dart';
import 'package:expect/expect.dart';
import 'package:js/js.dart';
@JS()
@staticInterop
class JSFileReader {}
extension JSFileReaderExtension on JSFileReader {
external void readAsBinaryString(Blob blob);
}
void main() async {
var reader = new FileReader();
reader.onLoad.listen(expectAsync((event) {
String result = reader.result as String;
Expect.equals(result, '00000000');
}));
var jsReader = reader as JSFileReader;
jsReader.readAsBinaryString(new Blob(['00000000']));
}
```
Alternatively, you can directly use the `js_util` library, using the methods
`getProperty`, `setProperty`, `callMethod`, and `callConstructor`.
```dart
import 'dart:html';
import 'dart:js_util' as js_util;
@ -46,9 +78,37 @@ void main() async {
}
```
In the case where the API is missing a constructor, we can use
`callConstructor`. For example, instead of using the factory constructor for
`KeyboardEvent`, we can do the following:
In the case where the API is missing a constructor, we can define a constructor
within a `@staticInterop` class. Note that constructors, `external` or
otherwise, are disallowed in extensions currently. For example:
```dart
@JS()
library workarounds;
import 'dart:js_util' as js_util;
import 'package:expect/expect.dart';
import 'package:js/js.dart';
@JS('KeyboardEvent')
@staticInterop
class JSKeyboardEvent {
external JSKeyboardEvent(String typeArg, Object keyboardEventInit);
}
extension JSKeyboardEventExtension on JSKeyboardEvent {
external String get key;
}
void main() {
var event = JSKeyboardEvent('KeyboardEvent',
js_util.jsify({'key': 'A'}));
Expect.equals(event.key, 'A');
}
```
or with `js_util`'s `callConstructor`:
```dart
import 'dart:html';
@ -73,11 +133,45 @@ There are several native interfaces that are suppressed e.g.
`USBDevice` ([#42200][]) due to historical reasons. These native interfaces are
marked with `@Native`, are private, and have no attributes associated with them.
Therefore, unlike other `@Native` objects, we cant access any of the APIs or
attributes associated with this interface. We can use the `js_util` library
again to circumvent this issue. For example, we can manipulate a
`_SubtleCrypto` object:
attributes associated with this interface. We can again either use the
`@staticInterop` annotation or use the `js_util` library to circumvent this
issue. For example, we can abstract a `_SubtleCrypto` object:
```dart
@JS()
library workarounds;
import 'dart:html';
import 'dart:js_util' as js_util;
import 'dart:typed_data';
import 'package:js/js.dart';
@JS()
external Crypto get crypto;
@JS()
@staticInterop
class JSSubtleCrypto {}
extension JSSubtleCryptoExtension on JSSubtleCrypto {
external dynamic digest(String algorithm, Uint8List data);
Future<ByteBuffer> digestFuture(String algorithm, Uint8List data) =>
js_util.promiseToFuture(digest(algorithm, data));
}
void main() async {
var subtle = crypto.subtle! as JSSubtleCrypto;
var digest = await subtle.digestFuture('SHA-256', Uint8List(16));
}
```
or with `js_util`:
```dart
@JS()
library workarounds;
import 'dart:html';
import 'dart:js_util' as js_util;
import 'dart:typed_data';
@ -96,10 +190,13 @@ void main() async {
}
```
What you shouldnt do is attempt to cast these native objects using your own JS
interop types, e.g.
What you shouldnt do is attempt to cast these native objects using the
non-`@staticInterop` `package:js` types e.g.
```dart
@JS()
library workarounds;
import 'dart:html';
import 'package:js/js.dart';
@ -115,14 +212,13 @@ void main() {
}
```
With the above, youll see a type error:
With the above, youll see a static error:
`Uncaught TypeError: Instance of 'SubtleCrypto': type 'Interceptor' is not a subtype of type 'SubtleCrypto'`
`Error: Non-static JS interop class 'SubtleCrypto' conflicts with natively supported class '_SubtleCrypto' in 'dart:html'.`
This is because the types in the `@Native` annotation are reserved and the above
leads to namespace conflicts between the `@Native` type and the user JS interop
type in the compiler. These `@Native` types inherit the `Interceptor` class,
which is why you see the message above.
type in the compiler. `@staticInterop` classes, however, don't have this issue.
[#42834]: https://github.com/dart-lang/sdk/issues/42834
[#42200]: https://github.com/dart-lang/sdk/issues/42200