External (Bind to Any JS Library)
external
is the primary ReScript features for bringing in and using JavaScript values.
external
is like a let binding, but:
The right side of
=
isn't a value; it's the name of the JS value you're referring to.The type for the binding is mandatory, since we need to know what the type of that JS value is.
Can only exist at the top level of a file or module.
There are several kinds of external
s, differentiated and/or augmented through the attribute they carry. This page deals with the general, shared mechanism behind most external
s. The different are documented in their respective pages later. A few notable ones:
@val
,@scope
: bind to global JS values.@module
: bind to JS imported/exported values.@send
: bind to JS methods.
You can also use our Syntax Lookup tool to find them.
Related: see also our list of external decorators.
Usage
Once declared, you can use an external
as a normal value, just like a let binding.
Tips & Tricks
external
+ ReScript objects are a wonderful combination for quick prototyping. Check the JS output tab:
// The type of document is just some random type 'a
// that we won't bother to specify
@val external document: 'a = "document"
// call a method
document["addEventListener"](."mouseup", _event => {
Console.log("clicked!")
})
// get a property
let loc = document["location"]
// set a property
document["location"]["href"] = "rescript-lang.org"
We've specified document
's type as 'a
, aka a placeholder type that's polymorphic. Any value can be passed there, so you're not getting much type safety (except the inferences at various call sites). However, this is excellent for quickly getting started using a JavaScript library in ReScript without needing the equivalent of a repository of typed bindings like TypeScript's DefinitelyTyped
repo.
However, if you want to more rigidly bind to the JavaScript library you want, keep reading the next few interop pages.
Performance & Output Readability
external
s declarations are inlined into their callers during compilation, and completely disappear from the JS output. This means any time you use one, you can be sure that you're not incurring extra JavaScript <-> ReScript conversion cost.
Additionally, no extra ReScript-specific runtime is better for output readability.
Note: do also use
external
s and the@blabla
attributes in the interface files. Otherwise the inlining won't happen.
Design Decisions
ReScript takes interoperating with existing code very seriously. Our type system has very strong guarantees. However, such strong feature also means that, without a great interop system, it'd be very hard to gradually convert a codebase over to ReScript. Fortunately, our interop are comprehensive and cooperate very well with most existing JavaScript code.
The combination of a sound type system + great interop means that we get the benefits of a traditional gradual type system regarding incremental codebase coverage & conversion, without the downside of such gradual type system: complex features to support existing patterns, slow analysis, diminishing return in terms of type coverage, etc.