Groovy to javascript transpiler

image

About

This is an open source project, with Apache 2 license. Is part of awesome Groovy ecosystem. If you want to contribute, help or support the project, you’re welcome, can contact in Github or via email.

Project started Aug 26, 2012 with one contributor. Now, we are four contributors, and we have nice ideas to move on. Project is starting, and all the feedback is more than welcome, we want keep on groovy’ing javascript!

Introduction

Be aware javascript isn’t Java or Groovy, lot of differences between them. The main idea of this project is use Groovy strengths in the browser. I’m looking for use groovy and java code in both sides (server and client), work in a single environment, use groovy and java tools and IDE’s.

Grooscript is a library that converts your groovy code to javascript. I’ve tried that converted code doesn’t contains strange code or magical things. So you can use and follow that converted javascript code. This code needs grooscript.js or grooscript.min.js to run in a javascript environment.

The typical flow is:

  • Create awesome groovy or java code while test it with Spock :)

  • Convert code to javascript.

  • Add that converted files to your html or js project.

  • Then, work in your groovy code and use javascript when you want to.

Grooscript offer some tools to help you, a gradle plugin, a grails plugin and a node.js npm.

Requirements

Grooscript will compile your piece of groovy code, so you need Groovy, at least version 2. If you use Groovy 2.0, you can’t convert traits, introduced in Groovy 2.3. So, add grooscript in your project dependencies and that’s all. By default grooscript depends from Groovy 2.4, but you can use the groovy version of your project excluding groovy dependencies:

compile 'org.grooscript:grooscript:1.3.0', {
    exclude module: "groovy"
}

The javascript generated code is ECMAScript 5 compatible. So will run at any browser or node.js.

Getting started

You can create a groovy script to make a conversion and see the converted code in your console:

@Grab('org.grooscript:grooscript:1.3.0')

import org.grooscript.GrooScript

def result = GrooScript.convert '''
    def sayHello = { println "Hello ${it}!" }
    ['Groovy','JavaScript','GrooScript'].each sayHello'''

println result

If you want to make conversions in your groovy / java project, you need grooscript.jar. You can get from mavenCentral or bintray org.grooscript:grooscript:1.3.0 in your project’s dependencies. Also need grooscript.js, inside the jar or download from here to run the converted code in your javascript environment.

Usually, you will convert groovy or java files to javascript:

import org.grooscript.GrooScript

//Convert some file's or folder's, generate .js files with same name of each source file.
GrooScript.convert('path/to/file/or/folder', 'path/to/destination/folder')
GrooScript.convert(['file', 'folder', ...], 'path/to/destination/folder')
//Also can convert all source files to one javascript file
GrooScript.convert('any/number/of/sources', 'path/file.js')
//You can set conversionOptions, is an optional parameter
Map conversionOptions = [anyConversionOption: value]
GrooScript.convert('source', 'destionation', conversionOptions)

When the number of files is increasing, or want to automatize conversions, the gradle plugin helps a lot, and I recommend you to use it. Guide

Conversion

For convert groovy code to javascript, that groovy code is compiled. Then javascript code is generated visiting generated AST tree. In the source of the project you can take a look at all the groovy code that is converted to javascript in tests, you have full scope of all supported features in a lot of groovy scripts

You usually will convert files with groovy code to a destination folder or js file. To be dynamic and automatize conversion process I strongly recommend to use grooscript gradle plugin. You can activate a daemon to automatically convert groovy code when files are modified.

Options

To help or improve conversion process, you can set some conversion options when you convert groovy code. There is an enum with available conversion options:

package org.grooscript.convert

enum ConversionOptions {

    CLASSPATH('classpath', null),
    CUSTOMIZATION('customization', null),
    MAIN_CONTEXT_SCOPE('mainContextScope', null),
    INITIAL_TEXT('initialText', null),
    FINAL_TEXT('finalText', null),
    RECURSIVE('recursive', false),
    ADD_GS_LIB('addGsLib', null),
    REQUIRE_JS_MODULE('requireJsModule', false),
    CONSOLE_INFO('consoleInfo', false),
    INCLUDE_DEPENDENCIES('includeDependencies', false),
    NASHORN_CONSOLE('nashornConsole', false)

