Quick Links
  • -Overview
  • -Language Features
  • -JS Interop
  • -Build System
Documentation
Language Manual
Reference for all language features
ReScript & React
First class bindings for ReactJS
GenType
Seamless TypeScript integration
Reanalyze
Dead Code & Termination analysis
Exploration
Packages
Explore third party libraries and bindings
Syntax Lookup
Discover all syntax constructs
APIPlaygroundBlogCommunity
  • Playground
  • Blog
  • Twitter
  • GitHub
  • Forum
Language Manual
Overview
  • Introduction
  • Installation
  • Editor Plugins
  • Migrate to ReScript Syntax
  • Try
Language Features
  • Overview
  • Let Binding
  • Type
  • Primitive Types
  • Tuple
  • Record
  • Object
  • Variant
  • Null, Undefined and Option
  • Array & List
  • Function
  • If-Else & Loops
  • Pipe
  • Pattern Matching / Destructuring
  • Mutation
  • JSX
  • Exception
  • Lazy Values
  • Promise
  • Module
  • Import & Export
  • Reserved Keyword
JavaScript Interop
  • Embed Raw JavaScript
  • Shared Data Types
  • External (Bind to Any JS Library)
  • Bind to JS Object
  • Bind to JS Function
  • Import from / Export to JS
  • Bind to Global JS Values
    • Global Modules
    • Special Global Values
  • JSON
  • Use Illegal Identifier Names
  • Generate Converters & Helpers
  • Browser Support & Polyfills
  • Interop Cheatsheet
Build System
  • Build System Overview
  • Build System Configuration
  • Interop with JS Build Systems
  • Build Performance
Guides
  • Converting from JS
  • Libraries
Extra
  • Newcomer Examples
  • Project Structure
  • FAQ
Docs / Language Manual / Bind to Global JS Values
Edit

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:

Reason (Old Syntax)ML (Older Syntax)JS Output
[@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 returns unit (which on the JS side turns into a function that accepts nothing and returns nothing aka undefined),

  • 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.

Reason (Old Syntax)ML (Older Syntax)JS Output
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 externals 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:

Reason (Old Syntax)ML (Older Syntax)JS Output
[@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:

Reason (Old Syntax)ML (Older Syntax)JS Output
[@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).

Reason (Old Syntax)ML (Older Syntax)JS Output
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:

Reason (Old Syntax)ML (Older Syntax)JS Output
switch ([%external __filename]) {
| Some(f) => Js.log(f)
| None => Js.log("non-node environment")
};
Import from / Export to JSJSON

© 2024 The ReScript Project

Software and assets distribution powered by KeyCDN.

About
  • Community
  • ReScript Association
Find us on