#1111 Using @Js facet

brian Sat 5 Jun 2010

Probably best to start a new discussion for this; @rfeldman wrote:

On the subject of Fantom targeting JavaScript (one of my favorite parts of the language, for sure!) - is the @Js annotation really the best way to specify JS-targeted classes?

It's always struck me as inconsistent; in all other respects, Fantom code is agnostic to its build target, and details of what should happen when you build are confined to its pod/build config. Only in specifying which classes are JS-targeted does Fantom deviate from this paradigm (that I'm aware of) and it seems to be both inconsistent and a bad precedent.

Suppose in the future Fantom targets another platform for which specifying on a per-class basis. Are we then going to have @Js and @OtherTargetPlatform annotations on every class where we want to target both? And if there are three such targets, then up to three annotations per class? I mean, certainly in 2010 this is not a realistic concern, but I assume you guys want this language around for the long haul and the unpredictable future that comes with it.

So am I just missing something, or wouldn't it make more sense to specify JS-targeted files in the pod/build config, using a list of file and/or directory pattern matchers to specify which ones are JS-targeted?

I think thats a good point and worthy of discussion.

The original thoughts behind that are this: in the case of the JVM or .NET we are working with full featured platforms so everything should port fully. This should also be true if porting to Parrot, LLVM, etc. However JavaScript (at least in the browser) is a limited platform.

But I think (especially now with Actors moved into concurrent pod) that you can make the case that JS should be an all or nothing thing at the pod level. Although in our production code we do mix client and server APIs together in a single pod and use the @Js annotation to denote the difference.

I'd like to hear some other thoughts.

dfreire Sat 5 Jun 2010

I'm new to Fantom, therefore still not acquainted with its capabilities and differences under different platforms. But if I were to choose Fantom for serious projects, my main concern about the future would be related to the fact that by trying to produce 100% cross platform compatible code, Fantom would be limited to the intersection of what can be done in each platform, and therefore not reaching the full potential in any of the platforms.

Maybe this is not so evident with JVM and .NET, but it becomes very clear when we try to fit JavaScript in the same picture. JavaScript has problems of its own, and at this point of its development we can't even talk about a single JavaScript standard being followed everywhere. (Although the future looks very promising in this regard with ECMA 5 and CommonJs).

So the part where "Fantom code is agnostic to its build target" for me, it raises a yellow flag. To me, as a Fantom user, it would be completely acceptable to have parts of the Fantom language that can be used in JavaScript environments and parts that can't. In fact it would be even a future-wise assurance to know that Fantom can operate on a "by platform" basis.

Given that, I would prefer the @Js annotation instead of growing the complexity of configuration files. But I accept that having "@Js and @OtherTargetPlatform annotations on every class where we want to target both" and "if there are three such targets, then up to three annotations per class" looks annoying.

JohnDG Sun 6 Jun 2010

Over time, desktop APIs will move more in the direction of browser APIs (getting asynchronous IO, actors instead of threads, geolocation, storing data in the cloud instead of locally), and browser APIs will move more in the direction of desktop APIs (local and cloud-based persistence, sockets, 3D graphics, etc.). This is inevitable and nothing can stop it -- it's best to embrace the future.

By baking cross-platform into Fantom, you're making a forward-thinking decision that will positively affect adoption of the language. Fantom is not likely to succeed as an incremental improvement over Java, but it is likely to succeed as a disruptive innovation: one that embraces the reality that software development is crossing over to the browser.

A medium-size enterprise application will consist of thousands of classes, hundreds of which can and should run unmodified on both client and server. Requiring explicit JS annotations is the wrong direction and encourages the old view that the browser is a toy platform that will never compete with the desktop.

Cross-platform forces you to promise less, which is actually a good thing since it gives you the ability to implement in more diverse ways. For example, what if File was strictly a data object, for example, and you had to interact with files using a FileSystem? And what if FileSystem operations behaved asynchronously instead of synchronously? The differences between the platforms force you to create these abstractions, which actually have many beneficial effects on the code base: pluggable file systems (SFTP, S3, etc.) without any changes to your code, super-fast IO on the server-side, easy ability to mock out the file system for tests, persistence on the browser-side via pluggable server-side persistence mechanisms, etc.