    String text
    def defaultValue

    ConversionOptions(String text, defaultValue) {
        this.text = text
        this.defaultValue = defaultValue
    }
}

More info about conversion options:

classpath

List of folders or jar’s to find dependencies. Before groovy code is converted to javascript, that code have to be successfully compiled. For example: [''src/groovy'']

customization

to each file to be converted, will be applied a groovy customization. For example you can for example type check all the groovy classes to be converted with { -> ast(TypeChecked)}

initialText

you can add test at the beginning of your converted javascript file. You can add comments or javascript code. For example: ''//This code has been generated with grooscript''

finalText

like initialText, but at the end of the converted file. For example: ''MyClass().init();''

recursive

if you specify a folder with the source conversion option, by default will not convert files inside folders. To convert recursive, just set to true

mainContextScope

you can help the converted, specifying functions and variables that will be availables in the main context of the javascript environment. For example: [''$'', ''window'',''myFunction'']

addGsLib

you can add a grooscript js lib at the beginning of the converted file. For example you can add grooscript.js to the result, and then you don’t have to worry about include that dependency in javascript ''grooscript''

requireJsModule

the result javascript code will be as a require.js module.

consoleInfo

you get info in console about conversion process setting to true. Is mainly a debug option.

includeDependencies

in the result javascript file, will be added conversion of dependencies. You can’t set both includeDependencies and requireJsModule to true.

nashornConsole

when you set to true, then in javascript will be used 'print' function to write in console. 'console.log()' in sot supported by Nashorn.

General support

As you can imagine, not all groovy and java is supported. The main focus is support the conversion of groovy code with the logic of your application, and use as many groovy magic as possible. Only groovy-core is supported, other modules as Json, File, Swing, Templates, …​ are not supported.

The more you try use a specific Java type or a complex metaprogramming feature, then you will get a fail. Java code is supported, but using same types as groovy does. Don’t hesitate to say that you need a non implemented type, or function, can be done in next releases.

I’m going to detail general rules.

Packages

They are supported. Actually conversion generate js code where package info is saved only for class names. So two classes with same name in distinct packages are converted with the same name. Lack of package support in javascript, but we have some ideas to improve this in the future.

package one

class First {}

package two

class First {}

Are converted to:

function First() { ... }

function First() { ... }

Interfaces

Totally ignored and not converted

interface Move {
    def start()
}

Objects

Objects are supported, with static and normal properties. You can have more than 1 constructor with same number of parameters. Overloading is not supported, you can’t have two methods with same name. Abstract methods are ignored, just convert implementations in extending classes. Private, public, protected, …​ are ignored, as in groovy you can access private methods and fields in javascript. Modifier final also ignored.

class MyObject {
    private static final NUMBER = 6
    def name = 'MyObject'
    Map map

    MyObject() {
        map = [:]
    }

    MyObject(name) {
        map = [newName: name]
        this.name = name
    }

    private addToMap(key, value) {
        map[key] = value
    }

    void register(listActions) {
        listActions.each {
            addToMap(it.name, it.data ?: NUMBER)
        }
    }
}

Class data not well supported, just a few important methods and properties:

class A { def a }
class B extends A {}
def a = new A()
def b = new B()

assert a.class == a.getClass()
assert a.metaClass == a.getMetaClass()
assert a.getClass().getName() == 'contribution.A'

assert a.getClass().getSimpleName() == 'A'
assert b instanceof A
assert b instanceof contribution.B
assert Class.forName('contribution.B') == B
assert A.newInstance()

Equals method is used in comparisons, and you can create / use in your classes.

Inheritance

Support is limited, is ok that you create classes that extends other to add behavior. You can’t extend any java or groovy class, just your own classes or other classes already converted.

class Bicycle implements Move {
    int speed

    Bicycle() {
        speed = 0
    }

    def start() {
        speed = 1
    }
}

class MountainBike extends Bicycle {
    def seatHeight

    MountainBike() {
        super() (1)
        seatHeight = 5
    }

    (2)
    //def start() {
    //    super.start()
    //}
}

class CoolMountainBike extends MountainBike {
    CoolMountainBike() {
        super()
    }
}
1 super is allowed in constructors
2 super in not allowed out of constructor

Try blocks

Try, catch, and finally blocks are supported. Only first catch block will be converted if you use more than one. Like I/O classes are not supported, I suppose that you will use try blocks a little.

def anyCatch = {
    try {
        throw new Exception()
        value = 4
    } catch (any) {
        return 2
    }
}

assert anyCatch() == 2
assert value == 3

Java support

Java support is not the target of this library, groovy is. Like groovy uses java, grooscript support a lot of java stuff. If you need any specific java type, other than a basic type, please before check if is supported. Grooscript convert your code to javascript, but the normal use of a normal programming language, not specific types or modules. You can expect variables, arrays, data types, operators, control flow, classes and objects you create, basic inheritance, numbers, string, dates, collections, maps, …​ are supported. But Thread, XML, Reflection, …​ are not supported.

Primitives

Basic types are ignored, they are just used in javascript. No differences between float, int, …​ just numbers are 'numbers' and String are javascript String’s. Integer, Float, Double, …​ types are not supported, just Integer.parseInt and Float.parseFloat are supported. Basic operations are supported: +, -, *, /, %, **

Equals, hashCode and compare

Equals is used comparing objects, hashCode and compareTo are not supported. Groovy truth is used in comparisons.

Enum

Not much work with enums, just good as a set of values, stuff like 'values' are not supported.

enum Days {
    monday, tuesday, wednesday, thursday, friday, saturday, sunday

    static tellMeOne() {
        'one'
    }
}

Days a = Days.monday

assert a == Days.monday
assert 'one' == Days.tellMeOne()
assert Days.monday.name() == 'monday'
assert a.name() == 'monday'
assert a.ordinal() == 0
assert Days.sunday.ordinal() == 6

Nested Classes

The support of creating classes inside other classes has not been tested well. Take care with them:

class OuterClass {
    ...
    class NestedClass {
        ...
    }
}

The use of anonymous classes is not allowed, and you will get an exception:

class WithRunnable {

    def running = false

    def goRun() {
        def runnable = new Runnable () {
            @Override
            public void run() {
                running = true
            }
        }
        runnable.run()
    }
}

Types

Good support in:

  • java.util.ArrayList

  • java.util.HashMap

  • java.util.HashSet

  • java.lang.String

  • java.util.Date

Basic support in:

  • java.math.BigDecimal

  • java.util.Random

  • java.lang.StringBuffer

Java 8

Nothing added in grooscript to support any java 8 feature or type. If you introduce for example lambda expression, compilation of the conversion will fail, as groovy fails.

Groovy support

In general groovy features are supported, except advanced metaprogramming techniques and AST’s applied in phases after semantic analysis. as is supported only at specific situations. Owner is totally ignored.

Let’s see examples of some conversions.

GStrings

things = '"bread","apple","egg"'

def multiline = '''\
Groovy is closely related to Java,
so it is quite easy to make a transition.
'''

multiline.eachLine { line, count ->
    assert count == 0 ? 'Groovy is closely related to Java,' : 'so it is quite easy to make a transition.'
}

def multilineDouble = """
things: $things
"""

assert multilineDouble == '''
things: "bread","apple","egg"
'''

Lazy evaluation with ${→ resource} is not supported.

Default constructor, optional parameters, pointer methods, multiple assignment

class Language {
    def name
    def features

    def twoNiceFeatures() {
        def result = []
        2.times {
            result << features[randomFeature()]
        }
        result.collect this.&nice
    }

    def randomFeature() {
        new Random().nextInt(features.size())
    }

    def nice(value) {
        "* ${value.toUpperCase()} *"
    }
}

