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)
Bind to Global JS Values
First, make sure the value you'd like to model doesn't already exist in our provided API.
Some JS values, like setTimeout
, live in the global scope. You can bind to them like so:
[@bs.val] external setTimeout: (unit => unit, int) => float = "setTimeout";
[@bs.val] external clearTimeout: float => unit = "clearTimeout";
(We already provide setTimeout
, clearTimeout
and others in the Js.Global module).
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 returnsunit
(which on the JS side turns into a function that accepts nothing and returns nothing akaundefined
),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.
Tips & Tricks
The above 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.
type timerId;
[@bs.val] external setTimeout: (unit => unit, int) => timerId = "setTimeout";
[@bs.val] external clearTimeout: timerId => unit = "clearTimeout";
let id = setTimeout(() => Js.log("hello"), 100);
clearTimeout(id);
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.
Since external
s are inlined, we end up with JS output as readable as hand-written JS.
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:
[@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
:
[@bs.val] [@bs.scope ("window", "location", "ancestorOrigins")]
external length: int = "length";
This binds to window.location.ancestorOrigins.length
.
Special Global Values
Global values like __filename
and __DEV__
don't always exist; you can't even model them as an option
, since the mere act of referring to them in ReScript (then compiled into JS) would trigger the usual Uncaught ReferenceError: __filename is not defined
error in e.g. the browser environment.
For these troublesome global values, ReScript provides a special approach: %external(a_single_identifier)
.
switch ([%external __DEV__]) {
| Some(_) => Js.log("dev mode")
| None => Js.log("production mode")
};
That first line's typeof
check won't trigger a JS ReferenceError.
Another example:
switch ([%external __filename]) {
| Some(f) => Js.log(f)
| None => Js.log("non-node environment")
};