Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
9248425
refactor: move DOM file out of base folder
jderochervlk May 20, 2026
bc1a0e7
update package-lock.json
jderochervlk May 20, 2026
1519d88
update to rescript 13-beta.4
jderochervlk May 20, 2026
bd0ebf4
add Base and element type
jderochervlk May 20, 2026
bea1b18
split out element into sub modules
jderochervlk May 20, 2026
6dce207
update remaining element
jderochervlk May 20, 2026
e5d67a7
update Element docs
jderochervlk May 20, 2026
43fcf3d
start splitting up document and switching to @scope
jderochervlk May 20, 2026
d8dc3ce
test out change to Document
jderochervlk May 20, 2026
c83f19c
refactor to break up modules
jderochervlk May 20, 2026
29ead8e
even more changes
jderochervlk May 20, 2026
c8ffac8
break up dom features
jderochervlk May 20, 2026
5b10b90
rename features
jderochervlk May 20, 2026
fdc8e56
breakup location and idledealing
jderochervlk May 20, 2026
ef46bda
refactor location
jderochervlk May 20, 2026
c762145
break up more of dom
jderochervlk May 20, 2026
49420e4
refactor to break out base
jderochervlk May 30, 2026
4854dbd
remove test binding
jderochervlk May 30, 2026
ea34e4a
update readme
jderochervlk May 30, 2026
bbed284
add back namespace
jderochervlk May 30, 2026
c526dd5
update some docs
jderochervlk May 30, 2026
ccfa64a
make random values type safe
jderochervlk May 30, 2026
d9b8f3c
add back missing private and spread types
jderochervlk Jun 1, 2026
536a2e0
fixing a couple that were missed
jderochervlk Jun 1, 2026
49e227d
pr feedback
jderochervlk Jun 2, 2026
dfff1c9
refactor: add shared event base types
jderochervlk Jun 2, 2026
9312db8
test: remove event unmonorepo assertions
jderochervlk Jun 2, 2026
9855b04
docs: update event base types plan
jderochervlk Jun 2, 2026
69a67e2
refactor: remove event aliases from DomTypes
jderochervlk Jun 2, 2026
7b99fc5
more splitting up
jderochervlk Jun 2, 2026
783bd12
split out even more from DOM.res
jderochervlk Jun 3, 2026
e229716
I think it's getting better?
jderochervlk Jun 3, 2026
2f6175b
Refactor DOM bindings and feature grouping (#278)
jderochervlk Jun 9, 2026
ca2a475
Merge remote-tracking branch 'origin/main' into codex/resolve-vlk-bas…
jderochervlk Jun 9, 2026
2611881
docs: consolidate follow-up plan
jderochervlk Jun 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
46 changes: 41 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
[![NPM Version](https://img.shields.io/npm/v/@rescript/webapi/experimental)](https://www.npmjs.com/package/@rescript/webapi)

# experimental-rescript-webapi
# ReScript WebAPI

Experimental successor to [rescript-webapi](https://github.com/TheSpyder/rescript-webapi)

This package requires ReScript 13, which is currently in alpha.

## Getting started

Install the package using your favorite package manager:
Expand All @@ -12,22 +14,56 @@ Install the package using your favorite package manager:
npm i @rescript/webapi@experimental
```

and add `@rescript/webapi` to your `rescript.json`:
and add `@rescript/webapi` to your `rescript.json` with the features your app uses:

```json
{
"dependencies": [
"@rescript/webapi"
{
"name": "@rescript/webapi",
"features": ["WebAPI.Crypto", "WebAPI.Location"]
}
]
}
```

You can also open the namespace globally if you prefer global access to modules such as `Location` instead of using `WebAPI.Location`. Another option is to use `open WebAPI` when working with the `WebAPI` namespace.

```json
{
"compiler-flags": ["-open WebAPI"]
}
```

## Usage

The package exposes browser APIs under the `WebAPI` namespace. Use the module that owns the
browser interface, access record fields with `.`, and call global singleton methods directly.

```rescript
let location = WebAPI.Location.current
let href = location.href

WebAPI.Location.reload()
```

With `"-open WebAPI"`, the same code can be written without the `WebAPI.` prefix:

```rescript
let location = WebAPI.Window.current->WebAPI.Window.location
let location = Location.current
let href = location.href
location->WebAPI.Location.reload

Location.reload()
```

Object-owned APIs still use the value as the receiver. For example, if you also enable
`WebAPI.DOM`, you can work with document and element values like this:

```rescript
let document = WebAPI.Window.current->WebAPI.Window.document
let button = document->WebAPI.Document.createElement("button")

button->WebAPI.Element.setAttribute(~qualifiedName="type", ~value="button")
```

## Documentation
Expand Down
77 changes: 49 additions & 28 deletions docs/content/docs/api-surface.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,44 @@ description: A practical summary of how to use the public @rescript/webapi modul
slug: "api-surface"
---

The package exposes browser APIs under the `WebAPI` namespace. In normal application code,
add the package to `rescript.json`:
The package exposes browser APIs under the `WebAPI` namespace. It requires ReScript 13,
which is currently in alpha.

In normal application code, add the package to `rescript.json` with the features your app
uses:

```json
{
"dependencies": [
{
"name": "@rescript/webapi",
"features": ["WebAPI.Crypto", "WebAPI.Location"]
}
]
}
```

The `features` list controls which Web API source groups are available to your project. Add
each feature you use, for example `WebAPI.DOM`, `WebAPI.Fetch`, `WebAPI.Crypto`, or
`WebAPI.Location`.

You can optionally open the namespace globally if you prefer unqualified module names:

```json
{
"dependencies": ["@rescript/webapi"]
"compiler-flags": ["-open WebAPI"]
}
```

Use `WebAPI.Window.current` for the browser `window`. Interface-specific methods live on
public modules such as `WebAPI.Window`, `WebAPI.Location`, `WebAPI.Document`,
`WebAPI.Element`, `WebAPI.Request`, and `WebAPI.Response`.
Global singleton APIs such as `Location`, `Crypto`, and `Performance` expose direct
functions and properties. Object-owned APIs still use the value as the receiver.

```ReScript
let location = WebAPI.Window.current->WebAPI.Window.location
let href = location.href
let href = WebAPI.Location.href
let location = WebAPI.Location.current
let sameHref = location.href

location->WebAPI.Location.reload
WebAPI.Location.reload()
```

@jderochervlk jderochervlk Jun 3, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use a simpler way to get the location instead of using current. I don't know how I feel about that pattern actually, but I suppose we need some way to pass around location? I actually don't know why you would need to do that...

let href = WebAPI.Location.href

WebAPI.Location.reload()


## Public module shape
Expand All @@ -32,11 +52,17 @@ helper modules.
```ReScript
let req: WebAPI.Request.t = WebAPI.Request.fromURL("https://example.com")
let headers = WebAPI.Headers.make()
let document = WebAPI.Window.current->WebAPI.Window.document
let element = document->WebAPI.Document.createElement("button")
let element: WebAPI.Element.t = WebAPI.Document.createElement("button")
```

Generated implementation modules such as `DomTypes`, `FetchTypes`, `EventTypes`, and
With `"-open WebAPI"`, the same modules can be referenced without the `WebAPI.` prefix:

```ReScript
let location = Location.current

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how I feel about that pattern actually, but I suppose we need some way to pass around location? I actually don't know why you would need to do that...

let id = Crypto.randomUUID()
```

Generated implementation modules such as `DOMTypes`, `FetchTypes`, `EventTypes`, and
`UiEventsTypes` are internal. If you need to name a public type, use the public interface
module's `t` type when it has one, or use a dedicated public type module. Otherwise, let
the value type be inferred from constructors and accessors.
Expand Down Expand Up @@ -240,29 +266,24 @@ let redirect = WebAPI.Response.redirect(~url="/login", ~status=302)

## DOM

DOM values are operated on through public interface modules.
Common document entry points are exposed directly on `WebAPI.Document`, bound to
`globalThis.document`. DOM object properties use dot syntax, and instance methods stay
receiver-based through the owner module. When you need to name an element type explicitly,
use `WebAPI.Element.t`.

```ReScript
let document = WebAPI.Window.current->WebAPI.Window.document

let maybeButton = document
->WebAPI.Document.querySelector("button")
->Null.toOption

switch maybeButton {
| Some(button) =>
switch button->WebAPI.Element.getAttribute("data-user-id") {
| Null.Value(id) => Console.log(id)
| Null => Console.log("anonymous")
}
| None => Console.log("button not found")
}
let button: WebAPI.Element.t = WebAPI.Document.createElement("button")
button.id = "save"
button.className = "primary"
button.innerHTML = "Save"
button->WebAPI.Element.setAttribute(~qualifiedName="data-state", ~value="ready")
let maybeButton = WebAPI.Document.querySelector("#save")->Null.toOption
```

Use conversion helpers when moving between related DOM interface types.

```ReScript
let element = document->WebAPI.Document.createElement("div")
let element: WebAPI.Element.t = WebAPI.Document.createElement("div")
let node = element->WebAPI.Element.asNode
```

Expand Down
15 changes: 10 additions & 5 deletions docs/content/docs/contributing/api-module-structure.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,18 @@ type rec node = {
// ... more properties
}

and element = {
// duplicated property from node
nodeName: string
// ... more properties
}
and element = Base__Element.t
```

Public interface modules own their public `t` type. For example, `Element.res` exposes
`Element.t`, `Document.res` exposes `Document.t`, and `Location.res` exposes `Location.t`.
These public `t` types are the names consumers should use in annotations.

When an interface needs a shared structural owner across APIs or inheritance chains, keep
that shared shape in a base module such as `Base__Element.t`. The public interface module
then aliases the base shape as its own `t`. Other modules should reference that owner type,
such as `Element.t`, or the base owner directly only when needed to break structural cycles.

## Auxiliary Types

Auxiliary types are used to represent types that are not directly related to the Web API but are used in the bindings.
Expand Down
28 changes: 28 additions & 0 deletions docs/content/docs/contributing/module-type-structure.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,31 @@ external checkValidity: htmlButtonElement => bool = "checkValidity"
`;

<Code code={buttonModule} title="DOMAPI/HTMLButtonElement.res" lang="ReScript"></Code>

## Shared Object Bases

Public interface modules expose their own `t` type even when the underlying shape is shared.
For a shared DOM object base, keep the structural owner in a base module and make the public
interface module a same-type alias of that base:

Comment on lines +80 to +84

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not 100% on this one. It is similar to how the old webapi bindings worked and does not expose keys on an object and instead relies on piping to access values using @get or @send.

This means you have to do this:

let title = element->Element.title

instead of this:

let title = element.title

By using the pipe option we can have the types not required by other libraries such as rescript-react which can make it more lightweight, but if Event and Element are lightweight enough I don't see why they can't be a requirement of rescript-react?

```ReScript
// Base__Element.res
type t = private {}

// Element.res
type t = Base__Element.t = private {...Base__Element.t}
```

This keeps `Element.t` as the public type name while still allowing structural
types to share the same object identity through the base owner. `Element.Impl` remains reusable for element subtypes,
and subtype modules should continue to include the nearest base method implementation:

```ReScript
// HTMLElement.res
include Element.Impl({type t = HTMLElement.t})

// HTMLButtonElement.res
include HTMLElement.Impl({type t = DOMTypes.htmlButtonElement})
```

Use `asElement` when a subtype needs to be passed to a function that expects `Element.t`.
29 changes: 22 additions & 7 deletions docs/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,45 @@ Install the package using your favorite package manager:
</TabItem>
</Tabs>

and add `@rescript/webapi` to your `rescript.json`:
This package requires ReScript 13, which is currently in alpha.

Add `@rescript/webapi` to your `rescript.json` with the features your app uses:

export const rescriptJson = `
{
"dependencies": [
"@rescript/webapi"
{
"name": "@rescript/webapi",
"features": ["WebAPI.Crypto", "WebAPI.Location"]
}
]
}
`;

<Code lang="json" code={rescriptJson} ins={[3]}></Code>
<Code lang="json" code={rescriptJson} ins={[4, 5, 6, 7]}></Code>

You can optionally open the namespace globally if you prefer unqualified module names:

export const openWebAPIJson = `
{
"compiler-flags": ["-open WebAPI"]
}
`;

<Code lang="json" code={openWebAPIJson}></Code>

## Usage

After installing the package , you can use bindings for the various Web APIs as defined in [MDN](https://developer.mozilla.org/en-US/docs/Web/API).
After installing the package, you can use bindings for the various Web APIs as defined in [MDN](https://developer.mozilla.org/en-US/docs/Web/API).

export const rescriptSample = `
let location = WebAPI.Window.current->WebAPI.Window.location
let location = WebAPI.Location.current

// Access properties using \`.\`
let href = location.href

// Invoke methods using the \`->TypeModule.method\`
location->WebAPI.Location.reload
// Invoke global singleton methods directly
WebAPI.Location.reload()
`;

export const compiledSample = `
Expand Down
22 changes: 13 additions & 9 deletions docs/content/docs/philosophy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ slug: "design-philosophy"

The core idea of these ReScript bindings is that each interface is modeled as a record type. Where possible, inheritance is represented using record spreading, and methods are modeled as functions in a separate module. This design allows for greater familiarity with the underlying JavaScript APIs, making it more friendly for newcomers.

## ReScript v12
## ReScript 13

These bindings utilize new features introduced in ReScript v12; thus, they are not compatible with older versions of ReScript.
These bindings use feature-gated package dependencies and source groups from ReScript 13,
which is currently in alpha. They are not compatible with older versions of ReScript.

## Web APIs

Expand All @@ -19,11 +20,14 @@ In other words, if you are searching for a specific JavaScript binding, begin yo
The bindings are exposed under the `WebAPI` namespace with the same flat module structure as the original package.

```ReScript
open WebAPI.DOM

let myElement: WebAPI.Element.t = document->WebAPI.Document.createElement("div")
let myElement: WebAPI.Element.t = WebAPI.Document.createElement("div")
let id = myElement.id
```

Consumers can also configure `rescript.json` with `"compiler-flags": ["-open WebAPI"]`
when they want to use modules such as `Document`, `Element`, and `Location` without the
`WebAPI.` prefix.

## Interfaces

Since ReScript does not have the concept of classes or interfaces, the bindings represent the interfaces as record types. These record types can inherit properties from a "base" type through spreading. This can only be done if there are no circular references in the inheritance chain. If circular references exist, spreading is avoided, and the base properties are duplicated instead.
Expand All @@ -34,6 +38,8 @@ Methods are modeled as functions in a separate module. The idea is that these wi

Inherited methods are duplicated in the inheriting module to eliminate the need to cast the type to the base type.

For DOM elements, `WebAPI.Element.t` is the public element type and `WebAPI.Element` is the method module. Most code does not need to annotate the type because functions such as `Document.createElement` and `Document.querySelector` already return it.

### Overloads

JavaScript supports function overloads, where a function can have multiple signatures. In ReScript, this is not possible, and a method can have multiple bindings with slightly different names. By entering the correct name, tooling should detect all variations of the method.
Expand All @@ -43,10 +49,8 @@ JavaScript supports function overloads, where a function can have multiple signa
In some cases, type conversion will be required. Subtypes can safely be cast to their base type using conversion helpers within their module.

```ReScript
open WebAPI.DOM

let element: WebAPI.Element.t = document->WebAPI.Document.createElement("div")
let node: WebAPI.Node.t = element->WebAPI.Element.asNode
let element: WebAPI.Element.t = WebAPI.Document.createElement("div")
let node = element->WebAPI.Element.asNode
```

Any other conversions should be treated as unsafe casts and used with caution, because the type system cannot guarantee they are valid at runtime.
1 change: 1 addition & 0 deletions docs/content/docs/project-status.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ slug: "project-status"

This project is highly experimental and subject to change.
It is a work in progress and might not cover every API.
It currently requires ReScript 13, which is also still in alpha.
The core idea is to prioritize ground coverage based on community needs.
We aim to focus on the most used APIs first and then expand from there.

Expand Down
Loading
Loading