Skip to content

Commit

Permalink
mountChildren shortcut
Browse files Browse the repository at this point in the history
  • Loading branch information
JoviDeCroock committed Nov 23, 2024
1 parent f680f3b commit 9f2fda3
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 76 deletions.
58 changes: 1 addition & 57 deletions src/diff/children.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { isArray } from '../util';
import { getDomSibling } from '../component';
import { mount } from './mount';
import { insert } from './operations';

/**
* Diff the children of a virtual node
Expand Down Expand Up @@ -324,63 +325,6 @@ function constructNewChildrenArray(
return oldDom;
}

/**
* @param {VNode} parentVNode
* @param {PreactElement} oldDom
* @param {PreactElement} parentDom
* @returns {PreactElement}
*/
function insert(parentVNode, oldDom, parentDom) {
// Note: VNodes in nested suspended trees may be missing _children.

if (typeof parentVNode.type == 'function') {
let children = parentVNode._children;
for (let i = 0; children && i < children.length; i++) {
if (children[i]) {
// If we enter this code path on sCU bailout, where we copy
// oldVNode._children to newVNode._children, we need to update the old
// children's _parent pointer to point to the newVNode (parentVNode
// here).
children[i]._parent = parentVNode;
oldDom = insert(children[i], oldDom, parentDom);
}
}

return oldDom;
} else if (parentVNode._dom != oldDom) {
if (oldDom && parentVNode.type && !parentDom.contains(oldDom)) {
oldDom = getDomSibling(parentVNode);
}
parentDom.insertBefore(parentVNode._dom, oldDom || null);
oldDom = parentVNode._dom;
}

do {
oldDom = oldDom && oldDom.nextSibling;
} while (oldDom != null && oldDom.nodeType === 8);

return oldDom;
}

/**
* Flatten and loop through the children of a virtual node
* @param {ComponentChildren} children The unflattened children of a virtual
* node
* @returns {VNode[]}
*/
export function toChildArray(children, out) {
out = out || [];
if (children == null || typeof children == 'boolean') {
} else if (isArray(children)) {
children.some(child => {
toChildArray(child, out);
});
} else {
out.push(children);
}
return out;
}

/**
* @param {VNode} childVNode
* @param {VNode[]} oldChildren
Expand Down
154 changes: 148 additions & 6 deletions src/diff/mount.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
UNDEFINED
} from '../constants';
import { BaseComponent } from '../component';
import { Fragment } from '../create-element';
import { diffChildren } from './children';
import { createVNode, Fragment } from '../create-element';
import { insert } from './operations';
import { setProperty } from './props';
import { assign, isArray, slice } from '../util';
import options from '../options';
Expand Down Expand Up @@ -155,11 +155,10 @@ export function mount(
tmp != null && tmp.type === Fragment && tmp.key == null;
let renderResult = isTopLevelFragment ? tmp.props.children : tmp;

oldDom = diffChildren(
oldDom = mountChildren(
parentDom,
isArray(renderResult) ? renderResult : [renderResult],
newVNode,
EMPTY_OBJ,
globalContext,
namespace,
excessDomChildren,
Expand Down Expand Up @@ -361,11 +360,10 @@ function mountElementNode(

newVNode._children = [];
} else {
diffChildren(
mountChildren(
dom,
isArray(newChildren) ? newChildren : [newChildren],
newVNode,
EMPTY_OBJ,
globalContext,
nodeType === 'foreignObject'
? 'http://www.w3.org/1999/xhtml'
Expand Down Expand Up @@ -415,3 +413,147 @@ function mountElementNode(
function doRender(props, _state, context) {
return this.constructor(props, context);
}

/**
* Diff the children of a virtual node
* @param {PreactElement} parentDom The DOM element whose children are being
* diffed
* @param {ComponentChildren[]} renderResult
* @param {VNode} newParentVNode The new virtual node whose children should be
* diff'ed against oldParentVNode
* @param {object} globalContext The current context object - modified by
* getChildContext
* @param {string} namespace Current namespace of the DOM node (HTML, SVG, or MathML)
* @param {Array<PreactElement>} excessDomChildren
* @param {Array<Component>} commitQueue List of components which have callbacks
* to invoke in commitRoot
* @param {PreactElement} oldDom The current attached DOM element any new dom
* elements should be placed around. Likely `null` on first render (except when
* hydrating). Can be a sibling DOM element when diffing Fragments that have
* siblings. In most cases, it starts out as `oldChildren[0]._dom`.
* @param {boolean} isHydrating Whether or not we are in hydration
* @param {any[]} refQueue an array of elements needed to invoke refs
*/
function mountChildren(
parentDom,
renderResult,
newParentVNode,
globalContext,
namespace,
excessDomChildren,
commitQueue,
oldDom,
isHydrating,
refQueue
) {
let i,
/** @type {VNode} */
childVNode,
/** @type {PreactElement} */
newDom,
/** @type {PreactElement} */
firstChildDom;

let newChildrenLength = renderResult.length;
newParentVNode._children = [];

for (i = 0; i < newChildrenLength; i++) {
// @ts-expect-error We are reusing the childVNode variable to hold both the
// pre and post normalized childVNode
childVNode = renderResult[i];

if (
childVNode == null ||
typeof childVNode == 'boolean' ||
typeof childVNode == 'function'
) {
childVNode = newParentVNode._children[i] = null;
continue;
}
// If this newVNode is being reused (e.g. <div>{reuse}{reuse}</div>) in the same diff,
// or we are rendering a component (e.g. setState) copy the oldVNodes so it can have
// it's own DOM & etc. pointers
else if (
typeof childVNode == 'string' ||
typeof childVNode == 'number' ||
// eslint-disable-next-line valid-typeof
typeof childVNode == 'bigint' ||
childVNode.constructor == String
) {
childVNode = newParentVNode._children[i] = createVNode(
null,
childVNode,
null,
null,
null
);
} else if (isArray(childVNode)) {
childVNode = newParentVNode._children[i] = createVNode(
Fragment,
{ children: childVNode },
null,
null,
null
);
} else if (childVNode.constructor === UNDEFINED && childVNode._depth > 0) {
// VNode is already in use, clone it. This can happen in the following
// scenario:
// const reuse = <div />
// <div>{reuse}<span />{reuse}</div>
childVNode = newParentVNode._children[i] = createVNode(
childVNode.type,
childVNode.props,
childVNode.key,
childVNode.ref ? childVNode.ref : null,
childVNode._original
);
} else {
childVNode = newParentVNode._children[i] = childVNode;
}

if (childVNode == null) continue;

childVNode._index = i;
childVNode._parent = newParentVNode;
childVNode._depth = newParentVNode._depth + 1;

// Morph the old element into the new one, but don't append it to the dom yet
const result = mount(
parentDom,
childVNode,
globalContext,
namespace,
excessDomChildren,
commitQueue,
oldDom,
isHydrating,
refQueue
);

// Adjust DOM nodes
newDom = childVNode._dom;
if (childVNode.ref) {
refQueue.push(
childVNode.ref,
childVNode._component || newDom,
childVNode
);
}

if (firstChildDom == null && newDom != null) {
firstChildDom = newDom;
}

if (typeof childVNode.type != 'function') {
oldDom = insert(childVNode, oldDom, parentDom);
} else if (typeof childVNode.type == 'function' && result !== UNDEFINED) {
oldDom = result;
} else if (newDom) {
oldDom = newDom.nextSibling;
}
}

newParentVNode._dom = firstChildDom;

return oldDom;
}
59 changes: 59 additions & 0 deletions src/diff/operations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { getDomSibling } from '../component';
import { isArray } from '../util';

