Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

reason-webapi

dboris5MIT0.7.0

Reason / ReScript bindings to DOM

reason, bucklescript, rescript, reason, ocaml, web, dom

readme

rescript-webapi

npm

Bindings to the DOM and other browser-specific web APIs.

Stability

This project is in the process of being forked; until the library reaches 1.0, expect major breaking changes to the API. See the 1.0 project for the current progress towards that goal.

Installation

npm install rescript-webapi

Then add rescript-webapi to bs-dependencies in your bsconfig.json. A minimal example:

{
  "name": "my-thing",
  "sources": "src",
  "bs-dependencies": ["rescript-webapi"]
}

Usage

See the examples folder.

Please only use the modules exposed through the toplevel module Webapi, for example Webapi.Dom.Element. In particular, don't use the 'flat' modules like Webapi__Dom__Element as these are considered private and are not guaranteed to be backwards-compatible.

Some notes on the DOM API

The DOM API is mostly organized into interfaces and relies heavily on inheritance. The ergonomics of the API is also heavily dependent on dynamic typing, which makes it somewhat challenging to implement a thin binding layer that is both safe and ergonomic. To achieve this we employ subtyping and implementation inheritance, concepts which aren't very idiomatic to OCaml (or Reason), but all the more beneficial to understand in order to be able to use these bindings effectively.

Subtyping

The Dom types, and the relationships between them, are actually defined in the Dom module that ships with bs-platform (Source code), where you'll find a bunch of types that look like this:

type _element<'a>;
type element_like<'a> = node_like<_element<'a>>;
type element = element_like<_baseClass>;

This is subtyping implemented with abstract types and phantom arguments. The details of how this works isn't very important (but see #23 for a detailed explanation of how exactly this trickery works) in order to just use them, but there are a few things you should know:

  • If you expand the alias of a concrete DOM type, you'll discover it's actually a list of abstract types. e.g. element expands to _baseClass _element _node _eventTarget_like This means element is a subtype of _element, _node and _eventTarget_like.
  • The _like type are "open" (because they have a type variable). This means that a function accepting an 'a element_like will accept any "supertype" of element_like. A function accepting just an element will only accept an element (Technically element is actually a "supertype" of element_like too).

This system works exceptionally well, but has one significant flaw: It makes type errors even more complicated than they normally are. If you know what to look for it's not that bad, but unfortunately the formatting of these errors don't make looking for it any easier.

Implementation inheritance

If you've looked through the source code a bit, you've likely come across code like this:

include Webapi__Dom__EventTarget.Impl({ type t = t });
include Webapi__Dom__Node.Impl({ type t = t });
include Webapi__Dom__ParentNode.Impl({ type t = t });
include Webapi__Dom__NonDocumentTypeChildNode.Impl({ type t = t });
include Webapi__Dom__ChildNode.Impl({ type t = t });
include Webapi__Dom__Slotable.Impl({ type t = t });
include Impl({ type t = t });

This is the implementation inheritance. Each "inheritable" module defines an "Impl" module where all its exported functions are defined. include Webapi__Dom__Node.Impl({ type t = t }); means that all the functions in Webapi__Dom__Node.Impl should be included in this module, but with the t type of that module replaced by the t type of this one. And that's it, it now has all the functions.

Implementation inheritance is used instead of subtyping to make it easier to understand which functions operate on any given "subject". If you have an element and you need to use a function defined in Node, let's say removeChild you cannot just use Node.removeChild. Instead you need to use Element.removeChild, which you can since Element inherits from Node. As a general rule, always use the function in the module corresponding to the type you have. You'll find this makes it very easy to see what types you're dealing with just by reading the code.

Changes

See CHANGELOG.md.

License and Credits

All code is licensed as MIT. See LICENSE.

This project was forked from bs-webapi after it was mostly abandoned. We took the opportunity to make major changes to the API and bring it up to modern ReScript development practices by renaming it.

This project also forks bs-fetch and includes fetch as a first-class concept in the webapi.

Primary development on this project is sponsored by Tiny.

changelog

1.0 Changes

Breaking Changes

  • Change all send.pipe externals to send, making the whole project "t-first" (#8)
    • Some Node APIs do not trigger compile errors due to the argument swap, because both arguments were Node instances. This logic error can cause severe migration headaches; the following methods had named arguments added to highlight places migration is necessary (#89)
    • appendChild
    • compareDocumentPosition
    • contains
    • insertBefore
    • removeChild
    • replaceChild
  • Imported bs-fetch as Webapi.Fetch and converted it to "t-first" (#31)
  • Removed deprecated APIs (#16)
  • Removed preview from File bindings. It doesn't seem to be in any specifications. (#56)
  • Removed type_ from Fetch.Request module. It doesn't seem to be in any specifications. (#67)
  • Updated Window.getSelection binding to return an option (#15)
  • Updated getClientRects binding on Document and Range to return a Dom.RectList.t (#36)
  • Updated Document.elementFromPoint binding to return a nullable value (#35)
  • Updated MutationRecord bindings nextSibling, attributeName, attributeNamespace and oldValue to return nullable values (#59)
  • Corrected spelling of nextSibling in MutationRecord (#59)
  • Updated History.scrollRestoration to use auto and manual instead of a boolean (#88)
  • PopStateEvent.state now returns a history state type instead of an open object (#88)
  • Updated InputEvent.data to return a nullable value (#90)

Added (non-breaking)

  • WebSocket bindings (#34)
  • Canvas2d.newPath2D() to bind Path2D objects (#45)
  • IntersectionObserver and IntersectionObserverEntry bindings (#27)
  • scrollToWithOptions binding to window (#26)
  • HtmlDocument bindings hasFocus and activeElement moved to Document (#41)
    • They're still available on HtmlDocument thanks to the include structure
  • StaticRange bindings (#48)
  • Added bindings for the File constructor (#56)
  • Added bindings for the Blob constructor (#56)
  • DataTransfer bindings (#51)
  • WorkerGlobalScope, WindowOrWorkerGlobalScope, WorkerNavigator, and WorkerLocation bindings (#57)
  • Response constructors to Fetch bindings (#64)
  • HTMLFormControlsCollection, HTMLOptionsCollection, HTMLFieldSetElement, Document.forms, HTMLFormElement.elements, HTMLObjectElement, HTMLOptGroupElement, HTMLOptionElement, HTMLOutputElement, HTMLSelectElement, HTMLTextAreaElement, and RadioNodeList bindings (#73)
  • Canvas2d bindings drawImage, drawImageScale, and drawImageFull added (#83)
  • InputEvent bindings for inputType, dataTransfer and getTargetRanges (#90)
  • Node.insertAtEnd binding (which does parent.insertBefore(child, null)) (#89)
  • CustomEvent Functor to make custom events with strongly typed detail fields (#93)
  • Url.createObjectURLFromBlob binding that takes a Blob.t (#106)
  • returnValue setter for BeforeUnloadEvent (#110)
  • Webapi.FormData.makeWithHtmlForm binding that takes a Webapi.Dom.HtmlFormElement.t (#108)
  • Canvas.toBlob & Canvas.toDataUrl methods, with multiple argument variants (#111)

Fixed

  • ofElement was incorrectly returning Dom.htmlElement type instead of the enclosing element type (#60)
  • Dom.CssStyleDeclaration.setPropertyValue was emitting the wrong function name (#114)

Miscellaneous

  • Converted project to rescript syntax (#18)
  • Added explicit values to all externals instead of using = "" (#40)
  • Deprecated Node.isSameNode (MDN recommends using === instead) (#89)