Decorators
All available @bs.
-decorators explained with examples.
#[@bs.val]
bs.val
allows you to access JS values that are available on the global scope.
For example:
RE[@bs.val] external setTimeout: (unit => unit, int) => int = "setTimeout";
setTimeout(() => Js.log("Hello"), 1000);
Tip: When BS and JS names match, you can use a ""
shorthand:
RE[@bs.val] external setTimeout : (unit => unit, int) => int = "";
[@bs.val] external clearTimeout: int => unit = "";
Let's make sure that clearTimeout
can only get timeoutID
s returned by setTimeout
, not any int
.
Abstract types to the rescue!
REtype timeoutID;
[@bs.val] external setTimeout: (unit => unit, int) => timeoutID = "";
[@bs.val] external clearTimeout: timeoutID => unit = "";
What if the value we want to access is on another global object, e.g. Math.floor
, Date.now
, or even location.origin.length
?
That's what bs.scope
is for. Check it out!
#[@bs.scope]
bs.scope
is used with other attributes like bs.val
and bs.module
to access "deep" values.
For example:
RE[@bs.scope "Math"] [@bs.val] external floor: float => int = "";
[@bs.scope ("location", "origin")] [@bs.val] external originLength: string = "length";
[@bs.scope "CONSTANTS"] [@bs.module "MyModule"] external initialDays: int = "";
Js.log(floor(3.4));
Js.log(originLength);
Js.log(initialDays);
compiles to:
JSvar MyModule = require("MyModule");
console.log(Math.floor(3.4));
console.log(location.origin.length);
console.log(MyModule.CONSTANTS.initialDays);
Note: The order of bs.*
attributes doesn't matter.
#[@bs.get]
bs.get
is used to get a property of an object.
For example, say you created a div
element:
REtype element;
[@bs.scope "document"] [@bs.val] external createElement: string => element = "";
let div = createElement("div");
How can we access its properties, e.g. scrollTop
?
Doing this doesn't work:
RElet top = div.scrollTop; // Error: The record field scrollTop can't be found.
That's what bs.get
is for:
RE[@bs.get] external scrollTop: element => float = "";
let top = scrollTop(div);
which compiles to:
JSvar top = div.scrollTop;
Note how we defined scrollTop
as a function that takes an element
, and BS compiled it to element.scrollTop
.
#[@bs.set]
bs.set
is used to set a property of an object.
For example, say you created a div
element:
REtype element;
[@bs.scope "document"] [@bs.val] external createElement: string => element = "";
let div = createElement("div");
How can we set its properties, e.g. scrollTop
?
Doing this doesn't work:
REdiv.scrollTop = 100; // Error: The record field scrollTop can't be found.
That's what bs.set
is for:
RE[@bs.set] external setScrollTop: (element, int) => unit = "scrollTop";
setScrollTop(div, 100);
which compiles to:
JSdiv.scrollTop = 100;
Note how we defined scrollTop
as a function that takes an element
and a value
, and BS compiled it to element.scrollTop = value
.
#[@bs.send]
#[@bs.send]
bs.send
is used to call a function on an object.
For example, say you created a div
and a button
:
REtype element;
[@bs.scope "document"] [@bs.val] external createElement: string => element = "";
let div = createElement("div");
let button = createElement("button");
How can we append the button
to the div
?
Doing this doesn't work:
REdiv.appendChild(button); // Error: The record field appendChild can't be found.
That's what bs.send
is for:
RE[@bs.send] external appendChild: (element, element) => element = "";
appendChild(div, button);
which compiles to:
JSdiv.appendChild(button);
In general, bs.send
external looks like this:
RE[@bs.send] external fn: (obj, arg1, arg2, arg3, ... , argN) => ...;
You call fn
like this:
REfn(obj, arg1, arg2, arg3, ... , argN);
/*
or equivalently:
obj->fn(arg1, arg2, arg3, ... , argN);
*/
and BS compiles this to:
JSobj.fn(arg1, arg2, arg3, ... , argN);
What if you want the object to come after the arguments like so:
REfn(arg1, arg2, arg3, ... , argN, obj);
/*
or equivalently:
obj |> fn(arg1, arg2, arg3, ... , argN);
*/
That's what bs.send.pipe
is for.
#[@bs.send.pipe]
bs.send.pipe
, like bs.send
, is used to call a function on an object.
Unlike bs.send
, bs.send.pipe
takes the object as an argument:
RE[@bs.send.pipe: obj] external fn: (arg1, arg2, arg3, ... , argN) => ...;
You call fn
like this:
REfn(arg1, arg2, arg3, ... , argN, obj);
/*
or equivalently:
obj |> fn(arg1, arg2, arg3, ... , argN);
*/
and BS compiles this to:
JSobj.fn(arg1, arg2, arg3, ... , argN);
[@bs.module]
Use bs.module
when you need to require
something.
For example:
RE[@bs.module] external uuidv4: unit => string = "uuid/v4";
Js.log(uuidv4());
compiles to:
JSvar V4 = require("uuid/v4");
console.log(V4());
By passing a string to bs.module
you can bind to a specific function in the module.
For example:
REtype t;
[@bs.module "cheerio"] external load: string => t = "";
let q = load("<h2 class='title'>Hello world</h2>");
compiles to:
JSvar Cheerio = require("cheerio");
var q = Cheerio.load("<h2 class='title'>Hello world</h2>");
Note: The string provided to bs.module
can be anything, e.g.: "./path/to/my_module"
.
#Dealing with ES6 export default
Say that your project has the following my_module.js
that is compiled with Babel:
JSexport default "fancy stuff";
You'd bind to it like this:
RE[@bs.module "./path/to/my_module"] external fancyStuff: string = "default";
Js.log(fancyStuff); /* => "fancy stuff" */
This compiles to:
JSvar My_module = require("./path/to/my_module");
console.log(My_module.default);
#[@bs.string]
You can use bs.string
together with polymorphic variant to constrain function's parameters.
For example:
RE[@bs.module "fs"]
external readFileSync: (
~name: string,
[@bs.string] [`utf8 | `ascii]
) => string = "";
readFileSync(~name="data.txt", `utf8);
compiles to:
JSvar Fs = require("fs");
Fs.readFileSync("data.txt", "utf8");
bs.string
compiled `utf8
to the string "utf8"
.
Now, if we try pass an unknown encoding:
REreadFileSync(~name="data.txt", `binary);
we'll get a compile error.
When your string causes a syntax error in the polymorphic variant (this can happen, for example, when the string starts with a number or contains a special character), use bs.as
:
RE[@bs.module "fs"]
external readFileSync: (
~name: string,
[@bs.string] [`utf8 | `ascii | [@bs.as "ucs-2"] `ucs_2]
) => string = "";
readFileSync(~name="data.txt", `ucs_2);
This compiles to:
JSvar Fs = require("fs");
Fs.readFileSync("data.txt", "ucs-2");
#[@bs.int]
You can use bs.int
together with polymorphic variant to constrain function's parameters.
For example:
RE[@bs.val]
external log: (
~status: [@bs.int] [[@bs.as 200] `ok | [@bs.as 400] `bad_request | `unauthorized],
~message: string
) => string = "";
log(~status=`unauthorized, ~message="Not allowed");
compiles to:
JSlog(401, "Not allowed");
bs.int
compiled `unauthorized
to the number 401
.
Now, if we try pass an unknown status:
RElog(~status=`not_found, ~message="This page is not found");
we'll get a compile error.
Note how we used bs.as
to better control the numbers that bs.int
compiles to.
If we omitted all the bs.as
above, bs.int
would compile:
`ok
to0
`bad_request
to1
`unauthorized
to2
#[@bs.unwrap]
Say you have the following JS function:
JSfunction padLeft(padding, str) {
if (typeof padding === "number") {
return " ".repeat(padding) + str;
}
if (typeof padding === "string") {
return padding + str;
}
throw new Error("Expected padding to be number or string");
}
Note how padding
can be either a number or a string.
Here is how you'd bind to this function:
RE[@bs.val]
external padLeft: (
[@bs.unwrap] [`Int(int) | `Str(string)],
string
) => string = "";
padLeft(`Int(7), "eleven");
padLeft(`Str("7"), "eleven");
which compiles to:
JSpadLeft(7, "eleven"); /* => " eleven" */
padLeft("7", "eleven"); /* => "7eleven" */
Note how bs.unwrap
simply strips the polymorphic variant constructor (i.e. it unwraps the wrapped value).
Now, any attempts to pass a padding that's not a number or a string, will result in compile error.
REpadLeft(true, "eleven"); /* => compile error */
padLeft(`Int(7.5), "eleven"); /* => compile error */
Alternatively, you might consider to create two separate functions that bind to the same padLeft
:
RE[@bs.val] external padLeftWithSpaces: (int, string) => string = "padLeft";
[@bs.val] external padLeftWithString: (string, string) => string = "padLeft";
padLeftWithSpaces(7, "eleven");
padLeftWithString("7", "eleven");
The compiled output is the same:
JSpadLeft(7, "eleven"); /* => " eleven" */
padLeft("7", "eleven"); /* => "7eleven" */
#[@bs.new]
When you need use the JS new
operator, use bs.new
.
For example:
REtype t;
[@bs.new] external createDate: unit => t = "Date";
let date = createDate();
compiles to:
JSvar date = new Date();
When the object is not available on the global scope and needs importing, add bs.module
:
REtype t;
[@bs.module] [@bs.new] external book: unit => t = "Book";
let myBook = book();
compiles to:
JSvar Book = require("Book");
var myBook = new Book();
#[@bs.deriving]
#[@bs.deriving accessors]
Reason records are represented as arrays in JS.
For example:
REtype person = {
name: string,
age: int,
country: string,
};
let misha: person = {
name: "Misha",
age: 18,
country: "Australia",
};
compiles to:
JSvar misha = ["Misha", 18, "Australia"];
Imagine that you converted some JS code to Reason and it uses the record above. The code that isn't converted to Reason yet wants to access misha
's fields.
In order to get the country
, for example, you would need to know to access misha[2]
.
You shouldn't need to worry about the array representation of a record or the indices where certain fields live.
That's exactly what bs.deriving accessors
is for! It creates getter functions for all fields of the annotated record.
For example:
RE[@bs.deriving accessors]
type person = {
name: string,
age: int,
country: string,
};
let misha: person = {
name: "Misha",
age: 18,
country: "Australia",
};
compiles to:
JSfunction name(param) {
return param[0];
}
function age(param) {
return param[1];
}
function country(param) {
return param[2];
}
var misha = ["Misha", 18, "Australia"];
Now, to get misha
's country
we can use the generated accessor: country(misha)
Similarly, imagine your new Reason code has the following variant:
REtype action =
| Add(string)
| Toggle(int)
| DeleteOne(int)
| DeleteAll;
and you want to create DeleteOne(12)
from JS, for example.
By adding bs.deriving accessors
, you get a creation function for every variant constructor.
RE[@bs.deriving accessors]
type action =
| Add(string)
| Toggle(int)
| DeleteOne(int)
| DeleteAll;
Now, in JS you can do:
JSlet actions = [
add("Drink coffee"),
toggle(34),
deleteOne(12),
deleteAll
];
#[@bs.deriving abstract]
Reason records are represented as arrays in JS:
REtype person = {
name: string,
age: int
};
let misha: person = {
name: "Misha",
age: 18
};
Js.log(misha); /* => ["Misha",18] */
To represent them as objects, add bs.deriving abstract
:
RE[@bs.deriving abstract]
type person = {
name: string,
age: int
};
bs.deriving abstract
converts a record to an abstract type, and also creates a creation function:
RElet misha = person(~name="Misha", ~age=18);
Js.log(misha); /* => {"name":"Misha","age":18} */
You can also mark fields as optional with bs.optional
and rename fields with bs.as
.
#Getters
bs.deriving abstract
generates a getter function for every field.
Continuing the example above:
REJs.log(nameGet(misha)); /* => "Misha" */
Js.log(ageGet(misha)); /* => undefined */
or, equivalently:
REJs.log(misha->nameGet); /* => "Misha" */
Js.log(misha->ageGet); /* => undefined */
#Setters
Field values can't be updated unless marked with mutable
.
bs.deriving abstract
generates a setter function for every mutable field.
For example:
RE[@bs.deriving abstract]
type person = {
name: string,
[@bs.optional] mutable age: int,
mutable country: string
};
let misha = person(~name="Misha", ~country="United States", ());
ageSet(misha, 18);
misha->countrySet("Australia");
compiles to:
JSvar misha = {
name: "Misha",
country: "United States"
};
misha.age = 18;
misha.country = "Australia";
Note: bs.*
must come before mutable
.
#[@bs.optional]
bs.optional
can be used with bs.deriving abstract
to mark fields as optional.
For example:
RE[@bs.deriving abstract]
type person = {
name: string,
[@bs.optional] age: int
};
let misha = person(~name="Misha", ());
compiles to:
JSvar misha = {
name: "Misha"
};
#[@bs.as]
bs.as
can be used with bs.deriving abstract
to rename fields. This is useful when field names are valid in JS, but not in BS.
For example:
RE[@bs.deriving abstract]
type button = {
[@bs.as "type"] type_: string,
[@bs.as "aria-label"] ariaLabel: string
};
let submitButton = button(~type_="submit", ~ariaLabel="Submit");
compiles to:
JSvar submitButton = {
type: "submit",
"aria-label": "Submit"
};
#[@bs.variadic]
To model a JS function that takes a variable number of arguments, and all arguments are of the same type, use bs.variadic
:
RE[@bs.scope "Math"] [@bs.val] [@bs.variadic] external max: array(int) => int = "";
Js.log(max([|5, -2, 6, 1|]));
This compiles to:
JSconsole.log(Math.max(5, -2, 6, 1));
Similarly to Function.apply()
in JS, bs.variadic
converts the arguments array in BS to separate arguments on the JS side.
If your function has mandatory arguments, you could use bs.as
to add them:
RE[@bs.val] [@bs.variadic] external warn: ([@bs.as 404] _, [@bs.as "NOT_FOUND"] _, array(string)) => unit = "log";
warn([||]);
warn([|"this", "page", "is", "not", "found"|]);
This compiles to:
JSlog(404, "NOT_FOUND");
log(404, "NOT_FOUND", "this", "page", "is", "not", "found");
#[@bs.inline]
TODO
#[@bs.meth]
TODO