Java to Javascript Conversion for Framework Developers

The Javascript engine is designed to allow you to flexibly expose Java apis for Javascript libraries, and run Java code in the browser. For most developers, you use the StrataCode command to compile and run your application and do not have to be aware of the details of how Java is converted to Javascript. This section describes the details for framework developers who want to add new libraries or who want to know more about the conversion process.

Framework developers that want to add new Javascript libraries write Java class stubs to wrap them. Instead of a complex native interface, you replace Java types with a Javascript implementation using the simple API mapping rules. To use JQuery in StrataCode for example, you would extend a JQuery layer which pulls in the necessary JS library files and exposes Java types which give you access to JQuery's features. At runtime, your generated JS classes will directly call the JQuery APIs in a more reliable way using Java's type system. If you need to write JS code for some reason, it can mix calls to the generated apis with calls to JQuery.

Packaging into JS Files

After Java files have been converted to JS, each type's JS code is stored into an intermediate file. Those intermediate files are then assembled into one or more JS files loaded by the browser. There are two mechanisms you can use for controlling how this packaging is performed.

One option is to use the jsModuleFile annotation on your classes. When you do this, all Java types using the same module are put into the same file. That file must have an ordered list of dependencies against all other types in how they are packaged in JS files. It's a conflict if one jsModuleFile depends on another which depends back on itself. TODO: this is primarily a problem when one class extends another in a different module which extends a class back on itself. Because we are loading the classes in each module file all-at-once, we need to determine the proper order and can't include a module file which may require the constructor of a downstream module to already be loaded. Today, we treat all dependencies the same as well so there's no way to figure out the proper order, if there is one which preserves the 'extends order'. I think the JS way to solve this is to create the classes dynamically from objects, so each class resolves it's extends class as soon as it's needed but something about that probably adds overhead.

If you don't use modules, StrataCode figures out all of the entry points required for a given HTML file, and puts all unassigned Java types that are used by those entry points into that file. With this approach, types which are not referenced are not included in the resulting JS file making it smaller, but there's no reuse of the lower-level pieces so they'll be downloaded again for each application loaded into the browser.

Java Runtime

A subset of Java's utility libraries (ArrayList, HashMap, etc.) have been added to the framework. Those were all just copied from the apache Java project into the js/sys layer. When there are native dependencies in types you must simulate that code in native JS. The native Java types are hand-written in the file js/javasys.js.

Using the JS System

To use the JS library, include js.core for basic Java to Javascript functionality. Mark your type with @MainInit or just have a main method and it will be called by default after all of the types have been loaded.

Include the js.schtml layer if you want to use schtml files as well that are compiled to .java files, then to .js files for loading along with the default runtime.

There are two base layers which configure how template pages are assembled. In the js.allInOne layer, all of the code in the application is put into a single index page. This is great for test programs or those cases where an entire app is contained in a single page. When you have separate applications for each page, use js.schtml which generates separate javascript and HTML files for each top-level template page (i.e. a template page with a surrounding html tag or one which extends the 'Page' class).

Controlling Compilation

The JSSettings annotation controls how your type is processed by the Javascript conversion engine.

  • jsLibFiles: Set the jsLibFiles attribute to replace all references to this type to a different type in the generated Javascript code.
  • replaceWith: Replaces all references to this type to a different type in the generated Javascript code.
  • typeTemplate: Override the frameworks type template used to generate code for this type and subtypes
  • prefixAlias: Registers an alias for this types package to use as the prefix instead of the default convention of pkgApkgB
  • usesJSFiles: Comma separated list of jsLibFiles which this lib file should include but does not change the loading order of JS files in the list.
  • extendsJSFiles: Comma separated list of jsLibFiles which this lib file has an extends or implements relationship with. These types must be loaded before the referencing type and so are put ahead in the JS files list that gets compiled into templates as the list of files to include.
  • jsModuleFile: Set this for library code which you want to group into a separate .js file. By default, all .js files are loaded into one main .js file.

The JSMethodSettings annotation is usually used for a type which set JSSettings.jsLibFiles. There are times when the name of the Javascript method conflicts with a field that already exists. For example, String.length is a field in Javascript but a method in Java. So an annotation layer for java.lang.String sets JSMethodSettings on the length method to _length(). That method is added to javasys.js for the prototype of the built-in String class.

Naming Conventions, and Runtime Rules

Most types use the prefixAlias feature to map a java package tree (e.g. java.*) into a prefix jv_String. The thinking is basically that two letters is all of the uniqueness that an application really needs for a package prefix, especially when it's configurable to work around the odd case. The global name space in Javascript is untenable without some unique prefix to avoid name conflicts, and even organize names.

Though a lot of Java code can be converted to Javascript unchanged, given the constraints of the Javascript runtime some changes in the type system will require changes in your code to get it to work. The main differences in the type systems are:

  • In Javascript the constructor is a function which cannot hold other functions as properties. So we need two objects to represent a Java type. The normal type name, e.g. jv_HashMap is used as the constructor. The value jv_HashMap_c is used to access static methods or as the placeholder for a super reference. The function sc_newClass is used to create the "_c" class from the constructor. That's where you can set the super classes and any interfaces.
  • In Javascript properties and methods are in the same name space so "size" and "size()" collide. To resolve that, when this happens, the field name is prefixed with an _.
  • Javascript does not support method overloading. To resolve this, the generated Java code is collected into one method, dispatched dynamically by testing the number and type of the parameters. There will be occasional inconsistencies due to the compile time versus runtime nature of the method dispatch but the benefit of preserving the naming of the JS api far outweights the minor inconvenience of having to annotate the rare exception using @JSMethodSettings.
  • In place of super, the SuperType_c.method.call(this, ...) pattern is used.
  • The types int, float, Integer, Float, Double, etc. all convert to Number in Javascript. Not attempt yet has been made to account for the behavioral differences in code this creates.
  • The Javascript instanceof operator can often be used with SC types but for interfaces and other conversions which that cannot handle, use sc_instanceOf

StrataCode initializes all fields with null before accessing them. If you hit "undefined" when accessing a field that should be there, that's a bug. Because of that assumption, it uses fieldName !== null instead of fieldName !== undefined in all comparisons.

Dependencies

At compile time, StrataCode computes the dependencies between each javascript file it processes, both those files specified as the jsFile annotation, those specified with jsModuleFile, and any default types. Default types may be included in more than one base file (for the appPerPage runtime) or they may be put into a default javascript file named: js/sc.js.

Within each file, the list of types is sorted so that super-types come before sub-types. Any javascript files which extend types in another javascript file will introduce a dependency between those two files. If you use jsModuleFile incorrectly, you can introduce cyclic dependencies between modules which is not allowed. Note that the dependency only exists when one type extends types in another file.

Future Optimizations

It would not be difficult to omit unused methods from the generated Javascript for a given compilation. This would thwart the sharing of js file modules between applications however.