As a result, I strongly recommend that whole pods be either cross-platform or platform-specific. If they can't be entirely cross-platform, that's a sign of one of two things: (1) they have code that belongs somewhere else, or (2) they are not using proper abstractions. JavaScript annotations should be ripped out, as a relic of a bygone era.

Fantom should become the true leader for "Web 3.0" development.

rfeldman Sun 6 Jun 2010

I don't feel quite that strongly about the subject, but I do like the idea of specifying supported platforms at the pod level. Suppose that's just one more part of pod metadata, like dependencies?

That way you can trivially figure out at build time if your script can target a given platform - and if not, exactly which pod dependencies are keeping you from targeting it.

katox Mon 7 Jun 2010

+1 with JohnDG, well said.

High level dynamic client-server model is Fantom's strong selling point. I've always regarded @Js facet as something temporary - I think even Brian and Andy do.

Moving @Js to pod level is a good start. It is always much easier to think about pods which are usable in a particular environment than about what parts in what pod could be used. Couldn't be an unsupported class used internally somewhere? Where? Do I have to transitively track its usage? At pod level this is much easier. And if some class is not implemented for js env? An exception is fine - not implemented yet - but it doesn't mean it makes no sense to do so. In a way fwt does this already.

I've been thinking about non-fwt js web components. That's something js specific - so far - but if someone needed that badly it could be ported to standard desktop environment (using canvas if neccessary). Even things like CSS styling could be implemented in desktop environment.

brian Mon 7 Jun 2010

Some excellent comments.

I think I am in agreement with JohnDG and katox. We capture dependencies at the pod level, so it seems consistent to capture platform support at the pod level. That simplifies out a lot of complexities for development and deployment. Especially with the Actor API moved to concurrent, I think we can say sys, dom, gfx, and fwt are fully supported in JavaScript (sys will always have a few unsupported methods, but I think that is ok).

So I would propose:

  • annotate javascript support at the pod level
  • deprecate and then remove the @Js facet

andy Mon 7 Jun 2010

Many good points here. I agree that using pods as the mechanism for platform support is the right move. So +1 for Brian's proposal.

tactics Mon 7 Jun 2010

With respect to pod-level platform decls, how should native methods and FFI be handled?

andy Tue 8 Jun 2010

Promoted to ticket #1111 and assigned to andy

andy Tue 8 Jun 2010

Splitting code between pods sorta sucks in practice - forcing you to create two pods for a single logical grouping just to get JavaScript support is a bit inconvenient. Plus I feel like there is an overall design here that encapsulates platform support in general, and which ties in with natives and FFI that is escaping me at the moment.

I'm going to keep this ticket open to track, but not going to make any changes yet.

tcolar Tue 8 Jun 2010

What about just having a convention that whatever is in the js folder (as defined in build.pod) is JavaScript (@Js implied) ?

dfreire Tue 8 Jun 2010

+1 @Js implied

In the case of natives, the Peer's file names already define which class they are implementing.

andy Tue 8 Jun 2010

What about just having a convention that whatever is in the js folder (as defined in build.pod) is JavaScript (@Js implied)

Thats not really the problem - its the pure Fantom code (that has no peers) that is the issue.

For example, probably 95%+ of the JavaScript code in SkySpark is pure Fantom. And about half the pods are server + client code together, which is the design that makes the most sense for those cases.

So that is sorta the basis of the @Js facet today - it lets you selectively target JavaScript so your pod js file doesn't get bloated with classes that will never be used.

rfeldman Tue 8 Jun 2010

Just some thinking out loud here...

The root problem seems to be how to specify:

1) Which classes can be built to JS (e.g. it should be possible to know that certain classes in the concurrent pod are unsupported for JS output) 2) Which classes should be built to JS (e.g. it should be possible to know that certain server-side-only classes are not intended for JS output even though it would be possible to target JS if desired)

As I understand it, #1 could be inferred by the compiler, by traversing each class's includes to figure out if anywhere in its include hierarchy it is trying to use something which has no JS peer. If it is, then that class is not JS-compatible; otherwise it is.

