10 Languages That Compile to JavaScript
This article includes a list of ten interesting languages that can compile to JavaScript to be executed in the browser or on a platform like Node.js.
Modern applications have different requirements from simple websites. But the browser is a platform with a (mostly) fixed set of technologies available, and JavaScript remains as the core language for web applications. Any application that needs to run in the browser has to be implemented in that language.
We all know that JavaScript isn’t the best language for every task, and when it comes to complex applications, it might fall short. To avoid this problem, several new languages and transpilers of existing ones have been created, all of them producing code that can work in the browser without any lines of JavaScript having to be written, and without you having to think about the limitations of the language.
Dart
Dart is a classical, object-oriented language where everything is an object and any object is an instance of a class (objects can act as functions too.) It’s specially made to build applications for browsers, servers, and mobile devices. It’s maintained by Google and is the language that powers the next generation AdWords UI, the most important product of Google regarding revenue, which is in itself a proof of its power at scale.
The language can be translated to JavaScript to be used in a browser, or be directly interpreted by the Dart VM, which allows you to build server applications too. Mobile applications can be made using the Flutter SDK.
Complex applications also require a mature set of libraries and language features specially designed for the task, and Dart includes all of this. An example of a popular library is AngularDart, a version of Angular for Dart.
It allows you to write type-safe code without being too intrusive. You can write types, but you aren’t required to do so,* since they can be inferred. This allows for rapid prototyping without having to overthink the details, but once you have something working, you can add types to make it more robust.
Regarding concurrent programming in the VM, instead of shared-memory threads (Dart is single-threaded), Dart uses what they call Isolates, with their own memory heap, where communication is achieved using messages. In the browser, the story is a little different: instead of creating new isolates, you create new Workers.
// Example extracted from dartlang.org
import 'dart:async';
import 'dart:math' show Random;
main() async {
print('Compute π using the Monte Carlo method.');
await for (var estimate in computePi()) {
print('π ≅ $estimate');
}
}
/// Generates a stream of increasingly accurate estimates of π.
Stream<double> computePi({int batch: 1000000}) async* {
var total = 0;
var count = 0;
while (true) {
var points = generateRandom().take(batch);
var inside = points.where((p) => p.isInsideUnitCircle);
total += batch;
count += inside.length;
var ratio = count / total;
// Area of a circle is A = π⋅r², therefore π = A/r².
// So, when given random points with x ∈ <0,1>,
// y ∈ <0,1>, the ratio of those inside a unit circle
// should approach π / 4. Therefore, the value of π
// should be:
yield ratio * 4;
}
}
Iterable<Point> generateRandom([int seed]) sync* {
final random = new Random(seed);
while (true) {
yield new Point(random.nextDouble(), random.nextDouble());
}
}
class Point {
final double x, y;
const Point(this.x, this.y);
bool get isInsideUnitCircle => x * x + y * y <= 1;
}
For more reading, I recommend Dart’s Get started with Dart resource.
TypeScript
TypeScript is a superset of JavaScript. A valid JavaScript program is also valid TypeScript, but with static typing added. The compiler can also work as a transpiler from ES2015+ to current implementations, so you always get the latest features.
Unlike many other languages, TypeScript keeps the spirit of JavaScript intact, only adding features to improve the soundness of the code. These are type annotations and other type-related functionality that makes writing JavaScript more pleasant, thanks to the enabling of specialized tools like static analyzers and other tools to aid in the refactoring process. Also, the addition of types improve the interfaces between the different components of your applications.
Type inference is supported, so you don’t have to write all the types from the beginning. You can write quick solutions, and then add all the types to get confident about your code.
TypeScript also has support for advanced types, like intersection types, union types, type aliases, discriminated unions and type guards. You can check out all these in the Advanced Types page in the TypeScript Documentation site.
JSX is also supported by adding the React typings if you use React:
class Person {
private name: string;
private age: number;
private salary: number;
constructor(name: string, age: number, salary: number) {
this.name = name;
this.age = age;
this.salary = salary;
}
toString(): string {
return `${this.name} (${this.age}) (${this.salary})`;
}
}
For more on typeScript, check out SitePoint’s getting started with TypeScript article.
Elm
Elm is a purely functional programming language that can compile to JavaScript, HTML, and CSS. You can build a complete site with just Elm, making it a great alternative to JavaScript frameworks like React. The applications that you build with it automatically use a virtual DOM library, making it very fast. One big plus is the built-in architecture that makes you forget about data-flow and focus on data declaration and logic instead.
In Elm, all functions are pure, which means they’ll always return the same output for a given input. They can’t do anything else unless you specify it. For example, to access a remote API you’d create command functions to communicate with the external world, and subscriptions to listen for responses. Another point for purity is that values are immutable: when you need something, you create new values, instead of modifying them.
The adoption of Elm can be gradual. It’s possible to communicate with JavaScript and other libraries using ports. Although Elm hasn’t reached version 1 yet, it’s being used for complex and large applications, making it a feasible solution for complex applications.
One of the most attractive features of Elm is the beginner-friendly compiler, which, instead of producing hard-to-read messages, generates code that helps you to fix your code. If you’re learning the language, the compiler itself can be of big help.
module Main exposing (..)
import Html exposing (..)
-- MAIN
main : Program Never Model Msg
main =
Html.program
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
}
-- INIT
type alias Model = String
init : ( Model, Cmd Msg )
init = ( "Hello World!", Cmd.none )
-- UPDATE
type Msg
= DoNothing
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
DoNothing ->
( model, Cmd.none )
-- VIEW
view : Model -> Html Msg
view model =
div [] [text model]
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
SitePoint has a handy getting started with Elm article if you want to find out more.
PureScript
PureScript is a purely functional and strongly typed programming language, created by Phil Freeman. It aims to provide strong compatibility with available JavaScript libraries, similar to Haskell in spirit, but keeping JavaScript at its core.
A strong point for PureScript is its minimalism. It doesn’t include any libraries for functionality that would be considered essential in other languages. For example, instead of including generators and promises in the compiler itself, you can use specific libraries for the task. You can choose the implementation you want for the feature you need, which allows a highly efficient and personalized experience when using PureScript, while keeping the generated code as small as possible.
Another distinctive feature of its compiler is the ability to make clean and readable code while maintaining compatibility with JavaScript, both concerning libraries and tools.
Like other languages, PureScript has its own build tool called Pulp, which can be compared to Gulp, but for projects written in this language.
Regarding the type system — unlike Elm, which is the other ML-like language — PureScript has support for advanced type features like higher-kinded types and type classes, which are taken from Haskell, allowing the creation of sophisticated abstractions:
module Main where
import Prelude
import Data.Foldable (fold)
import TryPureScript
main =
render $ fold
[ h1 (text "Try PureScript!")
, p (text "Try out the examples below, or create your own!")
, h2 (text "Examples")
, list (map fromExample examples)
]
where
fromExample { title, gist } =
link ("?gist=" <> gist) (text title)
examples =
[ { title: "Algebraic Data Types"
, gist: "37c3c97f47a43f20c548"
}
, { title: "Loops"
, gist: "cfdabdcd085d4ac3dc46"
}
, { title: "Operators"
, gist: "3044550f29a7c5d3d0d0"
}
]
To take your next step with PureScript, check out the getting started with PureScript guide on GitHub.
CoffeeScript
CoffeeScript is a language that aims to expose the good parts of JavaScript while providing a cleaner syntax and keeping the semantics in place. Although the popularity of the language has been waning in recent years, it’s changing direction and recently received a new major version providing support for ES2015+ features.
The code you write in CoffeeScript is directly translated to readable JavaScript code and maintains compatibility with existing libraries. From version 2, the compiler produces code compatible with the latest versions of ECMAScript. For example, every time you use a class
, you get a class
in JavaScript. Also, if you use React, there’s good news: JSX is compatible with CoffeeScript.
A very distinctive feature of the compiler is the ability to process code written in the literate style, where instead of making emphasis in the code and having comments as an extra, you write comments in the first place, and the code only occasionally appears. This style of programming was introduced by Donald Knuth, making a code file very similar to a technical article.
Unlike the other languages, CoffeeScript code can be interpreted directly in the browser using a library. So if you want to create a quick test, you can write your code in text/coffeescript
script tags, and include the compiler, which will translate the code to JavaScript on the fly:
# Assignment:
number = 42
opposite = true
# Conditions:
number = -42 if opposite
# Functions:
square = (x) -> x * x
# Arrays:
list = [1, 2, 3, 4, 5]
# Objects:
math =
root: Math.sqrt
square: square
cube: (x) -> x * square x
# Splats:
race = (winner, runners...) ->
print winner, runners
# Existence:
alert "I knew it!" if elvis?
# Array comprehensions:
cubes = (math.cube num for num in list)
The CoffeeScript site has a handy getting started with CoffeeScript 2 resource.
ClojureScript
ClojureScript is a compiler that translates the Clojure programming language to JavaScript. It’s a general-purpose, functional language with dynamic typing and support for immutable data structures.
It’s the only one from this list that belongs to the Lisp family of programming languages and, naturally, it shares a lot of the features. For example, the code can be treated as data, and a macro system is available, making metaprogramming techniques possible. Unlike other Lisps, Clojure has support for immutable data structures, making the management of side effects easier.
The syntax can look intimidating for newcomers because of its use of parentheses, but it has profound reasons to be that way, and you’ll certainly appreciate it in the long run. That minimalism in the syntax and its syntactic abstraction capabilities make Lisp a powerful tool for solving problems that require high levels of abstraction.
Although Clojure is mainly a functional language, it isn’t pure like PureScript or Elm. Side effects can still happen, but other functional features are still present.
ClojureScript uses Google Closure for code optimization and also has compatibility with existing JavaScript libraries:
; Extracted from https://github.com/clojure/clojurescript/blob/master/samples/dom/src/dom/test.cljs
(ns dom.test
(:require [clojure.browser.event :as event]
[clojure.browser.dom :as dom]))
(defn log [& args]
(.log js/console (apply pr-str args)))
(defn log-obj [obj]
(.log js/console obj))
(defn log-listener-count []
(log "listener count: " (event/total-listener-count)))
(def source (dom/get-element "source"))
(def destination (dom/get-element "destination"))
(dom/append source
(dom/element "Testing me ")
(dom/element "out!"))
(def success-count (atom 0))
(log-listener-count)
(event/listen source
:click
(fn [e]
(let [i (swap! success-count inc)
e (dom/element :li
{:id "testing"
:class "test me out please"}
"It worked!")]
(log-obj e)
(log i)
(dom/append destination
e))))
(log-obj (dom/element "Text node"))
(log-obj (dom/element :li))
(log-obj (dom/element :li {:class "foo"}))
(log-obj (dom/element :li {:class "bar"} "text node"))
(log-obj (dom/element [:ul [:li :li :li]]))
(log-obj (dom/element :ul [:li :li :li]))
(log-obj (dom/element :li {} [:ul {} [:li :li :li]]))
(log-obj (dom/element [:li {:class "baz"} [:li {:class "quux"}]]))
(log-obj source)
(log-listener-count)
To lean more, head over to the ClojureScript site’s getting started with ClojureScript resource.
Scala.js
Scala.js is a compiler that translates the Scala programming language to JavaScript. Scala is a language that aims to merge the ideas from object-oriented and functional programming into one language to create a powerful tool that is also easy to adopt.
As a strongly typed language, you get the benefits of a flexible type system with partial type inference. Most values can be inferred, but function parameters still require explicit type annotations.
Although many common object-oriented patterns are supported (for example, every value is an object and operations are method calls), you also get functional features like support for first-class functions and immutable data structures.
One of the special advantages of Scala.js is that you can start with a familiar, object-oriented approach and move to a more functional one as you need and at your own speed, without having to do a lot of work. Also, existing JavaScript code and libraries are compatible with your Scala code.
Beginner Scala developers will find the language not very different from JavaScript. Compare the following equivalent code:
// JavaScript
var xhr = new XMLHttpRequest();
xhr.open("GET",
"https://api.twitter.com/1.1/search/" +
"tweets.json?q=%23scalajs"
);
xhr.onload = (e) => {
if (xhr.status === 200) {
var r = JSON.parse(xhr.responseText);
$("#tweets").html(parseTweets(r));
}
};
xhr.send();
// Scala.js
val xhr = new XMLHttpRequest()
xhr.open("GET",
"https://api.twitter.com/1.1/search/" +
"tweets.json?q=%23scalajs"
)
xhr.onload = { (e: Event) =>
if (xhr.status == 200) {
val r = JSON.parse(xhr.responseText)
$("#tweets").html(parseTweets(r))
}
}
xhr.send()
Check out the Scala.js getting started with Scala.js docs for more.
Reason
Reason is a language created and maintained by Facebook, which offers a new syntax for the OCaml compiler, and the code can be translated to both JavaScript and native code.
Being part of the ML family and a functional language itself, it naturally offers a powerful but flexible type system with inference, algebraic data types and pattern matching. It also has support for immutable data types and parametric polymorphism (also known as generics in other languages) but, as in OCaml, support for object-oriented programming is available as well.
The use of existing JavaScript libraries is possible with bucklescript bindings. You can also mix in JavaScript alongside your Reason code. The inserted JavaScript code won’t be strictly checked, but it works fine for quick fixes or prototypes.
If you’re a React developer, bindings are available, and the language also has support for JSX:
/* A type variant being pattern matched */
let possiblyNullValue1 = None;
let possiblyNullValue2 = Some "Hello@";
switch possiblyNullValue2 {
| None => print_endline "Nothing to see here."
| Some message => print_endline message
};
/* Parametrized types */
type universityStudent = {gpa: float};
type response 'studentType = {status: int, student: 'studentType};
let result: response universityStudent = fetchDataFromServer ();
/* A simple typed object */
type payload = Js.t {.
name: string,
age: int
};
let obj1: payload = {"name": "John", "age": 30};
Check out the Reason site’s getting started with Reason guide for more.
Haxe
Haxe is a multi-paradigm programming language, and its compiler can produce both binaries and source code in other languages.
Although Haxe provides a strict type system with support for type inference, it can also work as a dynamic language if the target language supports it. In the same way, it provides support for a variety of programming styles like object-oriented, generic, and functional.
When you write Haxe code, you can target several platforms and languages for compilation without having to make considerable changes. Target-specific code blocks are also available.
You can write both back ends and front ends in Haxe with the same code and achieve communication using Haxe Remoting, for both synchronous and asynchronous connections.
As expected, Haxe code is compatible with existing libraries, but it also provides a mature standard library:
// Example extracted from http://code.haxe.org
extern class Database {
function new();
function getProperty<T>(property:Property<T>):T;
function setProperty<T>(property:Property<T>, value:T):Void;
}
abstract Property<T>(String) {
public inline function new(name) {
this = name;
}
}
class Main {
static inline var PLAYER_NAME = new Property<String>("playerName");
static inline var PLAYER_LEVEL = new Property<Int>("playerLevel");
static function main() {
var db = new Database();
var playerName = db.getProperty(PLAYER_NAME);
trace(playerName.toUpperCase());
db.setProperty(PLAYER_LEVEL, 1);
}
}
Check out the Haxe site’s getting started with Haxe pages for more.
Nim
Nim is a statically typed, multi-paradigm programming language with minimalist and whitespace-sensitive syntax that can compile to JavaScript as well as C, C++.
The language itself is very small, but its metaprogramming capabilities make it attractive to implement features by yourself that you might find built-in to other languages. The building blocks for this are macros, templates, and generics, and with them you can implement things from simple features to different paradigms. This makes Nim an extremely versatile language that can be adapted to your needs, in the spirit of Lisp.
The syntactic abstraction features of Nim allow you to adapt the language to your problems, making true DSLs possible. If you have specialized tasks to solve, you can get a higher level of expressiveness:
# Reverse a string
proc reverse(s: string): string =
result = ""
for i in countdown(high(s), 0):
result.add s[i]
var str1 = "Reverse This!"
echo "Reversed: ", reverse(str1)
# Using templates
template genType(name, fieldname: expr, fieldtype: typedesc) =
type
name = object
fieldname: fieldtype
genType(Test, foo, int)
var x = Test(foo: 4566)
echo(x.foo) # 4566
The Nim site has some useful getting started docs for more information.
Conclusion
If JavaScript isn’t your favorite language, you can still create web applications without having to suffer the shortcomings of the technology. The options available to create those applications can fill a wide spectrum of taste, from purely functional languages like PureScript to object-oriented ones like Dart. And if you want something more than a one-to-one language translation, you have options like Elm that provide you with tools like a virtual DOM and a built-in architecture.
Have you tried any of the languages from this article, or do you have one to recommend? Let us know in the comments!