Upcoming overhaul to JsInterop annotations in preparation towards v1.0 release

1,663 views
Skip to first unread message

Goktug Gokdogan

unread,
Apr 21, 2015, 2:42:10 PM4/21/15
to google-web-toolkit-contributors
There is some upcoming changes to JsInteorp in preparation toward v1.0 release.

The most major change is to the annotations and their meanings. Here is the doc explaining the changes and the reasoning. We are looking for your feedback, especially on alternatives.

Issues with existing design and annotations

  1. @JsExport/@JsType slicing is not intuitive for a lot of people esp. with gwt-exporter background. People are confused about when to use what.

  2. There is no reason to why @JsType doesn’t have any effect on the static methods. That is only because of the original use cases that the design was tackling only cared about well formed prototypal structures. Diving deeper into Elemental and different javascript output styles, ability to define the full class structure without exporting proves to be useful.

  3. @JsExport uses @JsType to define the prototype structure. However this imposes unnecessary restriction if/when there will be no javascript implementers of the @JsType contract. @JsType that extends non-JsType is normally ok if it is not implemented in js.

  4. You always need to fully qualify the name of the export even if you just want to change the simple name.


The New Annotation System

There will be single annotation called @Js. Applying @Js to a member is making that member available in javascript without any obfuscation. However it is not safe from pruning if there are no references in java code, so one needs to put enable exporting for the type if no pruning wanted. Applying @Js at class level should considered as a shortcut to apply @Js to all members. See following chart for the attributes and their corresponding behavior:


@JsType

@Js(exports = INSTANCE_MEMBERS)

@JsFunction

@Js(mode = FUNCTION)

@JsLiteral

@Js(mode = LITERAL)

@JsMethod

@Js(name = "myName")

@JsProperty

@Js(property = true)
@Js(name = "myName", property = true)

@JsNamespace

@Js(namespace = "mynamespace")

@JsExport

@Js(exports = STATIC_MEMBERS)
@Js(name = “A”, exports = ALL)

@Js(name = “A”, namespace=”a.b.c.”, exports = ALL)

// When applied to a member

@Js(export = true)

@Js(name = “myName”, export = true)

@JsNoExport

@Js(ignore=true)

@JsOpaque

@Js(opaque=true)


See Appendix below for a complete comparison to existing annotations.


Semantics / Implementation in GWT

Implementation:

  • Apply all Js names as bridge methods (or the reverse if Js extends Java object case needs to be supported).

  • Optimize away everything with regular optimization rules if the member is not exported.

  • Generate export statements for all pinned methods/classes.


Usage:

  • Hybrid / Inbox use case needs to use @Js with exports. This will make the whole object exported and not pruned.

  • Regular library importing should use @Js with interfaces (no exports), if it is a callback the @Js interface should be marked as exported so the methods are not pruned when the object is not pruned.

  • Elemental needs to use not exported Js types with prototype set and native methods.


Checks

  • mode and exports is only used in types.

  • export and ignore is only used in members.

  • property is only used in methods.

  • name is only used in members and types.

  • namespace is only used in exported static members, types and packages.

  • mode=FUNCTION cannot have any attribute set.


Considered Alternatives

Alternative 1:

We could follow the above design but keep using old annotations for class level annotations:

  • @Js(mode=OBJECT) --> @JsType

  • @Js(mode=FUNCTION) --> @JsFunction

  • @Js(mode=LITERAL) --> @JsLiteral

  • @Js(namespace=”...”) --> @JsNamespace

  • @JsMember for the rest.


Pros:

  • Reads well . (e.g. @JsType interface Element instead of @Js interface Element { .. } ).

  • These modes are substantially different so different annotations makes that explicit and helps to document.

  • Need to add additional checks as attributes are mostly disjoint. e.g exports doesn't apply to members, name/namespace isn't applicable for Js(mode=LITERAL) etc.


Cons:

  • Multiple annotations to learn.

  • Using JsType/JsFunction/JsLiteral in the same type is forbidden but having single annotations automatically enforces that.


Alternative 2:

We can introduce two different concepts JsImport and JsExport. Both annotation will imply old JsType behavior if applied at class level. It can be applied at method level to provide method level customizations.


The main advantage is that the name implies what the developer is trying to achieve. If importing a library or generating Elemental, @JsImport is used. For exporting code @JsExport is used.

However, it actually make things more confusing when it comes to callbacks. In that case, an imported callback is actually an export so @JsExport should be applied instead.


The main issue is, unlike the above designs it doesn’t let you configure your JS output without introducing exports.


@JsType

@JsImport

@JsFunction

@JsImport(mode = FUNCTION)

// Another gotcha, here we are actually logically not importing always. If it is to call some javascript code, it is for importing but when you implement it in java, it is for exporting. So one can argue we should just keep it @JsFunction.

@JsLiteral

@JsExport(mode = LITERAL)

@JsMethod

{@JsImport/@JsExport}(name = "myName")

// Another gotcha, need to choose one depending on context so we should probably stick to @JsExport/@JsName approach in the old design

@JsProperty

@JsImport/JsExport(isProperty=true)
@JsImport/JsExport(name = "myName", isProperty=true)

@JsNamespace

@JsNamespace

@JsExport

