Skip to content

Commit 9c2a3bd

Browse files
authored
Merge pull request #7 from rescript-lang/canvas-sample
Add small canvas sample
2 parents cd67e9b + e0e2ad6 commit 9c2a3bd

33 files changed

+2354
-30
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,7 @@ jobs:
3030
- name: Install dependencies
3131
run: npm ci
3232

33-
- name: Install tool dependencies
34-
working-directory: ./tools/TypeScript-DOM-lib-generator
35-
run: npm ci
36-
37-
- name: Generate ReScript code
38-
working-directory: ./tools/TypeScript-DOM-lib-generator
33+
- name: Build ReScript code
3934
run: npm run build
4035

4136
- name: Run tests

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
/lib/
33
.bsb.lock
44
.astro
5-
dist/
5+
dist/
6+
tmp/

astro.config.mjs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,21 @@ export default defineConfig({
2323
editLink: {
2424
baseUrl: 'https://github.com/rescript-lang/experimental-rescript-webapi/edit/main/',
2525
},
26+
sidebar: [
27+
{
28+
slug: '',
29+
},
30+
{
31+
slug: 'design-philosophy',
32+
},
33+
{
34+
slug: 'project-status',
35+
},
36+
{
37+
label: 'Contributing',
38+
autogenerate: { directory: 'contributing' },
39+
},
40+
],
2641
customCss: ["./docs/styles/fonts.css", "./docs/styles/theme.css"],
2742
expressiveCode: {
2843
shiki: {
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
---
2+
title: API Modelling
3+
description: Learn more about the API modelling process of @rescript/webapi.
4+
slug: "04-api-modelling"
5+
---
6+
7+
import { Aside, Code, Icon } from "@astrojs/starlight/components";
8+
9+
One of this projects goals is to provide a consistent and idiomatic API for the Web APIs.
10+
The interopt story of ReScript is quite good, but it it has limitations.
11+
JavaScript is a dynamic language and has a lot of flexibility.
12+
ReScript is a statically typed language and has to model the dynamic parts of JavaScript in a static way.
13+
14+
## Dynamic parameters and properties
15+
16+
Some Web APIs have a parameter or property that can be multiple things.
17+
In ReScript, you would model this as a variant type. This is an example wrapper around values with a key property to discriminate them.
18+
19+
In JavaScript, this strictness is not enforced and you can pass a string where a number is expected.
20+
There are multiple strategies to model this in ReScript and it depends on the specific API which one is the best.
21+
22+
### Overloads
23+
24+
One example is [addEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener).
25+
This can access either a boolean or an object as the third parameter.
26+
27+
```js
28+
addEventListener(type, listener);
29+
addEventListener(type, listener, options);
30+
addEventListener(type, listener, useCapture);
31+
```
32+
33+
Because, this is a method, we can model this as an overloaded function in ReScript.
34+
The first two overloads are the same, so we can merge them into one with an optional options parameter.
35+
36+
```ReScript
37+
@send
38+
external addEventListener: (
39+
htmlButtonElement,
40+
eventType,
41+
eventListener<'event>,
42+
~options: addEventListenerOptions=?,
43+
) => unit = "addEventListener"
44+
```
45+
46+
The third overload takes a boolean and is worth using when you want to change the default of the `useCapture` boolean parameter.
47+
We can use [a fixed argument](https://rescript-lang.org/docs/manual/latest/bind-to-js-function#fixed-arguments) to model this.
48+
49+
```ReScript
50+
@send
51+
external addEventListener_useCapture: (
52+
htmlButtonElement,
53+
~type_: eventType,
54+
~callback: eventListener<'event>,
55+
@as(json`true`) _,
56+
) => unit = "addEventListener"
57+
```
58+
59+
<Aside type="caution" title="Improving methods">
60+
Be aware that inherited interfaces duplicate their inherited methods. This
61+
means that improving `addEventListener` in the `EventTarget` interface
62+
requires to update all interfaces that inherit from `EventTarget`.
63+
</Aside>
64+
65+
### Decoded variants
66+
67+
We can be pragmatic with overloaded functions and use model them in various creative ways.
68+
For properties, we **cannot do this unfortunately**. A propery can only be defined once and have a single type.
69+
70+
The strategy here is to use a decoded variant.
71+
72+
Example for the [fillStyle](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle) property of the `CanvasRenderingContext2D` interface can be either a:
73+
74+
- `string`
75+
- `CanvasGradient`
76+
- `CanvasPattern`
77+
78+
These types are not all primitives and thus we cannot define it as [untagged variants](https://rescript-lang.org/docs/manual/latest/variant#untagged-variants).
79+
What we can do instead is represent the type as an empty type and use a helper module to interact with this.
80+
81+
export const fillStyleDef = `
82+
type fillStyle
83+
84+
type canvasRenderingContext2D = {
85+
// ... other propeties
86+
mutable fillStyle: fillStyle
87+
}
88+
`;
89+
90+
<Code code={fillStyleDef} title="DOMAPI.res" lang="ReScript"></Code>
91+
92+
When we wish to read and write the `fillStyle` property, we can use a helper module to lift the type to an actual ReScript variant:
93+
94+
export const fillStyleModule = `
95+
open Prelude
96+
open CanvasAPI
97+
open DOMAPI
98+
99+
external fromString: string => fillStyle = "%identity"
100+
external fromCanvasGradient: canvasGradient => fillStyle = "%identity"
101+
external fromCanvasPattern: canvasGradient => fillStyle = "%identity"
102+
103+
type decoded =
104+
| String(string)
105+
| CanvasGradient(canvasGradient)
106+
| CanvasPattern(canvasPattern)
107+
108+
let decode = (t: fillStyle): decoded => {
109+
if CanvasGradient.isInstanceOf(t) {
110+
CanvasGradient(unsafeConversation(t))
111+
} else if CanvasPattern.isInstanceOf(t) {
112+
CanvasPattern(unsafeConversation(t))
113+
} else {
114+
String(unsafeConversation(t))
115+
}
116+
}
117+
`
118+
119+
<Code
120+
code={fillStyleModule}
121+
title="DOMAPI/FillStyle.res"
122+
lang="ReScript"
123+
></Code>
124+
125+
We can now use `FillStyle.decode` to get the actual value of the `fillStyle` property.
126+
And use `FillStyle.fromString`, `FillStyle.fromCanvasGradient`, and `FillStyle.fromCanvasPattern` to set the value.
127+
128+
```ReScript
129+
let ctx = myCanvas->HTMLCanvasElement.getContext_2D
130+
131+
// Write
132+
ctx.fillStyle = FillStyle.fromString("red")
133+
134+
// Read
135+
switch ctx.fillStyle->FillStyle.decode {
136+
| FillStyle.String(color) => Console.log(`Color: ${color}`)
137+
| FillStyle.CanvasGradient(_) => Console.log("CanvasGradient")
138+
| FillStyle.CanvasPattern(_) => Console.log("CanvasPattern")
139+
}
140+
```
141+
142+
<Icon
143+
name="information"
144+
color="var(--sl-color-text-accent)"
145+
class="inline-icon"
146+
size="1.5rem"
147+
/>
148+
Try and use `decoded` and `decode` as conventions for the type and function
149+
names.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
title: Code Generation
3+
description: Learn more about the code generation process for @rescript/webapi.
4+
slug: "03-code-generation"
5+
---
6+
7+
The original bindings were generated using a modified version of [TypeScript-DOM-lib-generator](https://github.com/microsoft/TypeScript-DOM-lib-generator).
8+
These bindings were a great starting point, but they are not perfect.
9+
It is more than likely that you will need to **tweak the generated bindings by hand** to make them more idiomatic to ReScript.
10+
11+
For example the `window.fetch` function was generated as:
12+
13+
```ReScript
14+
/**
15+
[Read more on MDN](https://developer.mozilla.org/docs/Web/API/Window/fetch)
16+
*/
17+
@send
18+
external fetch: (window, ~input: request, ~init: requestInit=?)
19+
=> Promise.t<response> = "fetch"
20+
21+
/**
22+
[Read more on MDN](https://developer.mozilla.org/docs/Web/API/Window/fetch)
23+
*/
24+
@send
25+
external fetch2: (window, ~input: string, ~init: requestInit=?)
26+
=> Promise.t<response> = "fetch"
27+
```
28+
29+
While not that bad and usable, it can be improved:
30+
31+
- Rename `fetch2` to `fetch` because it is the more common usage of the function.
32+
- Rename `fetch` to `fetch_with_request` for clarity that this is the "overload" with a `Request` object.
33+
- Consider removing the named `~input` and `~init` arguments and use positional arguments instead.
34+
Motivation: If the function does not have any parameters with the same type, it is more ergonomic to use positional arguments.
35+
This heuristic is not set in stone and can be adjusted based on the specific function.
36+
- The documentation can be improved.
37+
38+
```ReScript
39+
/** TODO: add better docs */
40+
@send
41+
external fetch: (window, string, ~init: requestInit=?)
42+
=> Promise.t<response> = "fetch"
43+
44+
/** TODO: add better docs */
45+
@send
46+
external fetch_withRequest: (window, request, ~init: requestInit=?)
47+
=> Promise.t<response> = "fetch"
48+
```
49+
50+
Once these changes are made, the bindings can be tested and then committed to the repository.
51+
The generation does no longer happen automatically, so manual improvements will not be overwritten.
52+
53+
## Sandboxed Code Generation
54+
55+
Not every API was covered by the TypeScript-DOM-lib-generator.
56+
Potentially, you want to add a new API to the bindings and star from code generation.
57+
58+
In [emitter.ts](https://github.com/rescript-lang/experimental-rescript-webapi/blob/main/tools/TypeScript-DOM-lib-generator/src/build/emitter.ts),
59+
you can override the `interfaceHierarchy` with any new interface or type you want to add.
60+
61+
```typescript
62+
interfaceHierarchy = [
63+
{
64+
name: "Temp",
65+
entries: [
66+
enums(["WebGLPowerPreference"]),
67+
dictionaries([
68+
"ImageBitmapRenderingContextSettings",
69+
"WebGLContextAttributes",
70+
]),
71+
],
72+
opens: [],
73+
},
74+
];
75+
```
76+
77+
After running the generator (in `tools/TypeScript-DOM-lib-generator`):
78+
79+
```shell
80+
npm run build
81+
```
82+
83+
All the generated files will be in the `tmp` folder. You can use this as inspiration for your own bindings.
84+
85+
To figure out if you need `enums`, `dictionaries`, or `interfaces`, you can look at the [JSON dump](https://github.com/rescript-lang/experimental-rescript-webapi/blob/main/tools/TypeScript-DOM-lib-generator/dom-dump.json) file
86+
and see how the TypeScript-DOM-lib-generator represents the shape you are after.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
title: "Documentation"
3+
description: Learn more about the relevance of adding documentation to @rescript/webapi.
4+
slug: "06-documentation"
5+
---
6+
7+
After the bindings are generated, all you got was a link to the MDN documentation.
8+
While again, not that bad, it can be improved by adding the core of the binding to the documentation.
9+
And explain how it is supposed to be used.
10+
11+
## Importance
12+
13+
One could wonder how important documentation is in this case?
14+
A link to MDN is enough, right? Well, not always.
15+
When a method has multiple overloads, it can be hard to understand which one to use.
16+
By adding an example usage, the user can see easily see which one to use.
17+
It keeps the user inside the IDE and avoids context switching.
18+
19+
## Structure
20+
21+
The documentation for each binding should roughly follow this structure:
22+
23+
- signature
24+
- key description (tip: check MDN for inspiration)
25+
- example usage
26+
- link to the MDN documentation
27+
28+
For example, the `window.fetch` function could be documented as:
29+
30+
````ReScript
31+
/*
32+
`fetch(string, init)`
33+
34+
Starts the process of fetching a resource from the network,
35+
returning a promise that is fulfilled once the response is available.
36+
37+
```res
38+
window->Window.fetch("https://rescript-lang.org")
39+
```
40+
41+
[Read more on MDN](https://developer.mozilla.org/docs/Web/API/Window/fetch)
42+
*/
43+
@send
44+
external fetch: (window, string, ~init: requestInit=?)
45+
=> Promise.t<response> = "fetch"
46+
````
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
title: Project setup
3+
description: Practical information on how to get started to contribute to the project.
4+
slug: "01-getting-started"
5+
---
6+
7+
import { Aside } from "@astrojs/starlight/components";
8+
9+
<Aside type="caution">
10+
**Real talk**: First time diving into this project? Might feel like a letdown.
11+
But hey, the upside? It’s stupid easy to toss us a PR and make it better!
12+
</Aside>
13+
14+
The [WebAPI](https://developer.mozilla.org/en-US/docs/Web/API) are vast and ever-growing. We need your help to make them better.
15+
There is no way our [contributors](https://github.com/rescript-lang/experimental-rescript-webapi/graphs/contributors) can cover everything.
16+
And that's where you come in! A small PR, focused on what you want to get out of this project can make a huge difference.
17+
18+
## Recommended workflow
19+
20+
We recommend the following overall workflow when developing for this repository:
21+
22+
- Fork this repository
23+
- Always work in your fork
24+
- Always keep your fork up to date
25+
26+
Before updating your fork, run this command:
27+
28+
```shell
29+
git remote add upstream https://github.com/rescript-lang/experimental-rescript-webapi.git
30+
```
31+
32+
This will make management of multiple forks and your own work easier over time.
33+
34+
### Updating your fork
35+
36+
We recommend the following commands to update your fork:
37+
38+
```shell
39+
git checkout main
40+
git clean -xdf
41+
git fetch upstream
42+
git rebase upstream/main
43+
git push
44+
```
45+
46+
Or more succinctly:
47+
48+
```shell
49+
git checkout main && git clean -xdf && git fetch upstream && git rebase upstream/main && git push
50+
```
51+
52+
This will update your fork with the latest from `rescript-lang/experimental-rescript-webapi` on your machine and push those updates to your remote fork.
53+
54+
## Initial build
55+
56+
Install the dependencies and compile the bindings using [Rewatch](https://github.com/rescript-lang/rewatch):
57+
58+
```shell
59+
npm install && npm run build
60+
```

0 commit comments

Comments
 (0)