diff --git a/.prettierrc.mjs b/.prettierrc.mjs
new file mode 100644
index 0000000..4f88aa5
--- /dev/null
+++ b/.prettierrc.mjs
@@ -0,0 +1,13 @@
+/** @type {import("prettier").Config} */
+export default {
+ plugins: ["prettier-plugin-astro"],
+ overrides: [
+ {
+ files: "*.astro",
+ options: {
+ parser: "astro",
+ },
+ },
+ ],
+ endOfLine: "lf",
+ };
\ No newline at end of file
diff --git a/astro.config.mjs b/astro.config.mjs
index 38237d2..6f6ccff 100644
--- a/astro.config.mjs
+++ b/astro.config.mjs
@@ -1,11 +1,31 @@
import { readFileSync } from "node:fs";
import { defineConfig } from "astro/config";
import starlight from "@astrojs/starlight";
+import { apiModules } from "./docs/utils";
const rescriptTM = JSON.parse(
readFileSync("./docs/assets/rescript.tmLanguage.json", "utf-8"),
);
+const apiSidebarItems = apiModules.map(({ moduleName, link, items }) => {
+ const nestedItems = Object.values(items).map(({ moduleName, link }) => ({
+ label: moduleName,
+ link
+ }));
+
+ return ({
+ label: moduleName,
+ collapsed: true,
+ items: [
+ {
+ label: `Overview`,
+ link
+ },
+ ...nestedItems
+ ]
+ });
+});
+
export default defineConfig({
srcDir: "docs",
publicDir: "docs/public",
@@ -18,24 +38,36 @@ export default defineConfig({
src: "./docs/assets/rescript-logo.svg",
},
social: {
- github: 'https://github.com/rescript-lang/experimental-rescript-webapi',
+ github: "https://github.com/rescript-lang/experimental-rescript-webapi",
},
editLink: {
- baseUrl: 'https://github.com/rescript-lang/experimental-rescript-webapi/edit/main/',
+ baseUrl:
+ "https://github.com/rescript-lang/experimental-rescript-webapi/edit/main/",
},
sidebar: [
{
- slug: '',
+ slug: "",
+ },
+ {
+ slug: "design-philosophy",
},
{
- slug: 'design-philosophy',
+ slug: "project-status",
},
{
- slug: 'project-status',
+ label: "Contributing",
+ autogenerate: { directory: "contributing" },
},
{
- label: 'Contributing',
- autogenerate: { directory: 'contributing' },
+ label: "API Documentation",
+ collapsed: true,
+ items: [
+ {
+ label: "Overview",
+ link: "apidocs",
+ },
+ ...apiSidebarItems,
+ ],
},
],
customCss: ["./docs/styles/fonts.css", "./docs/styles/theme.css"],
diff --git a/docs/components/record.astro b/docs/components/record.astro
new file mode 100644
index 0000000..366e3a7
--- /dev/null
+++ b/docs/components/record.astro
@@ -0,0 +1,48 @@
+---
+import { micromark } from "micromark";
+import Signature from "./signature.astro";
+const { name, items, typesInOwnModule } = Astro.props;
+---
+
+
Record fields
+{
+ items.map((item) => {
+ const documentation =
+ item.docstrings && micromark(item.docstrings.join("\n"));
+
+ return (
+
+
{item.name}
+
+
+
+ {documentation &&
}
+
+ );
+ })
+}
+
+
diff --git a/docs/components/signature.astro b/docs/components/signature.astro
new file mode 100644
index 0000000..e927a58
--- /dev/null
+++ b/docs/components/signature.astro
@@ -0,0 +1,49 @@
+---
+import SignatureItem from "./signatureItem.astro";
+
+
+function tokenize(input) {
+ // Split by special characters while keeping them
+ const regex = /([<>,])/g;
+ return input
+ .split(regex)
+ .map((token) => token.trim())
+ .filter(Boolean);
+}
+
+function parseTokens(tokens) {
+ let index = 0;
+
+ const parseNode = () => {
+ const path = tokens[index++]; // Read the current token and increment index
+ let genericTypeParameters = [];
+
+ if (tokens[index] === "<") {
+ // Check for generics
+ index++; // Consume "<"
+ while (tokens[index] !== ">") {
+ genericTypeParameters.push(parseNode());
+ if (tokens[index] === ",") index++; // Consume ","
+ }
+ index++; // Consume ">"
+ }
+
+ return { path, genericTypeParameters };
+ };
+
+ return parseNode();
+}
+
+function parse(input) {
+ const tokens = tokenize(input);
+ return parseTokens(tokens);
+}
+
+
+const { signature, typesInOwnModule } = Astro.props;
+const tree = parse(signature);
+---
+
+
+ {()}
+
diff --git a/docs/components/signatureItem.astro b/docs/components/signatureItem.astro
new file mode 100644
index 0000000..1dc3157
--- /dev/null
+++ b/docs/components/signatureItem.astro
@@ -0,0 +1,38 @@
+---
+import { createAPIModuleLink } from "../utils.js";
+const { item, typesInOwnModule } = Astro.props;
+
+let link;
+if (typesInOwnModule && typesInOwnModule.has(item.path)) {
+ link = `#${item.path}`;
+}
+
+if (item.path.startsWith("WebAPI.")) {
+ const paths = item.path.split(".");
+ if (paths.length === 3) {
+ link = `${import.meta.env.BASE_URL}/${createAPIModuleLink(paths[1])}#${paths[2]}`;
+ }
+}
+
+const genericTypeParameters = item.genericTypeParameters || [];
+---
+
+
+ {link ? {item.path} : item.path}{
+ genericTypeParameters.length > 0 && (
+ <>
+ {"<"}
+ {genericTypeParameters.map((subItem) => (
+
+ ))}
+ {">"}
+ >
+ )
+ }
+
+
diff --git a/docs/components/value.astro b/docs/components/value.astro
new file mode 100644
index 0000000..66e9faa
--- /dev/null
+++ b/docs/components/value.astro
@@ -0,0 +1,27 @@
+---
+import SignatureItem from "./signatureItem.astro";
+const { parameters, returnType } = Astro.props;
+---
+
+
+
Parameters
+ {
+ parameters.map((p) => (
+
+ ))
+ }
+ Return type
+
+
+
diff --git a/docs/pages/apidocs/[API].astro b/docs/pages/apidocs/[API].astro
new file mode 100644
index 0000000..aa8d9b7
--- /dev/null
+++ b/docs/pages/apidocs/[API].astro
@@ -0,0 +1,114 @@
+---
+import * as path from "node:path";
+import { existsSync } from "fs";
+import { apiModules, getDoc, createTypeModuleLink } from "../../utils";
+import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
+import { Code } from "@astrojs/starlight/components";
+import { micromark } from "micromark";
+import Record from "../../components/record.astro";
+
+export async function getStaticPaths() {
+ return apiModules.map((apiModule) => {
+ return {
+ params: {
+ API: apiModule.apiRouteParameter,
+ },
+ props: apiModule,
+ };
+ });
+}
+
+function showRecord(details) {
+ return details && details.kind === "record" && details.items.length > 0;
+}
+
+function getModuleFileName(typeName) {
+ return `${typeName[0].toUpperCase()}${typeName.slice(1)}`;
+}
+
+function showModule(typeName, filePath) {
+ const moduleFileName = `${getModuleFileName(typeName)}.res`;
+ const potentialPath = path.join(filePath.replace(".res", ""), moduleFileName);
+ return existsSync(potentialPath);
+}
+
+const { moduleName, filePath, link } = Astro.props;
+
+const docInfo = await getDoc(filePath);
+
+const types = docInfo.items
+ .filter((item) => item.kind === "type")
+ .sort((a, b) => a.name.localeCompare(b.name))
+ .map((type) => {
+ const documentation =
+ type.docstrings && micromark(type.docstrings.join("\n"));
+ return {
+ name: type.name,
+ documentation,
+ signature: type.signature,
+ detail: type.detail,
+ };
+ });
+
+const typesInOwnModule = new Set(types.map((t) => t.name));
+
+const typeHeadings = types.map((type) => ({
+ depth: 3,
+ slug: type.name,
+ text: type.name,
+}));
+
+const frontmatter = {
+ title: moduleName,
+};
+
+const headings = [
+ {
+ depth: 2,
+ slug: "types",
+ text: "Types",
+ },
+ ...typeHeadings,
+];
+---
+
+
+
+
Types
+ {
+ types.map((type) => (
+
+
{type.name}
+
+
+ {showRecord(type.detail) ? (
+
+ ) : null}
+ {showModule(type.name, filePath) && (
+ <>
+
Module
+
+ There are methods and helpers defined in{" "}
+
+ {getModuleFileName(type.name)}
+
+ .
+
+ >
+ )}
+
+ ))
+ }
+
+
+
diff --git a/docs/pages/apidocs/[API]/[Module].astro b/docs/pages/apidocs/[API]/[Module].astro
new file mode 100644
index 0000000..e6a5590
--- /dev/null
+++ b/docs/pages/apidocs/[API]/[Module].astro
@@ -0,0 +1,86 @@
+---
+import { apiModules, getDoc } from "../../../utils";
+import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
+import { Code } from "@astrojs/starlight/components";
+import { micromark } from "micromark";
+import Value from "../../../components/value.astro";
+
+export async function getStaticPaths() {
+ return apiModules.flatMap((apiModule) => {
+ return Object.values(apiModule.items).map((typeModule) => {
+ return {
+ params: {
+ API: apiModule.apiRouteParameter,
+ Module: typeModule.apiRouteParameter,
+ },
+ props: { parentModule: apiModule, currentModule: typeModule },
+ };
+ });
+ });
+}
+
+function showValue(detail) {
+ return (
+ detail?.kind === "signature" && detail?.details?.parameters?.length > 0
+ );
+}
+
+const { filePath, moduleName } = Astro.props.currentModule;
+const docInfo = await getDoc(filePath);
+
+const values = docInfo.items
+ .filter((item) => item.kind === "value")
+ .sort((a, b) => a.name.localeCompare(b.name))
+ .map((value) => {
+ const documentation =
+ value.docstrings && micromark(value.docstrings.join("\n"));
+ return {
+ name: value.name,
+ documentation,
+ signature: value.signature,
+ detail: value.detail,
+ };
+ });
+
+const valueHeadings = values.map((value) => ({
+ depth: 3,
+ slug: value.name,
+ text: value.name,
+}));
+
+const frontmatter = {
+ title: moduleName,
+};
+
+const headings = [
+ {
+ depth: 2,
+ slug: "values",
+ text: "Values",
+ },
+ ...valueHeadings,
+];
+---
+
+
+ Values
+ {
+ values.map((value) => (
+
+
{value.name}
+
+
+ {showValue(value.detail) &&
}
+
+ ))
+ }
+
+{JSON.stringify(docInfo, null, 4)}
+
+
+
diff --git a/docs/pages/apidocs/index.astro b/docs/pages/apidocs/index.astro
new file mode 100644
index 0000000..f2bfe5d
--- /dev/null
+++ b/docs/pages/apidocs/index.astro
@@ -0,0 +1,30 @@
+---
+import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
+import { apiModules } from "../../utils";
+
+const frontmatter = {
+ title: "API Documentation",
+};
+
+const headings = [
+ {
+ depth: 2,
+ slug: "web-apis",
+ text: "Web APIs",
+ },
+];
+
+---
+
+
+ Web APIs
+
+ {
+ apiModules.map(({ moduleName, link }) => (
+ -
+ {moduleName}
+
+ ))
+ }
+
+
diff --git a/docs/styles/theme.css b/docs/styles/theme.css
index cc9f342..8915d1f 100644
--- a/docs/styles/theme.css
+++ b/docs/styles/theme.css
@@ -1,6 +1,7 @@
:root,
::backdrop {
--sl-font: "Inter", "Roboto Mono";
+ --sl-sidebar-width: 23rem;
}
/*
@@ -8,6 +9,12 @@ https://github.com/withastro/starlight/blob/main/packages/starlight/style/props.
https://github.com/rescript-lang/rescript-lang.org/blob/4e4f9520f6fc7a0376db82a0a6db52211e8a8187/tailwind.config.mjs#L13
*/
+@media (prefers-reduced-motion: no-preference) {
+ html {
+ scroll-behavior: smooth; /* Enables smooth scrolling for users without motion sensitivity */
+ }
+}
+
:root[data-theme="light"],
[data-theme="light"] ::backdrop {
--sl-color-accent-low: #4a0f14;
@@ -21,7 +28,7 @@ https://github.com/rescript-lang/rescript-lang.org/blob/4e4f9520f6fc7a0376db82a0
}
}
-body {
+body, #starlight__sidebar {
--scrollbar-track-background: #222222;
--scrollbar-track-border: #4a4a4a;
--scrollbar-thumb-background: #686868;
diff --git a/docs/utils.js b/docs/utils.js
new file mode 100644
index 0000000..a34e781
--- /dev/null
+++ b/docs/utils.js
@@ -0,0 +1,87 @@
+import * as path from "node:path";
+import { exec } from "node:child_process";
+import { promisify } from "node:util";
+import { readdirSync, existsSync } from "fs";
+
+const execAsync = promisify(exec);
+
+function toKebabCase(input) {
+ return input
+ .replace(/([a-z])([A-Z])/g, "$1-$2") // Insert dash between lowercase and uppercase
+ .replace(/[\s_]+/g, "-") // Replace spaces or underscores with dash
+ .toLowerCase(); // Convert to lowercase
+}
+
+export function createAPIModuleLink(moduleName) {
+ // This function is called before import.meta.env.BASE_URL is set.
+ // So, I'm hardcoding the path here.
+ return `apidocs/${toKebabCase(moduleName)}`;
+}
+
+export function createTypeModuleLink(parentModuleLink, typeName) {
+ return `${parentModuleLink}/${toKebabCase(typeName)}`;
+}
+
+function mapTypeModules(parentModuleLink, file) {
+ const folder = file.replace(".res", "");
+
+ if (!existsSync(folder)) {
+ return [];
+ }
+
+ const files = readdirSync(folder);
+ return files
+ .filter((f) => f.endsWith(".res"))
+ .map((file) => {
+ const filePath = path.join(folder, file);
+
+ const moduleName = file
+ .replace("$", "")
+ .replace(folder, "")
+ .replace(".res", "");
+ const apiRouteParameter = toKebabCase(moduleName);
+ const link = createTypeModuleLink(parentModuleLink, moduleName);
+ const typeName = moduleName[0].toLocaleLowerCase() + moduleName.slice(1);
+
+ return [
+ typeName,
+ {
+ filePath,
+ moduleName,
+ link,
+ apiRouteParameter,
+ },
+ ];
+ });
+}
+
+function mapRescriptFile(srcDir, file) {
+ const moduleName = path.basename(file, ".res").replace("$", "");
+ const filePath = path.join(srcDir, file);
+ const link = createAPIModuleLink(moduleName);
+ const items = Object.fromEntries(mapTypeModules(link, filePath));
+
+ return {
+ filePath,
+ moduleName,
+ link,
+ apiRouteParameter: toKebabCase(moduleName),
+ items,
+ };
+}
+
+const srcDir = path.resolve(process.cwd(), "src");
+export const apiModules = readdirSync(srcDir).filter(f => f.endsWith(".res")).map(r => mapRescriptFile(srcDir, r));
+
+export async function getDoc(absoluteFilePath) {
+ const { stdout, stderr } = await execAsync(
+ `rescript-tools doc ${absoluteFilePath}`,
+ {
+ maxBuffer: 1024 * 1024 * 10, // Increase buffer to 10 MB
+ },
+ );
+ if (stderr) {
+ throw new Error(stderr);
+ }
+ return JSON.parse(stdout);
+}
diff --git a/package-lock.json b/package-lock.json
index e7a736b..cc11b2c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,7 +14,9 @@
"devDependencies": {
"@astrojs/starlight": "^0.29.0",
"astro": "^4.16.10",
+ "micromark": "^4.0.1",
"prettier": "^3.3.3",
+ "prettier-plugin-astro": "^0.14.1",
"sharp": "^0.33.5"
}
},
@@ -4619,9 +4621,9 @@
}
},
"node_modules/micromark": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz",
- "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz",
+ "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==",
"dev": true,
"funding": [
{
@@ -5880,6 +5882,21 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
+ "node_modules/prettier-plugin-astro": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.14.1.tgz",
+ "integrity": "sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@astrojs/compiler": "^2.9.1",
+ "prettier": "^3.0.0",
+ "sass-formatter": "^0.7.6"
+ },
+ "engines": {
+ "node": "^14.15.0 || >=16.0.0"
+ }
+ },
"node_modules/prismjs": {
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
@@ -6424,6 +6441,23 @@
"queue-microtask": "^1.2.2"
}
},
+ "node_modules/s.color": {
+ "version": "0.0.15",
+ "resolved": "https://registry.npmjs.org/s.color/-/s.color-0.0.15.tgz",
+ "integrity": "sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/sass-formatter": {
+ "version": "0.7.9",
+ "resolved": "https://registry.npmjs.org/sass-formatter/-/sass-formatter-0.7.9.tgz",
+ "integrity": "sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "suf-log": "^2.5.3"
+ }
+ },
"node_modules/sax": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
@@ -6706,6 +6740,16 @@
"inline-style-parser": "0.2.4"
}
},
+ "node_modules/suf-log": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/suf-log/-/suf-log-2.5.3.tgz",
+ "integrity": "sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "s.color": "0.0.15"
+ }
+ },
"node_modules/tinyexec": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz",
diff --git a/package.json b/package.json
index 0e4c709..a197be0 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,7 @@
"scripts": {
"test": "node tests/index.js",
"build": "rescript",
- "format": "rescript format -all && prettier --write ./docs ./tests/index.js ./package.json",
+ "format": "rescript format -all && prettier --write ./tests/index.js ./package.json ./docs/pages",
"docs": "astro dev",
"build:docs": "astro build"
},
@@ -40,7 +40,9 @@
"devDependencies": {
"@astrojs/starlight": "^0.29.0",
"astro": "^4.16.10",
+ "micromark": "^4.0.1",
"prettier": "^3.3.3",
+ "prettier-plugin-astro": "^0.14.1",
"sharp": "^0.33.5"
}
}
diff --git a/src/DOMAPI/Window.js b/src/DOMAPI/Window.js
index 977be07..dbf8d92 100644
--- a/src/DOMAPI/Window.js
+++ b/src/DOMAPI/Window.js
@@ -1,7 +1,7 @@
// Generated by ReScript, PLEASE EDIT WITH CARE
-import * as EventTarget$WebApi from "../EventAPI/EventTarget.js";
+import * as EventTarget$WebAPI from "../EventAPI/EventTarget.js";
-EventTarget$WebApi.Impl({});
+EventTarget$WebAPI.Impl({});
/* Not a pure module */