@JsExport // also implies old @JsType semantics
@JsExport(name = “A”, pinned = true)

@JsExport(name = “A”, namespace=”a.b.c.”, pinned = true)

@JsNoExport

@JsIgnore

@JsOpaque

@JsOpaque


Goktug Gokdogan

unread,
Apr 21, 2015, 2:42:51 PM4/21/15
to google-web-toolkit-contributors

Appendix: Old way vs. New way

@JsExport @JsType

class MyClass {}

@Js(exports=ALL)

class MyClass {}

@JsExport

class MyClass {}

@Js(exports=STATIC_MEMBERS)

class MyClass {}

@JsExport(“Name”)

class MyClass {}


@JsExport(“a.b.c.someName”)

class MyClass {}

@Js(name=”Name”, namespace=GLOBAL, exports=STATIC_MEMBERS)

class MyClass {}


@Js(name=”Name”, namespace=”a.b.c”, exports=STATIC_MEMBERS)

class MyClass {}


Also see (*)

@JsType

class MyClass {}

@Js(exports=INSTANCE_MEMBERS)

class MyClass {}

@JsType

interface MyInterface{}

@Js(exports=INSTANCE_MEMBERS)

interface MyInterface{}

@JsType

interface ImportedLibrary{}

@Js

interface ImportedLibrary{}

@JsType

interface ImportedLibraryCallback{}

@Js(exports=INSTANCE_MEMBERS)

interface ImportedLibraryCallback{}

@JsExport

static void someStaticMethod{}

@Js(export=true)

static void someStaticMethod{}

@JsExport(“someName”)

static void someStaticMethod{}


@JsExport(“a.b.c.someName”)

static void someStaticMethod{}

@Js(name=”someName”, namespace=GLOBAL, export=true)

static void someStaticMethod{}


@Js(name=”someName”, namespace=”a.b.c”, export=true)

static void someStaticMethod{}


Also see (*)

@JsNoExport

static void someStaticMethod{}

@Js(ignore=true) or @Js(export=false)

static void someStaticMethod{}

@JsNoExport

void someMethod{}

@Js(ignore=true)

void someMethod{}

@JsProperty

void someMethod{}

@Js(property=true)

void someMethod{}

(*) In most of the cases, export name is provided for changing the simple name (not the fully qualified name). In those cases, namespace attribute doesn’t need to be set.

Appendix: (ALTERNATIVE) Old way vs. New way


@JsExport @JsType

class MyClass {}

@JsType(exports=ALL)

class MyClass {}

@JsExport

class MyClass {}

@JsType(exports=STATIC_MEMBERS)

class MyClass {}

@JsExport(“Name”)

class MyClass {}


@JsExport(“a.b.c.someName”)

class MyClass {}

@JsNamespace(GLOBAL)

@JsType(name=”Name”, exports=STATIC_MEMBERS)

class MyClass {}


@JsNamespace(“a.b.c”)

@JsType(name=”Name”, exports=STATIC_MEMBERS)

class MyClass {}


Also see (*)

@JsType

class MyClass {}

@JsType(exports=INSTANCE_MEMBERS)

class MyClass {}

@JsType

interface MyInterface{}

@JsType(exports=INSTANCE_MEMBERS)

interface MyInterface{}

@JsType

interface ImportedLibrary{}

@JsType

interface ImportedLibrary{}

@JsType

interface ImportedLibraryCallback{}

@JsType(exports=INSTANCE_MEMBERS)

interface ImportedLibraryCallback{}

@JsExport

static void someStaticMethod{}

@JsMember(export=true)

static void someStaticMethod{}

@JsExport(“someName”)

static void someStaticMethod{}


@JsExport(“a.b.c.someName”)

static void someStaticMethod{}

@JsNamespace(GLOBAL)

@JsMember(name=”someName”, export=true)

static void someStaticMethod{}


@JsNamespace(“a.b.c.”)

@JsMember(name=”someName”, export=true)

static void someStaticMethod{}


Also see (*)

@JsNoExport

static void someStaticMethod{}

@JsMember(ignore=true) or @JsMember(export=false)

static void someStaticMethod{}

@JsNoExport

void someMethod{}

@JsMember(ignore=true)

void someMethod{}

@JsProperty

void someMethod{}

@JsMember(property=true)

void someMethod{}

(*) In most of the cases, export name is provided for changing the simple name (not the fully qualified name). In those cases, JsNamespace doesn’t need to be set.



Jens

unread,
Apr 21, 2015, 8:40:20 PM4/21/15
to google-web-tool...@googlegroups.com
Damn it, Safari crashed. So now a shorter version of my answer:

First I agree that the current design has become quite confusing. Personally I really dislike the single @Js annotation approach because:
- can be applied everywhere so you throw away some compile time checks provided by Java's @Target annotation
- exports vs. export is a bit misleading. One must be used with interfaces/classes the other with methods. That issue only exists because @Js alone has no real meaning.
- You have to type more because it is always annotation name + property + value if you can't stick with the default "@Js".
- Can't see a good use case for splitting exports in ALL, INSTANCE_MEMBERS and STATIC_MEMBERS. When I want to export a class I want to export its public API.


Personally what describes JsInterop best is the alternative using the import/export concepts. So I would stick with:

Import/Export types:

@JsImport: can only be applied on interfaces.
@JsExport: always exports all public API in a given scope (package, class, method). So no ALL, INSTANCE_MEMBER, STATIC_MEMBER distinction. @JsIgnore can be used to opt-out selectively.

Configure import/export:

@JsNamespace: import/export from/to namespace
@JsName: workaround reserved keywords
@JsProperty: mark method as JS property
@JsIgnore: opt-out of export. Might even be useful for import, e.g. do not generate trampoline for annotated default method.

Special constructs:

@JsLiteral


I guess that is the cleanest you can get. Not sure what @JsOpaque is good for?!

-- J.

Ray Cromwell

unread,
Apr 21, 2015, 9:21:23 PM4/21/15
to google-web-toolkit-contributors
Goktug, I though we were keeping @JsType and others as syntactic sugar?


--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/google-web-toolkit-contributors/778b8f0b-8cc1-4a90-84d5-39c1b9c02afb%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Goktug Gokdogan

unread,
Apr 22, 2015, 12:06:49 AM4/22/15
to google-web-toolkit-contributors
Yes and no depending what you mean.

Before answering that I think there might have been some confusion the way I listed the options in the doc so let me try to summarize it again.

There are 3 options.

Option 1 uses @Js annotation everywhere. There is no other annotation. The @Js annotation can be applied to a class and in general it will behave similar to applying the annotation to all public members (if that is what you meant as syntactic sugar).
This is the one that is described first in the doc and it is the direction we are heading right now.

Option 2 uses @JsType/@JsFunction/@JsLiteral for types and @JsMember for members. @JsExport, @JsProperty, @JsMethod etc. are still gone (if that's what you meant with syntactic sugar). @JsNamespace is still used for setting namespaces where needed. So this is more or less the same design as Option 1 but separates the annotation used at class level from the one used at members.
This is listed as "Alternative 1" in the doc. Originally this was the direction we were heading and it was described at the beginning while Option 1 was listed as "Alternative 1". I changed the order because John and Roberto both argued having single annotation (i.e. @Js) is simpler.

Option 3 uses @JsImport/JsExport and it is completely separate design from the first two options.
This is listed as "Alternative 2".

Personally I am more towards Option 2 (see the pros and cons for the Option 2 in the doc) but I am getting mixed signals from outside so I need more feedback from developers.


Goktug Gokdogan

unread,
Apr 22, 2015, 12:35:52 AM4/22/15
to google-web-toolkit-contributors
On Tue, Apr 21, 2015 at 5:40 PM, Jens <jens.ne...@gmail.com> wrote:
Damn it, Safari crashed. So now a shorter version of my answer:

First I agree that the current design has become quite confusing. Personally I really dislike the single @Js annotation approach because:
- can be applied everywhere so you throw away some compile time checks provided by Java's @Target annotation

Agreed.

- exports vs. export is a bit misleading. One must be used with interfaces/classes the other with methods. That issue only exists because @Js alone has no real meaning.

Mostly agreed though @Js alone has meaning. As there are no exports, the methods can be pruned. This difference will be more significant when we change the output style to be more idiomatic javascript and have a closer integration with Closure compiler in the future.
 
- You have to type more because it is always annotation name + property + value if you can't stick with the default "@Js".

I'm assuming you are comparing Option 1 and Option 2 (see my recent email). Based on that I'm not sure which part you are referring. Can you give an example?
 
- Can't see a good use case for splitting exports in ALL, INSTANCE_MEMBERS and STATIC_MEMBERS. When I want to export a class I want to export its public API.


Yes, that was my assumption as well and that's how I started. But looking at real life code inside Google, especially in places where the code is shared by different platforms, I see people choosing to only export instance members or static members. I will need to re-evaluate this after we migrate all Google code to new annotations.
 

Personally what describes JsInterop best is the alternative using the import/export concepts. So I would stick with:

Import/Export types:

@JsImport: can only be applied on interfaces.
@JsExport: always exports all public API in a given scope (package, class, method). So no ALL, INSTANCE_MEMBER, STATIC_MEMBER distinction. @JsIgnore can be used to opt-out selectively.

Configure import/export:

@JsNamespace: import/export from/to namespace
@JsName: workaround reserved keywords
@JsProperty: mark method as JS property
@JsIgnore: opt-out of export. Might even be useful for import, e.g. do not generate trampoline for annotated default method.

Special constructs:

@JsLiteral


This is the one I listed as Alternative 2 in the doc (Option 3 as I re-listed in the recent email). It doesn't hold water well; works well for simple but gets confusing quickly as there are many gotchas.
 

I guess that is the cleanest you can get. Not sure what @JsOpaque is good for?!


The description in the patch needs an update but in most simple terms, it is used to mark parameters that are only intended to be used as opaque object in javascript side. This will prevent accidental passing of types that are not usable in javascript while it will give us more room in changing the behavior for non-jstype parameters in the future.

Julien Dramaix

unread,
Apr 22, 2015, 5:47:41 AM4/22/15
to google-web-tool...@googlegroups.com
I'm really more in favor of the second options because (in addition to Jens' arguments):
- it's more readable to have several meaningful annotations than one with several parameters. And I think it's more Javaish. simple example : @JsIgnore vs @Js(ignore=true)
- IMO, the option 1 won't be simpler to learn for developper and I think instead it will be more confusing for the gwt developers. Most  of parameters on the annotations cannot be used simultaneously and can be used only on specific case (for instance: exports parameter has only a meaning when the annotation is on type)