Assuming that's true, then - per Andy's comment that "its the pure Fantom code (that has no peers) that is the issue" - #2 is really an isolated question because #1 can be inferred with no additional input from the developer.

The simplest way that comes to mind is to specify intended build targets at a pod level. In the example of the client/server pod, you'd have to split that pod up into two pods, one for client-side code (with a specification at the pod level that it would target JS), and one for server-side code (with a specification that it would not target JS). That would work, but might leave a bitter taste in your mouth because you'd rather just have them coexisting in the same pod.

I wonder if a slight improvement over the above would be simply to split up client and server pods, and then to combine the two in a parent pod which could expose the underlying pods. (Is that even possible/easy? I know remember it was in Maven but can't recall seeing any documentation for if/how dependent pods can be exposed.) Assuming that's workable, then at least everything that depends on the parent pod would get both client and server code packaged together, but the build targets of each would be cleanly separated.

Again, I don't really have a thesis here - just thinking out loud.

brian Tue 8 Jun 2010

No matter what I think we need a simple pod level flag like jsAll which compiles every type in the pod as JavaScript. That design suites gfx, fwt, dom, etc.

However, in general sometimes you really do want to mix client and server side code in the same pod. This isn't much different than tests - we made a decision a long time ago, that in general tests and documentation for a pod all belong together (with tooling to strip out tests, etc when needed). What we want to avoid is breaking up a logical pod into lots of little pieces (client, server, tests, docs, etc). So I think we need to continue to a have a model like @Js facet.

The only question then is should the facet be opt-in or opt-out (assuming we have a mixed client/server pod). In that case, I think the safer option is definitely opt-in because you always want your JS code as optimized and small as possible.

rfeldman Tue 8 Jun 2010

Then what about making a more generalizable version of @Js? For example, instead of @Js you have something like @BuildGroup("client") which allows you to place the class into an arbitrary logical build group, in this case one called "client".

Then at the pod level you can specify how to handle each build group (e.g. @BuildGroup("client") targets JS, all others do not, etc.)

katox Tue 8 Jun 2010

The problem Andy outlined is a bit hazy to me. Which one of these is it?

  1. all code is compiled into JS and zipped into a pod - inflating its size,
  2. all crosscompiled code is packed into a huge single js target file - cluttering sources,
  3. all crosscompiled js has to be loaded from the client side - big client load times,
  4. there are in classes in system pods which can't be compiled to js - it makes no sense, pods must be split,
  5. something completely different ;).

I don't think it is about 1 and 2. Number 4 doesn't seem to be problem either with current pod organization. I'd like to hear about 5 because I don't see it now.

Number 3 could be resolved by some sort of automatic dependency checking. Statically this can be certainly done. Dynamic dependencies are more problematic as they can't be inferred. But this problem would be the same for any platform which requires finer dependency tracking than whole pods -- class-level dependency management.

Note: what I don't like about @Js annotation is that it forces devs to think upfront about potential uses of all classes in a pod. This hinders class and pod composition.

rfeldman Wed 9 Jun 2010

I had been assuming it was #3, but actually now that I think of it that shouldn't be a big deal unless I'm thinking of something wrong.

Suppose you do a build and end up with:

clientJs1.js clientJs2.js clientJs3.js serverJs1.js serverJs2.js

Obviously generating the server-side .js files was a waste of time.

