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")
};