You are currently looking at the v6.0 - v8.2 docs (Reason v3.6 syntax edition). You can find the latest manual page here.
(These docs are equivalent to the old BuckleScript docs before the ReScript rebrand)
Generate Converters & Helpers
When using ReScript, you will sometimes come into situations where you want to
Automatically generate functions that convert between ReScript's internal and JS runtime values (e.g. variants).
Convert a record type into an abstract type with generated creation, accessor and method functions.
Generate some other helper functions, such as functions from record attribute names.
You can use the @bs.deriving
decorator to trigger code generation. All different options and configurations will be discussed on this page.
Note: Please be aware that extensive use of code generation might make it harder to understand your programs (since the code being generated is not visible in the source code, and you just need to know what kind of functions / values a decorator generates).
Another Note: Since v8.3
you can drop the bs.
prefix for all our decorators (e.g. @bs.deriving
=> @deriving
).
Generate Functions & Plain Values for Variants
Use [@bs.deriving accessors]
on a variant type to create accessor functions for its constructors.
[@bs.deriving accessors]
type action =
| Click
| Submit(string)
| Cancel;
Variants constructors with payloads generate functions, payload-less constructors generate plain integers (the internal representation of variants).
Note:
The generated accessors are lower-cased.
You can now use these helpers on the JavaScript side! But don't rely on their actual values please.
Usage
let s = submit("hello"); /* gives Submit("hello") */
This is useful:
When you're passing the accessor function as a higher-order function (which plain variant constructors aren't).
When you'd like the JS side to use these values & functions opaquely and pass you back a variant constructor (since JS has no such thing).
Please note that in case you just want to pipe a payload into a constructor, you don't need to generate functions for that. Use the ->
syntax instead, e.g. "test"->Submit
.
Generate Field Accessors for Records
Use [@bs.deriving accessors]
on a record type to create accessors for its record field names.
[@bs.deriving accessors]
type pet = {
name: string,
};
let pets = [|{name: "bob"}, {name: "bob2"}|];
pets
->Belt.Array.map(name)
->Js.Array2.joinWith("&")
->Js.log;
Generate Converters for JS Object and Record
Note: In ReScript >= v7 records are already compiled to JS objects.
[@bs.deriving jsConverter]
is therefore obsolete and will generate a no-op function for compatibility instead.
Use [@bs.deriving jsConverter]
on a record type to create convertion functions between records / JS object runtime values.
[@bs.deriving jsConverter]
type coordinates = {
x: int,
y: int
};
Generates 2 functions of the following types:
let coordinatesToJs: coordinates => {. "x": int, "y": int};
let coordinatesFromJs: {.. "x": int, "y": int} => coordinates;
Note:
coordinatesFromJs
uses an open object type that accepts more fields, just to be more permissive.The converters are shallow. They don't recursively drill into the fields and convert them. This preserves the speed and simplicity of output while satisfying 80% of use-cases.
Usage
This exports a jsCoordinates
JS object (not a record!) for JS files to use:
let jsCoordinates = coordinatesToJs({x: 1, y: 2});
This binds to a jsCoordinates
record (not a JS object!) that exists on the JS side, presumably created by JS calling the function coordinatesFromJs
:
[@bs.module "myGame"] external jsCoordinates : coordinates = "jsCoordinates";
More Safety
The above generated functions use Js.t
object types. You can also hide this implementation detail by making the object type abstract by using the newType
option with [@bs.deriving jsConverter]
:
[@bs.deriving {jsConverter: newType}]
type coordinates = {
x: int,
y: int
};
Generates 2 functions of the following types:
let coordinatesToJs: coordinates => abs_coordinates;
let coordinatesFromJs: abs_coordinates => coordinates;
Usage
Using newType
, you've now prevented consumers from inadvertently doing the following:
let myCoordinates = {
x: 10,
y: 20
};
let jsCoords = coordinatesToJs(myCoordinates);
let x = jsCoords##x; /* disallowed! Don't access the object's internal details */
Same generated output. Isn't it great that types prevent invalid accesses you'd otherwise have to encode at runtime?
Generate Converters for JS Integer Enums and Variants
Use [@bs.deriving jsConverter]
on a variant type to create converter functions that allow back and forth conversion between JS integer enum and ReScript variant values.
[@bs.deriving jsConverter]
type fruit =
| Apple
| Orange
| Kiwi
| Watermelon;
This option causes jsConverter
to, again, generate functions of the following types:
let fruitToJs: fruit => int;
let fruitFromJs: int => option(fruit);
For fruitToJs
, each fruit variant constructor would map into an integer, starting at 0, in the order they're declared.
For fruitFromJs
, the return value is an option
, because not every int maps to a constructor.
You can also attach a [@bs.as alternativeIntValue]
to each constructor to customize their output.
Usage
[@bs.deriving jsConverter]
type fruit =
| Apple
| [@bs.as 10] Orange
| [@bs.as 100] Kiwi
| Watermelon;
let zero = fruitToJs(Apple); /* 0 */
switch (fruitFromJs(100)) {
| Some(Kiwi) => Js.log("this is Kiwi")
| _ => Js.log("received something wrong from the JS side")
};
Note: by using bs.as
here, all subsequent number encoding changes. Apple
is still 0
, Orange
is 10
, Kiwi
is 100
and Watermelon
is 101
!
More Safety
Similar to the JS object <-> record deriving, you can hide the fact that the JS enum are ints by using the same newType
option with [@bs.deriving jsConverter]
:
[@bs.deriving {jsConverter: newType}]
type fruit =
| Apple
| [@bs.as 100] Kiwi
| Watermelon;
This option causes jsConverter
to generate functions of the following types:
let fruitToJs: fruit => abs_fruit;
let fruitFromJs: abs_fruit => fruit;
For fruitFromJs
, the return value, unlike the previous non-abstract type case, doesn't contain an option
, because there's no way a bad value can be passed into it; the only creator of abs_fruit
values is fruitToJs
!
Usage
[@bs.deriving {jsConverter: newType}]
type fruit =
| Apple
| [@bs.as 100] Kiwi
| Watermelon;
let opaqueValue = fruitToJs(Apple);
[@bs.module "myJSFruits"] external jsKiwi : abs_fruit = "iSwearThisIsAKiwi";
let kiwi = fruitFromJs(jsKiwi);
let error = fruitFromJs(100); /* nope, can't take a random int */
Generate Converters for JS String Enums and Polymorphic Variants
Note: Since ReScript 8.2, polymorphic variants are already compiled to strings, so this feature is getting deprecated at some point. It's currently still useful for aliasing JS output with
@bs.as
.
Similarly as with generating int converters, use [@bs.deriving jsConverter]
on a polymorphic variant type to create converter functions for JS string and ReScript poly variant values.
Usage
[@bs.deriving jsConverter]
type fruit = [
| `Apple
| [@bs.as "miniCoconut"] `Kiwi
| `Watermelon
];
let appleString = fruitToJs(`Apple); /* "Apple" */
let kiwiString = fruitToJs(`Kiwi); /* "miniCoconut" */
You can also use [@bs.deriving {jsConverter: newType}]
to generate abstract types instead.
Convert Record Type to Abstract Record
Note: For ReScript >= v7, we recommend using plain records to compile to JS objects. This feature might still be useful for certain scenarios, but the ergonomics might be worse
Use [@bs.deriving abstract]
on a record type to expand the type into a creation, and a set of getter / setter functions for fields and methods.
Usually you'd just use ReScript records to compile to JS objects of the same shape. There is still one particular use-case left where the [@bs.deriving abstract]
convertion is still useful: Whenever you need compile a record with an optional field where the JS object attribute shouldn't show up in the resulting JS when undefined (e.g. {name: "Carl", age: undefined}
vs {name: "Carl"}
). Check the Optional Labels section for more infos on this particular scenario.
Usage Example
[@bs.deriving abstract]
type person = {
name: string,
age: int,
job: string,
};
[@bs.val] external john : person = "john";
Note: the person
type is not a record! It's a record-looking type that uses the record's syntax and type-checking. The [@bs.deriving abstract]
decorator turns it into an "abstract type" (aka you don't know what the actual value's shape).
Creation
You don't have to bind to an existing person
object from the JS side. You can also create such person
JS object from ReScript's side.
Since [@bs.deriving abstract]
turns the above person
record into an abstract type, you can't directly create a person record as you would usually. This doesn't work: {name: "Joe", age: 20, job: "teacher"}
.
Instead, you'd use the creation function of the same name as the record type, implicitly generated by the [@bs.deriving abstract]
annotation:
let joe = person(~name="Joe", ~age=20, ~job="teacher")
Note how in the example above there is no JS runtime overhead.
Rename Fields
Sometimes you might be binding to a JS object with field names that are invalid in ReScript/Reason. Two examples would be {type: "foo"}
(reserved keyword in ReScript/Reason) and {"aria-checked": true}
. Choose a valid field name then use [@bs.as]
to circumvent this:
[@bs.deriving abstract]
type data = {
[@bs.as "type"] type_: string,
[@bs.as "aria-label"] ariaLabel: string,
};
let d = data(~type_="message", ~ariaLabel="hello");
Optional Labels
You can omit fields during the creation of the object:
[@bs.deriving abstract]
type person = {
[@bs.optional] name: string,
age: int,
job: string,
};
let joe = person(~age=20, ~job="teacher", ());
Optional values that are not defined, will not show up as an attribute in the resulting JS object. In the example above, you will see that name
was omitted.
Note that the [@bs.optional]
tag turned the name
field optional. Merely typing name
as option(string)
wouldn't work.
Note: now that your creation function contains optional fields, we mandate an unlabeled ()
at the end to indicate that you've finished applying the function.
Accessors
Again, 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.
Read
One getter function is generated per bs.deriving abstract
record type field. In the above example, you'd get 3 functions: nameGet
, ageGet
, jobGet
. They take in a person
value and return string
, int
, string
respectively:
let twenty = ageGet(joe)
Alternatively, you can use the Pipe operator (->
) for a nicer-looking access syntax:
let twenty = joe->ageGet
If you prefer shorter names for the getter functions, we also support a light
setting:
[@bs.deriving {abstract: light}]
type person = {
name: string,
age: int,
};
let joe = person(~name="Joe", ~age=20);
let joeName = name(joe);
The getter functions will now have the same names as the object fields themselves.
Write
A [@bs.deriving abstract]
value is immutable by default. To mutate such value, you need to first mark one of the abstract record field as mutable
, the same way you'd mark a normal record as mutable:
[@bs.deriving abstract]
type person = {
name: string,
mutable age: int,
job: string,
};
Then, a setter of the name ageSet
will be generated. Use it like so:
let joe = person(~name="Joe", ~age=20, ~job="teacher");
ageSet(joe, 21);
Alternatively, with the Pipe First syntax:
joe->ageSet(21)
Methods
You can attach arbitrary methods onto a type (any type, as a matter of fact. Not just [@bs.deriving abstract]
record types). See Object Method in the "Bind to JS Function" section for more infos.
Tips & Tricks
You can leverage [@bs.deriving abstract]
for finer-grained access control.
Mutability
You can mark a field as mutable in the implementation (ml
/re
) file, while hiding such mutability in the interface file:
/* test.re */
[@bs.deriving abstract]
type cord = {
[@bs.optional] mutable x: int,
y: int,
};
/* test.rei */
[@bs.deriving abstract]
type cord = {
[@bs.optional] x: int,
y: int,
};
Tada! Now you can mutate inside your own file as much as you want, and prevent others from doing so!
Hide the Creation Function
Mark the record as private
to disable the creation function:
[@bs.deriving abstract]
type cord = pri {
[@bs.optional] x: int,
y: int,
};
The accessors are still there, but you can no longer create such data structure. Great for binding to a JS object while preventing others from creating more such object!
Use submodules to prevent naming collisions and binding shadowing
Oftentimes you will have multiple abstract types with similar attributes. Since ReScript will expand all abstract getter, setter and creation functions in the same scope where the type is defined, you will eventually run into value shadowing problems.
For example:
[@bs.deriving abstract]
type person = {name: string};
[@bs.deriving abstract]
type cat = {
name: string,
isLazy: bool,
};
let person = person(~name="Alice");
/* Error: This expression has type person but an expression was expected
of type cat */
person->nameGet();
To get around this issue, you can use modules to group a type with its related functions and later use them via local open statements:
module Person = {
[@bs.deriving abstract]
type t = {name: string};
};
module Cat = {
[@bs.deriving abstract]
type t = {
name: string,
isLazy: bool,
};
};
let person = Person.t(~name="Alice");
let cat = Cat.t(~name="Snowball", ~isLazy=true);
/* We can use each nameGet function separately now */
let shoutPersonName = Person.(person->nameGet->Js.String.toUpperCase);
/* Note how we use a local open Cat.([some expression]) to
get access to Cat's nameGet function */
let whisperCatName = Cat.(cat->nameGet->Js.String.toLowerCase);
Convert External into JS Object Creation Function
Use @bs.obj
on an external
binding to create a function that, when called, will evaluate to a Js.t
object with fields corresponding to the function's parameter labels.
This is very handy because you can make some of those labelled parameters optional and if you don't pass them in, the output object won't include the corresponding fields. Thus you can use it to dynamically create objects with the subset of fields you need at runtime.
For example, suppose you need a JavaScript object like this:
JSvar homeRoute = {
method: "GET",
path: "/",
action: () => console.log("Home"),
// options: ...
};
But only the first three fields are required; the options field is optional. You can declare the binding function like so:
[@bs.obj] external route: (
~_method:string,
~path:string,
~action:(list(string) => unit),
~options:Js.t({..})=?,
unit
) => _ = "";
Note: the = ""
part at the end is just a dummy placeholder, due to syntactic limitations. It serves no purpose currently.
This function has four labelled parameters (the fourth one optional), one unlabelled parameter at the end (which we mandate for functions with optional parameters, and one parameter (_method
) that requires an underscore prefix to avoid confusion with the ReScript keyword method
.
Also of interest is the return type: _
, which tells ReScript to automatically infer the full type of the Js.t
object, sparing you the hassle of writing down the type manually!
The function is called like so:
let homeRoute = route(~_method="GET", ~path="/", ~action=(_ => Js.log("Home")), ());