Skip to content

Commit

Permalink
Add internals for mount path
Browse files Browse the repository at this point in the history
  • Loading branch information
JoviDeCroock committed Nov 24, 2024
1 parent 9f2fda3 commit 2181325
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 29 deletions.
5 changes: 4 additions & 1 deletion src/diff/children.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { isArray } from '../util';
import { getDomSibling } from '../component';
import { mount } from './mount';
import { insert } from './operations';
import { createInternal } from '../tree';

/**
* Diff the children of a virtual node
Expand Down Expand Up @@ -103,9 +104,11 @@ export function diffChildren(
refQueue
);
} else {
// TODO: temp
const internal = createInternal(childVNode, null);
result = mount(
parentDom,
childVNode,
internal,
globalContext,
namespace,
excessDomChildren,
Expand Down
80 changes: 55 additions & 25 deletions src/diff/mount.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,22 @@ import { insert } from './operations';
import { setProperty } from './props';
import { assign, isArray, slice } from '../util';
import options from '../options';
import {
createInternal,
MODE_MATH,
MODE_SVG,
TYPE_CLASS,
TYPE_COMPONENT,
TYPE_ELEMENT,
TYPE_FUNCTION,
TYPE_INVALID,
TYPE_TEXT
} from '../tree';

/**
* Diff two virtual nodes and apply proper changes to the DOM
* @param {PreactElement} parentDom The parent of the DOM element
* @param {VNode} newVNode The new virtual node
* @param {Internal} internal The backing node.
* @param {object} globalContext The current context object. Modified by
* getChildContext
* @param {string} namespace Current namespace of the DOM node (HTML, SVG, or MathML)
Expand All @@ -31,7 +42,7 @@ import options from '../options';
*/
export function mount(
parentDom,
newVNode,
internal,
globalContext,
namespace,
excessDomChildren,
Expand All @@ -40,22 +51,24 @@ export function mount(
isHydrating,
refQueue
) {
// @ts-expect-error
const newVNode = internal.vnode;

// When passing through createElement it assigns the object
// constructor as undefined. This to prevent JSON-injection.
if (newVNode.constructor !== UNDEFINED) return null;
if (internal.flags & TYPE_INVALID) return null;

/** @type {any} */
let tmp,
newType = newVNode.type;
let tmp;

if ((tmp = options._diff)) tmp(newVNode);

if (typeof newType == 'function') {
if (internal.flags & TYPE_COMPONENT) {
try {
let c,
newProps = newVNode.props;
const isClassComponent =
'prototype' in newType && newType.prototype.render;
newProps = internal.props,
newType = /** @type {ComponentType} */ (internal.type);
const isClassComponent = !!(internal.flags & TYPE_CLASS);

// Necessary for createContext api. Setting this property will pass
// the context value as `this.context` just for this component.
Expand All @@ -69,11 +82,17 @@ export function mount(

// Instantiate the new component
if (isClassComponent) {
// @ts-expect-error The check above verifies that newType is suppose to be constructed
newVNode._component = c = new newType(newProps, componentContext); // eslint-disable-line new-cap
internal._component =
newVNode._component =
c =
// @ts-expect-error The check above verifies that newType is suppose to be constructed
new newType(newProps, componentContext); // eslint-disable-line new-cap
} else {
// @ts-expect-error Trust me, Component implements the interface we want
newVNode._component = c = new BaseComponent(newProps, componentContext);
// @ts-expect-error The check above verifies that newType is suppose to be constructed
internal._component =
newVNode._component =
c =
new BaseComponent(newProps, componentContext);
c.constructor = newType;
c.render = doRender;
}
Expand Down Expand Up @@ -156,6 +175,7 @@ export function mount(
let renderResult = isTopLevelFragment ? tmp.props.children : tmp;

oldDom = mountChildren(
internal,
parentDom,
isArray(renderResult) ? renderResult : [renderResult],
newVNode,
Expand Down Expand Up @@ -200,7 +220,7 @@ export function mount(
}
} else {
oldDom = newVNode._dom = mountElementNode(
newVNode,
internal,
globalContext,
namespace,
excessDomChildren,
Expand All @@ -217,7 +237,7 @@ export function mount(

/**
* Diff two virtual nodes representing DOM element
* @param {VNode} newVNode The new virtual node
* @param {Internal} internal The new virtual node
* @param {object} globalContext The current context object
* @param {string} namespace Current namespace of the DOM node (HTML, SVG, or MathML)
* @param {Array<PreactElement>} excessDomChildren
Expand All @@ -228,19 +248,21 @@ export function mount(
* @returns {PreactElement}
*/
function mountElementNode(
newVNode,
internal,
globalContext,
namespace,
excessDomChildren,
commitQueue,
isHydrating,
refQueue
) {
// @ts-expect-error
const newVNode = internal.vnode;
/** @type {PreactElement} */
let dom;
let oldProps = EMPTY_OBJ;
let newProps = newVNode.props;
let nodeType = /** @type {string} */ (newVNode.type);
let newProps = internal.props;
let nodeType = /** @type {string} */ (internal.type);
/** @type {any} */
let i;
/** @type {{ __html?: string }} */
Expand All @@ -252,8 +274,8 @@ function mountElementNode(
let checked;

// Tracks entering and exiting namespaces when descending through the tree.
if (nodeType === 'svg') namespace = 'http://www.w3.org/2000/svg';
else if (nodeType === 'math')
if (internal.flags & MODE_SVG) namespace = 'http://www.w3.org/2000/svg';
else if (internal.flags & MODE_MATH)
namespace = 'http://www.w3.org/1998/Math/MathML';
else if (!namespace) namespace = 'http://www.w3.org/1999/xhtml';

Expand All @@ -277,7 +299,7 @@ function mountElementNode(
}

if (dom == null) {
if (nodeType === null) {
if (internal.flags & TYPE_TEXT) {
return document.createTextNode(newProps);
}

Expand All @@ -298,7 +320,7 @@ function mountElementNode(
excessDomChildren = null;
}

if (nodeType === null) {
if (internal.flags & TYPE_TEXT) {
// During hydration, we still have to split merged text from SSR'd HTML.
dom.data = newProps;
} else {
Expand Down Expand Up @@ -361,6 +383,7 @@ function mountElementNode(
newVNode._children = [];
} else {
mountChildren(
internal,
dom,
isArray(newChildren) ? newChildren : [newChildren],
newVNode,
Expand Down Expand Up @@ -416,6 +439,7 @@ function doRender(props, _state, context) {

/**
* Diff the children of a virtual node
* @param {Internal} internal The DOM element whose children are being
* @param {PreactElement} parentDom The DOM element whose children are being
* diffed
* @param {ComponentChildren[]} renderResult
Expand All @@ -435,6 +459,7 @@ function doRender(props, _state, context) {
* @param {any[]} refQueue an array of elements needed to invoke refs
*/
function mountChildren(
internal,
parentDom,
renderResult,
newParentVNode,
Expand Down Expand Up @@ -517,10 +542,11 @@ function mountChildren(
childVNode._parent = newParentVNode;
childVNode._depth = newParentVNode._depth + 1;

const childInternal = createInternal(childVNode, internal);
// Morph the old element into the new one, but don't append it to the dom yet
const result = mount(
parentDom,
childVNode,
childInternal,
globalContext,
namespace,
excessDomChildren,
Expand All @@ -544,9 +570,13 @@ function mountChildren(
firstChildDom = newDom;
}

if (typeof childVNode.type != 'function') {
if (childInternal.flags & TYPE_ELEMENT || childInternal.flags & TYPE_TEXT) {
oldDom = insert(childVNode, oldDom, parentDom);
} else if (typeof childVNode.type == 'function' && result !== UNDEFINED) {
} else if (
(childInternal.flags & TYPE_FUNCTION ||
childInternal.flags & TYPE_CLASS) &&
result !== UNDEFINED
) {
oldDom = result;
} else if (newDom) {
oldDom = newDom.nextSibling;
Expand Down
37 changes: 37 additions & 0 deletions src/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,43 @@ declare global {
_flags: 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;
/** Callbacks to invoke when this internal commits */
_commitCallbacks: Array<() => void>;
_stateCallbacks: Array<() => void>; // Only class components
}

export interface Component<P = {}, S = {}> extends preact.Component<P, S> {
// When component is functional component, this is reset to functional component
constructor: ComponentType<P>;
Expand Down
6 changes: 3 additions & 3 deletions src/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createElement, Fragment } from './create-element';
import options from './options';
import { slice } from './util';
import { mount } from './diff/mount';
import { createInternal } from './tree';

/**
* Render a Preact virtual node into a DOM element
Expand All @@ -28,6 +29,7 @@ export function render(vnode, parentDom, replaceNode) {
let oldVNode = isHydrating ? null : parentDom._children;

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

// List of effects that need to be called after diffing.
let commitQueue = [],
Expand All @@ -51,9 +53,7 @@ export function render(vnode, parentDom, replaceNode) {
} else {
mount(
parentDom,
// Determine the new vnode tree and store it on the DOM element on
// our custom `_children` property.
vnode,
internal,
EMPTY_OBJ,
parentDom.namespaceURI,
parentDom.firstChild ? slice.call(parentDom.childNodes) : null,
Expand Down
70 changes: 70 additions & 0 deletions src/tree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { UNDEFINED } from './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;
export const TYPE_INVALID = 1 << 6;
export const TYPE_COMPONENT = TYPE_CLASS | TYPE_FUNCTION;

export const MODE_SVG = 1 << 4;
export const MODE_MATH = 1 << 5;
const INHERITED_MODES = MODE_MATH | MODE_SVG;

/**
*
* @param {VNode} vnode
* @param {Internal | null} parentInternal
* @returns {Internal}
*/
export function createInternal(vnode, parentInternal) {
let flags = parentInternal ? parentInternal.flags & INHERITED_MODES : 0,
type = vnode.type;

if (vnode.constructor !== UNDEFINED) {
flags |= TYPE_INVALID;
} else if (typeof vnode == 'string' || type == null) {
// type = null;
flags |= TYPE_TEXT;
} else {
// flags = typeof type === 'function' ? COMPONENT_NODE : ELEMENT_NODE;
flags |=
typeof type == 'function'
? type.prototype && type.prototype.render
? TYPE_CLASS
: TYPE_FUNCTION
: TYPE_ELEMENT;

if (flags & TYPE_ELEMENT && type === 'svg') {
flags |= MODE_SVG;
} else if (
parentInternal &&
parentInternal.flags & MODE_SVG &&
parentInternal.type === 'foreignObject'
) {
flags &= ~MODE_SVG;
} else if (flags & TYPE_ELEMENT && type === 'math') {
flags |= MODE_MATH;
}
}

return {
type,
props: vnode.props,
key: vnode.key,
ref: vnode.ref,
data:
flags & TYPE_COMPONENT
? { _commitCallbacks: [], _context: null, _stateCallbacks: [] }
: null,
flags,
// @ts-expect-error
vnode,
// TODO: rerender
_children: null,
_parent: parentInternal,
_vnodeId: vnode._original,
_component: null,
_depth: parentInternal ? parentInternal._depth + 1 : 0
};
}

0 comments on commit 2181325

Please sign in to comment.