diff --git a/.github/workflows/check.tsx b/.github/workflows/check.tsx
new file mode 100644
index 0000000..d45e659
--- /dev/null
+++ b/.github/workflows/check.tsx
@@ -0,0 +1,61 @@
+import { stringify } from "@std/yaml";
+import { CheckoutStep, SetupDenoStep } from "./shared.tsx";
+
+/**
+ * CheckWorkflow is a GitHub workflow for the jsonx project.
+ */
+export function CheckWorkflow() {
+ return {
+ name: "Check",
+ on: {
+ push: {
+ branches: ["main"],
+ },
+ pull_request: {
+ branches: ["main"],
+ },
+ },
+ jobs: {
+ check: {
+ "runs-on": "ubuntu-latest",
+ steps: [
+ ,
+ ,
+ ,
+ ,
+ {
+ name: "Test",
+ run: "deno task test",
+ },
+ ],
+ },
+ },
+ };
+}
+
+/**
+ * DenoFormatStep is a step that formats the code.
+ */
+export function DenoFormatStep() {
+ return {
+ name: "Format",
+ run: "deno fmt && git diff-index --quiet HEAD",
+ };
+}
+
+/**
+ * DenoLintStep is a step that lints the code.
+ */
+export function DenoLintStep() {
+ return {
+ name: "Lint",
+ run: "deno lint && git diff-index --quiet HEAD",
+ };
+}
+
+if (import.meta.main) {
+ Deno.writeTextFileSync(
+ Deno.args[0],
+ stringify(, { lineWidth: 80 }),
+ );
+}
diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml
new file mode 100644
index 0000000..a5ad76e
--- /dev/null
+++ b/.github/workflows/check.yaml
@@ -0,0 +1,20 @@
+name: Check
+'on':
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+jobs:
+ check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: denoland/setup-deno@v1
+ - name: Format
+ run: deno fmt && git diff-index --quiet HEAD
+ - name: Lint
+ run: deno lint && git diff-index --quiet HEAD
+ - name: Test
+ run: deno task test
diff --git a/.github/workflows/shared.tsx b/.github/workflows/shared.tsx
new file mode 100644
index 0000000..f238fda
--- /dev/null
+++ b/.github/workflows/shared.tsx
@@ -0,0 +1,17 @@
+/**
+ * CheckoutStep is a step that checks out the repository.
+ */
+export function CheckoutStep() {
+ return {
+ uses: "actions/checkout@v4",
+ };
+}
+
+/**
+ * SetupDenoStep is a step that sets up the Deno runtime.
+ */
+export function SetupDenoStep() {
+ return {
+ uses: "denoland/setup-deno@v1",
+ };
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..cca6f16
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,19 @@
+{
+ "deno.enable": true,
+ "deno.unstable": true,
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "[markdown]": {
+ "editor.defaultFormatter": "denoland.vscode-deno"
+ },
+ "[jsonc]": {
+ "editor.defaultFormatter": "denoland.vscode-deno"
+ },
+ "[typescript]": {
+ "editor.defaultFormatter": "denoland.vscode-deno"
+ },
+ "[typescriptreact]": {
+ "editor.defaultFormatter": "denoland.vscode-deno"
+ },
+ "files.eol": "\n"
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..456c488
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2004 Sam Hocevar
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..294edc0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,24 @@
+# jsonx_docs
+
+Documentation site of [FartLabs/jsonx](https://github.com/FartLabs/jsonx), a JSX
+runtime for composing JSON data.
+
+## Contribute
+
+### Style
+
+Run `deno fmt` to format the code.
+
+Run `deno lint` to lint the code.
+
+Run `deno task generate` to generate code.
+
+Run `deno task start` to locally serve the jsonx documentation site.
+
+### Testing
+
+Run `deno task test` to run the unit tests.
+
+---
+
+Developed with ❤️ [**@FartLabs**](https://github.com/FartLabs)
diff --git a/deno.jsonc b/deno.jsonc
new file mode 100644
index 0000000..eb7ebd0
--- /dev/null
+++ b/deno.jsonc
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "@fartlabs/jsonx"
+ },
+ "imports": {
+ "@fartlabs/jsonx": "jsr:@fartlabs/jsonx@^0.0.9",
+ "@std/cli": "jsr:@std/cli@^0.220.1",
+ "@std/http": "jsr:@std/http@^0.220.1",
+ "@std/semver": "jsr:@std/semver@^0.220.1",
+ "@std/yaml": "jsr:@std/yaml@^0.220.1"
+ },
+ "tasks": {
+ "start": "deno run -A --unstable-kv main.ts",
+ "generate": "deno run -Ar https://deno.land/x/generate/cli/main.ts gen.ts"
+ },
+ "exclude": ["./static"]
+}
diff --git a/deno.lock b/deno.lock
new file mode 100644
index 0000000..fe5bd46
--- /dev/null
+++ b/deno.lock
@@ -0,0 +1,83 @@
+{
+ "version": "3",
+ "packages": {
+ "specifiers": {
+ "jsr:@fartlabs/jsonx@^0.0.9": "jsr:@fartlabs/jsonx@0.0.9",
+ "jsr:@std/assert@^0.220.1": "jsr:@std/assert@0.220.1",
+ "jsr:@std/async@^0.220.1": "jsr:@std/async@0.220.1",
+ "jsr:@std/cli@^0.220.1": "jsr:@std/cli@0.220.1",
+ "jsr:@std/collections@^0.219.1": "jsr:@std/collections@0.219.1",
+ "jsr:@std/encoding@^0.220.1": "jsr:@std/encoding@0.220.1",
+ "jsr:@std/fmt@^0.220.1": "jsr:@std/fmt@0.220.1",
+ "jsr:@std/http@^0.220.1": "jsr:@std/http@0.220.1",
+ "jsr:@std/media-types@^0.220.1": "jsr:@std/media-types@0.220.1",
+ "jsr:@std/path@^0.220.1": "jsr:@std/path@0.220.1",
+ "jsr:@std/semver@^0.220.1": "jsr:@std/semver@0.220.1",
+ "jsr:@std/streams@^0.220.1": "jsr:@std/streams@0.220.1"
+ },
+ "jsr": {
+ "@fartlabs/jsonx@0.0.9": {
+ "integrity": "ec269a8e436e3052c5ea7a60b3ac525639a1c39b847e3fe14b9396070384a72f",
+ "dependencies": [
+ "jsr:@std/collections@^0.219.1"
+ ]
+ },
+ "@std/assert@0.220.1": {
+ "integrity": "88710d54f3afdd7a5761e7805abba1f56cd14e4b212feffeb3e73a9f77482425"
+ },
+ "@std/async@0.220.1": {
+ "integrity": "61f0e9c53bf81f516aa7f54843f307eb5f05c5cf9902575a636ead162b5dfafe"
+ },
+ "@std/cli@0.220.1": {
+ "integrity": "6c38388efc899c9d98811d38a46effe01e0d91138068483f1e6ae834caad5e82",
+ "dependencies": [
+ "jsr:@std/assert@^0.220.1"
+ ]
+ },
+ "@std/collections@0.219.1": {
+ "integrity": "ed0adf354efaf90069a1e9af263b9941f6a370d16e8a98f2fc67d12ed75d825a"
+ },
+ "@std/encoding@0.220.1": {
+ "integrity": "8dc38dd72e36cd68857a5837e24eb09a64bb296b96c295239c75eec17d45d23f"
+ },
+ "@std/fmt@0.220.1": {
+ "integrity": "3b1a698477a26b1dacbbee26db1a65030a005c6d71db3b3a0e59f8a638f04d7a"
+ },
+ "@std/http@0.220.1": {
+ "integrity": "1672a8d31f3e0ccf1c55221907daba4a22f3760cf2d079afb26def2dcfd0513b",
+ "dependencies": [
+ "jsr:@std/assert@^0.220.1",
+ "jsr:@std/async@^0.220.1",
+ "jsr:@std/cli@^0.220.1",
+ "jsr:@std/encoding@^0.220.1",
+ "jsr:@std/fmt@^0.220.1",
+ "jsr:@std/media-types@^0.220.1",
+ "jsr:@std/path@^0.220.1",
+ "jsr:@std/streams@^0.220.1"
+ ]
+ },
+ "@std/media-types@0.220.1": {
+ "integrity": "9dea30f7e0c87426ad8736678137ad277f1450f858efa1ef2c3aad6d9029b682"
+ },
+ "@std/path@0.220.1": {
+ "integrity": "cc63c1b5e5192e2f718dc2f365a3514fffb26cc0380959bab0f8fb5988a52f0c"
+ },
+ "@std/semver@0.220.1": {
+ "integrity": "f3df4abc715ad723680011f19d2934cfd436b4c41c317e82bfc14ac586b3b724"
+ },
+ "@std/streams@0.220.1": {
+ "integrity": "f26c7cb98c471af89de8309a0246cccb4c9bb6f3c639bd84aee5488127dc8545"
+ }
+ }
+ },
+ "remote": {},
+ "workspace": {
+ "dependencies": [
+ "jsr:@fartlabs/jsonx@^0.0.9",
+ "jsr:@std/cli@^0.220.1",
+ "jsr:@std/http@^0.220.1",
+ "jsr:@std/semver@^0.220.1",
+ "jsr:@std/yaml@^0.220.1"
+ ]
+ }
+}
diff --git a/docs.ts b/docs.ts
new file mode 100644
index 0000000..03d3a9e
--- /dev/null
+++ b/docs.ts
@@ -0,0 +1,110 @@
+import { compare, greaterThan, parse } from "@std/semver";
+import { serveDir } from "@std/http";
+
+/**
+ * Playground is a jsonx playground.
+ */
+export interface Playground {
+ id: string;
+ version: string;
+ code: string;
+}
+
+/**
+ * Meta is the module metadata.
+ */
+export interface Meta {
+ latest: string;
+ versions: string[];
+}
+
+/**
+ * Docs is a class for serving the documentation site including
+ * jsonx playgrounds.
+ */
+export class Docs {
+ public constructor(private fsRoot: string, private kv: Deno.Kv) {}
+
+ /**
+ * fetch handles incoming requests.
+ */
+ public async fetch(request: Request): Promise {
+ const url = new URL(request.url);
+ if (request.method === "GET" && url.pathname === "/meta") {
+ return Response.json(await this.meta);
+ }
+
+ if (request.method === "GET" && url.pathname.startsWith("/playgrounds/")) {
+ const id = url.pathname.split("/")[2];
+ const playground = await this.getPlayground(id);
+ if (playground === null) {
+ return new Response(null, { status: 404 });
+ }
+
+ return Response.json(playground);
+ }
+
+ if (request.method === "POST" && url.pathname === "/playgrounds") {
+ const playground = await request.json();
+ await this.setPlayground(playground);
+ return Response.json(playground);
+ }
+
+ return await serveDir(request, { fsRoot: this.fsRoot });
+ }
+
+ /**
+ * getPlayground gets a playground by ID.
+ */
+ private async getPlayground(id: string): Promise {
+ const result = await this.kv.get(playgroundKey(id));
+ return result.value;
+ }
+
+ /**
+ * setPlayground sets a playground by ID.
+ */
+ private async setPlayground(playground: Playground): Promise {
+ const result = await this.kv.set(playgroundKey(playground.id), playground);
+ if (!result) {
+ throw new Error("Failed to set playground!");
+ }
+ }
+
+ /**
+ * meta gets the latest module metadata.
+ */
+ private get meta(): Promise {
+ return fetch("https://jsr.io/@fartlabs/jsonx/meta.json")
+ .then((response) => response.json())
+ .then((meta) => playgroundMeta(meta));
+ }
+}
+
+/**
+ * createDocs creates a new Docs instance.
+ */
+export function createDocs({ fsRoot, kv }: {
+ fsRoot: string;
+ kv: Deno.Kv;
+}): Docs {
+ return new Docs(fsRoot, kv);
+}
+
+function playgroundMeta({ latest, versions }: {
+ latest: string;
+ versions: Record;
+}): Meta {
+ // https://github.com/FartLabs/jsonx/issues/13
+ const minCompatible = parse("0.0.8");
+ return {
+ latest: latest,
+ versions: Object.keys(versions)
+ .filter((versionTag) => greaterThan(parse(versionTag), minCompatible))
+ .sort((a, b) => compare(parse(b), parse(a))),
+ };
+}
+
+function playgroundKey(id: string): Deno.KvKey {
+ return ["playgrounds", id];
+}
diff --git a/gen.ts b/gen.ts
new file mode 100644
index 0000000..50ad92d
--- /dev/null
+++ b/gen.ts
@@ -0,0 +1 @@
+//deno:generate deno run --allow-write .github/workflows/check.tsx .github/workflows/check.yaml
diff --git a/main.ts b/main.ts
new file mode 100644
index 0000000..557bcd2
--- /dev/null
+++ b/main.ts
@@ -0,0 +1,20 @@
+import { parseArgs } from "@std/cli";
+import { createDocs } from "./docs.ts";
+
+if (import.meta.main) {
+ const flags = parseArgs(Deno.args, {
+ string: ["port", "root"],
+ default: {
+ port: "8000",
+ root: "static",
+ },
+ });
+ const docs = createDocs({
+ fsRoot: flags.root,
+ kv: await Deno.openKv(),
+ });
+ Deno.serve(
+ { port: parseInt(flags.port) },
+ docs.fetch.bind(docs),
+ );
+}
diff --git a/static/examples/animals.tsx b/static/examples/animals.tsx
new file mode 100644
index 0000000..e0ab6f8
--- /dev/null
+++ b/static/examples/animals.tsx
@@ -0,0 +1,16 @@
+function Cat() {
+ return { animals: ["🐈"] };
+}
+
+function Dog() {
+ return { animals: ["🐕"] };
+}
+
+const data = (
+ <>
+
+
+ >
+);
+
+console.log(data);
diff --git a/static/favicon.ico b/static/favicon.ico
new file mode 100644
index 0000000..5f48a9e
Binary files /dev/null and b/static/favicon.ico differ
diff --git a/static/index.html b/static/index.html
new file mode 100644
index 0000000..d12cc9f
--- /dev/null
+++ b/static/index.html
@@ -0,0 +1,88 @@
+
+
+
+
+
+ jsonx
+
+
+
+
+
+
+
+
+
+ TSX
+
+
+
+
+
+
+
+
+
+
+
+
+ Console
+
+
+
+
+
+
+
+
+ Build logs
+
+
+
+
+
+
+
+
+ Result
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/lib/playground/build.js b/static/lib/playground/build.js
new file mode 100644
index 0000000..d714fd8
--- /dev/null
+++ b/static/lib/playground/build.js
@@ -0,0 +1,21 @@
+import * as esbuild from "https://esm.sh/esbuild-wasm@0.20.1";
+
+export async function transform(options) {
+ const transformation = await esbuild.transform(options.code, {
+ loader: "tsx",
+ tsconfigRaw: {
+ compilerOptions: makeCompilerOptions(options.version),
+ },
+ });
+
+ return transformation;
+}
+
+export function makeCompilerOptions(version) {
+ return {
+ jsx: "react-jsx",
+ jsxFactory: "h",
+ jsxFragmentFactory: "Fragment",
+ jsxImportSource: `https://esm.sh/jsr/@fartlabs/jsonx@${version}`,
+ };
+}
diff --git a/static/lib/playground/editor.js b/static/lib/playground/editor.js
new file mode 100644
index 0000000..15bd2f9
--- /dev/null
+++ b/static/lib/playground/editor.js
@@ -0,0 +1,16 @@
+import {
+ EditorView,
+ keymap,
+ lineNumbers,
+} from "https://esm.sh/@codemirror/view@6.0.1";
+import { defaultKeymap } from "https://esm.sh/@codemirror/commands@6.0.1";
+
+export let cmEditor;
+
+export function createEditor(options) {
+ cmEditor = new EditorView({
+ doc: options.code,
+ parent: options.target,
+ extensions: [keymap.of(defaultKeymap), lineNumbers()],
+ });
+}
diff --git a/static/lib/playground/index.js b/static/lib/playground/index.js
new file mode 100644
index 0000000..5b45af5
--- /dev/null
+++ b/static/lib/playground/index.js
@@ -0,0 +1 @@
+export * from "./playground.js";
diff --git a/static/lib/playground/output.js b/static/lib/playground/output.js
new file mode 100644
index 0000000..fc05213
--- /dev/null
+++ b/static/lib/playground/output.js
@@ -0,0 +1,32 @@
+export const COLOR = {
+ log: "dodgerblue",
+ warn: "yellow",
+ error: "red",
+ info: "lime",
+ table: "lavender",
+};
+
+export function renderPrefix(type) {
+ const timestamp = new Date().toISOString();
+ return `${timestamp} [${type.toUpperCase()}] `;
+}
+
+export function appendBuildOutput(type, message) {
+ const li = document.createElement("li");
+ li.innerHTML = `${renderPrefix(type)}${message}`;
+ buildOutput.append(li);
+ if (!buildDetails.open) {
+ buildDetails.open = true;
+ }
+}
+
+export function appendConsoleOutput(type, message) {
+ const li = document.createElement("li");
+ li.innerHTML = `${renderPrefix(type)}${message}`;
+ consoleOutput.append(li);
+ if (!consoleDetails.open) {
+ consoleDetails.open = true;
+ }
+}
diff --git a/static/lib/playground/playground.css b/static/lib/playground/playground.css
new file mode 100644
index 0000000..cfc3ff1
--- /dev/null
+++ b/static/lib/playground/playground.css
@@ -0,0 +1,132 @@
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
+ Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
+}
+
+body {
+ background-color: #bbb;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ margin: 0 -5px 0 0;
+}
+
+nav {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 1em 1em 0 1em;
+}
+
+.badges {
+ display: flex;
+ gap: 0.5em;
+}
+
+.badges a {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ outline: 2px solid #333;
+ color: #333;
+ border-radius: 0.5em;
+ transition: outline 0.2s;
+ padding: 0.5em;
+ text-decoration: none;
+ font-weight: bold;
+}
+
+.badges a:hover {
+ outline: 5px solid #333;
+}
+
+@media (max-width: 400px) {
+ .badges a {
+ outline: none;
+ }
+
+ .badges a:hover {
+ outline: none;
+ }
+
+ .badges a span {
+ display: none;
+ }
+}
+
+.badges img {
+ height: 1.5em;
+ align-self: center;
+}
+
+main {
+ padding: 0 0.7em;
+}
+
+details {
+ color: #bbb;
+ outline: 2px solid #333;
+ border-radius: 0.5em;
+ margin: 1em 0;
+}
+
+details summary {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.5em;
+ border-radius: 0.5em;
+ background-color: #333;
+}
+
+details[open] summary {
+ border-bottom: 2px solid #333;
+ border-radius: 0.5em 0.5em 0 0;
+}
+
+details:not([open]) summary span:first-child::before {
+ content: "▶";
+ margin-right: 0.5em;
+}
+
+details[open] summary span:first-child::before {
+ content: "▼";
+ margin-right: 0.5em;
+}
+
+#editor,
+iframe {
+ width: 100%;
+ height: 34vh;
+ background-color: #fff;
+ padding: 0.5em;
+ resize: vertical;
+ margin-bottom: -5px;
+ border-radius: 0 0 0.5em 0.5em;
+}
+
+#editor {
+ height: initial;
+ color: black;
+}
+
+#buildOutput,
+#consoleOutput {
+ margin: 0;
+ padding: 0.5em;
+ list-style: none;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ background-color: #000;
+ font-family: monospace;
+}
+
+#buildOutput:empty::before,
+#consoleOutput:empty::before {
+ content: "No output";
+ color: #bbb;
+ display: block;
+ padding: 0.5em;
+}
diff --git a/static/lib/playground/playground.js b/static/lib/playground/playground.js
new file mode 100644
index 0000000..81b0cdc
--- /dev/null
+++ b/static/lib/playground/playground.js
@@ -0,0 +1,100 @@
+import { transform } from "./build.js";
+import { createEditor, cmEditor } from "./editor.js";
+import { appendBuildOutput, appendConsoleOutput } from "./output.js";
+
+/**
+ * createPlayground create a playground.
+ */
+export async function createPlayground(options) {
+ // Fetch the module meta.
+ await fetch("./meta")
+ .then((response) => response.json())
+ .then((json) => {
+ // Set up version input element.
+ version.value = json.latest;
+ json.versions.forEach((versionTag) => {
+ const option = document.createElement("option");
+ option.value = versionTag;
+ option.textContent = `Version: ${versionTag}`;
+ version.append(option);
+ });
+ });
+
+ // Set up default values.
+ if (options.version) {
+ version.value = options.version;
+ }
+
+ await createEditor({
+ target: editor,
+ code: options.code,
+ version: version.value,
+ });
+
+ // Set up event listeners.
+ play.addEventListener("click", () => handlePlay());
+ clearBuildOutput.addEventListener(
+ "click",
+ () => (buildOutput.innerHTML = "")
+ );
+ clearConsoleOutput.addEventListener(
+ "click",
+ () => (consoleOutput.innerHTML = "")
+ );
+ window.addEventListener("message", (event) => {
+ if (event.data.type === "console") {
+ appendConsoleOutput(
+ event.data.method,
+ event.data.arguments
+ .map((arg) => (typeof arg === "object" ? JSON.stringify(arg) : arg))
+ .join(" ")
+ );
+ }
+ });
+
+ // Enable button interactions.
+ play.disabled = false;
+ share.disabled = false;
+ version.disabled = false;
+
+ // Play the code if autoplay is enabled.
+ if (options.autoplay) {
+ handlePlay();
+ }
+}
+
+async function handlePlay() {
+ try {
+ const code = cmEditor?.state?.doc?.toString();
+ if (!code) {
+ appendBuildOutput("error", "No code to build.");
+ return;
+ }
+
+ const transformation = await transform({
+ code: cmEditor.state.doc.toString(),
+ version: version.value,
+ });
+ transformation.warnings.forEach((warning) => {
+ appendBuildOutput("warning", warning.text);
+ });
+
+ const html = ``;
+ result.srcdoc = html;
+ } catch (error) {
+ appendBuildOutput("error", error.message);
+ }
+}
+
+const CONSOLE_INTERCEPT = `const _console = { ...console };
+for (const key in _console) {
+ if (typeof _console[key] === "function") {
+ console[key] = function () {
+ _console[key](...arguments);
+ parent.postMessage({ type: "console", method: key, arguments: [...arguments] });
+ };
+ }
+}
+window.onerror = function (message, source, lineno, colno, error) {
+ parent.postMessage({ type: "console", method: "error", arguments: [error ? error.stack : message] });
+}`;
diff --git a/static/play.js b/static/play.js
new file mode 100644
index 0000000..0149a29
--- /dev/null
+++ b/static/play.js
@@ -0,0 +1,37 @@
+import * as esbuild from "https://esm.sh/esbuild-wasm@0.20.1";
+import { createPlayground } from "./lib/playground/index.js";
+
+main();
+
+async function main() {
+ // Create the playground.
+ const url = new URL(location.href);
+ const id = url.searchParams.get("id");
+
+ // Initialize esbuild.
+ await esbuild.initialize({
+ wasmURL: "https://esm.sh/esbuild-wasm@0.20.1/esbuild.wasm",
+ });
+
+ // Fetch the playground.
+ let code;
+ let version;
+ try {
+ if (id) {
+ const playground = await fetch(`./playgrounds/${id}`).then((response) =>
+ response.json()
+ );
+ code = playground.code;
+ version = playground.version;
+ }
+
+ // Fetch default code if unset.
+ if (!code) {
+ code = await fetch("./examples/animals.tsx").then((response) =>
+ response.text()
+ );
+ }
+ } finally {
+ await createPlayground({ code, version, autoplay: true });
+ }
+}