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]

[@bs.deriving accessors] can be used to either generate getter functions for a decorated record type, or to generate creator functions for constructors of a variant type.

Hint: Since BuckleScript v7, records are compiled to plain JS objects, so you might not need [@bs.deriving accessors] for record types. More about this here.

Record Accessors

The decorator can be applied to record types:

RE
[@bs.deriving accessors] type person = { name: string, age: int, country: string, };

This will create a set of getter functions in the same module scope:

RE
let name: (person) => string; let age: (person) => int; let country: (person) => string;

More details on this can be found here.

Variant Accessors

For variant type constructors we can use the same decorator:

RE
[@bs.deriving accessors] type action = | Add(string) | Toggle(int) | DeleteOne(int) | DeleteAll;

This will create a set of creator functions for every variant constructor in the same module scope which will look something like this:

RE
let add: (string) => action; let toggle: (int) => action; let deleteOne: (int) => action; let deleteAll: action;

We are now able to use the creator functions safely on the JS side without relying on the internal representation of a variant constructor:

JS
// Note: We assume we've imported the functions from the compiled bs.js file! const action1 = add("Drink coffee"); const action2 = toggle(34); /* Constructors without parameter are just a plain value*/ const action3 = deleteAll;

More details on this feature can be found here.

[@bs.deriving abstract]

Hint: Since BuckleScript v7, records are compiled to plain JS objects, so you might not need [@bs.deriving abstract]. More about this here.

bs.deriving abstract is used to convert a record type to the following set of constructs:

  • An abstract type representing the "abstract record"

  • A creator function for the new abstract type, where labeled arguments represent the attributes of the record

  • A set of setter / getter functions for each record attribute

When using this decorator, the original record type will be removed. You'll need to use the newly created functions to create / get / set values of this type.

For example let's define a decorated record type person:

RE
[@bs.deriving abstract] type person = { name: string, /* Use the mutable keyword for generating setters */ mutable age: int };

This will expand to the following code within the same module scope:

RE
/* The abstract type */ type person; /* Getters */ let nameGet: (~name: string, ~age: int) => person; let ageGet: (person) => int; /* Setters (only mutable attributes) */ let ageSet: (person, int) => unit;

This is how we would use the generated functions:

RE
let friend = person(~name="Misha", ~age=20); Js.log(friend->nameGet); /* => "Misha" */ /* Increase age by 1 via mutation */ friend->ageSet(friend->ageGet + 1); Js.log(friend->ageGet); /* => 21 */ /* This will NOT be possible (type is abstract now): */ let misha = { name: "Misha", age: 20 };

Refer to the Abstract Record Mode section for a more detailed explanation.

[@bs.as]

bs.as can be used with bs.deriving abstract or records (BuckleScript v7 and above) 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" };

Note: Since BuckleScript 7, bs.as can also be used in records:

RE
type action = { [@bs.as "type"] _type: string, };

will be compiled to:

JS
var action = { type: "ADD_USER" };

Refer to the Records as Objects section for a detailed explanation.

[@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]

A binding marked with [@bs.inline] will tell the compiler to copy (inline) its value in every place the binding is being used. This is especially useful for cases where e.g. string constants can't be used as variables, but need to be present as constant values.

Suppose you have a list of allowed colors which are represented as strings in JS:

RE
/* Colors.re */ [@bs.inline] let green = "green"; [@bs.inline] let red = "red";

You can use them in another module:

RE
let allowedColors = [|Colors.green, Colors.red|];

the corresponding values get directly inlined in the JS code:

JS
var allowedColors = [ "green", "red" ];

If you omitted the [@bs.inline] statements in the example above, the resulting JS code would look like this:

JS
var allowedColors = [ Colors.green, Colors.red ];

which is not zero-cost. Currently bs.inline works for string, int and bool. float cannot be inlined (yet).

A more advanced, but pretty common use-case is to inline the NODE_ENV strings:

RE
/* NodeEnv.re */ [@bs.val] [@bs.scope "process.env"] external env: string = "NODE_ENV"; [@bs.inline] let development = "development"; [@bs.inline] let production = "production";

Invoke them in another module:

RE
if (NodeEnv.env === NodeEnv.development) { Js.log("development"); }

[@bs.meth]

If you want to call a function of a Js.t object, you need to use bs.meth, to avoid issues with currying.

For instance, take the following JS object:

JS
function say (a, b) { console.log(a, b); }; var john = { say };

The shape of the john object could be typed as follows:

RE
type person = {. [@bs.meth] "say": (string, string) => unit}; [@bs.val] external john: person = "john";

and the say method can be called with the JS object access notation (##):

RE
john##say("hey", "jude");

which compiles to

JS
john.say("hey", "jude");

When you forget to use [@bs.meth] in the function signature, the type checker will error, because it is ensured by the compiler to enforce only fully applied functions in Js.t objects.

Refer to the Object 2 section for a more detailed explanation.