dart-custom-element-demo
dart-custom-element-demo copied to clipboard
Make a hypothetical package:html
So I kept tinkering with things and was starting to think what a pure package:js
version of a dart:html
lib might look like.
Talked with @jakemac53 while putting it together since he worked on polymer.dart. In there the dart object was attached to the js object. So this takes a similar approach.
Since not everything is wrapped it doesn't change color like the previous iteration. You can however play with the element by just entering the console. If you do that you can setAttribute on name and it'll change as expected.
@jmesserly @jifalops it would be interesting to hear your thoughts on it. Its probably closer to what would need to happen to have a package:html
for dart web apps.
Some other observations
- DDC is sending down some html bindings period. This seems wrong since
dart:html
was not included at all. - dart2js chokes on this completely. Can dig into more as to why but I'm assuming its probably similar and assuming that
dart:html
is a thing.
Hoping this'll maybe kick start a discussion. I could see how the JS stuff could be generated through something like build
added with the parsers since I think that was used for some chrome IDL binding parsing.
Update: This won't work until the above issue is resolved. Constructors in JS and in Dart both are not functions after compiled with dart2js. Thanks for including me anyway.
Okay I've got a rough plan of how this might work. One of the goals of this approach is to support random JS custom elements that may not be willing to support dart explicitly.
- Test that
customElements.define()
can be passed the constructor of a JS element when called from dart. (What about a dart constructor? Why not?) Assuming JS constructors can be passed and Dart constructors cannot: - Use
package:js
to mirror the interfaces that custom elements require, suchHTMLElement
,customElements.define()
, etc. Do it in a minimal way so this approach can be tested early. a. The JS classes might have a name such asHtmlElementJS
in dart. b. Wrap them with a Dart class that interfaces with the JS version (e.g.HtmlElement
). c. HtmlElement will have an abstractdefine()
method that passes its HtmlElementJS constructor. - Pure dart custom elements can just extend HtmlElement if they don't have constructor args. Other elements will have to have their own wrapper. This might be able to be simplified by having HtmlElement create objects or do some sort of initialization in JS land when it's constructed.
I've done some usability tests for js_interop that still need to check parent/child relationships before submitting a PR. I also started the package:js version of HTMLElement and it's parent classes before realizing a minimal test should be done first.
I should have this done in a week or two if tests validate my assumptions.
@jifalops it might save you some work later to think about how this can be done in general through package:js
. As an example the CSS Painting API allows you to register a JS class to do painting and it takes a constructor as well. It also assumes a paint
method which would be stripped in dart2js
.
I'm to privy to the dart team's goals but a lot of the worklet specs make things a bit more interesting for the interop case. I think it would be interesting if framework authors could roll their own JS interop in the browser.
Either way I'm really excited to see what you come up with.
This is super cool by the way, thanks for putting this together! Yeah, I'm working on improvements to JS interop, so we can do an interop-based package:html. (Also trying to figure out the migration story.) I'll definitely keep custom elements in mind.
@jmesserly I was actually experimenting further with a package:html
to see what happened. One thing that is going to be hit is that if you use dart2js
is that it takes control of things like Event
and turns them into Interceptor
s. Ideally if you wanted to hit stuff like being able to create worklets like what the CSS Painting API does is to not insert code that takes over any potential JS-interop classes.
Not sure if that's clear enough but you can try and wrap Event and you'll hit it really quick. Only other thing I found had to do with allowInterop
which can have some problems with the type system when you do a typedef
over a generic function.
Happy to add more examples for you if that would be of use. You and @jifalops are the experts with the interop so I'd defer to you guys on all that.
@jmesserly One thing that is going to be hit is that if you use
dart2js
is that it takes control of things likeEvent
and turns them intoInterceptor
s. Ideally if you wanted to hit stuff like being able to create worklets like what the CSS Painting API does is to not insert code that takes over any potential JS-interop classes.
Yeah, one of my goals is to make the Interceptor system unnecessary for the DOM.
@donny-dont @JS
can be very picky. There isn't much that can be done outside of the examples from https://github.com/matanlurey/dart_js_interop. Interestingly, most things are allowed by either DDC or Dart2JS, but almost nothing is allowed by both. Some SDK tests help further describe limtiations.
Some takeaways so far:
-
@JS
classes can only reference external JS stuff without adding anything, even a static const. - Only primitives, Lists, and Functions are supported, but
- List elements are always
dynamic
- Functions passed as parameters need to have the signature JS expects, at least in terms of the number of args. I thought I tested that Dart could pass extra args, but then it errored later. I'm hazy on some of the details surrounding Functions.
- List elements are always
- Numbers are interchangeable. A Dart
int
can be set to an@JS
double and the int will still have the fractional part when printed (DDC and d2js). This was surprising to me. - Dart2JS allows using
Object.defineProperty
and using@JS
name-collision avoidance likeexternal JS$call()
. DDC will not allow either. - Using the JS
Function
class will allow you to pass a function tocustomElements.define()
, but it's really just a string of javascript so it doesn't solve the problem of truer function interop.
I think I've finally laid any hopes of non-closure function interop to rest for now. As @jmesserly said on StackOverflow regarding this, it's best to just use @JS
to access APIs written in javascript.
... I started on this path because I have been developing ways of creating Mobile+Web applications. Sharing a theme with AngularDart was kind of tough and I thought MDC web offered a way to use rather plain HTML/JS and achieve a similar result. Anyway, I found this job post for what sounds like a position on the Dart team doing exactly that, developing mobile+web solutions. Maybe it involves helping sort out this @JS
custom elements blocker. At any rate I just applied. I'd love to help.
With regard to best practices when 'wrapping' JavaScript using package:js
, I've been going back and declaring @JS
classes as abstract only if their underlying JS class is abstract. This leads to a caveat where a concrete JS class that implements some other JS class must either include the implemented functions, or more practically change implements
to with
. I'm not sure if there are any down sides to doing that.
Also the wrapper class can declare itself to implement its underlying @JS
class, but then @JS
members or function args on the underlying class need to be declared as dynamic so the wrapper class can declare the actual type without conflict. That isn't such a big deal since the underlying class is usually private. A second side effect comes through on the wrapper class itself though. The argument to a setter must be declared as dynamic instead of the proper type, if the proper type is from JS. I'm not sure having a wrapper class implement
the underlying class is worth that side effect.
This might be unclear, there's an example of the second paragraph here
With regard to best practices when 'wrapping' JavaScript using package:js, I've been going back and declaring
@JS
classes as abstract only if their underlying JS class is abstract.
Do you mind me asking why you're avoiding abstract classes?
It's really just to be transparent and denote that the actual javascript version can be instantiated.
Abstract lets you declare a property as T asDart;
like in this repo, instead of the more verbose external T get asDart
and related setter. The downside to doing that though is concrete descendant classes must do the implementation. But if an @JS
class isn't going to be extended it would save some time.
I'm not sure that it really makes a difference though, since all @JS
can be abstract. I thought I ran into a problem with this when trying to mirror HTMLElement, but now that I'm second guessing myself and looking at it, I have a gut feeling you are right and there's no reason they can't all be abstract. My mistake.
@jmesserly if there's a bug you create for Interceptor
could you let us know here? Would like to watch it.
@jmesserly happy new year! Just pinging you to see if there's anything in the works since I hadn't heard anything with my last message.
happy new year! Just pinging you to see if there's anything in the works since I hadn't heard anything with my last message.
Happy new year to you to! Yeah, there's a lot happening, I'll be updating this issue: https://github.com/dart-lang/sdk/issues/35084
@jmesserly so I managed to get Custom Elements working with DDC and dart2js.
https://github.com/rampage-dart/rampage/commit/e3a90c42cf2cc572c311591b3abdf2c69443d84f