BuckleScript

BuckleScript

  • Docs
  • Try
  • API
  • Community
  • Blog
  • Languages iconEnglish
    • 日本語
    • Español
    • Français
    • 한국어
    • Português (Brasil)
    • Русский
    • 中文
    • 繁體中文
    • Help Translate
  • GitHub

›Interop

Intro

  • What & Why
  • Installation
  • New Project
  • Try
  • Concepts Overview
  • Upgrade Guide to v7

Interop

  • Overview
  • Cheatsheet
  • Embed Raw JavaScript
  • Common Data Types
  • Intro to External
  • Bind to Global Values
  • Null, Undefined & Option
  • Object
  • Object 2
  • Class
  • Function
  • Property access
  • Return value wrapping
  • Import & Export
  • Regular Expression
  • Exceptions
  • JSON
  • Pipe First
  • Generate Converters & Helpers
  • Better Data Structures Printing (Debug Mode)
  • NodeJS Special Variables
  • Miscellaneous
  • Browser Support & Polyfills

Build System

  • Overview
  • Configuration
  • Automatic Interface Generation
  • Interop with Other Build System
  • Performance
  • Advanced

Standard Library

  • Overview

Advanced

  • Conditional Compilation
  • Extended Compiler Options
  • Use Existing OCaml Libraries
  • Difference from Native OCaml
  • Compiler Architecture & Principles
  • Comparison to Js_of_ocaml
Edit

Generate Converters & Helpers

Sometimes, you might want to generate e.g. function accessors from a variant declaration, or a Js.t object + converter functions from a record definition. BuckleScript comes with a few annotations that allow you to generate those.

Functions & Plain Values for Variant

Use accessors.

type action =
  | Click
  | Submit of string
  | Cancel
  [@@bs.deriving accessors]
[@bs.deriving accessors]
type action =
  | Click
  | Submit(string)
  | Cancel;

Variants constructors with payloads generate functions, payload-less constructors generate plain integers. 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.
  • Doesn't work with polymorphic variants yet.

Output:

function submit(param_0) {
  return /* Submit */[param_0];
}

var click = /* Click */0;

var cancel = /* Cancel */1;

exports.click  = click;
exports.submit = submit;
exports.cancel = cancel;

Usage

let s = submit "hello" (* gives Submit "hello" *)
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).

Generate first-class accessors for record types.

Do you find yourself typing something like this a lot?

type pet = {
  name: string,
};

let pets = [{name: "bob"}, {name: "bob2"}];
pets
  |> List.map(pet => pet.name)
  |> String.concat("&")
  |> Js.log;

This call to map: List.map(pet => pet.name) could be a bit more concise. Wouldn't it be nice if it looked like this instead: List.map(name)?

If we use @bs.deriving accessors on the record constructor, we can do just that. The decorator will generate a function matching the name of each field in the record which takes the record type, and returns the field type:

[@bs.deriving accessors]
type pet = {
  name: string,
};

let pets = [{name: "bob"}, {name: "bob2"}];
pets
  |> List.map(name)
  |> String.concat("&")
  |> Js.log;

Convert Between Js.t Object and Record

Note: In BuckleScript >= v7 records are already compiled to JS objects. jsConverter is therefore obsolete and will generate a no-op function for compatibility instead.

Use jsConverter.

type coordinates = {
  x : int;
  y : int
} [@@bs.deriving jsConverter]
[@bs.deriving jsConverter]
type coordinates = {
  x: int,
  y: int
};

Generates 2 functions of the following types:

val coordinatesToJs : coordinates -> <x : int ; y : int > Js.t

val coordinatesFromJs : < x: int ; y : int ; .. > Js.t -> coordinates
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. If you'd like recursive conversion, upvote and subscribe to this issue: https://github.com/BuckleScript/bucklescript/issues/2294 (note: please don't leave 'I want this too!' comments, it spams all subscribers of the issue ;-)

Usage

This exports a jsCoordinates JS object (not a record!) for JS files to use:

let jsCoordinates = coordinatesToJs {x = 1; y = 2}
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:

external jsCoordinates: coordinates = "jsCoordinates" [@@bs.module "myGame"]
[@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 passing the newType option to the jsConverter plugin:

type coordinates = {
  x : int;
  y : int
} [@@bs.deriving {jsConverter = newType}]
[@bs.deriving {jsConverter: newType}]
type coordinates = {
  x: int,
  y: int
};

Generates 2 functions of the following types:

val coordinatesToJs : coordinates -> abs_coordinates

val coordinatesFromJs : abs_coordinates -> coordinates
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 *)
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?

Convert between JS Integer Enum and BS Variant

Use jsConverter.

type fruit =
  | Apple
  | Orange
  | Kiwi
  | Watermelon
  [@@bs.deriving jsConverter]
[@bs.deriving jsConverter]
type fruit =
  | Apple
  | Orange
  | Kiwi
  | Watermelon;

This option causes jsConverter to, again, generate functions of the following types:

val fruitToJs : fruit -> int

val fruitFromJs : int -> fruit option
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

type fruit =
  | Apple
  | Orange [@bs.as 10]
  | Kiwi [@bs.as 100]
  | Watermelon
  [@@bs.deriving jsConverter]

let zero = fruitToJs Apple (* 0 *)

let _ = match fruitFromJs 100 with
| Some Kiwi -> Js.log "this is Kiwi"
| _ -> Js.log "received something wrong from the JS side"
[@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 passing the same newType option to the jsConverter plugin:

type fruit =
  | Apple
  | Kiwi [@bs.as 100]
  | Watermelon
  [@@bs.deriving {jsConverter = newType}]
[@bs.deriving {jsConverter: newType}]
type fruit =
  | Apple
  | [@bs.as 100] Kiwi
  | Watermelon;

This option causes jsConverter to generate functions of the following types:

val fruitToJs : fruit -> abs_fruit

val fruitFromJs : abs_fruit -> fruit
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

type fruit =
  | Apple
  | Kiwi [@bs.as 100]
  | Watermelon
  [@@bs.deriving {jsConverter = newType}]

let opaqueValue = fruitToJs Apple

external jsKiwi: abs_fruit = "iSwearThisIsAKiwi" [@@bs.module "myJSFruits"]
let kiwi = fruitFromJs jsKiwi

let error = fruitFromJs 100 (* nope, can't take a random int *)
[@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 */

Convert between JS String Enum and BS Polymorphic Variant

Similar to previous section, except polymorphic variants are converted to string instead of int.

Usage

type fruit = [
  | `Apple
  | `Kiwi [@bs.as "miniCoconut"]
  | `Watermelon
] [@@bs.deriving jsConverter]

let appleString = fruitToJs `Apple (* "Apple" *)
let kiwiString = fruitToJs `Kiwi (* "miniCoconut" *)
[@bs.deriving jsConverter]
type fruit = [
  | `Apple
  | [@bs.as "miniCoconut"] `Kiwi
  | `Watermelon
];

let appleString = fruitToJs(`Apple); /* "Apple" */
let kiwiString = fruitToJs(`Kiwi); /* "miniCoconut" */

Deriving converters with abstract type through newType also still works.

Last updated on 4/13/2020
← Pipe FirstBetter Data Structures Printing (Debug Mode) →
  • Functions & Plain Values for Variant
    • Usage
  • Generate first-class accessors for record types.
  • Convert Between Js.t Object and Record
    • Usage
    • More Safety
  • Convert between JS Integer Enum and BS Variant
    • Usage
    • More Safety
  • Convert between JS String Enum and BS Polymorphic Variant
    • Usage