Skip to content

Commit

Permalink
temp
Browse files Browse the repository at this point in the history
  • Loading branch information
JoviDeCroock committed Nov 10, 2024
1 parent 067be2b commit 0904ea4
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 31 deletions.
88 changes: 88 additions & 0 deletions src/backing-node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
TYPE_FUNCTION,
TYPE_ELEMENT,
TYPE_TEXT,
TYPE_CLASS,
TYPE_ROOT,
TYPE_COMPONENT,
MODE_SVG,
UNDEFINED
} from './constants';
import { enqueueRenderInternal } from './component';

/**
* Create an internal tree node
* @param {VNode | string} vnode
* @param {Internal} [parentInternal]
* @returns {Internal}
*/
export function createInternal(vnode, parentInternal) {
let type = null,
props,
key,
ref;

/** @type {number} */
let flags = parentInternal ? parentInternal.flags : 0;

// Text VNodes/Internals have an ID of 0 that is never used:
let vnodeId = 0;

if (typeof vnode == 'string') {
// type = null;
flags |= TYPE_TEXT;
props = vnode;
}
// Prevent JSON injection by rendering injected objects as empty Text nodes
else if (vnode.constructor !== UNDEFINED) {
flags |= TYPE_TEXT;
props = '';
} else {
type = vnode.type;
props = vnode.props;
key = vnode.key;
ref = vnode.ref;
vnodeId = vnode._original;

flags |=
typeof type == 'function'
? type.prototype && type.prototype.render
? TYPE_CLASS
: props._parentDom
? TYPE_ROOT
: TYPE_FUNCTION
: TYPE_ELEMENT;

// TODO: add math mode
if (flags & TYPE_ELEMENT && type === 'svg') {
flags |= MODE_SVG;
} else if (
parentInternal &&
parentInternal.flags & MODE_SVG &&
parentInternal.type === 'foreignObject'
) {
flags &= ~MODE_SVG;
}
}

/** @type {Internal} */
const internal = {
type,
props,
key,
ref,
data:
flags & TYPE_COMPONENT
? { _commitCallbacks: [], _context: null, _stateCallbacks: [] }
: null,
rerender: enqueueRenderInternal,
flags,
_children: null,
_parent: parentInternal,
_vnodeId: vnodeId,
_component: null,
_depth: parentInternal ? parentInternal._depth + 1 : 0
};

return internal;
}
19 changes: 18 additions & 1 deletion src/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { assign } from './util';
import { diff, commitRoot } from './diff/index';
import options from './options';
import { Fragment } from './create-element';
import { MODE_HYDRATE } from './constants';
import { DIRTY_BIT, MODE_HYDRATE } from './constants';

/**
* Base Component class. Provides `setState()` and `forceUpdate()`, which
Expand Down Expand Up @@ -206,6 +206,23 @@ export function enqueueRender(c) {
}
}

/**
* Enqueue a rerender of a component
* @param {Internal} internal The component to rerender
*/
export function enqueueRenderInternal(internal) {
if (
(!(internal.flags & DIRTY_BIT) &&
(internal.flags |= DIRTY_BIT) &&
rerenderQueue.push(internal) &&
!process._rerenderCount++) ||
prevDebounce !== options.debounceRendering
) {
prevDebounce = options.debounceRendering;
(prevDebounce || queueMicrotask)(process);
}
}

/**
* @param {Component} a
* @param {Component} b
Expand Down
57 changes: 56 additions & 1 deletion src/constants.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,69 @@
// Internal.flags bitfield constants
export const TYPE_TEXT = 1 << 0;
export const TYPE_ELEMENT = 1 << 1;
export const TYPE_CLASS = 1 << 2;
export const TYPE_FUNCTION = 1 << 3;
/** Signals this internal has a _parentDom prop that should change the parent
* DOM node of it's children */
export const TYPE_ROOT = 1 << 4;

/** Any type of internal representing DOM */
export const TYPE_DOM = TYPE_TEXT | TYPE_ELEMENT;
/** Any type of component */
export const TYPE_COMPONENT = TYPE_CLASS | TYPE_FUNCTION | TYPE_ROOT;

/** Normal hydration that attaches to a DOM tree but does not diff it. */
export const MODE_HYDRATE = 1 << 5;
/** Top level render unspecified behaviour (old replaceNode parameter to render) */
export const MODE_MUTATIVE_HYDRATE = 1 << 6;
/** Signifies this VNode suspended on the previous render */
export const MODE_SUSPENDED = 1 << 7;
/** Signifies this VNode errored on the previous render */
export const MODE_ERRORED = 1 << 8;
/** Signifies an error has been thrown and this component will be attempting to
* handle & rerender the error on next render. In other words, on the next
* render of this component, unset this mode and set the MODE_RERENDERING_ERROR.
* This flag is distinct from MODE_RERENDERING_ERROR so that a component can
* catch multiple errors thrown by its children in one render pass (see test
* "should handle double child throws").
*/
export const MODE_PENDING_ERROR = 1 << 9;
/** Signifies this Internal is attempting to "handle" an error and is
* rerendering. This mode tracks that a component's last rerender was trying to
* handle an error. As such, if another error is thrown while a component has
* this flag set, it should not handle the newly thrown error since it failed to
* successfully rerender the original error. This prevents error handling
* infinite render loops */
export const MODE_RERENDERING_ERROR = 1 << 10;
/** Signals this internal has been unmounted */
export const MODE_UNMOUNTING = 1 << 11;
/** This Internal is rendered in an SVG tree */
export const MODE_SVG = 1 << 12;
/** Signifies that bailout checks will be bypassed */
export const FORCE_UPDATE = 1 << 13;
/** Signifies that a node needs to be updated */
export const DIRTY_BIT = 1 << 14;
/** Signals the component can skip children due to a non-update */
export const SKIP_CHILDREN = 1 << 15;
/** Indicates that this node needs to be inserted while patching children */
export const INSERT_VNODE = 1 << 16;
/** Indicates a VNode has been matched with another VNode in the diff */
export const MATCHED = 1 << 17;
/** This Internal is rendered in a math tree */
export const MODE_MATH = 1 << 18;

