21. JavaScript

Overview

Fantom provides support for compiling to JavaScript and running in JavaScript VMs such as web browsers. Most of the sys API is available, however not all pods and APIs are accessible due to limitations of the JavaScript VM environment.

Js Facet

You must explicitly mark types you intend to compile to JavaScript using the Js facet:

@Js
class GonnaBeJs
{
  Void sayHi() { Win.cur.alert("Hello!") }
}

Deployment

The JavaScript compiler works by translating Fantom source code directly to JavaScript source code at compile time. This differs from the JVM/CLR, which work by using an intermediate format and translating to the target platform at runtime.

All Fantom source code in a pod marked as @Js will be compiled to a single JavaScript source file. This file will also include reflection information and other meta-data needed at runtime. The file is named <podname>.js and is packaged into the root directory of the pod file.

As JavaScript files are interpreted in the order they are parsed, each pod JS file must be written in the correct order. To simplify this process, the FilePack API provides conveniences to generate a file list that guarantee the correct dependency order:

// expands to: sys.js, concurrent.js, graphics.js, web.js, dom.js
files := FilePack.toAppJsFiles([Pod.find("dom")])

Browser Runtime

Web browsers are the primary target for Fantom JS, so most of the APIs are focused on simplifying how to wire up Fantom-based web apps.

Using FilePack is the easiest way to bundle up your dependencies and serve up to the browser:

files := FilePack.toAppJsFiles(pods)
pack  := FilePack(files)

...

override Void onGet()
{
  switch (req.modRel.path.first)
  {
    case "myApp.js": pack.onGet
    ...
  }
}

...

out.head
  .initJs(["main":"myApp::Main"])
  .includeJs(`/myApp.js`)
  .headEnd

A complete example can be found in js-hello.

Environment Initialization

The Fantom JS runtime can be initialized with custom configuration using the Env.vars API. No explicit initialization is required by default. Simply parsing the source code will produce a valid runtime available at Win.onLoad:

out.head.includeJs(`/myApp.js`).headEnd

To customize the default behavior, use WebOutStream.initJs to initialize the desired Env.vars. This must occur before any pod JS is parsed:

out.head
  .initJs(["main":"myApp::Main", "timezone":"Denver"])
  .includeJs(`/myApp.js`)
  .headEnd

Principally this method is used to specify the "main" method to bootstrap your application at load time. See WebOutStream.initJs for full list supported Env.vars.

See js-env for example code for setting up timezones and locales.

Alternative Runtimes

You can run JavaScript compiled from Fantom by loading the pod's JavaScript source file into any JavaScript VM. There are no special requirements. Most of the information from the above sections should apply to other JsVMs.

Invoking Fantom from JavaScript

Fantom types are normal JavaScript objects, so they can be invoked natively from JavaScript code. Types are are formatted as:

fan.<myPod>.<myType>.<method>

fan.myPod.MyType.staticMain();           // invoke a static method
fan.myPod.MyType.make().instanceMain();  // invoke method from instance

An example using an HTML event handler:

<button onclick="fan.myPod.MyType.doSomething();">Click Me</button>

Fields are compiled into JavaScript as follows:

  • the field itself is prefixed with "m_"
  • getter matches field name
  • the setter is suffixed with the "$" character
  • const fields do not have a getter (must access as m_myField)

Field examples:

instance.myField()        // call getter
instance.myField$(value)  // call setter
instance.m_field          // access field storage

Any Fantom slot that conflicts with a JavaScript keyword will be prefixed with "$". For example a slot named "var" will be accessed in JavaScript as "$var".

Refer to the JavaScript source code in sys and dom to see how the various types are implemented.

Natives

To compile JavaScript natives, add the source directories to your build script using the jsDirs field. See Build Pod for an example.

The JavaScript code must follow the compiler conventions discussed above.

Testing

Fantom includes built-in support for fant to run units test in a JavaScript VM using the -js flag:

$ fant -js myPod

To run JS tests you need to have NodeJs installed. For macOS you can install using Homebrew:

$ brew install node