Coder Social home page Coder Social logo

Comments (8)

gkdn avatar gkdn commented on June 18, 2024

Thanks for sharing @realityforge. This is an interesting project.

We also actually started with IDL and due to many IDLs having inconsistent extension it turned out to be quite painful. After that we started looking into d.ts and later finally settled down on Closure externs. @jDramaix could better summarize all the different pro/cons discovered during this process but IIRC essentially matching Closure externs was the lowest maintenance option and ultimately most important target for us since those are what J2CL code was checked against. There are lots of thought put into externs and TypeScript typings so that is quite valuable to inherit as well.

Comments and ability to use data from MDN is very helpful. We actually always wanted such a feature. It is great to incorporate them into generated APIs.

Following are also actually quite interesting as well. I would like to discuss more about them:

jsinterop action generates source code with a more java-esque feel than is present"

I think we can incorporate some of those ideas. Let me know if there are other stuff not covered in your post.

Constants in WebIDL are represented as constants in java. This typically results in smaller code size and may open
up additional optimization opportunities.

This one is; I'm not sure. Why do they typically result in smaller code size? You will be embedding them to your final output instead of reading them from browser.

Read-only attributes in WebIDL are implemented as methods rather than mutable fields of properties with setters.

setters vs. getters is one of things we get back and forth but I'm not sure generating them inconsistently based on const is good. I would like to hear your take on it.

Dictionaries in WebIDL use a "builder" pattern to make construction of these types much easier.

What kind if builders do you use? Any problems did you hit?

@jDramaix what did we end up settling for with dictionaries, just plain objects?

Event handlers and event listeners are typed according to type of event expected to be delivered and have a void
return type.

Could you explain more how did you diverge from externs here?

@JsOverlay methods are added for known events emitted by an interface.

How do you extract known events?

WebIDL enumerations are annotated with the @MagicConstant

I think this is not good since you miss the type safety here. I wonder why you preferred this over enums?

Several other minor usability improvements, particularly with respect to union types.

Would like to hear more :)

from elemental2.

realityforge avatar realityforge commented on June 18, 2024

FWIW This library uses the same technique that is used to generate the Typescipt typings. It also started with the set of IDL sources as the typescript tool but it has evolved since then.

I would be interested in why WebIDL was problematic. Once I had a parser in place, extracting the WebIDL from the specifications was relatively easy and IIRC retrieving and parsing the entire gecko WebIDL was fine to. Partially as it is collected in one place. I looked at chrome and that seemed relatively easy to parse although I did not try and the WebIDL is scattered all over their repository. So I would need to figure out which ones to collect and parse. I don't see anything too arduous involved in parsing the chrome IDLs but it is a bit of grunt work. My ultimate goal is to read both of those WebIDL sources and blend it with the spec sourced WebIDL to get maximum coverage.

The specs do require some massaging at times. Sometimes because spec versions evolved and sometimes because WebIDL can not represent some differences. However I have a pipeline of transforms that make this relatively easy.

I can understand why you would want to stick with existing closure externs as it seems you guys have a lot of code going back years that makes changing those externs difficult. There is a lot of knowledge in the externs but a lot of it is dated. I submitted a few PRs to remove code that no browser had supported in ~8 years IIRC :) There is also some constructs that are just not representable in closure type system present in some specs (i.e. See WebAssembly externs).

The prospect of an automated, browser compliant set of closure externs that had low maintenance costs was what drove me to this project. Ensuring compatibility and a nice API for the WebGPU, WebXR and the GL* specs seemed like something painful to track ... I am sure this is true i other specs as well but 🤷‍♂️

Constants in WebIDL are represented as constants in java. This typically results in smaller code size and may open
up additional optimization opportunities.

This one is; I'm not sure. Why do they typically result in smaller code size? You will be embedding them to your final output instead of reading them from browser.

It all depends on the specific constants but in most cases the symbol that represents the constant is larger than the underlying constant. And this is the primary way by which code ends up smaller. Then in some specific circumstances other optimizations happen.

Unfortunately jsinterop-generator+constants result in <clinit> being present in sometimes. A limitation wither either gwt or jsinterop-generator depending on who you ask but an issue regardless ;)

For some constant integer values either GWT or javac performs a few more optimizations. i.e. if( WebSocket.CONNECTING == webSocket.readyState ) translates to the spiritual equivalent of if( !webSocket.readyState ). For other integer constants that participate in bit operations (just WebGL API?) these can be baked ahead of time. This is largely code golf but no reason not to do so when it can be automated ;)

For strings, GWT pulls the strings out into a string table and aliases the constant with a var which is usually smaller than the symbol and the string can be reused multiple times. We use brotli to compress the assets and that did not eliminate this advantage compared to using an extern. I haven't tried gzip but brotli is better in almost every test we run so I assume the same is true for it.

jsinterop action generates source code with a more java-esque feel than is present"

I think we can incorporate some of those ideas. Let me know if there are other stuff not covered in your post.

There is a few - I will try to collect and write up somewhere when I get a chance

Read-only attributes in WebIDL are implemented as methods rather than mutable fields of properties with setters.

setters vs. getters is one of things we get back and forth but I'm not sure generating them inconsistently based on const is good. I would like to hear your take on it.

Other developers seemed to like this ... although several also requested I add getters and setters for WebIDL attributes that are currently represented as fields to make it more "java-like" but I have so far resisted such things as I like copy-pasting javascript code and translating ;)

Dictionaries in WebIDL use a "builder" pattern to make construction of these types much easier.

What kind if builders do you use? Any problems did you hit?

The pattern is best understood by looking at RTCReceiverAudioTrackAttachmentStats. The only problem is that GWT does not seem to optimize method chains that return this. Closure seems to have no problem. Although I have not dug into the cause and if it can be improved by changing the code.

Event handlers and event listeners are typed according to type of event expected to be delivered and have a void
return type.

Could you explain more how did you diverge from externs here?

The spec defines the WebIDL for event handler as

[LegacyTreatNonObjectAsNull]
callback EventHandlerNonNull = any ( Event event );

typedef EventHandlerNonNull? EventHandler;

and closure extern defines event handlers on each event handler property. Historically they have been defined sufficiently for use in javascript (i.e. @type {?function(Event)} or @type {?function(!Event)}). For ones I was using, I started to submit changes so the externs started to return void ala

/**
 * An event handler called on open event.
 * @type {?function(!Event): void}
 */
WebSocket.prototype.onopen;

and in some cases adding the "correct" event typing ala

/**
 * An event handler called on close event.
 * @type {?function(!CloseEvent): void}
 */
WebSocket.prototype.onclose;

These patches took a long time to get through at times and some were rejected because it had too great an impact on internal codebases ... or so I believe. Given sufficient time and effort the changes could be applied across the closure externs. I actually considered writing a WebIDL parser back then to help me do this ;)

Event listeners are a bit more a synthetic artifact. For example the WebSocket.prototype.onclose event handler is actually mirrored by an underlying close event. The is detected by scraping MDN through a chain of actions.

  1. We scan and fetch the WebIDL from the whatwg HTML spec, which defines WebSocket type and download it to whatwg_html
  2. Then we scan the MDN website based on a bunch of heuristics we end up at https://developer.mozilla.org/en-US/docs/Web/API/WebSocket where scrape links to other properties, functions and event pages and download the resulting page data to https://github.com/realityforge/webtack/tree/master/data/docs/WebSocket
  3. This eventually results in close event data downloaded to https://github.com/realityforge/webtack/blob/master/data/docs/WebSocket/close_event.json
  4. A processing pipeline will pick this data up and will decorate the idl (using a custom event definition syntax) as well as change the type of the event hander property to match the data.
  5. The jsinterop action then reads this idl and will generate the CloseEventListener as well as https://github.com/react4j/react4j-vchat/blob/master/src/main/java/elemental3/WebSocket.java#L296-L328

You could probably use the data I have already scraped along with a small mapping table (as not all closure extern types are named as the underlying browser types) and add the same transform to event handlers, event listeners and add javadocs as well in Elemental2. If you wait a month or two I will hopefully have got off my butt and scraped method parameters and return types too! You may also be able to completely eliminate the integer type listing in elemental2 by reading from the webidl data already fetched and looking at the types in the webidl. It also means you can use more precise types (i.e. double, float, int, short rather than just integer or double)

WebIDL enumerations are annotated with the @MagicConstant

I think this is not good since you miss the type safety here. I wonder why you preferred this over enums?