/** Reset all mode flags */
export const RESET_MODE = ~(MODE_HYDRATE | MODE_SUSPENDED);
export const RESET_MODE = ~(
MODE_HYDRATE |
MODE_MUTATIVE_HYDRATE |
MODE_SUSPENDED |
MODE_ERRORED |
MODE_RERENDERING_ERROR |
FORCE_UPDATE |
SKIP_CHILDREN
);

export const UNDEFINED = undefined;

export const EMPTY_OBJ = /** @type {any} */ ({});
export const EMPTY_ARR = [];
Expand Down
14 changes: 1 addition & 13 deletions src/create-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,8 @@ export function createVNode(type, props, key, ref, original) {
props,
key,
ref,
_children: null,
_parent: null,
_depth: 0,
_dom: null,
// _nextDom must be initialized to undefined b/c it will eventually
// be set to dom.nextSibling which can return `null` and it is important
// to be able to distinguish between an uninitialized _nextDom and
// a _nextDom that has been set to `null`
_nextDom: undefined,
_component: null,
constructor: undefined,
_original: original == null ? ++vnodeId : original,
_index: -1,
_flags: 0
_original: original == null ? ++vnodeId : original
};

// Only invoke the vnode hook if this was *not* a direct copy:
Expand Down
62 changes: 47 additions & 15 deletions src/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,52 @@ declare global {
contextType?: PreactContext;
}

export interface VNode<P = {}> extends preact.VNode<P> {
// Redefine type here using our internal ComponentType type
type: string | ComponentType<P>;
props: P & { children: ComponentChildren };
/**
* Internal GUID for this VNode, used for fast equality checks.
* Note: h() allocates monotonic positive integer IDs, jsx() allocates negative.
* @private
*/
_original: number;
}

/**
* An Internal is a persistent backing node within Preact's virtual DOM tree.
* Think of an Internal like a long-lived VNode with stored data and tree linkages.
*/
export interface Internal<P = {}> {
type: string | ComponentType<P>;
/** The props object for Elements/Components, and the string contents for Text */
props: (P & { children: ComponentChildren }) | string | number;
key: any;
ref: Ref<any> | null;

/** Bitfield containing information about the Internal or its component. */
flags: number;
/** Polymorphic property to store extensions like hooks on */
data: object | PreactNode;
/** The function that triggers in-place re-renders for an internal */
rerender: (internal: Internal) => void;

/** children Internal nodes */
_children: Internal[];
/** next sibling Internal node */
_parent: Internal;
/** most recent vnode ID */
_vnodeId: number;
/**
* Associated DOM element for the Internal, or its nearest realized descendant.
* For Fragments, this is the first DOM child.
*/
/** The component instance for which this is a backing Internal node */
_component: Component | null;
/** This Internal's distance from the tree root */
_depth: number | null;
}

// Redefine ComponentType using our new internal FunctionComponent interface above
export type ComponentType<P = {}> = ComponentClass<P> | FunctionComponent<P>;

Expand Down Expand Up @@ -121,7 +167,7 @@ declare global {
checked?: HTMLInputElement['checked'];

// Internal properties
_children?: VNode<any> | null;
_children?: Internal<any> | null;
/** Event listeners to support event delegation */
_listeners?: Record<string, (e: Event) => void>;
}
Expand All @@ -145,22 +191,8 @@ declare global {
type: (string & { defaultProps: undefined }) | ComponentType<P>;
props: P & { children: ComponentChildren };
ref?: Ref<any> | null;
_children: Array<VNode<any>> | null;
_parent: VNode | null;
_depth: number | null;
/**
* The [first (for Fragments)] DOM child of a VNode
*/
_dom: PreactElement | null;
/**
* The last dom child of a Fragment, or components that return a Fragment
*/
_nextDom: PreactElement | null | undefined;
_component: Component | null;
constructor: undefined;
_original: number;
_index: number;
_flags: number;
}

export interface Component<P = {}, S = {}> extends preact.Component<P, S> {
Expand Down
4 changes: 3 additions & 1 deletion src/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { commitRoot, diff } from './diff/index';
import { createElement, Fragment } from './create-element';
import options from './options';
import { slice } from './util';
import { createInternal } from './backing-node';

/**
* Render a Preact virtual node into a DOM element
Expand All @@ -26,7 +27,8 @@ export function render(vnode, parentDom, replaceNode) {
// means that we are mounting a new tree for the first time.
let oldVNode = isHydrating ? null : parentDom._children;

vnode = parentDom._children = createElement(Fragment, null, [vnode]);
vnode = createElement(Fragment, null, [vnode]);
const rootInternal = (parentDom._children = createInternal(vnode));

// List of effects that need to be called after diffing.
let commitQueue = [],
Expand Down

0 comments on commit 0904ea4

Please sign in to comment.