def groovy = new Language(name: 'Groovy', features: [
        'Flat learning curve', 'Smooth Java integration', 'Vibrant and rich ecosystem',
        'Powerful features', 'Domain-Specific Languages', 'Scripting and testing glue'
])

def (feature1, feature2) = groovy.twoNiceFeatures()
//* DOMAIN-SPECIFIC LANGUAGES *
//* POWERFUL FEATURES *

Operators

<=>, .@, *., …​, in, is, ?. and ?: are supported.

def compare = { a,b -> a <=> b }
assert compare(2, 1) == 1 && compare(1, 1) == 0 && compare(1, 2) == -1

class Item {
    def value
    def config = [:]
    def getValue() {
        1
    }
    boolean equals(other) {
        this.config == other.config
    }
}
def item = new Item(value: 5)
assert item.value == 1
assert item.@value == 5

assert 5 in [1, 9, 5]
assert !(2 in [1])

def otherItem = new Item(value: 5)
assert item == otherItem
assert !(item.is(otherItem))

assert item?.config?.other == null
assert 5 == item?.config?.data ?: 5

* is not supported in *list, to divide a list in parameters for a function / method.

Also you can overload this operators in your classes:

Table 1. Overload operators
operator function

a + b

a.plus(b)

a - b

a.minus(b)

a * b

a.multiply(b)

a ** b

a.power(b)

a / b

a.div(b)

a % b

a.mod(b)

a << b

a.leftShift(b)

a >> b

a.rightShift(b)

Regular expressions

Javascript support regular expressions, so I use it with groovy style. Not much tests on this, try it before .

assert "abc" == /abc/
assert "\\d" == /\d/
def reference = "hello"
assert reference == /$reference/
assert "\$" == /$/

assert "potatoe" ==~ /potatoe/
assert !("potatoe with frites" ==~ /potatoe/)

myFairStringy = 'The rain in Spain stays mainly in the plain!'

found = ''
(myFairStringy =~ /\b\w*ain\b/).each { match ->
        found += match + ' '
}
assert found == 'rain Spain plain '

