JScala
Scala macro that produces JavaScript from Scala code. Let it be type safe!
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