matthewmueller / joy

A delightful Go to Javascript compiler (ON HOLD)
https://mat.tm/joy
GNU General Public License v3.0
1.32k stars 35 forks source link

Passing types to functions #66

Open matthewp opened 6 years ago

matthewp commented 6 years ago

Since Go doesn't have generics, how could this be possible? This is necessary in cases where some foreign function expects to receive a constructor function. I looked at the jsx tests hoping to see something like this, but instead instances are created.

matthewmueller commented 6 years ago

JSX / React integration is tricky and the reason we need to support it in the compiler first class for now.

Fortunately other than that important use case, I haven't run into any other situations where you absolutely need to pass class functions around like that. This will definitely come up though and in the cases where you do need this, you can usually make minor modifications to the Javascript file to tweak the API a little. It's not ideal, but it works.

I found a way to target very specific Go interfaces (e.g. target structs that implement the react.Component interface), so for these essential JS libraries, we can modify the compiler to produce different JS. This will be case by case.

Longer term, this is one of the motivations for the macro system I mention in 3.0: https://mat.tm/joy/#next-steps

matthewp commented 6 years ago

The case I was thinking of was custom elements. customElements.define('foo-bar', FooBar).

matthewmueller commented 6 years ago

Ahh okay, I haven't played around with web components yet. Do you have a standalone example? I can try to make it work with Joy now otherwise I'll look for ways to add support.

matthewp commented 6 years ago

I can't give you an example in Go since it doesn't have generics.. in HTML/JS it would be:

<!doctype html>
<title>customElement example</title>

<hello-world></hello-world>
<script>
  class HelloWorld extends HTMLElement {
    connectedCallback() {
      this.textContent = 'Hello world';
    }
  }

  customElements.define('hello-world', HelloWorld);
</script>

To save you some time, we really do need to pass the class. No trickery like passing a function and then returning a new instance yourself will work unfortunately :/

matthewmueller commented 6 years ago

thanks for this, i didn't realize web components was heading in this direction with classes. i'll keep this in mind going forward.

little confused by that piece of the ecosystem as it relates to polymer. seems like there's competing standards. e.g. would this become a part of web components? https://github.com/PolymerLabs/lit-html

matthewp commented 6 years ago

polymer is just a javascript library on top of the web component apis. That particular library does templating, because web components doesn't have an answer for templating (except for the <template> tag which doesn't really do much at the moment).

There aren't competing standards. It's better to ignore the term "web component" imo, as it's a bit confusing, just think about the DOM apis. In this case we're only concerned about registering a custom element tag. The custom elements API are in stable releases of Chrome and Safari and is in development for FF at the moment. It's not an experimental API or an idea floating around; it's here now :-)

matthewmueller commented 6 years ago

ahh i see. my head has been in react world for awhile now, haven't been following web components at all haha.

I have a potential solution to this. If your package name is helloworld:

Now, when you're passing functions around like that, the function's definition has to be exact, (even func(v interface{})).

I think this is fine for virtual DOM (func(Props) Component), but would it be fine for web components? In other words, are the constructor's parameters always the same?

matthewp commented 6 years ago

I don't think there are ever constructor params, so that's fine. The key thing is that the constructor has to extend HTMLElement, and it must call super(). Only class can use super() though, so if it doesn't transpile to classes it would need to instead call Reflect.construct like so:

function HelloWorld() {
  Reflect.construct(HTMLElement, arguments, this.constructor);
}

customElements.define('hello-world', HelloWorld);

I have a branch of joy with this, so I'll try out your suggestion and let you know.

matthewmueller commented 6 years ago

Ahh gotcha, I think something like this mighttt work:

type HTMLElement interface{}
type WebComponent interface{}

type HelloWorld struct {
    HTMLElement
}

func New() WebComponent {
    return nil
}

func Define(name string, fn func() WebComponent) {

}

func main() {
    Define("hello-world", New)
}

I think HTMLElement is going to be an interface dom.HTMLElement when the DOM APIs are settled.

Would be curious what you come up with. I'm realizing there's a gazillion ways to make things work, just need to find the most appropriate one.

matthewmueller commented 6 years ago

Ah sorry, I missed the part where you said must call super. Joy already has support for adding info on top of functions.

E.g.

// js:"new,omit"
func New() WebComponent {
    return nil
}

to do things like renaming and omitting functions from the output. If need be, we can add something there that turns it into a class or treat a WebComponent interface inside Joy's import path as special and do something different there.

I'm a little surprised there's not a more compatible equivalent though using prototypal inheritance. Have you tried something like this? http://github.com/component/inherit

matthewp commented 6 years ago

Yeah, I've tried several things and haven't come up with something. I'm a bit amateur at Go so that might be part of it.

The key with this idea is that New has to be a function that inherits from HTMLElement. It can't create and return an element in its body. Maybe the directive idea is the way to go.

Old inheritance patterns don't work because HTMLElement is a built-in that you can't call, ie you can't do HTMLElement.call(this) (try in your console and see), that's why Reflect.construct is required.

matthewp commented 6 years ago

@matthewmueller can you give me any pointers as to where in the compiler this info is looked for?

matthewmueller commented 6 years ago

Ahh right, I see. I guess since it's a new API that wouldn't work in older browsers anyway, they can get away with that.

Man, really need to get a Joy playground going to test this stuff out easier. The compiler logic is all in here: https://github.com/matthewmueller/joy/tree/master/internal/compiler

Not exactly sure what you're looking for, but the actual translation is done here: https://github.com/matthewmueller/joy/blob/master/internal/compiler/translator/translator.go

With the top-level translation for functions being here: https://github.com/matthewmueller/joy/blob/master/internal/compiler/translator/translator.go#L75

Alternatively, if you add a failing test case in ./testdata, I'll definitely try and make it work.

theclapp commented 6 years ago

Man, really need to get a Joy playground going to test this stuff out easier

Maybe you could use GopherJS to compile Joy, until Joy can self-host.

matthewmueller commented 6 years ago

Hahaha worth a try! I'd be majorly impressed if GopherJS can compile Joy. Also want to try using lambda to improve the initial page load time.

I'd definitely like to self-host at some point, there's just a lot of backend-related stuff in Joy that's not too important for frontend apps, so it's on the back burner.