Jens

unread,
Apr 22, 2015, 6:06:19 AM4/22/15
to google-web-tool...@googlegroups.com
- exports vs. export is a bit misleading. One must be used with interfaces/classes the other with methods. That issue only exists because @Js alone has no real meaning.

Mostly agreed though @Js alone has meaning. As there are no exports, the methods can be pruned. This difference will be more significant when we change the output style to be more idiomatic javascript and have a closer integration with Closure compiler in the future.

Yeah ok probably bad wording from my side. @Js is equivalent to @JsImport. 

What I wanted to express is that the export flag is kind of bad because its a "marker" and the act of marking something for export should already be covered by applying the corresponding annotation. So if you would have @JsExport then applying @JsExport automatically means that it is marked for export. You don't need that boolean flag then. You would end up with @JsExport(STATIC_MEMBERS) for types and @JsExport for members which seems more straight forward (yes I know @JsExport(STATIC_MEMBERS) applied to a member seems strange but you would have the same issue with @Js).

Also see next inline answer.

 
 
- You have to type more because it is always annotation name + property + value if you can't stick with the default "@Js".

I'm assuming you are comparing Option 1 and Option 2 (see my recent email). Based on that I'm not sure which part you are referring. Can you give an example?

I think what bugs me most are the boolean properties that are used as "markers", e.g.

@Js(property = true)
@JsProperty

@Js(export = true, property = true)
@JsExport @JsProperty

@Js(export = false)
@Js(ignore = true)
@JsIgnore

It simply reads better without these flags. It is not so much an "issue" for more complex examples, e.g.

@Js(exports = ALL, namespace = GLOBAL, name = "Foo")
@JsExport(ALL) @JsNamespace(GLOBAL) @JsName("Foo")



 
- Can't see a good use case for splitting exports in ALL, INSTANCE_MEMBERS and STATIC_MEMBERS. When I want to export a class I want to export its public API.


Yes, that was my assumption as well and that's how I started. But looking at real life code inside Google, especially in places where the code is shared by different platforms, I see people choosing to only export instance members or static members. I will need to re-evaluate this after we migrate all Google code to new annotations.

Interesting. Wondering why that is the case. Given that it is just a shortcut to avoid lots of @Js(ignore=true) or @Js(export = true) annotations it is not so much of an issue. However I am wondering if the shared Java code would end up better if we enforce the "all public API will be exported unless you opt out using @JsIgnore" route. For example a developer would then have created a static factory class and then choose to export that factory or not.

But I guess we'll just trust the guys with the real world code and if you think its a good idea to have these shortcuts then fine.
 

 
Personally what describes JsInterop best is the alternative using the import/export concepts. So I would stick with:

Import/Export types:

@JsImport: can only be applied on interfaces.
@JsExport: always exports all public API in a given scope (package, class, method). So no ALL, INSTANCE_MEMBER, STATIC_MEMBER distinction. @JsIgnore can be used to opt-out selectively.

Configure import/export:

@JsNamespace: import/export from/to namespace
@JsName: workaround reserved keywords
@JsProperty: mark method as JS property
@JsIgnore: opt-out of export. Might even be useful for import, e.g. do not generate trampoline for annotated default method.

Special constructs:

@JsLiteral


This is the one I listed as Alternative 2 in the doc (Option 3 as I re-listed in the recent email). It doesn't hold water well; works well for simple but gets confusing quickly as there are many gotchas.

Honestly I don't get the gotchas probably because I am not so deep into the matter. Basically you say for Javascript callbacks it is unclear if import or export should be used. But for that case we would have @JsFunction which simply does the right thing and you do not have to think about it. Also the @JsMethod annotation does not exists in the spec so not sure about that one.

Can you give me a confusing example?
 

-- J.

Goktug Gokdogan

unread,
Apr 23, 2015, 12:34:50 AM4/23/15
to google-web-toolkit-contributors
On Wed, Apr 22, 2015 at 3:06 AM, Jens <jens.ne...@gmail.com> wrote:
- exports vs. export is a bit misleading. One must be used with interfaces/classes the other with methods. That issue only exists because @Js alone has no real meaning.

Mostly agreed though @Js alone has meaning. As there are no exports, the methods can be pruned. This difference will be more significant when we change the output style to be more idiomatic javascript and have a closer integration with Closure compiler in the future.

Yeah ok probably bad wording from my side. @Js is equivalent to @JsImport. 

What I wanted to express is that the export flag is kind of bad because its a "marker" and the act of marking something for export should already be covered by applying the corresponding annotation. So if you would have @JsExport then applying @JsExport automatically means that it is marked for export. You don't need that boolean flag then. You would end up with @JsExport(STATIC_MEMBERS) for types and @JsExport for members which seems more straight forward (yes I know @JsExport(STATIC_MEMBERS) applied to a member seems strange but you would have the same issue with @Js).

Also see next inline answer.


