Skip to content

Instantly share code, notes, and snippets.

@shovon
Created May 24, 2024 05:45
Show Gist options
  • Save shovon/82aa576a8cb5835145a643a85e6adc6a to your computer and use it in GitHub Desktop.
Save shovon/82aa576a8cb5835145a643a85e6adc6a to your computer and use it in GitHub Desktop.
A dummy React clone

Kawaii: a dumb little JSX front-end library, purely created for educational purposes

I wrote this to teach myself on how React and other JSX library work behind the scenes.

Usage

First, you will need a bundler that doesn't exclusively work with React. Unfortunately, how to configure it is beyond the scope of this README file, since every bundler has their own way of converting JSX to JavaSript.

That said, there are some tutorials on how to do that.

Next, you can just use it like any other JSX-based library.

import "./style.css";
import { useState } from "./lib/kawai.ts";

function Counter() {
	const [value, setState] = useState(0);
	return (
		<div>
			<button
				onClick={() => {
					setState(value + 1);
				}}
			>
				Increment
			</button>{" "}
			{`${value}`}
		</div>
	);
}

document.querySelector<HTMLDivElement>("#app")!.appendChild(<Counter />);
type Renderer = (props: unknown) => Element;
let currentRendererAndParent: null | {
renderer: () => Element;
node?: Element;
// parent: Element;
} = null;
const stateMapping = new WeakMap<object, unknown>();
export function useState<T>(initialValue: T): [T, (value: T) => void] {
if (currentRendererAndParent === null) {
return [initialValue, () => {}];
}
if (!stateMapping.has(currentRendererAndParent)) {
stateMapping.set(currentRendererAndParent, initialValue);
}
let currentValue = stateMapping.get(currentRendererAndParent);
return [
currentValue as T,
((reference) => (value: T) => {
stateMapping.set(reference, value);
const newNode = reference.renderer();
reference?.node?.replaceWith(newNode);
reference.node = newNode;
})(currentRendererAndParent),
];
}
export function create(
node: Renderer | string,
props: unknown,
...children: unknown[]
): Element {
if (children.length === 1 && Array.isArray(children[0])) {
children = children[0];
}
switch (typeof node) {
case "function":
return ((newProps) => {
const domNodeRetriever = () => {
return node(newProps);
};
currentRendererAndParent = {
renderer: domNodeRetriever,
// node: element,
// parent: element,
};
const element = domNodeRetriever();
currentRendererAndParent.node = element;
return element;
})({ ...(props ?? {}), children });
case "string":
const toReturn = document.createElement(node);
if (!!props) {
const p = props as { onClick?: unknown };
if (typeof p.onClick === "function") {
const onClick = p.onClick;
toReturn.addEventListener("click", () => {
onClick();
});
}
}
for (const child of children) {
switch (typeof child) {
case "string":
toReturn.appendChild(document.createTextNode(child));
break;
case "object":
if (child instanceof Element) {
toReturn.appendChild(child);
break;
} else {
throw new Error("Unknown child element type");
}
default:
throw new Error(`Unknown children type ${typeof children}`);
}
}
return toReturn;
default:
throw new Error(`Unknown typeo of node ${typeof node}`);
}
}
export function Fragment(value: unknown) {
// console.log(value);
console.log(value);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment