Skip to content
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

Make a hypothetical package:html #2

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

donny-dont
Copy link
Contributor

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
Copy link
Contributor Author

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.

Copy link
Contributor Author

@donny-dont donny-dont left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comments about what happens with dart2js. If you don't minify and modify the call sites for the JS code to hit the <method>$<argCount> variants then things actually work as expected.

I know there's a package:meta/dart2js.dart perhaps something like an @extern might be useful here to signal to the compiler that it shouldn't mangle.

customElements.define(name, class extends CustomElement {
constructor() {
super();
construct(this);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First problem this hits in dart2js is that its not a function at this point. If you change it locally to construct.call$1 it gets further.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which call isn't a function yet?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the inspector its listed as a Closure so its not a JS function.
closure

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's why I added the construct.call$1 in there.


void connected() {}
void disconnected() {}
void attributeChanged(String attribute, String oldValue, String newValue) {}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These methods will get removed by dart2js as they are really only referenced by the JS. The other problem is they get mangled as well so the JS code can't call them directly.

@jifalops
Copy link

jifalops commented Oct 20, 2018

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
Copy link
Contributor Author

@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
Copy link
Owner

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
Copy link
Contributor Author

@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
Copy link
Owner

jmesserly commented Oct 30, 2018

@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
Copy link

@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
Copy link

jifalops commented Nov 2, 2018

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
Copy link
Owner

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
Copy link

jifalops commented Nov 2, 2018

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
Copy link
Contributor Author

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

@donny-dont
Copy link
Contributor Author

@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
Copy link
Owner

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: dart-lang/sdk#35084

@donny-dont donny-dont mentioned this pull request Jan 8, 2019
@donny-dont
Copy link
Contributor Author

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

rampage-dart/rampage@e3a90c4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants