Comprehensive documentation is currently missing for understanding bs/reason extensions. Thus, I have created this catalog of all the extensions I used for this library.
Extension points or annotations in OCaml/Reason tell the compiler to transform the parsed syntax tree in a particular way. - @yawaramin
They are used extensively when we are binding to external JavaScript libraries and data.
It binds existing JS value (function/primitive/object) to its counter-part.
external setTimeout: (unit -> unit) -> int -> float = "setTimeout" [[@@bs.val]
When the name you're using on the BS side matches the JS value you're modeling, you can use the empty string shorthand:
external setTimeout: (unit -> unit) -> int -> float = "" [[@@bs.val]
It is used to bind to a value or a function supplied by the module. Depending upon package-specs
, it is either ES module or Common.js module.
external dirname: string -> string = "dirname" [@@bs.module]
(* Shorthand *)
external dirname: string -> string = "" [@@bs.module]
(* Usage *)
let directory = dirname "/home/harshal/file.txt"
(* Output - package-specs: commonjs *)
var Path = require("path");
var root = Path.dirname("/home/harshal/file.txt");
(* Output - package-specs: es6 *)
import * as Path from 'path';
var root = Path.dirname("/home/harshal/file.txt");
While you can use relative paths with @@bs.module
but be careful with it. It is not really recommended. The problem will happen when you create a binding with external
and then use it in other module with let
. Read more here.
If you want to bind to a value inside a global module, you can use this extension.
external random: unit -> float = "random" [@@bs.val][@@bs.scope "Math"]
If you wish to easily bind to window.history.length
, you can do this.
external historyLen: int = "length" [@@bs.val][@@bs.scope "window", "history"]
You can also bind to a value from an external module.
external create : ('a -> unit) -> 'b = ""
[@@bs.val][@@bs.scope "Observable"] [@@bs.module "rxjs"]
Many JS libraries have functions that take multiple number of arguments. As long as they are homogeneous (same type), then this extension can be used:
external join: string array -> string = "" [@@bs.module "path"] [@@bs.variadic]
let v = join [| "a"; "b"|]
Bucklescript compiles record types, variants to array as mentioned here. But it means record will be compiled as follows:
type person = {
age: int;
name: string;
}
let me = { age = 5; name = "Big Reason" }
The corresponding JS code is:
var me = /* record */[
/* age */5,
/* name */"Big Reason"
];
exports.me = me;
The bs.deriving abstract
annotation turns it into an "abstract type" (aka you don't know what the actual value's shape). This is when Record Mode feature of the Bucklescript can be used where it converts the record into plain JS object instead of array. Note that, since bs.deriving
abstract hides the actual record shape, you can't access a field using e.g. joe.age
. We remediate this by generating getter and setters.
Also note that, JS objects and OCaml records are diffferent - OCaml cares about the order while JS object does not. Read here.
type person = {
age: int;
name: string;
} [@@bs.deriving abstract]
let me = person ~name:"Joe" ~age:20
This will be compiled to plain JS object as:
var me = {
age: 20,
name: "Joe"
};
exports.me = me;
Read more about this issue issue here.