Docs / BuckleScript / Decorators

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 timeoutIDs returned by setTimeout, not any int.

Abstract types to the rescue!

RE
type 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:

JS
var 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:

RE
type 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:

RE
let 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:

JS
var 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:

RE
type 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:

RE
div.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:

JS
div.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:

RE
type 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:

RE
div.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:

JS
div.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:

RE
fn(obj, arg1, arg2, arg3, ... , argN); /* or equivalently: obj->fn(arg1, arg2, arg3, ... , argN); */

and BS compiles this to:

JS
obj.fn(arg1, arg2, arg3, ... , argN);

What if you want the object to come after the arguments like so:

RE
fn(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:

RE
fn(arg1, arg2, arg3, ... , argN, obj); /* or equivalently: obj |> fn(arg1, arg2, arg3, ... , argN); */

and BS compiles this to:

JS
obj.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:

JS
var 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:

RE
type t; [@bs.module "cheerio"] external load: string => t = ""; let q = load("<h2 class='title'>Hello world</h2>");

compiles to:

JS
var 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:

JS
export 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:

JS
var 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:

JS
var Fs = require("fs"); Fs.readFileSync("data.txt", "utf8");

bs.string compiled `utf8 to the string "utf8".

Now, if we try pass an unknown encoding:

RE
readFileSync(~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:

JS
var 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:

JS
log(401, "Not allowed");

bs.int compiled `unauthorized to the number 401.

Now, if we try pass an unknown status:

RE
log(~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 to 0

  • `bad_request to 1

  • `unauthorized to 2

[@bs.unwrap]

Say you have the following JS function:

JS
function 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:

JS
padLeft(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.

RE
padLeft(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:

JS
padLeft(7, "eleven"); /* => " eleven" */ padLeft("7", "eleven"); /* => "7eleven" */

[@bs.new]

When you need use the JS new operator, use bs.new.

For example:

RE
type t; [@bs.new] external createDate: unit => t = "Date"; let date = createDate();

compiles to:

JS
var date = new Date();

When the object is not available on the global scope and needs importing, add bs.module:

RE
type t; [@bs.module] [@bs.new] external book: unit => t = "Book"; let myBook = book();

compiles to:

JS
var Book = require("Book"); var myBook = new Book();

[@bs.deriving]

[@bs.deriving accessors]

Reason records are represented as arrays in JS.

For example:

RE
type person = { name: string, age: int, country: string, }; let misha: person = { name: "Misha", age: 18, country: "Australia", };

compiles to:

JS
var 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:

JS
function 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:

RE
type 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:

JS
let actions = [ add("Drink coffee"), toggle(34), deleteOne(12), deleteAll ];

[@bs.deriving abstract]

Reason records are represented as arrays in JS:

RE
type 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:

RE
let 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:

RE
Js.log(nameGet(misha)); /* => "Misha" */ Js.log(ageGet(misha)); /* => undefined */

or, equivalently:

RE
Js.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:

JS
var 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:

JS
var 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:

JS
var 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:

JS
console.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:

JS
log(404, "NOT_FOUND"); log(404, "NOT_FOUND", "this", "page", "is", "not", "found");

[@bs.inline]

TODO

[@bs.meth]

TODO