IIUC, even if we go with the Option 1, you are arguing JsExport should be separate than JsType. I disagree with that. With the new semantics, JsExport requires JsType; that is better to be explicit; when it is explicit JsExport alone doesn't have a meaning. 'marking' on top of JsType seems better to me as an attribute in JsType but I'll think more about that.
 
 
 
- You have to type more because it is always annotation name + property + value if you can't stick with the default "@Js".

I'm assuming you are comparing Option 1 and Option 2 (see my recent email). Based on that I'm not sure which part you are referring. Can you give an example?

I think what bugs me most are the boolean properties that are used as "markers", e.g.

@Js(property = true)
@JsProperty

@Js(export = true, property = true)
@JsExport @JsProperty

@Js(export = false)
@Js(ignore = true)
@JsIgnore

It simply reads better without these flags. It is not so much an "issue" for more complex examples, e.g.

@Js(exports = ALL, namespace = GLOBAL, name = "Foo")
@JsExport(ALL) @JsNamespace(GLOBAL) @JsName("Foo")


I think this comes to more of aesthetics and personal preferences. Multiple annotations easily convert into an annotation hell where you can easily hit 3 annotations for members; and I can even come up with cases where you need even 4:
@JsExport @JsNamepace("...") @JsName("..") @JsProperty

Also single @JsIgnore doesn't have the same level of expressibility as setting export and ignore independently. Also then you have the problem when you apply JsIgnore on top of JsName or JsProperty.
 

 
- Can't see a good use case for splitting exports in ALL, INSTANCE_MEMBERS and STATIC_MEMBERS. When I want to export a class I want to export its public API.


Yes, that was my assumption as well and that's how I started. But looking at real life code inside Google, especially in places where the code is shared by different platforms, I see people choosing to only export instance members or static members. I will need to re-evaluate this after we migrate all Google code to new annotations.

Interesting. Wondering why that is the case. Given that it is just a shortcut to avoid lots of @Js(ignore=true) or @Js(export = true) annotations it is not so much of an issue. However I am wondering if the shared Java code would end up better if we enforce the "all public API will be exported unless you opt out using @JsIgnore" route. For example a developer would then have created a static factory class and then choose to export that factory or not.

But I guess we'll just trust the guys with the real world code and if you think its a good idea to have these shortcuts then fine.
 

 
Personally what describes JsInterop best is the alternative using the import/export concepts. So I would stick with:

Import/Export types:

@JsImport: can only be applied on interfaces.
@JsExport: always exports all public API in a given scope (package, class, method). So no ALL, INSTANCE_MEMBER, STATIC_MEMBER distinction. @JsIgnore can be used to opt-out selectively.

Configure import/export:

@JsNamespace: import/export from/to namespace
@JsName: workaround reserved keywords
@JsProperty: mark method as JS property
@JsIgnore: opt-out of export. Might even be useful for import, e.g. do not generate trampoline for annotated default method.

Special constructs:

@JsLiteral


This is the one I listed as Alternative 2 in the doc (Option 3 as I re-listed in the recent email). It doesn't hold water well; works well for simple but gets confusing quickly as there are many gotchas.

Honestly I don't get the gotchas probably because I am not so deep into the matter. Basically you say for Javascript callbacks it is unclear if import or export should be used. But for that case we would have @JsFunction which simply does the right thing and you do not have to think about it. Also the @JsMethod annotation does not exists in the spec so not sure about that one.

Can you give me a confusing example?
 

Let me give you some examples.
 - An interface that could be implemented both by javascript or java; should be exported or imported or both?
 - For the non-function callback contracts from javascript (e.g. web component lifecycle callbacks), should they by imported or exported?
 - I can apply JsExport at class level and member level; by symmetry what happens if I apply JsImport at member level?
 - Import and export concepts immediately fails to extend into JsFunction, JsLiteral; so on one side you have importing/exporting concept ('type' concept is lost) and on the other-side you have completely disjoint function/literal concept. This is really bothersome, especially if you go into mixing annotations like JsExport and JsFunction on the same type.

The behavior becomes ambiguous most of the time so it is difficult to explain (and even decide!); on the other hand the alternatives are way simpler in this respect.
 

-- J.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-co...@googlegroups.com.

Stephen Haberman

unread,
Apr 23, 2015, 10:08:47 AM4/23/15
to 'Goktug Gokdogan' via GWT Contributors

I'm attempting to follow along. No real input, other than I did
not realize the (perhaps obvious) nuance of JS interop handling
both importing (e.g. wrapping DOM and existing libraries) and
exporting. And how callbacks are both-ish. Huh.

- Stephen


On Wed, 22 Apr 2015 21:34:27 -0700
"'Goktug Gokdogan' via GWT Contributors"
<google-web-tool...@googlegroups.com> wrote:

> >>> <https://groups.google.com/d/msg/google-web-toolkit/PHtfLTSAJDM/oJjAo3qWa7sJ>
> >>> )
> > <https://groups.google.com/d/msgid/google-web-toolkit-contributors/0e267848-d4ec-4121-899e-a9a09b47c9e7%40googlegroups.com?utm_medium=email&utm_source=footer> .

Marcin Okraszewski

unread,
Apr 28, 2015, 10:15:25 AM4/28/15
to google-web-tool...@googlegroups.com
I agree - I much more prefer the separate annotations. 

