Docs / BuckleScript / BindToGlobalValues

Bind to Global Values

Do you want to use window? Or Math? Or anything that you don't have to first import or require in JavaScript? This section describes how to access those from BuckleScript.

First, make sure the value you'd like to model doesn't already exist in our provided API. For a quick search of values, see the index of values.

Then, make sure it's not already on https://github.com/reasonml-community or NPM.

Now, here's how you bind to a JS value:

RE
[@bs.val] external setTimeout : (unit => unit, int) => float = "setTimeout"; [@bs.val] external clearTimeout : float => unit = "clearTimeout";

This binds to the JavaScript setTimeout methods and the corresponding clearTimeout. The external's type annotation specifies that setTimeout:

  • takes a function that accepts unit and returns unit (which on the JS side turns into a function that accepts nothing and returns nothing. More on modeling functions later)

  • and an integer that specifies the duration before calling said function

  • returns a number that is the timeout's ID. This number might be big, so we're modeling it as a float rather than the 32-bit int

Abstract Type

The above still isn't ideal. See how setTimeout returns a float and clearTimeout accepts one. There's no guarantee that you're passing the float created by setTimeout into clearTimeout! For all we know, someone might pass it Math.random() into the latter.

We're in a language with a great type system now! Let's leverage a popular feature to solve this problem: abstract types.

RE
type timerId; [@bs.val] external setTimeout : (unit => unit, int) => timerId = "setTimeout"; [@bs.val] external clearTimeout : timerId => unit = "clearTimeout";

Clearly, timerId is a type that can only be created by setTimeout! Now we've guaranteed that clearTimeout will be passed a valid ID. Whether it's a number under the hood is now a mere implementation detail.

Inspect the output here. It's as clean as hand-written JS code, except you know it comes from correctly typed BuckleScript. If these kind of features are all you use from BuckleScript, you'd already have derived values from them!

This trick is often used to allow folks to agree on what JS functionalities to bind to, without prescribing how to bind to them. For example, the BS library exposes a few abstract types like window. The DOM API is huge and extremely hard to bind to correctly; we don't provide an opinionated way of doing it, so exposing the types and allowing you to use them as input/output types of your externals and have everyone's opinionated wrapper still agreeing on the types is a great middle-ground. You, for example, might only need 3 methods from DOM, and so wrote your own, thin wrappers for them.

Global Modules

If you want to bind to a value inside a global module, e.g. Math.random, attach a bs.scope to your bs.val external:

RE
[@bs.scope "Math"] [@bs.val] external random : unit => float = "random"; let someNumber = random();

you can bind to an arbitrarily deep object by passing a tuple to bs.scope:

RE
[@bs.val] [@bs.scope ("window", "location", "ancestorOrigins")] external length : int = "length";

This binds to window.location.ancestorOrigins.length.