First commit
Create basic render engine that only allows to add elements
This commit is contained in:
parent
0d3eb3d40f
commit
f411544fe9
14 changed files with 414 additions and 0 deletions
164
src/lib/YarJS.ts
Normal file
164
src/lib/YarJS.ts
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import {
|
||||
YarElement,
|
||||
YarFiber,
|
||||
YarHTMLTagName,
|
||||
YarProps,
|
||||
} from "./YarJs.interfaces";
|
||||
|
||||
export function createTextElement(text: string) {
|
||||
return {
|
||||
type: "TEXT",
|
||||
props: {
|
||||
nodeValue: text,
|
||||
children: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createElement(
|
||||
type: YarHTMLTagName,
|
||||
props: YarProps,
|
||||
...children: YarElement[]
|
||||
) {
|
||||
return {
|
||||
type,
|
||||
props: {
|
||||
...props,
|
||||
children: children.map((child) =>
|
||||
typeof child === "object" ? child : createTextElement(child),
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createDom(fiber: YarFiber) {
|
||||
const dom =
|
||||
fiber.type === "TEXT"
|
||||
? document.createTextNode("")
|
||||
: document.createElement(fiber.type);
|
||||
|
||||
Object.keys(fiber.props)
|
||||
.filter((key) => key !== "children")
|
||||
.forEach((key) => {
|
||||
// @ts-expect-error: I cannot figure it out how to properly type the dom props
|
||||
dom[key] = fiber.props[key];
|
||||
});
|
||||
|
||||
return dom;
|
||||
}
|
||||
|
||||
let nextUnitOfWork: YarFiber | null | undefined = null;
|
||||
let wipRoot: YarFiber | null | undefined = null;
|
||||
|
||||
function commitWork(fiber: YarFiber | null) {
|
||||
if (!fiber) return;
|
||||
|
||||
const domParent = fiber.parent!.dom!;
|
||||
domParent.appendChild(fiber.dom!);
|
||||
commitWork(fiber.child);
|
||||
commitWork(fiber.sibling);
|
||||
}
|
||||
|
||||
function commitRoot() {
|
||||
if (!wipRoot) return;
|
||||
|
||||
commitWork(wipRoot.child);
|
||||
wipRoot = null;
|
||||
}
|
||||
|
||||
function workLoop(deadline: IdleDeadline) {
|
||||
let shouldYield = false;
|
||||
while (nextUnitOfWork && !shouldYield) {
|
||||
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
|
||||
shouldYield = deadline.timeRemaining() < 1;
|
||||
}
|
||||
|
||||
if (!nextUnitOfWork && wipRoot) {
|
||||
commitRoot();
|
||||
}
|
||||
|
||||
requestIdleCallback(workLoop);
|
||||
}
|
||||
|
||||
requestIdleCallback(workLoop);
|
||||
|
||||
function performUnitOfWork(fiber: YarFiber) {
|
||||
// Process the Fiber Tree, this is a representation of the DOM
|
||||
// Since this this process could be interrupted before the whole
|
||||
// tree is processed, we just do the representation and computation here,
|
||||
// then in "commitWork" we add the representation to the actual DOM
|
||||
|
||||
if (!fiber.dom) {
|
||||
// Create the actual dom element
|
||||
fiber.dom = createDom(fiber);
|
||||
}
|
||||
|
||||
// Create a new fiber for each child
|
||||
const elements = fiber.props.children;
|
||||
let index = 0;
|
||||
let prevSibling: YarFiber | null = null;
|
||||
|
||||
while (index < elements.length) {
|
||||
const element = elements[index];
|
||||
|
||||
const newFiber = {
|
||||
parent: fiber,
|
||||
type: element.type,
|
||||
props: element.props,
|
||||
child: null,
|
||||
sibling: null,
|
||||
dom: null,
|
||||
};
|
||||
|
||||
// Each Fiber only holds a reference to it's first child (in the Fiber Tree representation),
|
||||
// so if is the first new Fiber we add it as a child to the parent,
|
||||
// if is not, we add as a sibling of the last child
|
||||
// Nonetheless, each fiber has a reference to it's parent
|
||||
if (index === 0) {
|
||||
fiber.child = newFiber;
|
||||
} else if (prevSibling !== null) {
|
||||
prevSibling.sibling = newFiber;
|
||||
}
|
||||
|
||||
prevSibling = newFiber;
|
||||
index++;
|
||||
}
|
||||
|
||||
// Search for the new fiber that needs to be processed
|
||||
if (fiber.child) {
|
||||
// return the first child of the current fiber
|
||||
return fiber.child;
|
||||
}
|
||||
|
||||
let nextFiber = fiber;
|
||||
|
||||
while (nextFiber) {
|
||||
if (nextFiber.sibling) {
|
||||
// return the next sibling of the current fiber
|
||||
return nextFiber.sibling;
|
||||
}
|
||||
|
||||
// Reference the parent so we look at the "uncle" (parent sibling)
|
||||
// in the next itereation
|
||||
nextFiber = nextFiber.parent!;
|
||||
}
|
||||
}
|
||||
|
||||
export function render(element: React.JSX.Element, container: HTMLElement) {
|
||||
wipRoot = {
|
||||
type: "",
|
||||
dom: container,
|
||||
parent: null,
|
||||
child: null,
|
||||
sibling: null,
|
||||
props: {
|
||||
children: [element],
|
||||
},
|
||||
};
|
||||
|
||||
nextUnitOfWork = wipRoot;
|
||||
}
|
||||
|
||||
export function Fragment() {}
|
||||
|
||||
export default { render, createElement, Fragment };
|
||||
18
src/lib/YarJs.interfaces.ts
Normal file
18
src/lib/YarJs.interfaces.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
export type YarHTMLTagName = keyof React.JSX.IntrinsicElements | string;
|
||||
|
||||
export type YarProps = {
|
||||
children: YarElement[];
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export interface YarElement {
|
||||
type: YarHTMLTagName;
|
||||
props: YarProps;
|
||||
}
|
||||
|
||||
export interface YarFiber extends YarElement {
|
||||
parent: null | YarFiber;
|
||||
dom: null | HTMLElement | Text;
|
||||
child: null | YarFiber;
|
||||
sibling: null | YarFiber;
|
||||
}
|
||||
Reference in a new issue