improved cr by very basic placement commit
This commit is contained in:
parent
98b0752441
commit
f4427f7b3f
|
|
@ -10,6 +10,18 @@
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
border: 1px solid white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,44 @@
|
||||||
interface FiberNode {
|
import { domFiber, ElementSymbol, FiberNodeSymbol } from "./symbols";
|
||||||
child?: FiberNode;
|
import {
|
||||||
sibling?: FiberNode;
|
EffectTag,
|
||||||
parent?: FiberNode;
|
isFunctionFiber,
|
||||||
}
|
isHostFiber,
|
||||||
|
type Child,
|
||||||
|
type FiberNode,
|
||||||
|
type FunctionFiber,
|
||||||
|
type HostFiber,
|
||||||
|
type JSXElement,
|
||||||
|
type RootFiber,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
let unitOfWork: FiberNode | null = null;
|
let unitOfWork: FiberNode | null = null;
|
||||||
|
let wipFiber: FiberNode;
|
||||||
|
let hookIndex: number;
|
||||||
|
let effectFiber: FiberNode | null = null;
|
||||||
|
let wipRoot: RootFiber | null = null;
|
||||||
|
|
||||||
|
const textElementType = "$$TEXT_ELEMENT$$";
|
||||||
|
const createTextElement = (text: string): JSXElement => ({
|
||||||
|
$$typeof: ElementSymbol,
|
||||||
|
type: textElementType,
|
||||||
|
props: {
|
||||||
|
nodeValue: text,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
function performUnitOfWork(fiber: FiberNode) {
|
function performUnitOfWork(fiber: FiberNode) {
|
||||||
console.log("performUnitOfWork", fiber);
|
if (isFunctionFiber(fiber)) {
|
||||||
|
updateFunctionComponent(fiber);
|
||||||
|
} else if (isHostFiber(fiber)) {
|
||||||
|
updateHostComponent(fiber);
|
||||||
|
}
|
||||||
|
|
||||||
if (fiber.child) {
|
if (fiber.child) {
|
||||||
return fiber.child;
|
return fiber.child;
|
||||||
}
|
}
|
||||||
|
|
||||||
let nextFiber: FiberNode | undefined = fiber;
|
let nextFiber: FiberNode | RootFiber | null = fiber;
|
||||||
while (nextFiber) {
|
while (nextFiber) {
|
||||||
if (nextFiber.sibling) {
|
if (nextFiber.sibling) {
|
||||||
return nextFiber.sibling;
|
return nextFiber.sibling;
|
||||||
|
|
@ -31,19 +56,266 @@ function workLoop(deadline: IdleDeadline) {
|
||||||
|
|
||||||
if (unitOfWork) {
|
if (unitOfWork) {
|
||||||
requestIdleCallback(workLoop);
|
requestIdleCallback(workLoop);
|
||||||
} else {
|
} else if (wipRoot) {
|
||||||
// Commit all the changes that have accumulated.
|
commitRoot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function linkParents(fiber: FiberNode, parent?: FiberNode) {
|
function createFiberFromElement(element: JSXElement): FiberNode {
|
||||||
fiber.parent = parent;
|
return {
|
||||||
if (fiber.child) {
|
$$typeof: FiberNodeSymbol,
|
||||||
linkParents(fiber.child, fiber);
|
type: element.type,
|
||||||
|
key: element.key ?? null,
|
||||||
|
child: null,
|
||||||
|
sibling: null,
|
||||||
|
parent: null,
|
||||||
|
index: 0,
|
||||||
|
dom: null,
|
||||||
|
hooks: [],
|
||||||
|
pendingProps: element.props,
|
||||||
|
effectTag: EffectTag.NoEffect,
|
||||||
|
alternate: null,
|
||||||
|
nextEffect: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFunctionComponent(fiber: FunctionFiber) {
|
||||||
|
wipFiber = fiber;
|
||||||
|
hookIndex = 0;
|
||||||
|
wipFiber.hooks = [];
|
||||||
|
wipFiber.effectTag = EffectTag.NoEffect;
|
||||||
|
const children = [fiber.type(fiber.pendingProps)];
|
||||||
|
reconcileChildren(fiber, children);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateHostComponent(fiber: HostFiber) {
|
||||||
|
wipFiber = fiber;
|
||||||
|
reconcileChildren(fiber, fiber.pendingProps.children);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushEffect(fiber: FiberNode) {
|
||||||
|
if (!wipRoot) {
|
||||||
|
throw "pushEffect() must be called inside a root";
|
||||||
}
|
}
|
||||||
if (fiber.sibling) {
|
if (effectFiber) {
|
||||||
linkParents(fiber.sibling, fiber);
|
effectFiber.nextEffect = fiber;
|
||||||
|
effectFiber = fiber;
|
||||||
|
} else {
|
||||||
|
effectFiber = fiber;
|
||||||
|
// @ts-ignore // HACK: Only for debugging
|
||||||
|
window.effectFiber = fiber;
|
||||||
|
unitOfWork = fiber;
|
||||||
|
}
|
||||||
|
if (!wipRoot.firstEffect) {
|
||||||
|
wipRoot.firstEffect = fiber;
|
||||||
|
}
|
||||||
|
wipRoot.lastEffect = fiber;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reconcileChildren(wipFiber: FiberNode, children: Child[] = []) {
|
||||||
|
let index = 0;
|
||||||
|
let oldFiber = wipFiber.alternate?.child;
|
||||||
|
let prevSibling: FiberNode | null = null;
|
||||||
|
const elements = children
|
||||||
|
.filter((child) => child !== null && child !== undefined)
|
||||||
|
.map((child) => {
|
||||||
|
if (typeof child === "string") {
|
||||||
|
return createTextElement(child);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
});
|
||||||
|
|
||||||
|
while (index < elements.length || oldFiber) {
|
||||||
|
const element = elements[index];
|
||||||
|
let newFiber: FiberNode | null = null;
|
||||||
|
|
||||||
|
const isSameType = oldFiber && element && element.type === oldFiber.type;
|
||||||
|
if (isSameType && oldFiber) {
|
||||||
|
newFiber = {
|
||||||
|
$$typeof: FiberNodeSymbol,
|
||||||
|
type: oldFiber.type,
|
||||||
|
key: element.key ?? null,
|
||||||
|
child: null,
|
||||||
|
sibling: null,
|
||||||
|
parent: wipFiber,
|
||||||
|
index: 0,
|
||||||
|
dom: oldFiber.dom,
|
||||||
|
hooks: [],
|
||||||
|
pendingProps: element.props,
|
||||||
|
effectTag: EffectTag.Update,
|
||||||
|
alternate: oldFiber,
|
||||||
|
nextEffect: null,
|
||||||
|
};
|
||||||
|
pushEffect(newFiber);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element && !isSameType) {
|
||||||
|
newFiber = {
|
||||||
|
$$typeof: FiberNodeSymbol,
|
||||||
|
type: element.type,
|
||||||
|
key: element.key ?? null,
|
||||||
|
child: null,
|
||||||
|
sibling: null,
|
||||||
|
parent: wipFiber,
|
||||||
|
index: 0,
|
||||||
|
dom: null,
|
||||||
|
hooks: [],
|
||||||
|
pendingProps: element.props,
|
||||||
|
effectTag: EffectTag.Placement,
|
||||||
|
alternate: null,
|
||||||
|
nextEffect: null,
|
||||||
|
};
|
||||||
|
pushEffect(newFiber);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldFiber && !isSameType) {
|
||||||
|
oldFiber.effectTag = EffectTag.Deletion;
|
||||||
|
pushEffect(oldFiber);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldFiber) {
|
||||||
|
oldFiber = oldFiber.sibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
wipFiber.child = newFiber;
|
||||||
|
} else if (element && prevSibling) {
|
||||||
|
prevSibling.sibling = newFiber;
|
||||||
|
}
|
||||||
|
|
||||||
|
prevSibling = newFiber;
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
requestIdleCallback(workLoop);
|
requestIdleCallback(workLoop);
|
||||||
|
|
||||||
|
// TODO: Maybe store next parent HostFiber
|
||||||
|
function findDomParent(fiber: FiberNode) {
|
||||||
|
let parent = fiber.parent;
|
||||||
|
while (parent) {
|
||||||
|
if (parent.dom) {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
parent = parent.parent;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function commitRoot() {
|
||||||
|
if (!wipRoot) return;
|
||||||
|
effectFiber = wipRoot.firstEffect;
|
||||||
|
while (effectFiber) {
|
||||||
|
// Do effect
|
||||||
|
if (effectFiber.effectTag & EffectTag.Placement) {
|
||||||
|
let dom: Node;
|
||||||
|
if (effectFiber.type === textElementType) {
|
||||||
|
dom = document.createTextNode(effectFiber.pendingProps.nodeValue);
|
||||||
|
} else {
|
||||||
|
dom = document.createElement(effectFiber.type as string);
|
||||||
|
// TODO: Set props
|
||||||
|
// TODO: Maybe add event section
|
||||||
|
// TODO: Maybe add ref in far future might consider a general on mount and on unmount event
|
||||||
|
}
|
||||||
|
effectFiber.dom = dom;
|
||||||
|
const parent = findDomParent(effectFiber);
|
||||||
|
if (parent?.dom) {
|
||||||
|
console.log("Appending", dom, "into", parent.dom);
|
||||||
|
parent.dom.appendChild(dom);
|
||||||
|
} else {
|
||||||
|
throw "Can't commit without parent";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
effectFiber = effectFiber.nextEffect;
|
||||||
|
}
|
||||||
|
wipRoot = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const events: (keyof GlobalEventHandlersEventMap)[] = [
|
||||||
|
"click",
|
||||||
|
"mousemove",
|
||||||
|
"mouseenter",
|
||||||
|
"mouseleave",
|
||||||
|
"mouseover",
|
||||||
|
"mouseout",
|
||||||
|
"mousedown",
|
||||||
|
"mouseup",
|
||||||
|
"pointermove",
|
||||||
|
"pointerenter",
|
||||||
|
"pointerleave",
|
||||||
|
"pointerover",
|
||||||
|
"pointerout",
|
||||||
|
"blur",
|
||||||
|
"focus",
|
||||||
|
"drag",
|
||||||
|
"dragend",
|
||||||
|
"dragenter",
|
||||||
|
"dragleave",
|
||||||
|
"dragover",
|
||||||
|
"dragstart",
|
||||||
|
"drop",
|
||||||
|
"touchcancel",
|
||||||
|
"touchend",
|
||||||
|
"touchmove",
|
||||||
|
"touchstart",
|
||||||
|
];
|
||||||
|
const getEventProp = (eventType: string) => {
|
||||||
|
return `on${eventType.substring(0, 1).toUpperCase()}${eventType.substring(1)}`;
|
||||||
|
};
|
||||||
|
function handleEvent(event: Event, root: RootFiber) {
|
||||||
|
const target = event.target as HTMLElement;
|
||||||
|
const type = event.type;
|
||||||
|
let node: FiberNode | RootFiber | null = target[domFiber] ?? null;
|
||||||
|
let propagationStopped = false;
|
||||||
|
const ogStopPropagation = event.stopPropagation;
|
||||||
|
event.stopPropagation = () => {
|
||||||
|
propagationStopped = true;
|
||||||
|
ogStopPropagation.call(event);
|
||||||
|
};
|
||||||
|
do {
|
||||||
|
node?.pendingProps[getEventProp(type)]?.(event);
|
||||||
|
if (propagationStopped) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (event.bubbles) {
|
||||||
|
node = node?.parent ?? null;
|
||||||
|
}
|
||||||
|
} while (node?.dom !== root.dom);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRootFiber(root: HTMLElement): RootFiber {
|
||||||
|
const rootFiber: RootFiber = {
|
||||||
|
$$typeof: FiberNodeSymbol,
|
||||||
|
type: undefined,
|
||||||
|
key: null,
|
||||||
|
child: null,
|
||||||
|
sibling: null,
|
||||||
|
parent: null,
|
||||||
|
index: 0,
|
||||||
|
dom: root,
|
||||||
|
hooks: [],
|
||||||
|
pendingProps: null,
|
||||||
|
effectTag: EffectTag.NoEffect,
|
||||||
|
alternate: null,
|
||||||
|
nextEffect: null,
|
||||||
|
isRoot: true,
|
||||||
|
firstEffect: null,
|
||||||
|
lastEffect: null,
|
||||||
|
};
|
||||||
|
for (const event of events) {
|
||||||
|
rootFiber.dom.addEventListener(event, (event) =>
|
||||||
|
handleEvent(event, rootFiber),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return rootFiber;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function render(rootFiber: RootFiber, element: JSXElement) {
|
||||||
|
wipRoot = rootFiber;
|
||||||
|
const fiber = createFiberFromElement(element);
|
||||||
|
fiber.parent = rootFiber;
|
||||||
|
rootFiber.child = fiber;
|
||||||
|
wipRoot.child = fiber;
|
||||||
|
unitOfWork = fiber;
|
||||||
|
console.log(fiber);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
namespace JSX {
|
||||||
|
interface FC {
|
||||||
|
(props: any): JSX.Element | string | null;
|
||||||
|
}
|
||||||
|
type Children =
|
||||||
|
| JSX.Element
|
||||||
|
| (string | JSX.Element | JSX.Element[])[]
|
||||||
|
| string
|
||||||
|
| null
|
||||||
|
| undefined;
|
||||||
|
type BaseElementPropsWithoutChildren = {
|
||||||
|
className?: string;
|
||||||
|
id?: string;
|
||||||
|
tabindex?: number | string;
|
||||||
|
style?: import("./types").Styles;
|
||||||
|
[`data-${string}`]?: string | boolean;
|
||||||
|
onClick?: (event: MouseEvent) => void;
|
||||||
|
} & Partial<ARIAMixin>;
|
||||||
|
type BaseElementProps = BaseElementPropsWithoutChildren & {
|
||||||
|
children?: JSX.Children;
|
||||||
|
};
|
||||||
|
type SvgProps = BaseElementProps & {
|
||||||
|
["xml:lang"]?: string;
|
||||||
|
["xml:space"]?: string;
|
||||||
|
xmlns?: string;
|
||||||
|
// XLink attributes
|
||||||
|
["xlink:hrefDeprecated"]?: string;
|
||||||
|
["xlink:type"]?: string;
|
||||||
|
["xlink:role"]?: string;
|
||||||
|
["xlink:arcrole"]?: string;
|
||||||
|
["xlink:title"]?: string;
|
||||||
|
["xlink:show"]?: string;
|
||||||
|
["xlink:actuate"]?: string;
|
||||||
|
// Presentation attributes
|
||||||
|
["alignment-baseline"]?: string;
|
||||||
|
["baseline-shift"]?: string;
|
||||||
|
["clip"]?: string;
|
||||||
|
["clipPath"]?: string;
|
||||||
|
["clipRule"]?: string;
|
||||||
|
["color"]?: string;
|
||||||
|
["colorInterpolation"]?: string;
|
||||||
|
["colorInterpolationFilters"]?: string;
|
||||||
|
["cursor"]?: string;
|
||||||
|
["cx"]?: string;
|
||||||
|
["cy"]?: string;
|
||||||
|
["d"]?: string;
|
||||||
|
["direction"]?: string;
|
||||||
|
["display"]?: string;
|
||||||
|
["dominantBaseline"]?: string;
|
||||||
|
["fill"]?: string;
|
||||||
|
["fillOpacity"]?: string;
|
||||||
|
["fillRule"]?: string;
|
||||||
|
["filter"]?: string;
|
||||||
|
["floodColor"]?: string;
|
||||||
|
["floodOpacity"]?: string;
|
||||||
|
["fontFamily"]?: string;
|
||||||
|
["fontSize"]?: string;
|
||||||
|
["fontSize-adjust"]?: string;
|
||||||
|
["fontStretch"]?: string;
|
||||||
|
["fontStyle"]?: string;
|
||||||
|
["fontVariant"]?: string;
|
||||||
|
["fontWeight"]?: string;
|
||||||
|
["glyphOrientation-horizontal"]?: string;
|
||||||
|
["glyphOrientation-vertical"]?: string;
|
||||||
|
["height"]?: string;
|
||||||
|
["imageRendering"]?: string;
|
||||||
|
["letterSpacing"]?: string;
|
||||||
|
["lightingColor"]?: string;
|
||||||
|
["markerEnd"]?: string;
|
||||||
|
["markerMid"]?: string;
|
||||||
|
["markerStart"]?: string;
|
||||||
|
["mask"]?: string;
|
||||||
|
["maskType"]?: string;
|
||||||
|
["opacity"]?: string;
|
||||||
|
["overflow"]?: string;
|
||||||
|
["pointerEvents"]?: string;
|
||||||
|
["r"]?: string;
|
||||||
|
["rx"]?: string;
|
||||||
|
["ry"]?: string;
|
||||||
|
["shapeRendering"]?: string;
|
||||||
|
["stopColor"]?: string;
|
||||||
|
["stopOpacity"]?: string;
|
||||||
|
["stroke"]?: string;
|
||||||
|
["strokeDasharray"]?: string;
|
||||||
|
["strokeDashoffset"]?: string;
|
||||||
|
["strokeLinecap"]?: string;
|
||||||
|
["strokeLinejoin"]?: string;
|
||||||
|
["strokeMiterlimit"]?: string;
|
||||||
|
["strokeOpacity"]?: string;
|
||||||
|
["strokeWidth"]?: string;
|
||||||
|
["textAnchor"]?: string;
|
||||||
|
["textDecoration"]?: string;
|
||||||
|
["textOverflow"]?: string;
|
||||||
|
["textRendering"]?: string;
|
||||||
|
["transform"]?: string;
|
||||||
|
["transformOrigin"]?: string;
|
||||||
|
["unicodeBidi"]?: string;
|
||||||
|
["vectorEffect"]?: string;
|
||||||
|
["visibility"]?: string;
|
||||||
|
["whiteSpace"]?: string;
|
||||||
|
["width"]?: string;
|
||||||
|
["wordSpacing"]?: string;
|
||||||
|
["writingMode"]?: string;
|
||||||
|
["x"]?: string;
|
||||||
|
["y"]?: string;
|
||||||
|
viewbox?: string;
|
||||||
|
};
|
||||||
|
interface IntrinsicElements {
|
||||||
|
a: BaseElementProps & {
|
||||||
|
href?: string;
|
||||||
|
target?: HTMLAnchorElement["target"];
|
||||||
|
};
|
||||||
|
img: BaseElementProps & {
|
||||||
|
src: string;
|
||||||
|
alt?: string;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Void IntrinsicElements
|
||||||
|
// https://github.com/wooorm/html-void-elements
|
||||||
|
area: BaseElementPropsWithoutChildren;
|
||||||
|
base: BaseElementPropsWithoutChildren;
|
||||||
|
basefont: BaseElementPropsWithoutChildren;
|
||||||
|
bgsound: BaseElementPropsWithoutChildren;
|
||||||
|
br: BaseElementPropsWithoutChildren;
|
||||||
|
col: BaseElementPropsWithoutChildren;
|
||||||
|
command: BaseElementPropsWithoutChildren;
|
||||||
|
embed: BaseElementPropsWithoutChildren;
|
||||||
|
frame: BaseElementPropsWithoutChildren;
|
||||||
|
hr: BaseElementPropsWithoutChildren;
|
||||||
|
image: BaseElementPropsWithoutChildren;
|
||||||
|
img: BaseElementPropsWithoutChildren;
|
||||||
|
input: BaseElementPropsWithoutChildren;
|
||||||
|
keygen: BaseElementPropsWithoutChildren;
|
||||||
|
link: BaseElementPropsWithoutChildren;
|
||||||
|
meta: BaseElementPropsWithoutChildren;
|
||||||
|
param: BaseElementPropsWithoutChildren;
|
||||||
|
source: BaseElementPropsWithoutChildren;
|
||||||
|
track: BaseElementPropsWithoutChildren;
|
||||||
|
wbr: BaseElementPropsWithoutChildren;
|
||||||
|
svg: SvgProps;
|
||||||
|
path: SvgProps;
|
||||||
|
g: SvgProps;
|
||||||
|
[tagName: string]: BaseElementProps;
|
||||||
|
}
|
||||||
|
interface Element {
|
||||||
|
$$typeof: symbol;
|
||||||
|
type: string | FC | null;
|
||||||
|
key?: string;
|
||||||
|
ref?: string;
|
||||||
|
children?: JSX.Children;
|
||||||
|
props: any;
|
||||||
|
}
|
||||||
|
interface ElementChildrenAttribute {
|
||||||
|
children: {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { createRootFiber, render } from "./cr";
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<div>Hello world</div>
|
||||||
|
<ul>
|
||||||
|
{items.map((item) => (
|
||||||
|
<li key={item}>{String(item)}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("foo");
|
||||||
|
const root = createRootFiber(document.getElementById("app")!);
|
||||||
|
console.log(<div>Hello World</div>);
|
||||||
|
render(root, <App />);
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { jsx, Fragment } from "./jsx-runtime";
|
||||||
|
export const jsxDEV = jsx;
|
||||||
|
export { jsx, Fragment };
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { ElementSymbol } from "~/symbols";
|
||||||
|
import type { FC, JSXElement } from "~/types";
|
||||||
|
|
||||||
|
export const jsx = function (type: string | FC, fullProps: any): JSXElement {
|
||||||
|
const { key, children, ref, ...props } = fullProps;
|
||||||
|
return {
|
||||||
|
$$typeof: ElementSymbol,
|
||||||
|
type,
|
||||||
|
props: {
|
||||||
|
...props,
|
||||||
|
children: Array.isArray(children) ? children : [children],
|
||||||
|
},
|
||||||
|
key: key,
|
||||||
|
ref: ref,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Fragment = function (fullProps: any): JSXElement {
|
||||||
|
const { children, ...props } = fullProps;
|
||||||
|
return {
|
||||||
|
$$typeof: ElementSymbol,
|
||||||
|
type: null,
|
||||||
|
props,
|
||||||
|
key: props.key,
|
||||||
|
ref: props.ref,
|
||||||
|
children,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
export const ElementSymbol = Symbol.for("cr.element");
|
||||||
|
export const FragmentSymbol = Symbol.for("cr.fragment");
|
||||||
|
export const FiberNodeSymbol = Symbol.for("cr.fiber-node");
|
||||||
|
export type DomFiberKey = "__cr_fiber";
|
||||||
|
export type DomPropsKey = "__cr_props";
|
||||||
|
export const domFiber = "__cr_fiber" satisfies DomFiberKey;
|
||||||
|
export const domProps = "__cr_props" satisfies DomPropsKey;
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
import { domProps, ElementSymbol, FiberNodeSymbol, domFiber } from "./symbols";
|
||||||
|
|
||||||
|
export type FC = (props: any) => JSXElement | string | null;
|
||||||
|
|
||||||
|
export type Child = JSXElement | string | null | undefined;
|
||||||
|
export type Children = Child | Child[];
|
||||||
|
export type ElementType = string | FC | null;
|
||||||
|
|
||||||
|
export interface JSXElement {
|
||||||
|
$$typeof: typeof ElementSymbol;
|
||||||
|
type: ElementType;
|
||||||
|
key?: string;
|
||||||
|
ref?: string;
|
||||||
|
props: any;
|
||||||
|
children?: Children;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EffectTag {
|
||||||
|
NoEffect = 0x0,
|
||||||
|
Placement = 0x1,
|
||||||
|
Update = 0x2,
|
||||||
|
Deletion = 0x4,
|
||||||
|
Hydration = 0x8,
|
||||||
|
Passive = 0x10,
|
||||||
|
Layout = 0x20,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StateHook = Symbol.for("cr.state-hook");
|
||||||
|
export const EffectHook = Symbol.for("cr.effect-hook");
|
||||||
|
type Hook =
|
||||||
|
| {
|
||||||
|
type: typeof StateHook;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: typeof EffectHook;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface FiberNode {
|
||||||
|
$$typeof: typeof FiberNodeSymbol;
|
||||||
|
// Identity
|
||||||
|
type: ElementType;
|
||||||
|
key: string | null;
|
||||||
|
isRoot?: boolean;
|
||||||
|
|
||||||
|
child: FiberNode | null;
|
||||||
|
sibling: FiberNode | null;
|
||||||
|
parent: FiberNode | RootFiber | null;
|
||||||
|
index: number;
|
||||||
|
|
||||||
|
// State / Instance
|
||||||
|
dom: Node | null;
|
||||||
|
hooks: Hook[];
|
||||||
|
pendingProps: any;
|
||||||
|
|
||||||
|
effectTag: EffectTag;
|
||||||
|
|
||||||
|
// Used for double buffering
|
||||||
|
alternate: FiberNode | null;
|
||||||
|
nextEffect: FiberNode | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FunctionFiber = FiberNode & {
|
||||||
|
type: FC;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HostFiber = FiberNode & {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RootFiber = Omit<FiberNode, "type"> & {
|
||||||
|
type: undefined;
|
||||||
|
dom: HTMLElement;
|
||||||
|
isRoot: true;
|
||||||
|
firstEffect: FiberNode | null;
|
||||||
|
lastEffect: FiberNode | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isFiberNode = (fiber: any): fiber is FiberNode =>
|
||||||
|
fiber && fiber.$$typeof === FiberNodeSymbol;
|
||||||
|
|
||||||
|
export const isElement = (fiber: any): fiber is JSXElement =>
|
||||||
|
fiber && fiber.$$typeof === ElementSymbol;
|
||||||
|
|
||||||
|
export const isFunctionFiber = (fiber: any): fiber is FunctionFiber =>
|
||||||
|
fiber.type instanceof Function;
|
||||||
|
|
||||||
|
export const isHostFiber = (fiber: any): fiber is HostFiber =>
|
||||||
|
typeof fiber.type === "string";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElement {
|
||||||
|
[domFiber]?: FiberNode;
|
||||||
|
[domProps]?: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
"module": "Preserve",
|
"module": "Preserve",
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "~/runtime",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
|
||||||
// Bundler mode
|
// Bundler mode
|
||||||
|
|
@ -24,6 +25,9 @@
|
||||||
// Some stricter flags (disabled by default)
|
// Some stricter flags (disabled by default)
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noPropertyAccessFromIndexSignature": false
|
"noPropertyAccessFromIndexSignature": false,
|
||||||
|
"paths": {
|
||||||
|
"~/*": ["./src/*"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue