Skip to content

Instantly share code, notes, and snippets.

@bigmistqke
Last active January 23, 2025 18:45
Show Gist options
  • Save bigmistqke/1bafd4bde945ca168409b598fe37b8b1 to your computer and use it in GitHub Desktop.
Save bigmistqke/1bafd4bde945ca168409b598fe37b8b1 to your computer and use it in GitHub Desktop.
solid-vue
// v-dom driven by signals
import {
createSignal,
createMemo,
} from "solid-js";
interface TextNode {
text: string;
}
interface ElementNode {
tag: string;
props: Record<string, string>;
children: Array<ValidNode>;
}
interface ComponentNode {
tag: Component;
props: Record<string, string>;
children: Array<ValidNode>;
}
type VNode = ElementNode | ComponentNode;
type Component = (props: Record<string, any>) => () => VNode;
type ValidChild = VNode | string | number | Component;
type ValidNode = VNode | TextNode;
function isTextNode(node: any): node is TextNode {
return typeof node === "object" && "text" in node;
}
function isElementNode(node: any): node is ElementNode {
return typeof node === "object" && "tag" in node;
}
function isComponentNode(node: any): node is ComponentNode {
return (
typeof node === "object" && "tag" in node && typeof node.tag === "function"
);
}
function h(
tag: string | Component,
props: Record<string, any>,
...children: Array<ValidChild>
) {
return {
tag,
props: Object.fromEntries(
Object.entries(props).map(([key, value]) => [
key,
typeof value === "object"
? Object.entries(value)
.map(([key, value]) => `${key}: ${value}; `)
.join("")
: value,
]),
),
children: children.map((child) =>
typeof child === "object" ? child : { text: child.toString() },
),
} as VNode;
}
function vdom(
cb: (render: (node: VNode) => void) => Promise<void> | void,
parent = document.body,
) {
let previous: ElementNode;
const map = new WeakMap<ValidNode, HTMLElement | Text>();
function traverse({
node,
previous,
sibling,
parent,
}: {
node: ValidNode;
previous?: ValidNode;
sibling?: HTMLElement | Text;
parent: HTMLElement;
}): HTMLElement | Text {
if (node === previous) {
const element = map.get(previous)!;
map.set(node, element);
return element;
}
const shouldUnmount =
previous &&
("text" in node || "text" in previous || node.tag !== previous.tag);
const shouldMount = !previous || shouldUnmount;
if (shouldUnmount) {
parent.removeChild(map.get(previous)!);
}
if (isComponentNode(node)) {
let previousNode: VNode;
const currentTemplate = node.tag?.({
...node.props,
children: node.children,
});
const draw = createMemo(() => {
const node = currentTemplate();
const element = traverse({
node,
previous: previousNode,
parent,
sibling,
});
previousNode = node;
return element;
});
return draw();
}
const element = shouldMount
? isTextNode(node)
? document.createTextNode(node.text.toString())
: document.createElement(node.tag)
: map.get(previous)!;
map.set(node, element);
if (shouldMount) {
if (sibling) {
sibling.after(element);
} else {
parent.prepend(element);
}
}
if (isElementNode(node)) {
if (!(element instanceof HTMLElement)) {
throw `Element of ElementNode should be instance of HTMLElement`;
}
Object.entries(node.props).forEach(([key, value]) => {
if (
!shouldUnmount &&
isElementNode(previous) &&
value === previous.props[key]
) {
return;
}
element.setAttribute(key, value);
});
let sibling: HTMLElement | Text;
node.children.forEach((child, index) => {
sibling = traverse({
node: child,
previous:
!shouldUnmount && isElementNode(previous)
? previous.children[index]
: undefined,
parent: element,
sibling,
});
});
if (
isElementNode(previous) &&
previous.children.length > node.children.length
) {
previous.children.slice(node.children.length).forEach((child) => {
element.removeChild(map.get(child)!);
});
}
}
return element;
}
cb((node) => {
traverse({ node, previous, parent });
previous = node;
});
}
// USAGE
const Button: Component = (props) => {
const [title, set] = createSignal(props.title);
setTimeout(() => set((title) => title + title[title.length - 1]), 1000);
return () => h("button", {}, title() ?? props.title, ...props.children);
};
document.body.innerHTML = "";
vdom(async (render) => {
render(
h(
"div",
{},
"whatever",
"forever",
h(
Button,
{ title: "yolo", style: { border: "1px solid red" } },
h("h1", { style: { color: "red" } }, "hallo"),
),
"ok",
h(
Button,
{ title: "yeeehaaa", style: { border: "1px solid red" } },
h("h1", { style: { color: "red" } }, "hallo"),
),
),
);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment