0f109da0d1
Closes #37883 https://github.com/dart-lang/sdk/pull/37883 GitOrigin-RevId: e14be88991f39db80422e98399fab26c8e9c038e Change-Id: Ic4e45420dcf6fe71b5c215c435cd72ffb6e665f4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/113265 Reviewed-by: Nate Bosch <nbosch@google.com> Commit-Queue: Kathy Walrath <kathyw@google.com> |
||
---|---|---|
.. | ||
example | ||
lib | ||
AUTHORS | ||
CHANGELOG.md | ||
LICENSE | ||
PATENTS | ||
proposal.md | ||
pubspec.yaml | ||
README.md |
Use this package when you want to call JavaScript APIs from Dart code, or vice versa.
This package's main library, js
, provides annotations and functions
that let you specify how your Dart code interoperates with JavaScript code.
The Dart-to-JavaScript compilers — dartdevc and dart2js — recognize these
annotations, using them to connect your Dart code with JavaScript.
Important: This library supercedes dart:js
, so don't import dart:js
.
Instead, import package:js/js.dart
.
A second library in this package, js_util
, provides low-level utilities
that you can use when it isn't possible to wrap JavaScript with a static, annotated API.
Example
See the Chart.js Dart API for an end-to-end example.
Usage
The following examples show how to handle common interoperability tasks.
Calling JavaScript functions
@JS()
library stringify;
import 'package:js/js.dart';
// Calls invoke JavaScript `JSON.stringify(obj)`.
@JS('JSON.stringify')
external String stringify(Object obj);
Using JavaScript namespaces and classes
@JS('google.maps')
library maps;
import 'package:js/js.dart';
// Invokes the JavaScript getter `google.maps.map`.
external Map get map;
// The `Map` constructor invokes JavaScript `new google.maps.Map(location)`
@JS()
class Map {
external Map(Location location);
external Location getLocation();
}
// The `Location` constructor invokes JavaScript `new google.maps.LatLng(...)`
//
// We recommend against using custom JavaScript names whenever
// possible. It is easier for users if the JavaScript names and Dart names
// are consistent.
@JS('LatLng')
class Location {
external Location(num lat, num lng);
}
Passing object literals to JavaScript
Many JavaScript APIs take an object literal as an argument. For example:
// JavaScript
printOptions({responsive: true});
If you want to use printOptions
from Dart a Map<String, dynamic>
would be
"opaque" in JavaScript.
Instead, create a Dart class with both the @JS()
and @anonymous
annotations.
@JS()
library print_options;
import 'package:js/js.dart';
void main() {
printOptions(Options(responsive: true));
}
@JS()
external printOptions(Options options);
@JS()
@anonymous
class Options {
external bool get responsive;
// Must have an unnamed factory constructor with named arguments.
external factory Options({bool responsive});
}
Making a Dart function callable from JavaScript
If you pass a Dart function to a JavaScript API as an argument,
wrap the Dart function using allowInterop()
or allowInteropCaptureThis()
.
Warning: Although dart2js requires allowInterop()
, dartdevc does not.
To make a Dart function callable from JavaScript by name, use a setter
annotated with @JS()
.
@JS()
library callable_function;
import 'package:js/js.dart';
/// Allows assigning a function to be callable from `window.functionName()`
@JS('functionName')
external set _functionName(void Function() f);
/// Allows calling the assigned function from Dart as well.
@JS()
external void functionName();
void _someDartFunction() {
print('Hello from Dart!');
}
void main() {
_functionName = allowInterop(_someDartFunction);
// JavaScript code may now call `functionName()` or `window.functionName()`.
}
Reporting issues
Please file bugs and feature requests on the SDK issue tracker.
Known limitations and bugs
Differences between dart2js and dartdevc
Dart's production and development JavaScript compilers use different calling conventions and type representation, and therefore have different challenges in JavaScript interop. There are currently some known differences in behavior and bugs in one or both compilers.
allowInterop is required in dart2js, optional in dartdevc
DDC uses the same calling conventions as JavaScript and so Dart functions passed
as callbacks can be invoked without modification. In dart2js the calling
conventions are different and so allowInterop()
or allowInteropCaptureThis()
must be used for any callback.
Workaround:: Always use allowInterop()
even when not required in dartdevc.
Callbacks allow extra ignored arguments in DDC
In JavaScript a caller may pass any number of "extra" arguments to a function and they will be ignored. dartdevc follows this behavior, dart2js will have a runtime error if a function is invoked with more arguments than expected.
Workaround: Write functions that take the same number of arguments as will be passed from JavaScript. If the number is variable use optional positional arguments.
Dartdevc and dart2js have different representation for Maps
Passing a Map<String, String>
as an argument to a JavaScript function will
have different behavior depending on the compiler. Calling something like
JSON.stringify()
will give different results.
Workaround: Only pass object literals instead of Maps as arguments. For json
specifically use jsonEncode
in Dart rather than a JS alternative.
Missing validation for anonymous factory constructors in dartdevc
When using an @anonymous
class to create JavaScript object literals dart2js
will enforce that only named arguments are used, while dartdevc will allow positional
arguments but may generate incorrect code.
Workaround: Try builds in both development and release mode to get the full scope of static validation.
Common problems
Dart and JavaScript have different semantics and common patterns, which makes it easy to make some mistakes and difficult for the tools to provide safety. These common problems are also known as sharp edges.
Lack of runtime type checking
The return types of methods annotated with @JS()
are not validated at runtime,
so an incorrect type may "leak" into other Dart code and violate type system
guarantees.
Workaround: For any calls into JavaScript code that are not known to be safe
in their return values, validate the results manually with is
checks.
List instances coming from JavaScript will always be List<dynamic>
A JavaScript array does not have a reified element type, so an array returned
from a JavaScript function cannot make guarantees about it's elements without
inspecting each one. At runtime a check like result is List
may succeed, while
result is List<String>
will always fail.
Workaround: Use a .cast<String>().toList()
call to get a List
with the
expected reified type at runtime.
The JsObject
type from dart:js
can't be used with @JS()
annotation
JsObject
and related code in dart:js
uses a different approach and may not
be passed as an argument to a method annotated with @JS()
.
Workaround: Avoid importing dart:js
and only use the package:js
provided
approach. To handle object literals use @anonymous
on an @JS()
annotated
class.