However, this would not really contribute to client download times because (unless I'm missing something) no request would ever result in a "<script src='server____.js'>" tag being sent to a client. No response would ever make use of that dependency, so the client would never even know it existed.

So true or false: download time experienced by the end user is not a relevant concern in this discussion?

andy Wed 9 Jun 2010

Well I think all these are basically the same thing - and thats performance. Including unnecessary classes in the target js file can inflate it by tens or hundreds of thousands of lines for a typical project - so thats an important detail. Our current solution requires developers to explicitly opt-in to that arrangement.

In an ideal world, we'd just always compile to JavaScript, that would be the best design. Thats how Java/C# work - you just get them for free (excluding natives and FFI of course). But the web has the additional constraint that theres a very real cost to including unnecessary lines of code.

Given we don't do anything special for Java or C#, it doesn't seem right to create special rules for JavaScript - one the reasons I don't love the current @Js design. So seems like there is a holistic design here missing maybe. Need to think about it more.

Or course we could also just always compile to JavaScript, and leave the design up to the developer to organize his pods efficiently - in which case you would just not request "server-side" pods over the wire. But then you get hit with the compilerJs pipeline on pods that don't need it - so it has its own problems.

rfeldman Wed 9 Jun 2010

Regarding a holistic design, I would definitely prefer if you could assign arbitrary build group facets - like my @BuildGroup("client") example a few posts back - and specify at the pod level which build groups should be included/excluded from which target platforms.

This would allow exactly the current facet-based design (I mean, I guess you'd have to go through and search/replace @Js for @BuildGroup("client")' or whatever you wanted to call it, and then add some only generate JS in the case of build groups marked with "client" rules to the pods in question), and all the benefits that come with opting in on a per-class basis.

However, this would do so without having to support a special-case facet for the language of JavaScript in the code, which is really what seems inconsistent and un-future-proof to me.

brian Wed 9 Jun 2010

I think annotating types as @Js and @TestOnly is what will give us the fine grained control we need.

The ability to do that by directory or some other filtering mechanism in the build file might be handy. Not sure what it might look like.

JohnDG Wed 9 Jun 2010

I think annotating types as @Js and @TestOnly is what will give us the fine grained control we need.

This is the wrong decision. People are conflating two different issues:

  1. Identification/isolation of JavaScript-compatible code
  2. Performance implications of generated JavaScript code

(2) is an implementation detail that should not dictate any language features. Choosing language features for performance reasons is short-sighted and ultimately misguided, because while technology and research allow performance to increase continuously (see Haskell), backward compatibility slows and finally stops evolution of the language.

(2) can be solved presently by post-compiling the JavaScript using Closure or similar tool, or even directly in the Fantom compiler through static analysis, or by a community-developed Proguard-like tool made explicitly for Fan.

(1) is the more important issue here. @Js presents a burden on developers to think in advance whether a given class will ever be used anywhere but on the server. It insists the programmer embed build information directly in the source code, which would be rejected in any other circumstance (imagine embedding SSI directives in the source code for "performance reasons"!). It places extreme burden on developers writing cross-platform code, who need to pay attention not just to the pods they are importing, but to all the types in all the pods, carefully checking each to ensure it is cross-platform. It encourages a sloppy style of programming whereby each platform has its own platform-native types. And it encourages mixing of IO code with manipulation/processing code, which is a mistake for all of testing, maintainability, abstraction, and cross-platform support.

In short, it's a terrible decision made for all the wrong reasons.

brian Wed 9 Jun 2010

I don't think we are really talking about language features or anything that has long term effects on the language. Everything written in Fantom can be compiled to JS. Put another way everything in the Fantom language can be compiled to JS, but not necessarily all the APIs are available.

This discussion is merely about:

  • how to mark APIs and/or pods which haven't been implemented in JS (yet)
  • how to provide developers the hooks to optimize code generation to minimize JS code size loaded in the browser

You are right that long term neither of these things may matter. But they do matter now because many APIs can't be implemented in the browser and code size matters a lot for deploying to today's browsers.

When we talk about platforms like JVM, .NET or even future targets like LLVM, these are platforms that work off fcode and so need no special support from the language or build tools other than how to compile their native hooks.

JS on the other hand is special because it is compiled from source AST, not fcode (for all the same reasons GWT works that way). So it becomes a build time problem instead of a runtime problem.

kevinpeno Mon 8 Aug 2011

Just to throw a wrench in an old ticket, I think that the advent of node.js pretty much makes the @Js issue a problem. If Fantom's goal is to be platform agnostic, then it should not stick to things like this.

kevinpeno Sun 14 Aug 2011

Here's another idea. Since some code may not be supported in certain instances (javascript front-end vs a back-end like node.js or .Net vs. Java even) why not let the code maintainer specify where it can work. This way anyone using the package, pod, whatever knows what compilation it supports. This could be at the class or method level as needed so that, perhaps, the compiler can just throw that method/class out if it is never used.

Login or Signup to reply.