Creating JavaScript modules
With TeaVM you can not only compile Java application to JavaScript or WebAssembly, but also create JavaScript and WebAssembly libraries with Java. You are required to put additional annotations on your code and make some extra setup in the build system. These topics are uncovered below.
Setting up module system
This section is only relevant for JS backend. Currently, WebAssembly can't compile to JS modules.
By default, TeaVM generates UMD wrapper, which is compatible both with AMD and CommonJS module systems, or puts Java entry points into global namespace, if none available. You can change this behaviour, e.g. to produce ES2015 modules.
If you are using Gradle, you need following configuration:
teavm.js {
moduleType = JSModuleType.ES2015
}
where available module types are following:
COMMON_JS
– CommonJS module, e.g. for compilation with Node.js.UMD
– (default) UMD wrapper for compatibility with AMD, Node.js, if available.NONE
– make TeaVM entry points available as global declarations (for example, to use in web).ES2015
– produce ES2015 modules.
another way is to specify teavm.js.moduleType
gradle property or js.moduleType
TeaVM property
(please, refer to Gradle documentation).
In Maven you need following in plugin configuration:
<jsModuleType>ES2015</jsModuleType>
Generating a module
By default, you use Java convention with public static main
method of the main
class specified in configuration.
A module will be generated that exports one method called main
.
If you need to convert a library, don't follow this convention.
Instead, create static methods in your main class and annotate them with @JSExport
annotation,
as follows:
public class MyModuleExample {
@JSExport
public static void foo() {
System.out.println("foo called");
}
@JSExport
public static String bar(int a, int b) {
return "bar: " + (a + b);
}
}
TeaVM will produce module that exports foo
and bar
functions.
In case you set up ES2015 module type in TeaVM, you can use this module from JS side like follows:
import { foo, bar } from './myModuleExample.js'
foo();
bar(2, 3);
Exporting properties from module
To export properties from modules, use Java beans naming convention and put @JSProperty
annotation on methods:
public class ModuleWithExportedProperties {
private static int bar = 23;
@JSProperty
public static String getFoo() {
return "foo value";
}
@JSProperty
public static int getBar() {
return bar;
}
@JSProperty
public static void setBar(int value) {
bar = value;
}
}
TeaVM will produce module that exports foo
readonly property and bar
read-write property.
Usage example:
import * as java from './myModuleExample.js'
console.log(java.foo);
console.log(java.bar);
java.bar = 42;
console.log(java.bar);
Returning non-primitive values from module
If you need a function that produces an object, you can do with either of two ways:
- Return a simple Java class that exports its declarations to JavaScript with
@JSExport
. - Return a Java class that implements one of sub-interfaces of JSObject interface (see Interacting with JavaScript).
For example:
public class Point {
private int x;
private int y;
@JSExport
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@JSExport
@JSProperty
public int getX() {
return x;
}
@JSExport
@JSProperty
public int getY() {
return y;
}
@JSExport
public static Point getZero() {
return new Point(0, 0);
}
}
public interface Color extends JSObject {
@JSProperty
int getRed();
@JSProperty
int getGreen();
@JSProperty
int getBlue();
}
public class ColorImpl implements Color {
private int red;
private int green;
private int blue;
public ColorImpl(int red, int green, int blue) {
this.red = red;
this.green = green;
this.blue = blue;
}
@Override
public int getRed() {
return red;
}
@Override
public int getGreen() {
return green;
}
@Override
public int getBlue() {
return blue;
}
}
public class MyModule {
@JSExport
public static Point createPoint(int x, int y) {
return new Point(x, y);
}
@JSExport
public static Color createColor(int red, int green, int blue) {
return new ColorImpl(red, green, blue);
}
}
There's some differences between these approaches.
The first approach exports entire class, so that you can consume it and then, for example,
use it in instanceof
expression, or call its static methods.
The second approach returns just anonymous object with certain methods and properties.
From JS side:
import { createPoint, createColor, Point } from 'myModule.js';
let pt = createPoint(2, 3);
console.log(pt.x, pt.y);
let color = createColor(255, 255, 0);
console.log(color.red, color.green, color.blue);
console.log(pt instanceof Point); // prints 'true'
console.log(color instanceof Point); // prints 'false'
Note that if you want to specify exported class name explicitly, you can use @JSClass
annotation:
// This class will be seen as 'Bar' in JS
@JSExport("Bar")
public class Foo {
}
Taking non-primitive parameters
To take non-primitive parameters to exported methods, you can use the same two approaches, that you use to return values, i.e.:
public class MyModule {
@JSExport
public static Point createPoint(int x, int y) {
return new Point(x, y);
}
@JSExport
public static double length(Point pt) {
return Math.sqrt(pt.getX() * pt.getX() + pt.getY() * pt.getY());
}
@JSExport
public static void logColor(Color color) {
System.out.println("rgb: " + color.getRed() + ", " + color.getGreen()
+ ", " + color.getBlue());
}
}
The difference is however, a bit more sensitive.
In the first case you should pass exactly instance of Point
class that you got from Java module.
In the second case you are only required to pass object with corresponding properties.
For example, this is the right usage of length
method:
import { createPoint, length } from './myModule.js';
let pt = createPoint(2, 3);
console.log(length(pt));
And this is invalid:
import { length } from './myModule.js';
console.log(length({ x: 2, y: 3 }));
However, this is valid:
import { logColor } from './myModule.js';
logColor({ red: 255, green: 255, blue: 255 });
Exporting classes explicitly
When a Java class mentioned explicitly somewhere in signature of module methods,
they are exported automatically.
However, sometimes you need to export some class that does not present in signatures of other methods.
In this scenario you just put @JSExportClasses
annotation to the module entry point class,
or alternatively to other exported classes.
The behaviour is following: as soon as a class with @JSExportClasses
annotation is exported to JavaScript
for some reason, all classes enumerated in the annotation will also be exported. For example:
@JSExportedClasses({ Point.class })
public class MyModule {
// This class is empty, it does not export any methods to JavaScript
// However, Point class is exported
}