Compiling to JavaScript

Compiling to JavaScript

Most Crow code can be compiled to JavaScript.
Functions that can't be used in a JavaScript build have an extern modifier. (But js extern functions can only be used in a JavaScript build). That will be explained in Extern functions.

The following example uses the alert global function from JavaScript.

import crow/js: call, js-global main void() summon, trusted, js extern _ = "alert".js-global call "Hello, browser!"::string info log "This will appear below"

Build commands

Script

crow build foo.crow --out foo.js outputs a file suitable for use with a <script> tag.
As with a C or machine code build, the details of the script are opaque; it contains only what it needs to run main.

This will also generate a source map file foo.js.map which browsers will automatically use for debugging the .js file if you serve it.
The source map contains all source content in it, so it's all you need for debugging.
If you don't need to debug, you can delete the .map file.

Modules

For easier debugging, you can build to a directory full of modules: crow build foo.crow --out js:foo.
This is slower to load in the browser, but easier to debug as each output module corresponds to a single Crow module.

Node.js

crow build foo.crow --out node-js:foo is similar to a module build, but it can run in Node.js.
This is only really useful for debugging JS code locally in an IDE. It doesn't support Node-specific features like file system access.
The only difference is it uses global instead of window, adds a package.json, and gives the main file a shebang.
crow run foo.crow --node-js builds, runs, then deletes the build.

Using JavaScript values

Operations on JavaScript values are in the js module. The usual way to initiate contact with JavaScript is to access a global variable through js-global.

A JavaScript value will have the type js-any.
This is a "top" type, since any value can convert to it using as-js. Then cast converts it back to any type. Casts are unchecked. See the "type translation" section below to see how JS types can be cast to Crow types.

Most operations on js-any are unsafe; mark the use trusted if you're sure of what it does.
Be sure to also use summon if appropriate.

import crow/js main void() trusted, js extern Number js-any = "Number" js-global inf js-any = Number["POSITIVE_INFINITY"] info log "{inf.cast::float}"

The above program is equivalent to console.log(Number.POSITIVE_INFINITY) in JavaScript. As shown above, subscript gets a property from a JS object. As in JS, this will return undefined if the property does not exist.

To call a JS function, use call. However, in JS, taking a function off of an object makes it lose its this value, so you generally want to use call-property instead.

import crow/js main void() summon, trusted, js extern document = "document" js-global abc = document call-property "querySelector", "#abc"::string abc["textContent"] := "This was set by the script"

Here we need to specify some types when dealing with JS. call-property takes a js-any (the object) and string (the property name), but the other arguments could be anything, so we need ::string to disambiguate.

Type translation

To interact with JS code, you'll need to understand what JS values can cast to what Crow types and vice versa.

Crow type JavaScript type
bool boolean
string, symbol string
int8 through int64
nat8 through nat64
bigint
float32, float64 number
t[], t mut[], t mut-slice Array
Be sure to only pass these to JS code that will follow Crow's rules: Never mutate a t[], and don't do anything that would change the length of a t mut-slice (e.g., push, pop, or splice).
Function types (r function(x p)) function with the same number of parameters.
Lambda types (r data(x p), r shared(x p), r mut(x p)) function with a single parameter, which might be a Crow tuple.
t future Promise
js-any As the name implies, this could be any value.

The above chart shows that if you know a JS value is a number, you can cast it to a float32 or float64, but casting to a nat may cause problems.

Crow functions still have Crow semantics. For example, nat32 adddition will still throw on 32-bit overflow, and float32 operations have 32-bit precision.

Type checks

Compiling to JavaScript does insert some type checks. This can help detect errors coming from erroneous casts. However, it's not guaranteed to detect all errors!

import crow/js main void() unsafe, js extern # MAX_SAFE_INTEGER is actually a float "Number".js-global["MAX_SAFE_INTEGER"].cast foo foo void(a nat) info log "This will not appear: {a}"

Asynchrony

JavaScript code needs to await any time it calls an async function.
When compiling to JavaScript, Crow will automatically mark functions with async and their calls with await if it detects that they my use asynchrony.
However, it defaults to assuming that any JavaScript function you call is synchronous.
If you call a JavaScript async function, treat this like a function returning a future (which is as a Promise) and call await on it.

import crow/js main void() trusted, js extern response = "fetch".js-global call "/tutorial/example.json"::string await json = response call-property "json" await info log "{(json.to::json)["crow"]}"

Runtime

When Crow compiles to JavaScript, it doesn't include the Crow runtime (meaning the allocator and task scheduler).
The JavaScript runtime follows all the rules of a Crow runtime limited to a single thread.
Crow "fibers" are implemented using JS async functions.
A Crow shared lambda just compiles to a normal JS function; with only a single thread, there is no need for any exclusion.

How do I?

Import a module

Currently, there's no way to import a JS module.
If you want to use a JS library, store it in a global variable so Crow can access it.

Define a class

There is no syntax for this, but js/util has a function make-class. (You can also eval a class expression if you just want to write the entire class in JavaScript.)

Do exotic JS operations

For other JS operations with no Crow analog, you could use eval from js.

Use the DOM

js/dom has functions for this.