summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-01-12 19:09:39 +0900
committernsfisis <nsfisis@gmail.com>2025-01-12 21:37:23 +0900
commitf3a77cb2f83ff2f91a5cc78da2bd735ad0d4edec (patch)
tree98518452454750d6784619a9232f1f72958f6571
parentaecf316775c995f089012d8fec5c5cc77f6300be (diff)
downloadnsfisis.dev-f3a77cb2f83ff2f91a5cc78da2bd735ad0d4edec.tar.gz
nsfisis.dev-f3a77cb2f83ff2f91a5cc78da2bd735ad0d4edec.tar.zst
nsfisis.dev-f3a77cb2f83ff2f91a5cc78da2bd735ad0d4edec.zip
refactor(blog/nuldoc): support JSX notation in nuldoc sources
-rw-r--r--vhosts/blog/deno.jsonc7
-rw-r--r--vhosts/blog/nuldoc-src/jsx/jsx-runtime.ts27
-rw-r--r--vhosts/blog/nuldoc-src/jsx/render.ts45
-rw-r--r--vhosts/blog/nuldoc-src/jsx/types.d.ts61
4 files changed, 140 insertions, 0 deletions
diff --git a/vhosts/blog/deno.jsonc b/vhosts/blog/deno.jsonc
index 1751a80c..1a7ee5cd 100644
--- a/vhosts/blog/deno.jsonc
+++ b/vhosts/blog/deno.jsonc
@@ -1,10 +1,17 @@
{
"imports": {
+ "myjsx/jsx-runtime": "./nuldoc-src/jsx/jsx-runtime.ts",
+ "myjsx-types/jsx-runtime": "./nuldoc-src/jsx/types.d.ts",
"checksum/": "https://deno.land/x/checksum@1.4.0/",
"highlight.js": "npm:highlight.js@11.9.0",
"std/": "https://deno.land/std@0.224.0/",
"zod/": "https://deno.land/x/zod@v3.23.8/",
},
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "myjsx",
+ "jsxImportSourceTypes": "myjsx-types",
+ },
"tasks": {
"check": "deno check nuldoc-src/main.ts && deno lint -- nuldoc-src/ && deno fmt --check -- nuldoc-src/",
"fmt": "deno fmt -- nuldoc-src",
diff --git a/vhosts/blog/nuldoc-src/jsx/jsx-runtime.ts b/vhosts/blog/nuldoc-src/jsx/jsx-runtime.ts
new file mode 100644
index 00000000..9571e87d
--- /dev/null
+++ b/vhosts/blog/nuldoc-src/jsx/jsx-runtime.ts
@@ -0,0 +1,27 @@
+import type { Node } from "../dom.ts";
+
+export type JSXElement = {
+ tag: string | FunctionComponent;
+ props: Props;
+};
+
+export type JSXNullNode = false | null | undefined;
+export type JSXSimpleNode = JSXElement | Node | string;
+export type JSXNullableSimpleNode = JSXSimpleNode | JSXNullNode;
+export type JSXNode = JSXNullableSimpleNode | JSXNode[];
+export type RenderableJSXNode = JSXElement;
+
+type Props = { children?: JSXNode } & Record<string, unknown>;
+export type FunctionComponentResult = JSXElement | Promise<JSXElement>;
+type FunctionComponent = (props: Props) => FunctionComponentResult;
+
+export function jsx(
+ tag: string | FunctionComponent,
+ props: Props,
+): JSXElement {
+ return { tag, props };
+}
+
+export { jsx as jsxs };
+
+// TODO: support Fragment
diff --git a/vhosts/blog/nuldoc-src/jsx/render.ts b/vhosts/blog/nuldoc-src/jsx/render.ts
new file mode 100644
index 00000000..8603f6c3
--- /dev/null
+++ b/vhosts/blog/nuldoc-src/jsx/render.ts
@@ -0,0 +1,45 @@
+import type { Element, Node } from "../dom.ts";
+import type {
+ JSXNode,
+ JSXNullableSimpleNode,
+ JSXSimpleNode,
+ RenderableJSXNode,
+} from "myjsx/jsx-runtime";
+
+function transformNode(node: JSXNode): Promise<Node[]> {
+ const flattenNodes: JSXNullableSimpleNode[] = Array.isArray(node)
+ // @ts-ignore prevents infinite recursion
+ ? (node.flat(Infinity) as JSXNullableSimpleNode[])
+ : [node];
+ return Promise.all(
+ flattenNodes
+ .filter((c): c is JSXSimpleNode => c != null && c !== false)
+ .map((c) => {
+ if (typeof c === "string") {
+ return { kind: "text", content: c, raw: false };
+ } else if ("kind" in c) {
+ return c;
+ } else {
+ return renderToDOM(c);
+ }
+ }),
+ );
+}
+
+export async function renderToDOM(
+ element: RenderableJSXNode,
+): Promise<Element> {
+ const { tag, props } = element;
+ if (typeof tag === "string") {
+ const { children, ...attrs } = props;
+ const attrsMap = new Map(Object.entries(attrs)) as Map<string, string>;
+ return {
+ kind: "element",
+ name: tag,
+ attributes: attrsMap,
+ children: await transformNode(children),
+ };
+ } else {
+ return renderToDOM(await tag(props));
+ }
+}
diff --git a/vhosts/blog/nuldoc-src/jsx/types.d.ts b/vhosts/blog/nuldoc-src/jsx/types.d.ts
new file mode 100644
index 00000000..6a9d2b91
--- /dev/null
+++ b/vhosts/blog/nuldoc-src/jsx/types.d.ts
@@ -0,0 +1,61 @@
+import type {
+ FunctionComponentResult,
+ JSXElement,
+ JSXNode,
+} from "myjsx/jsx-runtime";
+
+export { JSXNode };
+
+interface IntrinsicElementType {
+ children?: JSXNode;
+ className?: string;
+ id?: string;
+}
+
+declare global {
+ namespace JSX {
+ type Element = JSXElement;
+ type ElementType =
+ | string
+ // deno-lint-ignore no-explicit-any
+ | ((props: any) => FunctionComponentResult);
+
+ interface IntrinsicElements {
+ a: IntrinsicElementType & { href?: string };
+ article: IntrinsicElementType;
+ body: IntrinsicElementType;
+ button: IntrinsicElementType;
+ canvas: { id?: string; "data-slide-link"?: string };
+ div: IntrinsicElementType;
+ footer: IntrinsicElementType;
+ h1: IntrinsicElementType;
+ h2: IntrinsicElementType;
+ head: unknown;
+ header: IntrinsicElementType;
+ html: IntrinsicElementType & { lang?: string };
+ img: { src: string };
+ li: IntrinsicElementType;
+ link: { rel: string; href: string; type?: string };
+ main: IntrinsicElementType;
+ meta: {
+ charset?: string;
+ name?: string;
+ content?: string;
+ property?: string;
+ };
+ nav: IntrinsicElementType;
+ noscript: IntrinsicElementType;
+ ol: IntrinsicElementType;
+ p: IntrinsicElementType;
+ script: { src: string; type?: string };
+ section: IntrinsicElementType;
+ time: IntrinsicElementType & { datetime?: string };
+ title: IntrinsicElementType;
+ ul: IntrinsicElementType;
+ }
+
+ interface ElementChildrenAttribute {
+ children: unknown;
+ }
+ }
+}