dart-custom-element-demo icon indicating copy to clipboard operation
dart-custom-element-demo copied to clipboard

Make a hypothetical package:html

Open donny-dont opened this issue 6 years ago • 14 comments

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.

donny-dont avatar Oct 19 '18 21:10 donny-dont

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.

donny-dont avatar Oct 19 '18 21:10 donny-dont

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.

jifalops avatar Oct 20 '18 22:10 jifalops

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

donny-dont avatar Oct 22 '18 17:10 donny-dont

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 avatar Oct 30 '18 18:10 jmesserly

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

donny-dont avatar Oct 30 '18 19:10 donny-dont

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

jmesserly avatar Oct 30 '18 19:10 jmesserly

@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.
  • 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 like external JS$call(). DDC will not allow either.
  • Using the JS Function class will allow you to pass a function to customElements.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.

jifalops avatar Oct 31 '18 18:10 jifalops

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

jifalops avatar Nov 02 '18 20:11 jifalops

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?

jmesserly avatar Nov 02 '18 22:11 jmesserly

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.

jifalops avatar Nov 02 '18 22:11 jifalops

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

donny-dont avatar Nov 20 '18 01:11 donny-dont

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

donny-dont avatar Jan 02 '19 23:01 donny-dont

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 avatar Jan 02 '19 23:01 jmesserly

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

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

donny-dont avatar Jul 09 '19 15:07 donny-dont