js

Last updated:

0 purchases

js Image
js Images
Add to Cart

Description:

js

Important: Prefer using dart:js_interop instead of this package for JS
interop. See the JS interop documentation for more details.
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.
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.
Static Interop #
Important: Static interop is now supported with extension types in Dart 3.3.
Prefer using dart:js_interop with extension types instead of @staticInterop.
See the JS interop documentation for more details.
In the past, package:js has allowed users to use JavaScript interoperability
in more dynamic, class-based ways. While we will continue to allow users to use
that functionality in the foreseeable future, Dart is transitioning to a more
static, inline class-based interop. What this largely means is that we're moving
away from dynamic invocations and instead requiring static typing to use
interop. We're calling this model "static interop".
We are doing this for several reasons, such as idiomaticity, performance, type
soundness, ability to interop with DOM types, and compatibility with Wasm. This
is an ongoing effort that will affect our web library offerings as well.
In version 0.6.6, we introduced a way to use static interop with
@staticInterop. package:js classes that have this annotation are required
to use the new static semantics via extensions and do not support dynamic
invocations. For more details on how to use @staticInterop classes, see below.
To test these classes, see the sections below on @JSExport and
js_util.createStaticInteropMock.
As this is an ongoing effort, we are also working on interop with inline
classes. Inline classes will be a new language feature that enables
zero-cost wrapping. In the future, users should opt-in to the semantics of
static interop using inline classes instead of @staticInterop. It will be
easier to use, more idiomatic, and better supported going forward.
For now, static interop remains experimental and in development. We may make
breaking changes in the future. We'll update this text when the new interop
model matures and is considered stable.
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);
copied to clipboard
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);
}
copied to clipboard
Passing object literals to JavaScript #
Many JavaScript APIs take an object literal as an argument. For example:
// JavaScript
printOptions({ responsive: true });
copied to clipboard
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});
}
copied to clipboard
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().
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()`.
}
copied to clipboard
@staticInterop #
With package:js, we have historically had two different types of classes:
plain @JS (those with just the @JS annotation) and @anonymous classes.
Now, you can use a new one: @staticInterop.
These classes are different in that they do not allow instance members within
the class itself. All such members need to go into an extension (hence
“static”). Let’s look at an example:
@JS()
library static_interop;

import 'package:js/js.dart';

// Assumes there is a top-level `StaticInterop` class in a JS module.
@JS()
@staticInterop
class StaticInterop {
external factory StaticInterop();
}

extension on StaticInterop {
external int field;
external int get getSet;
external set getSet(int val);
external int method();
}

void main() {
var jsObj = StaticInterop();
jsObj.field = 1;
jsObj.method();
}
copied to clipboard
The external static extension members get lowered to JS naturally:
jsObj.field becomes a property get of field in JS and jsObj.method()
becomes a function invocation of method on jsObj.
In many ways, these classes are just like the plain @JS and @anonymous
classes. Like with plain @JS classes, you can provide a value in @JS if you
want the constructor to use a particular JS class e.g.
@JS(‘module.MyJSClass’). You can also add @anonymous to @staticInterop
classes if you want the factory constructor with named arguments in order to
make an object literal e.g.
external factory AnonymousStaticInterop({int? field1, int? field2}). Also like
with plain @JS classes, you can’t inherit non-package:js classes. You should
only inherit other @staticInterop classes for subtyping and inheriting
extension methods. Lastly, you can freely cast JS objects to and from the three
types of package:js classes.
What makes @staticInterop unique, however, is that you can use them to
represent DOM objects as well as other JS objects, which you can’t with previous
package:js classes. Historically, you’ve needed to use dart:html to interact
with the DOM e.g. DivElement. Now, you can create your own abstraction for
these objects instead of using the ones we provide in dart:html:
@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);
}
copied to clipboard
Note that you can have both external and non-external members in the
extension.
Compared to non-@staticInterop package:js classes, @staticInterop classes:

Are more performant
Have better type guarantees
Generate less code
Allow non-external members
Allow external extension members to be renamed using @JS() e.g.
@JS('renamedField')

The only catch is that virtual/dynamic dispatch is disallowed. That means
methods are resolved using only the static type of the object.
In general, it's advised to use @staticInterop wherever you can, as future JS
interop will only target static dispatch.
@JSExport and js_util.createDartExport #
One of the difficulties with JS interop is that most of it is exclusively
focused on importing JS code to Dart, not the other way around. We have some
functionality like allowInterop, which allows you to call Dart functions in
JS, but this becomes cumbersome when you want to use a Dart object. You need to
essentially allowInterop all members manually.
createDartExport instead lets you do this automatically. Let’s see how with an
example:
import 'dart:js_util';

import 'package:expect/minitest.dart';
import 'package:js/js.dart';

// The Dart class must have `@JSExport` on it or one of its instance members.
@JSExport()
class Counter {
int value = 0;
@JSExport('increment')
void renamedIncrement() {
value++;
}
}

@JS()
@staticInterop
class JSCounter {}

extension on JSCounter {
external int value;
external void increment();
}

void main() {
var dartCounter = Counter();
var counter = createDartExport<Counter>(dartCounter) as JSCounter;
expect(counter.value, 0);
counter.increment();
expect(counter.value, 1);
expect(dartCounter.value, 1); // Dart object gets modified
dartCounter.value = 0;
expect(counter.value, 0); // Changes in Dart object affect the exported object
}
copied to clipboard
There are a number of things happening here. At a high level, you pass
createDartExport an instance of some Dart object that has @JSExport either
on it or one of its instance members and the object’s static type if needed.
Using the static type, we transform the createDartExport call into a JS object
literal that is a mapping from each member’s Dart name (accounting for renames
using the @JSExport annotation) to the member. The JS object essentially wraps
and acts as a proxy to the exported Dart object.
Now, when we use it as a JS object (in this case, using @staticInterop), we
can use the same names to access these members. We can also use the same syntax
to access these members e.g. counter.value = 0. This now gives us an easy to
do what we wanted before with allowInterop for each member.
There are, of course, limitations.
The only members that are “exported” are concrete instance members i.e. fields,
getters, setters, and methods. That means you can’t export static members,
constructors, factories, operators (the syntax complicates things), and
extension methods. You can still have these members - they just won’t be present
in the resulting exported object. Of course, you can use another instance member
to call these members as well, and that instance member will be exported.
In order to use createDartExport, you need to have a class that uses
@JSExport.If you want to export only some members of a class, omit the
annotation on the class, and only use it on the members you want. If you need to
rename members, you can provide the @JSExport annotation on that member a
string value, similar to renaming done via @JS(). Inheritance respects the
individual superclass’ annotations. In other words, if the class of the object
you want to export has a superclass, but that superclass has no @JSExport
annotation anywhere, none of its superclass’ members are exported.
Lastly, different members can’t have the same export name, unless they are a
getter and setter pair. So, for example, if you have a field and a method and
one of them is renamed to the other’s name, that’s a conflict:
@JSExport()
class DartClass {
int member = 0;
@JSExport('member') // Two incompatible members have the same export name.
void method() {}
}
copied to clipboard
This holds true with inheritance as well, unless the member is overridden.
js_util.createStaticInteropMock #
One of the neat things about the above example with Counter is we’ve
essentially created a mock for JSCounter. In the past, to mock a plain @JS
or @anonymous class, you could create a Dart class that implements that
interop class, and due to Dart's virtual dispatch, this would call the Dart
class' members instead. Now that we're using external extension members, this
no longer works. We now have to mock at the JS level instead. With
createDartExport, you’re essentially using a Dart object to replace a JS
object. This functionality is equivalent to mocking at the JS level, and you can
also use it to mock the old non-@staticInterop package:js classes!
One useful feature of the old style of mocking using implements is it lets you
know if you've implemented the needed members. We can't do that with
createDartExport. For example:
@JSExport()
class Counter {
// Where is `value` and `increment`?
}
copied to clipboard
This would obviously not be a satisfactory mock for JSCounter.
createDartExport has no idea what class you're trying to mock, so it can't
tell you if you’ve got your mock class right.
This is where createStaticInteropMock comes in. It takes in a separate type
argument, e.g. createStaticInteropMock<JSCounter, Counter>(Counter()), to
determine whether mocking conformance is satisfied. This type argument must be
a @staticInterop class. With this, you’ll see an error saying that you haven’t
implemented all the needed members. If the mock class implements all the needed
members, the function does the same thing as createDartExport, and returns an
object literal that wraps the Dart object.
You can also use package:mockito to do the mocking with this API, by providing
a generated mocking object from package:mockito to createStaticInteropMock.
There are some corner cases here that are worth noting.
It is possible, through the expressiveness of extension methods, to have name
conflicts like this:
@JS()
@staticInterop
class StaticInterop {}

extension A on StaticInterop {
external Function member;
}

extension B on StaticInterop {
external void member();
}
copied to clipboard
This present an issue as a single Dart class cannot implement member as both a
field and a function. So, what to do? We require that you only implement one
of these members. So, either a Function field or a function are satisfactory.
It is also sometimes desired that the mocking object is the same underlying type
as the JS object you are interfacing. For example, if you want to mock a JS
Element, you’d want the type of the mocking object to also be a Element in
order to pass instanceof checks. In order to do this, we let users pass the JS
prototype of the type they want the mocking object to be as an argument to
createStaticInteropMock.
An important note here is that createStaticInteropMock looks for all
extensions of the @staticInterop type in the program, even if they are out of
scope of the current file. In order to avoid a case where other libraries
extending the @staticInterop type break your usage of
createStaticInteropMock, you should try to only use this API in tests.
createStaticInteropMock is meant to detect issues earlier at compile-time, but
if it's too restrictive, you can still use createDartExport to workaround that
(and please provide us feedback on why it's restrictive!).
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.
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. This is not true for @staticInterop classes unless the
@trustTypes annotation is used.
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 .cast() or construct a new List to get an instance with
the expected reified type. For instance if you want a List<String> use
.cast<String>() or List<String>.from.
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.
is checks and as casts between JS interop types will always succeed
For any two @JS() types, with or without @anonymous, a check of whether an
object of one type is another type will always return true, regardless of
whether those two types are in the same prototype chain. Similarly, an explicit
cast using as will also succeed.

License:

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Customer Reviews

There are no reviews.