/**
* @param {VNode} parentVNode
* @param {PreactElement} oldDom
* @param {PreactElement} parentDom
* @returns {PreactElement}
*/
export function insert(parentVNode, oldDom, parentDom) {
// Note: VNodes in nested suspended trees may be missing _children.

if (typeof parentVNode.type == 'function') {
let children = parentVNode._children;
for (let i = 0; children && i < children.length; i++) {
if (children[i]) {
// If we enter this code path on sCU bailout, where we copy
// oldVNode._children to newVNode._children, we need to update the old
// children's _parent pointer to point to the newVNode (parentVNode
// here).
children[i]._parent = parentVNode;
oldDom = insert(children[i], oldDom, parentDom);
}
}

return oldDom;
} else if (parentVNode._dom != oldDom) {
if (oldDom && parentVNode.type && !parentDom.contains(oldDom)) {
oldDom = getDomSibling(parentVNode);
}
parentDom.insertBefore(parentVNode._dom, oldDom || null);
oldDom = parentVNode._dom;
}

do {
oldDom = oldDom && oldDom.nextSibling;
} while (oldDom != null && oldDom.nodeType === 8);

return oldDom;
}

/**
* Flatten and loop through the children of a virtual node
* @param {ComponentChildren} children The unflattened children of a virtual
* node
* @returns {VNode[]}
*/
export function toChildArray(children, out) {
out = out || [];
if (children == null || typeof children == 'boolean') {
} else if (isArray(children)) {
children.some(child => {
toChildArray(child, out);
});
} else {
out.push(children);
}
return out;
}
16 changes: 4 additions & 12 deletions src/diff/patch.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import {
RESET_MODE,
UNDEFINED
} from '../constants';
import { BaseComponent, getDomSibling } from '../component';
import { getDomSibling } from '../component';
import { Fragment } from '../create-element';
import { diffChildren } from './children';
import { setProperty } from './props';
import { assign, isArray, slice } from '../util';
import { assign, isArray } from '../util';
import options from '../options';

/**
Expand Down Expand Up @@ -233,7 +233,7 @@ export function diff(
} catch (e) {
newVNode._original = null;
// if hydrating or creating initial tree, bailout preserves DOM:
if (isHydrating || excessDomChildren != null) {
if (isHydrating) {
if (e.then) {
newVNode._flags |= isHydrating
? MODE_HYDRATE | MODE_SUSPENDED
Expand All @@ -256,10 +256,7 @@ export function diff(
}
options._catchError(e, newVNode, oldVNode);
}
} else if (
excessDomChildren == null &&
newVNode._original === oldVNode._original
) {
} else if (newVNode._original === oldVNode._original) {
newVNode._children = oldVNode._children;
newVNode._dom = oldVNode._dom;
} else {
Expand Down Expand Up @@ -520,8 +517,3 @@ export function unmount(vnode, parentVNode, skipRemove) {

vnode._component = vnode._parent = vnode._dom = UNDEFINED;
}

/** The `.render()` method for a PFC backing instance. */
function doRender(props, state, context) {
return this.constructor(props, context);
}
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ export {
export { BaseComponent as Component } from './component';
export { cloneElement } from './clone-element';
export { createContext } from './create-context';
export { toChildArray } from './diff/children';
export { toChildArray } from './diff/operations';
export { default as options } from './options';

0 comments on commit 9f2fda3

Please sign in to comment.