jmesserly / dart-custom-element-demo

Example of a custom element written in Dart, using JS interop for registration
MIT License
21 stars 4 forks source link

Make a hypothetical package:html #2

Open donny-dont opened 5 years ago

donny-dont commented 5 years ago

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

donny-dont commented 5 years ago

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.

jifalops commented 5 years ago

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.

  1. 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:
  2. Use package:js to mirror the interfaces that custom elements require, such HTMLElement, 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 as HtmlElementJS in dart. b. Wrap them with a Dart class that interfaces with the JS version (e.g. HtmlElement). c. HtmlElement will have an abstract define() method that passes its HtmlElementJS constructor.
  3. 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.

donny-dont commented 5 years ago

@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.

jmesserly commented 5 years ago

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.

donny-dont commented 5 years ago

@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 Interceptors. 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 commented 5 years ago

@jmesserly 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 Interceptors. 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.

jifalops commented 5 years ago

@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:

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.

jifalops commented 5 years ago

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

jmesserly commented 5 years ago

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?

jifalops commented 5 years ago

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.

donny-dont commented 5 years ago

@jmesserly if there's a bug you create for Interceptor could you let us know here? Would like to watch it.

donny-dont commented 5 years ago

@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.

jmesserly commented 5 years ago

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

donny-dont commented 5 years ago

@jmesserly so I managed to get Custom Elements working with DDC and dart2js.

https://github.com/rampage-dart/rampage/commit/e3a90c42cf2cc572c311591b3abdf2c69443d84f