Regards,
Marcin Okraszewski
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-contributors+unsubscribe@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-contributors+unsubscribe@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-contributors+unsubscribe@googlegroups.com.

Marcin Okraszewski

unread,
May 4, 2015, 9:58:32 AM5/4/15
to google-web-tool...@googlegroups.com
Hi,
I can a bit give my perspective as I understood @JsType and @JsExport seeing it only on conference presentations. It most likely is quite off the intended semantics:
- @JsType - bridge when initiative is started by GWT code. So, we call JS and get JS object from it. Or we want to pass object to JS function. But JS cannot call it without getting object from GWT. 
- @JsExport - where the initiative of invoking is on JS side. So, JS code doesn't need to have any object passed from GWT. It out of the blue calls some method or creates a GWT object. 

I thought that once I annotate a class (and constructor) with @JsExport, then JS code will be able to instantiate object and interact with it, without @JsType annotation.

My personal taste on annotations is that the name should describe major action it is causing. The parameters should be attributes that fine tune it, but you should not look into parameters to figure it out what it does. So, overriding name and namespace in JsExport is fine to me, as it exports under specific namespace and name. Though, the question what is the main purpose of @JsMember? It just tells we will do something with it, but you need to read parameters to understand if it won't be exported or it will be seen as property. Therefore, I would rather still split @JsMember into supported cases: @JsProperty, @JsIgnore (don't know why this one is not expressive enough), ... . It is like a meta method call. Method name should tell what it does and parameters are inputs to execution, but perfectly don't have a lot of distinct execution path based on values. 

Marcin

-- J.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-contributors+unsubscribe@googlegroups.com.

Alex White

unread,
May 9, 2015, 5:44:41 PM5/9/15
to google-web-tool...@googlegroups.com
+1 to keeping the original system. For an interface a finite number of types > infinite number of String parameters.
Once it gets properly documented on gwtproject.org I doubt people will consider it confusing. The problem imo is that most of the existing stuff out there is pseudocode.

We just started using JsInterop and the only stumbling block we encountered was that at first we weren't using @JsNamespace.
The other thing we have found is really weird bugs in some of the nightlies a few days ago, like types deleted from our codebase still existing and other new types not existing.
It was from about 4-7 days ago and seems to have stopped now. It may be related to the sourcemaps. The emergent behavior was that after a hard cache reset Chrome would be trying to fetch a sourcemap for a deleted type. If we grepped for that symbol in our codebase, we would find references to it despite it being long gone in a cleanly built proj. Does the gwt compiler keep some state information hidden somewhere on the hd? Because that was weird.
...

Ray Cromwell

unread,
May 9, 2015, 9:35:08 PM5/9/15
to google-web-toolkit-contributors
There are multiple things JsInterop needs to accomplish:

1) preventing method/field renames
2) pinning methods (preventing code pruning)
3) giving a global name/namespace alias to something
4) auto-converting parameters to allow idiomatic programming
5) allowing GWT objects to extend native objects

@JsType actually combines #1/#2/#5 (although it only pins methods if the object is instantiated)
@JsExport combines #2 and #3 (it not only pins a method, but treats the type as instantiable, plus it gives it a global alias)

#4 is handled by @JsConvert/JsAware/JsFunction

#5 is handled by @JsType(prototype="...")

Goktug is trying separate out the behavior into the 5 types of interop semanics:

1) a way of indicating a method/field should not be renamed
2) a way of indicating not to prune something
3) a way of indicating giving a global alias to something
4) a way of indicating something extends a native object

There are cases where you want to prevent renaming, but allow dead code elimination. 

You could make these separate annotations, that's matter of aesthetics, e.g.

@JsPin
@JsExport
@JsName
@JsPrototype

etc



--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/google-web-toolkit-contributors/50de995a-3b18-4432-9b79-76ae51940729%40googlegroups.com.

Goktug Gokdogan

unread,
May 19, 2015, 6:18:14 PM5/19/15
to google-web-toolkit-contributors, Paulo Schlup
Thanks for the nice summary Ray.

This is still work in progress but here is the tentative list of annotations and details of the new semantics. Please play with it and continue making suggestions.

@JsConstructor
JsConstructor marks a constructor so that it will be the constructor function for the JavaScript type. Note that, there could be only one JsConstructor in a type and all other constructors should be delegating to it.

public @interface JsConstructor {
  /**
   * If a constructor is exported, then it will be not be pruned by the compiler.
   */
  boolean export() default false;
}

@JsMethod
JsMethod marks a method in a type as a method that will be directly translated into a JavaScript method without any obfuscation to its name. Note that, while instance members are slotted in the prototype, class members will be defined under the constructor function of the type.

public @interface JsMethod {
  /**
   * Customizes the name of the method in generated JavaScript. If not provided, the Java name will
   * be used.
   */
  String name() default "";

  /**
   * If a method is exported, then it will be not be pruned by the compiler. Note that if the class
   * is pruned then instance members will also be pruned even they are exported (i.e. exporting
   * instance members doesn't prevent class pruning).
   */
  boolean export() default false;
}

@JsProperty:
JsProperty marks a field in a type as a method that will be directly translated into a javascript property without any obfuscation to its name.
If it is applied to a method, it will be treated as a property accessor. As a result, instead of translating method calls to JsProperty methods as method calls in JS, they will be translated as property lookups. When a JsProperty method implemented by a Java class, such methods will be generated as custom property setter and getter in JavaScript, hence the property access will trigger the execution of the matching getter or setter methods.

JsProperty follows JavaBean style naming convention to extract the default property name. If the JavaBean convention is not followed, the name should be set explicitly. For example:
  @JsProperty getX() or @JsProperty isX() translates as <tt>this.x</tt>
  @JsProperty setX(int y) translates as <tt>this.x=y</tt>

Note that, while non-static member are slotted in the prototype, static members will be defined under the constructor function of the type.

public @interface JsProperty {
  /**
   * Customizes the name of the member in generated javascript. If none is provided;
   * <p>
   * <li>if it is field, the simple Java name will be used.
   * <li>if it is a method, the name will be generated based on JavaBean conventions.
   */
  String name() default "";

  /**
   * If a method is exported, then it will be not be pruned by the compiler. Note that if the class
   * is pruned then non-static members will also be pruned even they are exported (i.e. exporting
   * non-static methods doesn't prevent class pruning).
   */
  boolean export() default false;
}

@JsType:
JsType is used to describe the JavaScript API of an object, either one that already exists from the external JavaScript environment, or one that will be accessible from the external JavaScript environment.

Marking an object with JsType is similar to marking each public member of the class with {@link JsProperty}/{@link JsMethod}/{@link JsConstructor} respectively. In order for this to work correctly the JavaScript name needs to be unique for each member. Some unobvious ways to cause such name collisions are:
 * Having method or constructor overloads.
 * Using the same name for a method and a field.
 * Shadowing a field from parent.