def matcher = 'class A{ class B {}}' =~ /\bclass\s+(\w+)\s*\{/
assert matcher.size() == 2

Lists, ranges, maps ans sets

A lot of lists and map functions are implemented, so the support is very good. The * operator to get lists is available too.

def odd = [1,2,3].findAll{ item ->
    item % 2 == 1
}
assert odd.size() == 2
assert odd[0] == 1
assert odd[1] == 3

assert [1,2] + [3,5] == [1,2,3,5]
assert [1,2,3,4,5] - [3,5] == [1,2,4]

list = [1, 2, 2, 3, 4]
assert list.unique() == [1, 2, 3, 4]
list = [1, 2, 2, 3, 4]
def uniqList = list.unique(false)
assert list == [1, 2, 2, 3, 4]
assert uniqList == [1, 2, 3, 4]

Ranges with negative numbers are not supported.

Groovy truth

def a = true
def b = true
def c = false
assert a
assert a && b
assert a || c
assert !c

def numbers = [1,2,3]
assert numbers
numbers = []
assert !numbers

assert ['one':1]
assert ![:]

assert ('Hello World' =~ /World/)

assert 'This is true'
assert !''

def s = ''
assert !("$s")
s = 'x'
assert ("$s")

assert !0
assert 1

assert new Object()
assert !null

Also, you can customize groovy truth of your classes

class Account {
    String name
    boolean disabled = false

    boolean asBoolean() { !disabled }
}

assert new Account(name: 'current')
assert !new Account(name: 'old', disabled: true)

Expando class

JavaScript object is similar to Expando object, and is supported

clever = new Expando()

clever.name = 'Groovy'
clever."$clever.name" = { 'YES'}

assert clever.Groovy() == 'YES'
clever."name" = 'Groovy 2.0'
assert clever.name ==  'Groovy 2.0'

def anotherExpando = new Expando(one:1,two:2)

assert anotherExpando.one == 1
assert anotherExpando.two == 2

Numbers

Numbers types are ignored, in conversion we are working with javascript numbers. Math library is compatible with javascript, and you can use it.

assert 0.1 + 0.2 == 0.3
assert 1.1 + 0.2 == 1.3
assert 0.1G + 0.2G == 0.3G
assert 4 * 5 + 45 % 4 == 21

assert 5 / 2 == 2.5
assert 5 % 2 == 1
assert 4 ** 3 == 64
assert -(-5) == 5
assert +(-5) == -5
assert Math.PI > 3.14159
assert Math.PI < 3.1416

Categories and Mixins

Categories are suported with annotation and with the ´use´ keyword. Support on categories and mixins is not very great, you can expect good support in categories, but not in mixins cause deprecated.

final class Distance {
    def number
    String toString() { "${number}m" }
}

class NumberCategory {
    static Distance getMeters(Number self) {
        new Distance(number: self)
    }
}

use(NumberCategory) {
    def dist = 300.meters

    assert dist instanceof Distance
    assert dist.toString() == "300m"
}

Switch

Groovy features are not supported, can’t assign variables inside, not comparing with lists, sets, classes, regular expressions, …​ Just a basic switch.

def res

switch ([true,false]) {
    case {it[0]}: res = 0; break
    case {it[1]}: res = 1; break
    default : res = 2
}

assert res == 0

Closures

Closures are converted to javascript functions. call, curry, rcurry, ncurry, memoize, leftShift are supported. If you set delegate of a closure, it will be used. dehydrate and rehydrate are not supported. Support closures as last argument in methods, and closure composition.

def plus2 = { it + 2}
def times3 = { it * 3}

def times3plus2 = plus2 << times3
assert times3plus2(3) == 11
assert times3plus2(4) == plus2(times3(4))

def plus2times3 = times3 << plus2
assert plus2times3(3) == 15
assert plus2times3(5) == times3(plus2(5))

assert times3plus2(3) == (times3 >> plus2)(3)
//
void unless(boolean b, Closure c) {
    if (!b) c()
}

def (speed, limit) = [80, 90]
def tooFast = false

unless (speed > limit) {
    tooFast = true
}

assert tooFast

GroovyBeans

Support getter and setters as groovy does, named parameters in the default constructor

class MyBean {
    def a
    def b

    def getA() { 5 }

    def getC() { 6 }
}

def myBean = new MyBean(a: 3, b: 4)
assert myBean.a == 5
assert myBean.@a == 3
assert myBean.b == 4
assert myBean.c == 6

Metaprogramming

Can add/modify properties and methods in execution time. Some problems with primitive types as Integer or String. Can use getProperties() and getMethods() to get names. Can add properties/methods to classes. Supporting getProperty, setProperty, methodMissing, propertyMissing. Added invokeMethod but doesn’t intercept calls. Just working on dynamic features needed, never a full groovy metaClass, metaInfo, …​ support.

class Magic {
    def data = [:]
    void setProperty(String name, value) {
        data[name] = value
    }
    def getProperty(name) {
        data[name]
    }
}
Magic.metaClass.getAllData = { ->
    data
}

def magic = new Magic()
magic.a = 5
magic.allData == [a: 5]

magic.metaClass.methodMissing = { String name, args ->
    name
}

assert magic.Groovy() == 'Groovy'

AST’s

Support to @Mixin, @TailRecursive, @Builder, @Category and @Delegate. Annotations in semantic phase, will be applied and converted, is phase where grooscript converts compiled code.

Traits

Good support, in compile and runtime. Don’t support SAM coercion and super.

interface Named {
    String name()
}
trait Greetable implements Named {
    String greeting() { "Hello, ${name()}!" }
}
class Person implements Greetable {
    String name() { 'Bob' }
}

def p = new Person()
assert p.greeting() == 'Hello, Bob!'
assert p instanceof Named
assert p instanceof Greetable

Module extensions

Not supported.

Converted code

Once you have converted code to javascript, you have to use in javascript environment. To run this converted code you need grooscript.js or grooscript.min.js loaded. In Node.js load grooscript npm module, and in your web pages include the lib manually.

<script type="text/javascript" src="js/grooscript.min.js"></script>
<script type="text/javascript" src="js/yourConvertedFile.js"></script>

Now, your groovy code is available. If you have converted a groovy script, in javascript the code will run. If the code that you converted has dependencies from other files, you have to be sure that files are also converted and are loaded. You have to convert any parent class, trait, category,…​ but you don’t have to convert interfaces, they are not used. For next versions of grooscript, we are working to use Require.js for package management.

If you convert a class and you want to use it in javascript, you are working with the 'static' form of the class. To create instances of the class don’t use new, just call the function, for example:

//To create a class instance
var instance = MyClass();

//Using default constructor
var otherInstance = MyClass({ data: 1, name: 'name'});

//Call a method or access a property as usual
instance.method();
instance.methodTwo(otherInstance.name);

When you are using converted code in javascript, you don’t have 'dynamic' magic, as getters, setters, methodMissing, getProperty,…​ The resolution of method calling, access properties, context stuff,…​ is done by functions in grooscript.js lib. Just take a look at converted code, there are functions as gs.mc or gs.gp that do that work. So if you are planning to use groovy magic, do in groovy and convert it, in javascript you can’t do.

Combine both worlds

When you are creating groovy code, that will be converted to javascript and will use javascript libraries, maybe you have to use any conversion between variables. For example, if you have a groovy map, or object, and you calling a javascript library with a method that accepts a javascript map with the groovy object, that groovy object has more things (methods, properties) that javascript expects. You don’t have to use that conversion if you work with strings, numbers and usually arrays. You have two static function to do that conversions, that functions do nothing in groovy, but in javascript will convert objects. Let’s see an example:

import static org.grooscript.GrooScript.toJavascript
import static org.grooscript.GrooScript.toGroovy

def map = [x: 1, y: 2]

//point is a javascript object that has a method init, that accepts an object
point.init(map) // [BAD] You are passing a groovy object to javascript, maybe can fail
point.init(toJavascript(map)) // [GOOD] You now are passing {x: 1, y: 2}

//point has a method info() that returns a javascript object with some data
def badData = point.info() // [BAD] You will have a 'javascript' object, and if you try use 'each', exception
def goodData = toGroovy(point.info()) // [GOOD] goodData will be a 'groovy' object

In javascript, you have similar functions to do this conversions:

point.init(gs.toJavascript(map));
var goodData = gs.toGroovy(point.info());

Apart from @GsNative, you can put javascript code directly when you want to, with:

import static org.grooscript.GrooScript.nativeJs

nativeJs('console.log(this)')

ToJavascript function only pass properties to the new javascript object. I have detected, that some frameworks as React.js has methods that expect a javascript object that can contains functions. To solve this, I have created a new function that converts a groovy map to a javascript object, including closure entries.

import static org.grooscript.GrooScript.toJsObj

def map = [a: 1, double: { number -> number * 2}]
toJsObj(map)

//It will create the following js object: {a: 1, double: function(number) { return number * 2 }}

Annotations

Grooscript offer two annotations to add in your groovy code.

GsNative

You can annotate groovy methods with @GsNative. The javascript code of that method will be the code inside comment, between /* and */, that fragment has to be after { method start. I use a lot this feature to insert javascript code in the groovy code. So when I run the code in groovy nothing happens (tests pass), but the javascript code runs in converted code.

import org.grooscript.asts.GsNative

class Numbers {

    @GsNative
    def getFive() {/*
        $('something').andMoreJsCode();
        return 5;
    */}

    def getSix() {
        6
    }
}

This is a very useful feature, even you can put javascript code in a block comment and groovy code after.

@GsNative
def methodB(){/*
        gs.println('methodB');
        this.a++;
        this.methodA();*/
    println 'inMethodB'; a = a + 1; this.methodA()
}

GsNotConvert

You can annotate your classes and methods with @GsNotConvert and that code will not be converted to javascript.

import org.grooscript.asts.GsNotConvert

@GsNotConvert
class ClassNotConvert {
    def a
}

class A {
    @GsNotConvert
    def methodNotConvert() {
        println 'No!'
    }

    def hello() {
        println 'Hello!'
    }
}

Helper Tools

Grooscript comes with some tools. This tools are some classes that you can use in your groovy code. If you convert code that use this tools, you need the js code of that tools. Actually only grooscript-html-builder.js file. You can find it inside the jar or you can download it.

This tools are little helpers, with only a bit lines of code, don’t expect full libraries. I have introduced this libraries because I think that are interesting in you javascript development. Progress in this tools depends of people’s feedback.

HtmlBuilder

Groovy itself comes with more than one html builder, and from last versions of groovy there is a new template engine. Is a fast and modern engine, and my tool has some things from it.

I like this builders to create html code, using a dsl is a very powerful way to create pages or templates. You can use variables or groovy code inside the builder to convert your templates into code. Let’s see an example:

yieldUnescaped '<!DOCTYPE html>'
html {
  head {
    title('Title of the page')
    link(rel: 'stylesheet', href: '/css/bootstrap.min.css')
  }
  body {
    div(class: 'container') {
      ul {
        //persons is a list of persons that is used to generate the final HTML
        persons.each { person ->
          li {
            a(href:"/person/$person.id", "$person.lastName $person.firstName")
          }
        }
      }

      div {
        a(href:'/person/add', 'Add new person')
      }
    }
  }
}

To use the builder just call the static method build to generate string html code:

package org.grooscript.builder

class HtmlBuilder {

    static String build(@DelegatesTo(HtmlBuilder) Closure closure)

The groovy code can be converted to javascript and will run if you add grooscript-html-builder.js

given:
def result = HtmlBuilder.build {
    body {
        ul(class: 'list', id: 'mainList') {
            2.times { number ->
                li number + 'Hello!'
            }
        }
    }
}

expect:
result == "<body><ul class='list' id='mainList'><li>0Hello!</li><li>1Hello!</li></ul></body>"

Available functions from groovy templates are:

  • yield

  • comment

  • yieldUnescaped

  • newLine

Tips

  • Keep your groovy code tested, sometimes your converted code doesn’t work because groovy code fails.

  • Don’t use grooscript to create a big project, just introduce it in your projects.

  • If you use @GsNative, keep that code in isolated classes, that can be easy to mock them.

  • Don’t abuse @GsNative, is code untested. Better write it in javascript and keep that code tested.

  • Grooscript doesn’t support all groovy and java, check support.

  • You can activate debug console info in javascript with gs.consoleInfo = true;.

  • Remember import grooscript-html-builder.js if you use HtmlBuilder.

  • Use grooscript gradle plugin daemon task to do groovy changes, and see immediately in your browser.

  • You can test dom manipulation with jsoup.

  • Try develop functional, don’t abuse this or variables from other contexts.

  • Curry functions to inject variables better than expect to be available after callbacks hell.

Tools

Grooscript itself is just a Java/Groovy library. We have created some tools to improve the experience using it, in your Gradle projects, using it in Grails 2 or 3, in Node.js projects or adding javascript lib dependencies with Bower.

Gradle plugin

We are working to make good integration with gradle. See the getting started guide to start working with grooscript. Later you will discover more advanced features, as convert groovy templates or tell your server that some files have changed. Source on github. See in gradle plugin repository.

Grails plugin

There is an available plugin for grails 2.4 with nice features, like use your domain classes in the client, websocket support or put groovy code in your gsp’s that will be run in javascript. Take a look at the documentation for more info. Github source code.

Also, it exists a Grails 3 plugin, you can take a look at documentation or source code in github

Npm package

If you have a Node.js project, and you want to use some converted code, you can use grooscript npm package, that will include grooscript.js, then your converted code will run.

var gs = require('grooscript');

eval(fs.readFileSync('aConvertedFile.js')+'');

Little guide

Bower package

You can add grooscript libs using Bower with >bower install grooscript, Then you can add grooscript.js, grooscript.min.js or grooscript-html-builder.js dependencies in your project.

<script src="bower_components/grooscript/src/main/resources/META-INF/resources/grooscript.js"></script>

Require.js modules

From version 1.1, you can generate Require.js modules. Require.js is a javascript modules loader, like a package manager in javascript. In javascript, there isn’t package manager, so if your project grows you have to start to think in a solution to manage all javascript files and dependencies. I have chosen Require.js because is a mature utility, used by a lot of projects and compatible with other frameworks like Node.js.

Start with one javascript file

To convert a groovy file to require.js modules, you have to set one groovy file and a destination path. If you set any classpath for the conversions, that classpath will be used to find groovy files.

import org.grooscript.GrooScript

Map conversionOptions = [classpath: 'src/main/groovy']
GrooScript.convertRequireJs('src/main/groovy/Initial.groovy', 'jsModules', conversionOptions)

If you run this, a file jsModules/Initial.js will be generated. The source files has to be inside the classPath folder.

This conversion is clever, and if Initial.groovy use other source files, that files also will be converted to modules. A file in src/main/groovy/util/Data.groovy will be converted to a require.js module in jsModules/util/Data.js. Also that dependency will be added in Initial.js module.

Then, you don’t have to take care about all the files and dependencies, each time that you convert Initial.groovy, all groovy files will be converted to modules, and all dependencies will be include in that modules.

Add javascript dependencies

If you want to include dependencies to other external require.js modules, you can use AST @RequireJsModule. For example, in this class we are going to use data from a require.js module in lib/data.js.

package files

import org.grooscript.asts.RequireJsModule

class Require {
    @RequireJsModule(path = 'lib/data')
    def data
}

The javascript result of this conversion is:

define(['lib/data'], function (data) {

  function Require() {
    //..
    gSobject.data = data;
    //..

    return gSobject;
  };

  return Require;
});

In groovy you can use and test data field as a normal field.

Using it

You have all that javascript modules, now you can use in your require.js application. You have to be sure that you include grooscript.js in your require.js dependencies. All require.js modules that you have generated are converted groovy code that needs grooscript.js to run.

Some examples require.js initial file:

requirejs.config({
    baseUrl: 'jsModules',
    paths: {
        lib: 'js/lib'
    }
});

requirejs(['lib/grooscript.min'], function() {
    requirejs(['Initial']);
});
requirejs.config({
    baseUrl: 'js/app',
    paths: {
        lib: '../lib',
        jquery: '../lib/jquery'
    },
    shim: {
        'bookDemo': ['lib/grooscript.min', 'lib/grooscript-html-builder']
    }
});

requirejs(['jquery', 'bookDemo']);

And that’s all, you don’t have to worry about more groovy files, just add javascript dependencies in your project.

For more info about this, can take a look at this github improvement. Also the books demo will be updated soon to use this feature.

The grooscript gradle plugin has task to generate require.js modules, and listen for changes to speed up your development experience.

Acknowledgements

Thank you for this amazing experience, a lot of fun, I learned a lot. With all the groovy community, groovy and grails developers and all people interested in the project, this journey has been an incredible experience. Is hard put names of people that helped me this years, sure I forget someone.

Special thanks to @glaforge, @marioggar and René, they spent time at the beginning of this project. Also thanks to @burtbeckwith and @graemerocher for grails help, only good things to say about all groovy and grails teams.

Thank you Greach and Gr8Conf teams for let me talk about grooscript in gr8 conferences. All that people that sent me emails, commented me something, asked me about the project. Don’t stop it, I love your feedback.

Thank you @jetbrains for a IntelliJ IDEA open source license, the best groovy IDE.

Jetbrains

Finally thanks to last contributors in github. First pull request by yellowsnow. Nice suggestions from pioneer pushing package support. abelsromero has done amazing job improving build. alnavart giving feedback and nice suggestions. We have nice stuff to do after 1.0 release.