-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve JS interop #35084
Comments
I've added a section about opt-in support for dynamic dispatch. Mainly this is intended to ease dart:html migration. But it's also a capability that we support in both dart4web compilers, so I believe we can offer it for broader use. It makes the interop a bit friendlier to use, at the cost of some extra runtime data. (probably not as much as dart:html currently retains, due to the elimination of checks for JS interop types) |
Cool, thanks for adding that! |
Most of these correctly are emitted as expressions that are non-dynamic. Notably: * Using collection literals (`[]`, `{}`) causes a dynamic call. * Using nested `*ngFor` causes a dynamic call. There are probably other ways to (accidentally) cause a dynamic call that otherwise appears to be static. We should continue to add examples, as these would block the use of advanced Dart language features such as dart-lang/language#41 or dart-lang/sdk#35084 in the template. PiperOrigin-RevId: 221693970
Most of these correctly are emitted as expressions that are non-dynamic. Notably: * Using collection literals (`[]`, `{}`) causes a dynamic call. * Using nested `*ngFor` causes a dynamic call. There are probably other ways to (accidentally) cause a dynamic call that otherwise appears to be static. We should continue to add examples, as these would block the use of advanced Dart language features such as dart-lang/language#41 or dart-lang/sdk#35084 in the template. PiperOrigin-RevId: 221693970
Most of these correctly are emitted as expressions that are non-dynamic. Notably: * Using collection literals (`[]`, `{}`) causes a dynamic call. * Using nested `*ngFor` causes a dynamic call. There are probably other ways to (accidentally) cause a dynamic call that otherwise appears to be static. We should continue to add examples, as these would block the use of advanced Dart language features such as dart-lang/language#41 or dart-lang/sdk#35084 in the template. PiperOrigin-RevId: 221693970
Most of these correctly are emitted as expressions that are non-dynamic. Notably: * Using collection literals (`[]`, `{}`) causes a dynamic call. * Using nested `*ngFor` causes a dynamic call. There are probably other ways to (accidentally) cause a dynamic call that otherwise appears to be static. We should continue to add examples, as these would block the use of advanced Dart language features such as dart-lang/language#41 or dart-lang/sdk#35084 in the template. PiperOrigin-RevId: 221693970
Very early draft (apologies, it may have lots of typos/bugs) is here: https://github.com/dart-lang/sdk/blob/js_interop/pkg/js/proposal.md ... I'm out of town for a week, but I'd like to do an edit pass when I get back. |
@jmesserly – do you plan to merge that branch in? After feedback? Just curious... |
yes, there's a CL up for it, will send out after a bit more time to gather feedback |
Change-Id: I77a01469113cdfe6c9c3533cfb398ab97d5cd3b0 Reviewed-on: https://dart-review.googlesource.com/c/89404 Reviewed-by: Vijay Menon <vsm@google.com> Commit-Queue: Jenny Messerly <jmesserly@google.com>
@jmesserly any updates on this? |
@jmesserly I have built a package that fully transpiles WebIDLs specs to Dart: https://pub.dev/packages/js_bindings. Maybe it can help move this issue forward? |
@srujzs is investigating this area right now. |
Thanks for the answer. We are doing math here in what should be our next steps regarding Dart and web. Even if a step at all. So could I kindly ask you what we can expect from Google efforts towards this issue and others related to HTML/JS interop? However, it is important to the community to know what to expect. Can you clarify this please? |
Couple of comments because there's a lot of goals here. There have been several static checks added to ensure Our current interop goal in order to resolve some of the above goals is to allow the use of a language feature in the shape of What this entails us to do is to treat JS objects as static extension types. In other words, we have some basic type for all JS objects which the user can then wrap with their own interface/extension type, similar to what we have today with
We don't do an underlying check on the JS type, so you can easily use some other wrapper on the same object. For Still, we're thinking of ways that maybe we can avoid the collisions between interop and So the following are what we are currently working on:
Long term:
What we haven't figured out yet or might not add support for currently:
I'm likely missing details, so I'll edit as I figure out what I missed. |
Hi @srujzs glad to see someone working on this! I had done a couple PRs against Jenny's original So rampage is my stab at making Web Components work in Dart. The example in In it there are more or less three layers. The one closest to the "metal" is the js libraries. These are just extensions on The highest level is just the interface. These are just abstract classes representing the hierarchy from the WebIDL definitions of the interfaces. Between the two is the implementation. Primarily this just implements the interface by holding on to a Finally it also needs to provide for some quality of life features that are present in To actually do the Custom Element part there's a custom_element.js which proxies calls to the If I remember correctly I think this is the sort of stuff @sigmundch and @jakemac53 had to do back in the day with All the implementation in the library is hand rolled. I have a WebIDL parser that I am planning on using in conjunction with I primarily wanted to give you another point of reference with this so maybe the Custom Element stuff could be figured out. Happy to assist in any way to help move this along quicker. |
In google_maps package I already use extensions to dartify the underlying JS api and it works quite well. For instance LatLng is implemented like this: @JS('google.maps.LatLng')
class LatLng {
external LatLng(num? lat, num? lng, [bool? noWrap]);
external bool? equals(LatLng? other);
external String toString();
external String? toUrlValue([num? precision]);
}
// `latLng.lat` in dart instead of `latLng.lat()` in JS
extension LatLng$Ext on LatLng {
num get lat => _lat();
num get lng => _lng();
num _lat() => callMethod(this, 'lat', []);
num _lng() => callMethod(this, 'lng', []);
} What can be mapped directly to the JS api goes in the class and what need reshape goes in the extension.
That has been a concern when google_maps_flutter migrated to google_maps-5.x (that used extensions) If we go further with only extension types/views how will constructors be declared? Will it be possible to have static members? |
Recently, we ran into wanting to quite a bit of JS interop, exposing business logic entrypoints to JS world. This issue was opened in 2018. Is this still on roadmap, is it likely to get some caring attention in the near future? |
@donny-dont, I believe the conversation around this is tied to #46248 so I'll keep it there instead. :)
The current plan is to enable factories (external or otherwise redirecting to a Mocking will be interesting, and something still in our roadmap to figure out with
Yes, there's still ongoing effort here, but some of the goals have changed as I mention above. Are you specifically interested in exposing Dart classes to JS? This is a more complicated goal and one that would be in the further future after some of other interop goals mentioned above. |
Closes b/195948578 Modifies Trusted Types APIs to be compliant with the spec in https://w3c.github.io/webappsec-trusted-types/dist/spec/. Change-Id: I65d52ace12342ce777ab596a9dd2e9a3f74b2f05 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/212270 Commit-Queue: Srujan Gaddam <srujzs@google.com> Reviewed-by: Riley Porter <rileyporter@google.com>
I am wrestling with this, and I have a simple question: Is it possible to call Flutter/Dart code from JavaScript? I have done a lot of digging and there is a lot of contradictory information, which is to be expected for a moving target like this. I was sent here from https://dart.dev/web/js-interop which talks of Dart 2.19. I am:
so that document feels rather old. Is it worth pursuing this as a developer or should I look for a different approach? (I am successfully calling into Javascript from Dart) |
Yes! it's possible - but you have to explicitly export the API you want to call. Right now, that means storing callbacks (like you do with Recently @srujzs addded support for
That document is fairly recent. Note that 2.19 was the last SDK version before the jump to 3.0, so it's only 1 version ago :). That page doesn't discuss exports in detail, but it also has a link to the JSExport docs I mentioned above. Hope you find this helpful! |
We launched a first step toward this next-gen JS interop in Dart 3.3. For an introduction, see the blog post: We also have some documentation available: |
Indeed! This new release brings up to address many of the goals initially stated at the beginning of this issue. In particular:
Yes! Some of the documentation linked by @mit-mit above provides more details. Today dart2js, ddc, and dart2wasm share a compiler pass that checks for valid uses of JS-interop APIs and provides compile-time errors for invalid usage. Some of it was added to old interop APIs, but going forward the main support lives with
Yes! Most inconsistencies were fixed by adding a shared checker to all compilers.
Yes! Some optimizations still need to be implemented, but the design supports it.
Yes! Same comment as earlier above.
With extension-types this has really improved!
We have explored tools like js_facade_gen in the past, but recently some of our community members have created new alternatives like
Yes! This is fully supported now.
Yes. This still requires a step to progamatically expose them, but it is much easier than in the past. Check out createJSInteropWrapper
Many of these types now have direct conversion functions (e.g. Future/Promise).
Not yet. This was not prioritized. We only do implicit conversions for primitive values.
Yes, the new JS-types make clear how these types get interepreted in Dart.
Check! The new JS-interop treats all JS objects as external entities.
This is now a non-goal.
We decided to take a different approach here, and not support mixing class hierarchies. Especially with the recent requirements to support Wasm.
Yes! This is now very natural to do with the extension types syntax.
Yes! All interop types are now extension types, which are a compile-time only feature. THey all get erased to a simple type representing all interop objects.
This was reevaluated. Instead, tearoffs of external members are statically banned. It is not as convenient, but was done to ensure consistency. In doing so, we prevent nuances with binding of
This was treated as a non-goal now that conditional imports are part of the language and packages can hide uses of JS-interop in the same way they hide access to other web-specific APIs. It's worth highlighting that the new Dart/JS interop introduced JS types to clearly denote the Dart/JS boundary in the type system, it is sound, and it is supported in Dart2wasm. As such, I think it is fair to close this issue as complete 🎉 @srujzs - would you do the honors? |
This is great stuff!
Just wanted to confirm with you both @sigmundch and @srujzs if one wanted to do Custom Elements you'd have to have a Dart object that wraps the js element. I've already gotten this to work with the new |
Hi @donny-dont! Indeed, I believe that's the best approach at the moment. It's possible that some of the new helpers (like |
This is a great new feature and a step forward. However what is currently missing is a concrete example of how to call a function in Dart from JS. All the examples are focused on Dart -> JS but not the other way around. Is there any way to get a concrete example of how to call Dart code from JS? |
If I understand your ask correctly, you can convert Dart functions to a value that can be passed to JS using @JS('some_library')
library;
import 'dart:js_interop';
// Some `external` member in order to pass the function to JS.
@JS()
external JSFunction jsFunction;
void printString(String s) {
print(s);
}
void main() {
// Has to be a function that only uses types that are allowed. `String` and `void` are okay.
jsFunction = printString.toJS;
} and then you can call it in JS: some_library.jsFunction('hello world'); There's some ongoing work to further document some of the conversions (as well as migrations from other forms of interop) that should make operations like the above a bit more obvious. Let me know if you still have more questions. |
We'd like to significantly improve Dart's ability to interoperate with JavaScript libraries and vice versa. This will build on existing capabilities (see examples of current JS interop).
The new design will take advantage of Dart 2's sound static typing to provide JS interop that is convenient, high performance, and with minimal code size impact.
Some of the tasks for this:
Some of the things we'll need to address (details are subject to change):
JSMap
type)@JS()
doesn't support or fail onexternal List<NotDynamic> get
#34195 for an example)dynamic
(e.g.JSDynamic
that dispatches directly to JS members)Other details:
this
binding for tearoffs (DDC: JS-Interop tearoffs cause type errors #32370)Dynamic dispatch support:
We will likely want to keep opt-in dynamic dispatch support, similar to what "dart:html" currently provides, but well specified and available for use by any JS interop class. The classes must opt-in for this. This will cause extra data to be retained for use by runtime dispatch. Type checks are not required, however. The types will use the relaxed JS interop casting rules. Calling conventions in dart:html can likely be simplified (e.g. we do not require
obj.foo(x)
andobj.foo(x, null)
to substitute undefined for null). Similar to JS Array and JS function types, tearoffs of JS methods will be untyped and not checked (they can be converted to any function type).Because of these changes, we should be able to reduce the signature data stored for dart:html. These changes should be largely non-breaking, as code rarely relies on dart:html throwing type errors (indeed, such code will rarely work in production mode, which omits checks). It is also unlikely that any code is relying on dynamic tearoffs having a specific reified function type.
Note that any data for automatic conversions, or extension methods added to the types, will need to be stored so runtime dispatch can access it. Currently it is implementation defined how these methods are stored (dart2js uses interceptors , dartdevc adds symbolized members to the JS classes). If we want these methods to be accessible from JS (e.g. they are exported), we'll need to specify this precisely. A similar issue exists for the static extension methods (described above).
Longer term: we'd like to use the new JS interop to implement the DOM in a package, and then migrate from "dart:html". This will necessarily be a slow migration/deprecation process, since "dart:html" is necessary for all web apps today. It may involve automated refactoring tools or opt-in static analysis to assist the migration.Due to these changes, migration to the new dart:html package should not be difficult (likely as simple as importing "package:html/html1.dart"). We will want a static-only version of dart:html, however (e.g. "package:html/html2.dart"). Types from those two HTML libraries should be compatible, so one can cast between them (after all, it is the same JS object underneath). At compile time, an explicit cast may be required (we could allow old types to be converted to new types, though, and you could always go in the other direction with
as html1.Element
because runtime casting will be allowed).Ideally, we use re-export to declare the "dynamic version" of package:html:
That library simply indicates that dynamic dispatch data should be generated. If "html1.dart" is not imported, the runtime dispatch data would not be generated.
The text was updated successfully, but these errors were encountered: