Skip to content

Commit 362ab54

Browse files
committed
Add internals for mount path
1 parent be7bd67 commit 362ab54

File tree

7 files changed

+195
-74
lines changed

7 files changed

+195
-74
lines changed

src/diff/children.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { isArray } from '../util';
1111
import { getDomSibling } from '../component';
1212
import { mount } from './mount';
1313
import { insert } from './operations';
14+
import { createInternal } from '../tree';
1415

1516
/**
1617
* @typedef {import('../internal').ComponentChildren} ComponentChildren
@@ -111,9 +112,11 @@ export function diffChildren(
111112
refQueue
112113
);
113114
} else {
115+
// TODO: temp
116+
const internal = createInternal(childVNode, null);
114117
result = mount(
115118
parentDom,
116-
childVNode,
119+
internal,
117120
globalContext,
118121
namespace,
119122
excessDomChildren,

src/diff/mount.js

+75-43
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,29 @@ import { insert } from './operations';
1111
import { setProperty } from './props';
1212
import { assign, isArray, slice } from '../util';
1313
import options from '../options';
14+
import {
15+
createInternal,
16+
MODE_MATH,
17+
MODE_SVG,
18+
TYPE_CLASS,
19+
TYPE_COMPONENT,
20+
TYPE_ELEMENT,
21+
TYPE_FUNCTION,
22+
TYPE_INVALID,
23+
TYPE_TEXT
24+
} from '../tree';
1425

1526
/**
1627
* Diff two virtual nodes and apply proper changes to the DOM
17-
* @param {PreactElement} parentDom The parent of the DOM element
18-
* @param {VNode} newVNode The new virtual node
28+
* @param {import('../internal').PreactElement} parentDom The parent of the DOM element
29+
* @param {import('../internal').Internal} internal The backing node.
1930
* @param {object} globalContext The current context object. Modified by
2031
* getChildContext
2132
* @param {string} namespace Current namespace of the DOM node (HTML, SVG, or MathML)
22-
* @param {Array<PreactElement>} excessDomChildren
23-
* @param {Array<Component>} commitQueue List of components which have callbacks
33+
* @param {Array<import('../internal').PreactElement>} excessDomChildren
34+
* @param {Array<import('../internal').Component>} commitQueue List of components which have callbacks
2435
* to invoke in commitRoot
25-
* @param {PreactElement} oldDom The current attached DOM element any new dom
36+
* @param {import('../internal').PreactElement} oldDom The current attached DOM element any new dom
2637
* elements should be placed around. Likely `null` on first render (except when
2738
* hydrating). Can be a sibling DOM element when diffing Fragments that have
2839
* siblings. In most cases, it starts out as `oldChildren[0]._dom`.
@@ -31,7 +42,7 @@ import options from '../options';
3142
*/
3243
export function mount(
3344
parentDom,
34-
newVNode,
45+
internal,
3546
globalContext,
3647
namespace,
3748
excessDomChildren,
@@ -40,22 +51,26 @@ export function mount(
4051
isHydrating,
4152
refQueue
4253
) {
54+
// @ts-expect-error
55+
const newVNode = internal.vnode;
56+
4357
// When passing through createElement it assigns the object
4458
// constructor as undefined. This to prevent JSON-injection.
45-
if (newVNode.constructor !== UNDEFINED) return null;
59+
if (internal.flags & TYPE_INVALID) return null;
4660

4761
/** @type {any} */
48-
let tmp,
49-
newType = newVNode.type;
62+
let tmp;
5063

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

53-
if (typeof newType == 'function') {
66+
if (internal.flags & TYPE_COMPONENT) {
5467
try {
5568
let c,
56-
newProps = newVNode.props;
57-
const isClassComponent =
58-
'prototype' in newType && newType.prototype.render;
69+
newProps = internal.props,
70+
newType = /** @type {import('../internal').ComponentType} */ (
71+
internal.type
72+
);
73+
const isClassComponent = !!(internal.flags & TYPE_CLASS);
5974

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

7085
// Instantiate the new component
7186
if (isClassComponent) {
72-
// @ts-expect-error The check above verifies that newType is suppose to be constructed
73-
newVNode._component = c = new newType(newProps, componentContext); // eslint-disable-line new-cap
87+
internal._component =
88+
newVNode._component =
89+
c =
90+
// @ts-expect-error The check above verifies that newType is suppose to be constructed
91+
new newType(newProps, componentContext); // eslint-disable-line new-cap
7492
} else {
75-
// @ts-expect-error Trust me, Component implements the interface we want
76-
newVNode._component = c = new BaseComponent(newProps, componentContext);
93+
// @ts-expect-error The check above verifies that newType is suppose to be constructed
94+
internal._component =
95+
newVNode._component =
96+
c =
97+
new BaseComponent(newProps, componentContext);
7798
c.constructor = newType;
7899
c.render = doRender;
79100
}
@@ -156,6 +177,7 @@ export function mount(
156177
let renderResult = isTopLevelFragment ? tmp.props.children : tmp;
157178

158179
oldDom = mountChildren(
180+
internal,
159181
parentDom,
160182
isArray(renderResult) ? renderResult : [renderResult],
161183
newVNode,
@@ -200,7 +222,7 @@ export function mount(
200222
}
201223
} else {
202224
oldDom = newVNode._dom = mountElementNode(
203-
newVNode,
225+
internal,
204226
globalContext,
205227
namespace,
206228
excessDomChildren,
@@ -217,43 +239,45 @@ export function mount(
217239

218240
/**
219241
* Diff two virtual nodes representing DOM element
220-
* @param {VNode} newVNode The new virtual node
242+
* @param {import('../internal').Internal} internal The new virtual node
221243
* @param {object} globalContext The current context object
222244
* @param {string} namespace Current namespace of the DOM node (HTML, SVG, or MathML)
223-
* @param {Array<PreactElement>} excessDomChildren
224-
* @param {Array<Component>} commitQueue List of components which have callbacks
245+
* @param {Array<import('../internal').PreactElement>} excessDomChildren
246+
* @param {Array<import('../internal').Component>} commitQueue List of components which have callbacks
225247
* to invoke in commitRoot
226248
* @param {boolean} isHydrating Whether or not we are in hydration
227249
* @param {any[]} refQueue an array of elements needed to invoke refs
228-
* @returns {PreactElement}
250+
* @returns {import('../internal').PreactElement}
229251
*/
230252
function mountElementNode(
231-
newVNode,
253+
internal,
232254
globalContext,
233255
namespace,
234256
excessDomChildren,
235257
commitQueue,
236258
isHydrating,
237259
refQueue
238260
) {
239-
/** @type {PreactElement} */
261+
// @ts-expect-error
262+
const newVNode = internal.vnode;
263+
/** @type {import('../internal').PreactElement} */
240264
let dom;
241265
let oldProps = EMPTY_OBJ;
242-
let newProps = newVNode.props;
243-
let nodeType = /** @type {string} */ (newVNode.type);
266+
let newProps = internal.props;
267+
let nodeType = /** @type {string} */ (internal.type);
244268
/** @type {any} */
245269
let i;
246270
/** @type {{ __html?: string }} */
247271
let newHtml;
248-
/** @type {ComponentChildren} */
272+
/** @type {import('../internal').ComponentChildren} */
249273
let newChildren;
250274
let value;
251275
let inputValue;
252276
let checked;
253277

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

@@ -277,7 +301,7 @@ function mountElementNode(
277301
}
278302

279303
if (dom == null) {
280-
if (nodeType === null) {
304+
if (internal.flags & TYPE_TEXT) {
281305
return document.createTextNode(newProps);
282306
}
283307

@@ -298,7 +322,7 @@ function mountElementNode(
298322
excessDomChildren = null;
299323
}
300324

301-
if (nodeType === null) {
325+
if (internal.flags & TYPE_TEXT) {
302326
// During hydration, we still have to split merged text from SSR'd HTML.
303327
dom.data = newProps;
304328
} else {
@@ -361,6 +385,7 @@ function mountElementNode(
361385
newVNode._children = [];
362386
} else {
363387
mountChildren(
388+
internal,
364389
dom,
365390
isArray(newChildren) ? newChildren : [newChildren],
366391
newVNode,
@@ -416,25 +441,27 @@ function doRender(props, _state, context) {
416441

417442
/**
418443
* Diff the children of a virtual node
419-
* @param {PreactElement} parentDom The DOM element whose children are being
444+
* @param {import('../internal').Internal} internal The DOM element whose children are being
445+
* @param {import('../internal').PreactElement} parentDom The DOM element whose children are being
420446
* diffed
421-
* @param {ComponentChildren[]} renderResult
422-
* @param {VNode} newParentVNode The new virtual node whose children should be
447+
* @param {import('../internal').ComponentChildren[]} renderResult
448+
* @param {import('../internal').VNode} newParentVNode The new virtual node whose children should be
423449
* diff'ed against oldParentVNode
424450
* @param {object} globalContext The current context object - modified by
425451
* getChildContext
426452
* @param {string} namespace Current namespace of the DOM node (HTML, SVG, or MathML)
427-
* @param {Array<PreactElement>} excessDomChildren
428-
* @param {Array<Component>} commitQueue List of components which have callbacks
453+
* @param {Array<import('../internal').PreactElement>} excessDomChildren
454+
* @param {Array<import('../internal').Component>} commitQueue List of components which have callbacks
429455
* to invoke in commitRoot
430-
* @param {PreactElement} oldDom The current attached DOM element any new dom
456+
* @param {import('../internal').PreactElement} oldDom The current attached DOM element any new dom
431457
* elements should be placed around. Likely `null` on first render (except when
432458
* hydrating). Can be a sibling DOM element when diffing Fragments that have
433459
* siblings. In most cases, it starts out as `oldChildren[0]._dom`.
434460
* @param {boolean} isHydrating Whether or not we are in hydration
435461
* @param {any[]} refQueue an array of elements needed to invoke refs
436462
*/
437463
function mountChildren(
464+
internal,
438465
parentDom,
439466
renderResult,
440467
newParentVNode,
@@ -447,11 +474,11 @@ function mountChildren(
447474
refQueue
448475
) {
449476
let i,
450-
/** @type {VNode} */
477+
/** @type {import('../internal').VNode} */
451478
childVNode,
452-
/** @type {PreactElement} */
479+
/** @type {import('../internal').PreactElement} */
453480
newDom,
454-
/** @type {PreactElement} */
481+
/** @type {import('../internal').PreactElement} */
455482
firstChildDom;
456483

457484
let newChildrenLength = renderResult.length;
@@ -517,10 +544,11 @@ function mountChildren(
517544
childVNode._parent = newParentVNode;
518545
childVNode._depth = newParentVNode._depth + 1;
519546

547+
const childInternal = createInternal(childVNode, internal);
520548
// Morph the old element into the new one, but don't append it to the dom yet
521549
const result = mount(
522550
parentDom,
523-
childVNode,
551+
childInternal,
524552
globalContext,
525553
namespace,
526554
excessDomChildren,
@@ -544,9 +572,13 @@ function mountChildren(
544572
firstChildDom = newDom;
545573
}
546574

547-
if (typeof childVNode.type != 'function') {
575+
if (childInternal.flags & TYPE_ELEMENT || childInternal.flags & TYPE_TEXT) {
548576
oldDom = insert(childVNode, oldDom, parentDom);
549-
} else if (typeof childVNode.type == 'function' && result !== UNDEFINED) {
577+
} else if (
578+
(childInternal.flags & TYPE_FUNCTION ||
579+
childInternal.flags & TYPE_CLASS) &&
580+
result !== UNDEFINED
581+
) {
550582
oldDom = result;
551583
} else if (newDom) {
552584
oldDom = newDom.nextSibling;

src/diff/operations.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { getDomSibling } from '../component';
22
import { isArray } from '../util';
33

44
/**
5-
* @param {VNode} parentVNode
6-
* @param {PreactElement} oldDom
7-
* @param {PreactElement} parentDom
8-
* @returns {PreactElement}
5+
* @param {import('../internal').VNode} parentVNode
6+
* @param {import('../internal').PreactElement} oldDom
7+
* @param {import('../internal').PreactElement} parentDom
8+
* @returns {import('../internal').PreactElement}
99
*/
1010
export function insert(parentVNode, oldDom, parentDom) {
1111
// Note: VNodes in nested suspended trees may be missing _children.
@@ -41,9 +41,9 @@ export function insert(parentVNode, oldDom, parentDom) {
4141

4242
/**
4343
* Flatten and loop through the children of a virtual node
44-
* @param {ComponentChildren} children The unflattened children of a virtual
44+
* @param {import('../internal').ComponentChildren} children The unflattened children of a virtual
4545
* node
46-
* @returns {VNode[]}
46+
* @returns {import('../internal').VNode[]}
4747
*/
4848
export function toChildArray(children, out) {
4949
out = out || [];

src/internal.d.ts

+37-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,42 @@ export enum HookType {
1616
// Not a real hook, but the devtools treat is as such
1717
useDebugvalue = 11
1818
}
19+
/**
20+
* An Internal is a persistent backing node within Preact's virtual DOM tree.
21+
* Think of an Internal like a long-lived VNode with stored data and tree linkages.
22+
*/
23+
export interface Internal<P = {}> {
24+
type: string | ComponentType<P>;
25+
/** The props object for Elements/Components, and the string contents for Text */
26+
props: (P & { children: ComponentChildren }) | string | number;
27+
key: any;
28+
ref: Ref<any> | null;
29+
30+
/** Bitfield containing information about the Internal or its component. */
31+
flags: number;
32+
/** Polymorphic property to store extensions like hooks on */
33+
data: object | PreactNode;
34+
/** The function that triggers in-place re-renders for an internal */
35+
// rerender: (internal: Internal) => void;
36+
37+
/** children Internal nodes */
38+
_children: Internal[];
39+
/** next sibling Internal node */
40+
_parent: Internal;
41+
/** most recent vnode ID */
42+
_vnodeId: number;
43+
/**
44+
* Associated DOM element for the Internal, or its nearest realized descendant.
45+
* For Fragments, this is the first DOM child.
46+
*/
47+
/** The component instance for which this is a backing Internal node */
48+
_component: Component | null;
49+
/** This Internal's distance from the tree root */
50+
_depth: number | null;
51+
/** Callbacks to invoke when this internal commits */
52+
_commitCallbacks: Array<() => void>;
53+
_stateCallbacks: Array<() => void>; // Only class components
54+
}
1955

2056
export interface DevSource {
2157
fileName: string;
@@ -62,8 +98,7 @@ export type ComponentChild =
6298
| undefined;
6399
export type ComponentChildren = ComponentChild[] | ComponentChild;
64100

65-
export interface FunctionComponent<P = {}>
66-
extends preact.FunctionComponent<P> {
101+
export interface FunctionComponent<P = {}> extends preact.FunctionComponent<P> {
67102
// Internally, createContext uses `contextType` on a Function component to
68103
// implement the Consumer component
69104
contextType?: PreactContext;
@@ -113,7 +148,6 @@ export interface PreactElement extends preact.ContainerNode {
113148
readonly nextSibling: ContainerNode | null;
114149
readonly firstChild: ContainerNode | null;
115150

116-
117151
// Used to match DOM nodes to VNodes during hydration. Note: doesn't exist
118152
// on Text nodes
119153
readonly localName?: string;

0 commit comments

Comments
 (0)