nau / jscala   0.4

MIT License GitHub

Scala macro that produces JavaScript from Scala code.

Scala versions: 2.11 2.10

JScala

Scala macro that produces JavaScript from Scala code. Let it be type safe!

Build Status

Gitter

Supported Features:

  • Variable definitions, basic unary and binary operations
  • Named and anonymous functions
  • Scala Arrays/Seq as JavaScript Array literals
  • Scala Map and anonymous classes as JavaScript object
  • if, while, for..in and for statements
  • Scala if as an expression (e.g. val a = if (true) 1 else 2)
  • Scala match as JavaScript switch
  • Basic Scala class/trait definition to JavaScript object definition translation
  • Global JavaScript functions (parseInt etc)
  • Basic Browser objects (window, history, location etc)
  • Basic HTML DOM objects (Document, Element, Attribute, Node, NodeList etc)
  • Raw JavaScript inclusion
  • Values and function call injections from your Scala code
  • Generated JavaScript eval using Java ScriptEngine
  • Pretty printing and compression using YUI compressor
  • Basic @Javascript macro annotation support
  • Basic toJson/fromJson macros
  • @Typescripted annotation

Examples

This Scala code has no meaning but shows basic ideas:

val replacement = "text"
val js = javascript {
  window.setTimeout(() => {
    val r = new RegExp("d.*", "g")
    class Point(val x: Int, val y: Int)
    val point = new Point(1, 2)
    def func(i: String) = r.exec(i)
    val list = document.getElementById("myList2")
    val map = collection.mutable.Map[String, String]()
    if (typeof(map) == "string") {
      for (idx <- 0 until list.attributes.length) {
        val attr = list.attributes.item(idx).asInstanceOf[Attribute]
        map(attr.name) = func(attr.textContent).as[String]
      }
    } else {
      val obj = new {
        val field = 1
        def func2(i: Int) = "string"
      }
      val links = Array("https://github.com/nau/scala")
      for (link <- links) {
        include("var raw = 'JavaScript'")
        console.log(link + obj.func2(obj.field) + point.x)
      }
      window.location.href = links(0).replace("scala", "jscala")
    }
  }, 1000)
}
println(js.asString)

It will print

window.setTimeout(function () {
    var r = new RegExp("d.*", "g");
    function Point(x, y) {
      this.x = x;
      this.y = y;
    };
    var point = new Point(1, 2);
    function func(i) {
      return r.exec(i);
    };
    var list = document.getElementById("myList2");
    var map = {};
    if (typeof(map) == "string") for (var idx = 0; idx < list.attributes.length; ++idx) {
      var attr = list.attributes.item(idx);
      map[attr.name] = func(attr.textContent);
    } else {
      var obj = {
        field: 1,
        func2: function (i) {
          return "string";
        }
      };
      var links = ["https://github.com/nau/scala"];
      for (var linkIdx = 0, link = links[linkIdx]; linkIdx < links.length; link = links[++linkIdx]) {
        var raw = 'JavaScript';
        console.log((link + obj.func2(obj.field)) + point.x);
      };
      window.location.href = links[0].replace("scala", "jscala");
    };
  }, 1000)

How To Use

In your build.sbt add

scalaVersion := "2.13.2"

libraryDependencies += "org.jscala" %% "jscala-macros" % "0.5"

If you want to try the latest snapshot:

scalaVersion := "2.13.2"

resolvers += Resolver.sonatypeRepo("snapshots")

libraryDependencies += "org.jscala" %% "jscala-macros" % "0.6-SNAPSHOT"

In your code

import org.jscala._
val js = javascript { ... }
println(js.asString)
println(js.compress)
println(js.eval())

That's it!

How To Try Macro Annotations

In your build.sbt add

scalaVersion := "2.13.2"

libraryDependencies += "org.jscala" %% "jscala-macros" % "0.5"

libraryDependencies += "org.jscala" %% "jscala-annots" % "0.5"

In your code

import org.jscala._
@Javascript class User(val name: String, val id: Int)
@Javascript(json = false) class Greeter {
  def hello(u: User) {
    print("Hello, " + u.name + "\n")
  }
}
// Run on JVM
val u1 = new User("Alex", 1)
val greeter = new Greeter()
greeter.hello(u1) // prints "Hello, Alex"
val json = u1.js.json.asString
val main = javascript {
    val u = new User("nau", 2)
    val u1Json = eval("(" + inject(json) + ")").as[User] // read User from json string generated above
    val t = new Greeter()
    t.hello(u)
    t.hello(u1Json)
  }
val js = User.jscala.javascript ++ Greeter.jscala.javascript ++ main // join classes definitions with main code
js.eval() // run using Rhino
println(js.asString) // prints resulting JavaScript

Run it and you'll get

Hello, Alex

Hello, nau
Hello, Alex
{
  function User(name, id) {
    this.name = name;
    this.id = id;
  };
  function Greeter() {
    this.hello = function (u) {
      print(("Hello, " + u.name) + "\n");
    };
  };
  var u = new User("nau", 2);
  var u1Json = eval(("(" + "{\n  \"name\": \"Alex\",\n  \"id\": 1\n}") + ")");
  var t = new Greeter();
  t.hello(u);
  t.hello(u1Json);
}

See AES example:

https://github.com/nau/jscala/blob/master/jscala-examples/src/main/scala/org/jscalaexample/AES.scala

It's AES Scala implementation which is used for both Scala and JavaScript encryption/decryption.

How To Build And Play Some Tetris

Make sure you have at least -Xmx750Mb for your sbt. Don't know why but it takes up to 700Mb to compile jscala-macros project.

In sbt shell run tetris task. It will compile and generate tetris.js file in jscala-examples/javascript-tetris and open Tetris game in your browser. Tetris is fully written in Scala and translates to JavaScript mostly literally.

Tetris sources are here: jscala-examples/src/main/scala/org/jscalaexample/Tetris.scala

Planned Features

  • Language support improvements
  • Web frameworks support: Play, Lift

Feedback

Any feedback is very welcome!

You can use JScala mailing list if you have any questions.

Or simply ask me on Twitter: @atlanter