JSweet Documentation

repository·develop·Indexed 23 days ago

https://github.com/cincheo/jsweet

JSweet is a source-to-source transpiler that converts Java code into TypeScript and subsequently into JavaScript, enabling Java developers to build web applications for browsers, mobile Web views, or Node.js. It utilizes "candies"—well-typed Java API wrappers for TypeScript definitions—to bridge JavaScript libraries into Java. The project includes a candy-generator tool for creating these wrappers from .d.ts files and provides integration plugins for Maven, Gradle, and Eclipse.

Tokens
25.7K
Snippets
59
Records
94
Agent score
81%

What's inside JSweet

  1. Understand the JSweet core API structure

    develop

    The JSweet core API provides the necessary building blocks to program JavaScript applications using Java. It is organized into Maven projects categorized by JavaScript version (e.g., ES5). Each version-specific project provides four primary packages:

    • def.js: Contains the core JavaScript language APIs for that specific version.
    • def.dom: Contains the W3C DOM APIs for that specific version.
    • jsweet.lang: Provides JSweet-specific language extensions for Java, primarily consisting of annotations.
    • jsweet.util: Contains various utility helper classes.
  2. What is JSweet and how does it work?

    develop

    JSweet is a Java to JavaScript transpiler that allows you to write rich, responsive web applications in Java. It uses a two-step transpilation process: Java $\rightarrow$ TypeScript $\rightarrow$ JavaScript.

    By leveraging TypeScript, JSweet provides type-checking and generates fully type-checked JavaScript programs that can run in browsers, mobile Web views, or Node.js.

    To bridge JavaScript libraries into Java, JSweet uses "candies"—well-typed descriptions of JavaScript APIs (often automatically generated from TypeScript definition files) that act like C-style header files for Java developers.

  3. Constraints for Globals classes

    develop

    In JSweet, Globals classes are used to define top-level functions and variables that are erased at runtime to be directly accessible in the global scope. Because they represent the global namespace, they have strict constraints:

    • No non-static members: Only static fields and static methods are allowed.
    • No inheritance: They cannot have a superclass and cannot be extended.
    • No type usage: They cannot be used as types (e.g., Globals myVar; is invalid).
    • No public constructors: You may use an empty private constructor, but public constructors are forbidden.
    • No runtime manipulation: You cannot use $get, $set, or $delete within these methods.

    At runtime, mypackage.Globals.m() is erased to mypackage.m() in the generated JavaScript.

    class Globals {
        public int a; // error: no non-static members allowed
        
        public Globals() { // error: public constructors are not allowed
            this.a = 3;
        }
        
        public static void test() {
            $delete("key"); // error: no instance is available for $delete
        }
    }
    
    // error: Globals classes cannot be used as types
    Globals myVariable = null;
  4. Use @Interface for shared object types

    develop

    When an object type represents a shared entity that needs to be used across multiple contexts, use the @Interface annotation instead of @ObjectType. This is the recommended approach for reusable typing entities.

    @Interface
    public class Indexed {
        int index;
    }
    
    public class C {
        public void m(Indexed param) { ... }
    }
    
    // Usage:
    C c = ...;
    c.m(new Indexed {{ index = 2; }});
  5. Handle Intersection types via Union types

    develop

    TypeScript intersection types (A & B) represent a type that contains members of both A and B. Because intersection types are difficult to implement directly in Java, JSweet represents them using Union types (Union<A, B>).

    To access the members of the intersected types, you must use the jsweet.util.Lang.union helper method to cast the value to either A or B.

  6. Use Tuple types for typed arrays

    develop

    Tuple types represent JavaScript arrays where each element has a specific, tracked type. JSweet provides parameterized auxiliary classes TupleN<T0, ... TN-1> in the jsweet.util.tuple package.

    Elements are accessed via public fields named $0, $1, up to $N-1.

    • Tuple2 through Tuple6 are provided by default.
    • Larger tuples are automatically generated in candy APIs or can be manually added to the jsweet.util.tuple package.
    Tuple2<String, Integer> tuple = new Tuple2<String, Integer>("test", 10);
    
    assert tuple.$0 == "test";
    assert tuple.$1 == 10;
    tuple.$0 = "ok";
    tuple.$1--;
    assert tuple.$1 == 9;
  7. Understand JSweet's core type mappings

    develop

    JSweet transpiles Java types to JavaScript/TypeScript types. Understanding these mappings is crucial for managing precision and interoperability:

    • Numbers: int, byte, short, double, and float are all converted to JavaScript number (TypeScript number).
      • Note: Casting to int, byte, or short forces the number to be rounded to the appropriate integer length.
    • Characters: char follows Java rules but is converted to a JavaScript string.
    • Booleans: boolean maps to JavaScript boolean.
    • Strings: java.lang.String maps to JavaScript string.

    Warning on Overloading: Because multiple Java numeric types map to a single JavaScript number, method overloading based on numeric types (e.g., pow(int, int) vs pow(double, double)) can cause issues when calling the resulting JavaScript from outside JSweet. JavaScript cannot distinguish between these implementations via instanceof.

    // warning '==' behaves like JavaScript '===' at runtime
    int i = 2;
    assert i == 2;
    double d = i + 4;
    assert d == 6;
    String s = "string" + '0' + i;
    assert s == "string02";
    boolean b = false;
    assert !b;
  8. Use Initializers in JSweet

    develop

    JSweet supports both instance and static initializers, behaving similarly to Java:

    • Instance Initializers: Evaluated when the class is instantiated.
    • Static Initializers: Lazily evaluated to avoid forward-dependency issues, mimicking Java behavior.

    Note: Because of lazy evaluation, it is possible to define a static field or initializer that relies on a static field that has not yet been initialized.

    public class C1 {
        int n;
        {
            n = 4;
        }
    }
    
    public class C2 {
        static int n;
        static {
            n = 4;
        }
    }
  9. Access global variables and functions in JSweet

    develop

    Since Java does not have a concept of global variables or functions, JSweet provides the Globals classes and globals packages to handle JavaScript's global scope.

    Use these for two primary purposes:

    1. Generating global code: To create global variables or functions (though this is generally discouraged in Java patterns).
    2. Binding to existing JS code: To interact with existing JavaScript frameworks that define variables or functions in the global scope.
  10. Use @ObjectType for anonymous object types

    develop

    In TypeScript, object types are often inlined and anonymous (e.g., { index: number }). Since Java requires named types, JSweet uses the @ObjectType annotation to simulate this behavior.

    Use @ObjectType on a class (which can be an inner class) when you want to represent a lightweight, anonymous object structure that is erased at runtime. This is useful for passing simple data structures to methods without the overhead of a full interface.

    Note: Object types are erased at runtime, similar to interfaces.

    public class C {
        @ObjectType
        public static class Indexed {
            int index;
        }
        public void m(Indexed param) { ... }
    }
    
    // Usage:
    C c = ...;
    c.m(new Indexed() {{ index = 2; }});
  11. Extend JSweet using PrinterAdapter

    develop

    For complex tuning that declarative annotations cannot handle, you can write a programmatic extension using the org.jsweet.transpiler.extension API. This involves creating a PrinterAdapter and adding it to the transpiler's adaptation chain.

    Core Adapter Operations

    1. Type Mapping: Map Java types to TypeScript types using addTypeMapping(javaType, tsType) in the constructor.
    2. Dynamic Annotation: Add annotations via addAnnotation(annotationName, matchExpressions) or via an AnnotationManager.
    3. Code Substitution: Override printing methods to change how AST elements are generated. Common methods include:
      • substituteNewClass(NewClassElement): Change how new instances are created.
      • substituteMethodInvocation(MethodInvocationElement): Map Java API calls to JavaScript APIs.
      • afterType(TypeElement): Insert code immediately after a Java type is printed.

    Implementation Template

    An adapter must extend PrinterAdapter and accept a PrinterAdapter parent in its constructor to support the decorator pattern used in the chain.

    public class MyAdapter extends PrinterAdapter {
    
    public MyAdapter(PrinterAdapter parent) {
            super(parent);
            // Map Java types to TypeScript types
            addTypeMapping("AJavaType", "ATypeScriptType");
            addTypeMapping("AJavaType2", "any");
    
            // Add annotations dynamically
            addAnnotation("jsweet.lang.Erased", "**.readObject(..)", "**.writeObject(..)", "**.hashCode(..)");
        }
    
        @Override
        public boolean substituteNewClass(NewClassElement newClass) {
            if ("AJavaType".equals(newClass.getTypeAsElement().toString())) {
                print("new ATypeScriptType(")
                        .printArgList(newClass.getArguments()).print(")");
                return true; // Break the chain
            }
            return super.substituteNewClass(newClass);
        }
    
        @Override
        public void afterType(TypeElement type) {
            super.afterType(type);
            // insert whatever TypeScript you need here
        }
    }
  12. Variable scoping in lambda expressions

    develop

    In standard JavaScript, variables used inside a lambda (like an event listener) can be modified by the outer scope, leading to unexpected behavior in loops.

    JSweet handles this by re-scoping variables in lambda expressions to behave like final Java variables. This ensures that the variable captured by the lambda holds the value it had at the time the lambda was created, matching Java's expected behavior.

    NodeList nodes = document.querySelectorAll(".control");
    for (int i = 0; i < nodes.length; i++) {
        HTMLElement element = (HTMLElement) nodes.$get(i);
        element.addEventListener("keyup", (evt) -> {
            // In JSweet, 'element' is re-scoped and won't change
            element.classList.add("hit");
        });
    }