I originally went down this path but we are still on GWT :( I wanted a zero cost abstraction whereas enums in GWT result in all values unable to be DCEd. I presume @JsEnum in J2CL eliminates this issue but I haven't experimented and it is likely that when we get to J2Cl we will re-asses this part.

@MagicConstant gives an okay safety in intellij in that you get big red wiggly lines and compile errors if you use the wrong one but it also allows you to use browser specific values that I thought may be required ... although in our experiments we have yet to require any non-standard enum values.

from elemental2.

realityforge avatar realityforge commented on June 18, 2024

I just had a poke around and it looks like replacing the current enumeration strategy with a "real" java enumeration annotated with @JsEnum is reasonably trivial (and results in much simpler code in webtack). This could probably be supported in tandem without too much effort.

from elemental2.

realityforge avatar realityforge commented on June 18, 2024

It should also be noted that there is a handful of scenarios that closure externs can represent typing not available in WebIDL that you end up having to special case. The most obvious case being MessagePort.postMessage() where the transfer list should be types that have the extended property [Transferable] attached but custom code is required when translating to java or closure externs.

from elemental2.

jDramaix avatar jDramaix commented on June 18, 2024

So let me jump into this conversation.
First thanks for sharing our work with us @realityforge

We also actually started with IDL and due to many IDLs having inconsistent extension it turned out to be quite painful. After that we started looking into d.ts and later finally settled down on Closure externs. @jDramaix could better summarize all the different pro/cons discovered during this process

When I started the project 5 years ago, I used the WebIdl road. I had the prototype working using WebIdl from firefox and Chrome but we were not really happy with the generated api and decided to go with externs files. I don't remember exactly all the problems we reached with WebIdl but the main ones were:

  • api desynchronization with extern files provided by closure compiler. This is a big issue for j2cl apps and requires that you generate your own extern files and uses them instead of using the standard ones. This is not working for apps that uses a mix of j2cl and closure code.
  • Typing is less expressive in WebIdl. You don't have type parameters (generics) for example (except for a few core type like promise, sequence...)
  • no WebIdl for javascript core api. elemental2-core and elemental2-promise cannot be generated from WebIdl as there is no WebIdl for that.

@jDramaix what did we end up settling for with dictionaries, just plain objects?

Dictionaries are java interface in Elemental2. We have a static factory method on each dictionary interfaces in order to instantiate an empty instance and then you can use setters. We can add builder pattern if needed: we had a discussion about that in #6

In general, I think the improvements you mentioned can be added to Elemental2 instead of creating a new library. We can use WebIdl and any other sources of information for creating metadata files used by jsinterop generator for generating better code. I created the initial version of Integer-entities files by parsing WebIdl files for example.

from elemental2.

realityforge avatar realityforge commented on June 18, 2024
  • api desynchronization with extern files provided by closure compiler. This is a big issue for j2cl apps and requires that you generate your own extern files and uses them instead of using the standard ones. This is not working for apps that uses a mix of j2cl and closure code.

Still problematic if you live in that world.

  • Typing is less expressive in WebIdl. You don't have type parameters (generics) for example (except for a few core type like promise, sequence...)

Typing is different between WebIDL and closure externs. There are several constructs that the closure typing system is unable to represent adequately that are present in WebIDL and thus web APIs. WebIDL is primarily about representing a browser API and thus I haven't seen many problems generating a browser API layer from it. There is occasionally a construct I would like to see generated that is not representable in either WebIDL or closure-externs but is representable in more expressive system like Typescript (HTMLCanvaseElement.getContext(...) being an obvious example) but nothing that I have wanted that closure-externs can represent that is not available in WebIDL.

FWIW a different perspective: When the closure externs introduced some of the parameterised types into core (and thus elemental2 classes stopped being able to compile with linting enabled) was when we got off the Elemental2 train. We didn't find that it offered many benefits to java developers but YMMV.

  • no WebIdl for javascript core api. elemental2-core and elemental2-promise cannot be generated from WebIdl as there is no WebIdl for that.

Still an issue. However the code currently part of elemental2-(core|promise) is not always the easiest to work with as is. I have experimented with defining some types in WebIDL and generating from that but even that has not produced great code. For this I am looking at a combination of WebIDL and manually written code. The hope is that with some hand hackery, working with types like JsArray will not be too onerous.

In general, I think the improvements you mentioned can be added to Elemental2 instead of creating a new library. We can use WebIdl and any other sources of information for creating metadata files used by jsinterop generator for generating better code. I created the initial version of Integer-entities files by parsing WebIdl files for example.

Given enough effort it could be possible.

Once we have got all the data assembled I will send another ping ;)

from elemental2.

gkdn avatar gkdn commented on June 18, 2024

Keep in mind that typings are opinionated. There are coercions done with the API so many APIs would get any type (and I expect WebIDL to reflect that) however extern will limit that.

For the record, I am expecting externs to be eventually replaced by TypeScript typings but I will not hold my breath for it.

from elemental2.

realityforge avatar realityforge commented on June 18, 2024

FWIW worth I eventually got back to this and have got the library close to rc1. I am mostly pretty happy where it got to. Unfortunately the jsinterop classes differ between J2CL and GWT friendly libraries so I ended up with same java code but annotated slightly differently in the j2cl variant and j2cl variant also needs the generated externs and does not need the gwt module. Hence two artifacts.

You can see the library as it stands at https://github.com/akasha/akasha but if you just want to browse the j2cl variant of the jsinterop classes you can check out https://github.com/akasha/akasha-java/tree/master/akasha (and the externs at https://github.com/akasha/akasha-java/blob/master/akasha/akasha.externs.js).

Anyhoo ... this issue can probably be closed now. Thanks for allowing me to bounce ideas off you!

from elemental2.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.