-
Notifications
You must be signed in to change notification settings - Fork 10
refactor: clean up public owners and singleton APIs #270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9248425
bc1a0e7
1519d88
bd0ebf4
bea1b18
6dce207
e5d67a7
43fcf3d
d8dc3ce
c83f19c
29ead8e
c8ffac8
5b10b90
fdc8e56
ef46bda
c762145
49420e4
4854dbd
ea34e4a
bbed284
c526dd5
ccfa64a
d9b8f3c
536a2e0
49e227d
dfff1c9
9312db8
9855b04
69a67e2
7b99fc5
783bd12
e229716
2f6175b
ca2a475
2611881
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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() | ||
| ``` | ||
|
|
||
| ## Public module shape | ||
|
|
@@ -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 | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
|
@@ -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 | ||
| ``` | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 This means you have to do this: let title = element->Element.titleinstead of this: let title = element.titleBy using the pipe option we can have the types not required by other libraries such as |
||
| ```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`. | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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 aroundlocation? I actually don't know why you would need to do that...