A name collision needs to be avoided by providing a custom name (e.g. {@link JsProperty#name}) or
by completely ignoring the member using {@link JsIgnore}.

If the JsType is marked with a prototype reference, then classes extend from this will use the specified prototype as opposed to the ordinary one (e.g. java.lang.Object).

JsTypes act like JavaScriptObject in terms of castability, except when a prototype is specified, in which case, cast checks and instanceof checks will be delegated to the native JavaScript instanceof operator.

public @interface JsType {
  /**
   * Customizes the name of the type in generated javascript. If not provided, the simple Java name
   * will be used.
   */
  String name() default "";

  String prototype() default "";

  /**
   * Setting export here is a shortcut for setting export for each individual member of the class.
   * TODO: might replace with export={ALL, CLASS_MEMBERS, INSTANCE_MEMBERS} instead.
   */
  boolean export() default false;
}

@JsIgnore:
Marks a member to be ignored for JsInterop purposes.
This is particularly useful when {@link JsType} applied to a class and some members are needed to be ignored as they don't comply with restrictions (e.g. overloading) or shouldn't be exported.

public @interface JsIgnore {
}

@JsNamespace / @JsFunction: No changes.

@JsExport / @JsNoExport: Deleted.

Goktug Gokdogan

unread,
May 19, 2015, 6:21:34 PM5/19/15
to google-web-toolkit-contributors, Paulo Schlup
Here is the comparison chart:


@JsExport @JsType

class MyClass {}

@JsType(exports=ALL)

class MyClass {}

@JsExport

class MyClass {}

@JsType(exports=STATIC_MEMBERS)

class MyClass {}

@JsExport(“Name”)

class MyClass {}

@JsType(name=”Name”, exports=STATIC_MEMBERS)

class MyClass {}

@JsExport(“Name”) @JsType

class MyClass {}

@JsType(name=”Name”, exports=ALL)

class MyClass {}

@JsType

class MyClass {}

@JsType(exports=INSTANCE_MEMBERS)

class MyClass {}

@JsType

interface MyInterface{}

@JsType(exports=INSTANCE_MEMBERS)

interface MyInterface{}

@JsType

interface ImportedLibrary{}

@JsType

interface ImportedLibrary{}

@JsType

interface ImportedLibraryCallback{}

@JsType(exports=INSTANCE_MEMBERS)

interface ImportedLibraryCallback{}

@JsExport

SomeConstructor{}
@JsExport

static void someStaticMethod{}
@JsExport

static Object someStaticField;

@JsConstructor(export=true)

SomeConstructor{}
@JsMethod(export=true)

static void someStaticMethod{}
@JsProperty(export=true)

static Object someStaticField;

@JsExport(“someName”)

static void someStaticMethod{}
@JsExport(“someName”)

static Object someStaticField;

@JsMethod(name=”someName”, export=true)

static void someStaticMethod{}
@JsProperty(name=”someName”, export=true)

static Object someStaticField;

@JsNoExport

static void someStaticMethod{}

@JsMethod(export=false) or @JsIgnore

static void someStaticMethod{}

@JsNoExport

void someMethod{}

@JsIgnore

void someMethod{}

@JsProperty

void someMethod{}

@JsProperty

void someMethod{}

@JsProperty(“someName”)

int someField;
@JsProperty(“someName”)

void someMethod{}

@JsProperty(name = “someName”)

int someField;
@JsProperty(name = “someName”)

void someMethod{}

Juan Pablo Gardella

unread,
May 20, 2015, 8:16:52 AM5/20/15
to google-web-tool...@googlegroups.com, Paulo Schlup

Colin Alworth

unread,
May 20, 2015, 9:48:37 AM5/20/15
to google-web-tool...@googlegroups.com, Paulo Schlup
At least in my cases, I'm more interested in having access to the tools and APIs of the JS runtime within the browser and compiling my code out as small as possible - while there are cases where I want to see my own types exported and un-optimized, I think that might be the exception rather than the rule.

The long term goal of JsInterop seems to be to replace JSNI and JSOs entirely, and while there are cases of exporting that will *also* be covered by this, exporting by default means telling the compiler "by default, anything that interacts with the underlying browser can't be optimized out even if you don't use it".

Reading and writing JSON from a server, wrapping browser or other JS APIs, etc are all cases where export=false makes more sense. Exporting is also a goal, but I suspect that considering all users of this feature, its not the primary goal.

Goktug Gokdogan

unread,
May 20, 2015, 1:39:54 PM5/20/15
to google-web-toolkit-contributors, Paulo Schlup
+1 to what Colin said.

To add to that, 'export' is for exposing something to the outside of the binary, in time with tighter closure integration even less things will fall outside of the binary.

Marcin Okraszewski

unread,
May 22, 2015, 5:43:39 AM5/22/15
to google-web-tool...@googlegroups.com, psc...@google.com
Hi,
It looks pretty readable to me. Actually my first reaction was also that why export is default false, as initially I thought some of the annotations were used for exporting. So I assume that if for instance I use some JS library, then eg. @JsMethod(name="bar") would specify what JS method is called, regardless of name in Java. Is it correct?

Marcin
...

Goktug Gokdogan

unread,
May 22, 2015, 2:48:45 PM5/22/15
to google-web-toolkit-contributors, Paulo Schlup
Yes, that's correct.

Marcin
...

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-co...@googlegroups.com.

Marcin Okraszewski

unread,
May 22, 2015, 3:11:27 PM5/22/15
to google-web-tool...@googlegroups.com
Then +1 from me.

Marcin

--
You received this message because you are subscribed to a topic in the Google Groups "GWT Contributors" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/google-web-toolkit-contributors/Dwdk-aEHs8Y/unsubscribe.
To unsubscribe from this group and all its topics, send an email to google-web-toolkit-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/google-web-toolkit-contributors/CAN%3DyUA0S5%3DS0mvjS-%2BCx_2%2BG5wvLs0FhJ9cDhh80t6HCdRfoZw%40mail.gmail.com.

Jens

unread,
May 22, 2015, 3:22:58 PM5/22/15
to google-web-tool...@googlegroups.com, psc...@google.com
Seems to look good. Seems pretty similar to the single @Js annotation version but reads better because of separate @JsMethod/@JsProperty/@JsConstrutor annotations and gives the possibility to add specific attributes to just one of them if ever needed.

Two short questions:

- JsConstrutor does not have a name attribute so maybe its worth documenting that you can not use JavaScript reserved words as (yes, ugly lower case) class names when @JsConstructor(export = true) is used?

- Currently name and export attributes are repeated through @JsType/Constructor/Method/Property. Is there any reason why @JsNamespace stands alone? For consistency I would probably convert @JsNamespace into an attribute as well, especially if @JsNamespace can be used with all four annotations.


-- J.

Goktug Gokdogan

unread,
May 22, 2015, 11:38:46 PM5/22/15
to google-web-toolkit-contributors, Paulo Schlup
On Fri, May 22, 2015 at 12:22 PM, Jens <jens.ne...@gmail.com> wrote:
Seems to look good. Seems pretty similar to the single @Js annotation version but reads better because of separate @JsMethod/@JsProperty/@JsConstrutor annotations and gives the possibility to add specific attributes to just one of them if ever needed.

Two short questions:

- JsConstrutor does not have a name attribute so maybe its worth documenting that you can not use JavaScript reserved words as (yes, ugly lower case) class names when @JsConstructor(export = true) is used?


We will have a check for this but I agree it is a good idea to document this in javadoc.
 
- Currently name and export attributes are repeated through @JsType/Constructor/Method/Property. Is there any reason why @JsNamespace stands alone? For consistency I would probably convert @JsNamespace into an attribute as well, especially if @JsNamespace can be used with all four annotations.



Export and name attributes are always valid to set while namespace can be set on only static members. Also we need it so that we could set the namespace on package-info file.

An alternative solution would be introducing JsPackage annotation to use with package-info file and introduce namespace attribute where applicable. That would be more consistent with the rest.
 
-- J.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-co...@googlegroups.com.

Goktug Gokdogan

unread,
Oct 12, 2015, 6:37:00 PM10/12/15
to google-web-toolkit-contributors, Paulo Schlup
FYI,

After the introduction of native JsTypes[1], the value of an explicit "export" attribute is minimal to none.

We don't generate any code for native types anyway which is only place that you might consider having a JsType without an export so attribute doesn't add any value there.
If you extend a native JsType, you probably want the overridden methods exported anyway so we are good.

I will remove them from my latest patch and submit it so that you can start using new annotations and give us feedback.

Without the "export", everything is much simpler. I will share the updated JsInterop doc with the latest spec this week.

Cheers.

Reply all
Reply to author
Forward
0 new messages