Skip to content

Commit 2181325

Browse files
committed
Add internals for mount path
1 parent 9f2fda3 commit 2181325

File tree

5 files changed

+169
-29
lines changed

5 files changed

+169
-29
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
* Diff the children of a virtual node
@@ -103,9 +104,11 @@ export function diffChildren(
103104
refQueue
104105
);
105106
} else {
107+
// TODO: temp
108+
const internal = createInternal(childVNode, null);
106109
result = mount(
107110
parentDom,
108-
childVNode,
111+
internal,
109112
globalContext,
110113
namespace,
111114
excessDomChildren,

src/diff/mount.js

+55-25
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,22 @@ 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
1728
* @param {PreactElement} parentDom The parent of the DOM element
18-
* @param {VNode} newVNode The new virtual node
29+
* @param {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)
@@ -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,24 @@ 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 {ComponentType} */ (internal.type);
71+
const isClassComponent = !!(internal.flags & TYPE_CLASS);
5972

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

7083
// Instantiate the new component
7184
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
85+
internal._component =
86+
newVNode._component =
87+
c =
88+
// @ts-expect-error The check above verifies that newType is suppose to be constructed
89+
new newType(newProps, componentContext); // eslint-disable-line new-cap
7490
} else {
75-
// @ts-expect-error Trust me, Component implements the interface we want
76-
newVNode._component = c = new BaseComponent(newProps, componentContext);
91+
// @ts-expect-error The check above verifies that newType is suppose to be constructed
92+
internal._component =
93+
newVNode._component =
94+
c =
95+
new BaseComponent(newProps, componentContext);
7796
c.constructor = newType;
7897
c.render = doRender;
7998
}
@@ -156,6 +175,7 @@ export function mount(
156175
let renderResult = isTopLevelFragment ? tmp.props.children : tmp;
157176

158177
oldDom = mountChildren(
178+
internal,
159179
parentDom,
160180
isArray(renderResult) ? renderResult : [renderResult],
161181
newVNode,
@@ -200,7 +220,7 @@ export function mount(
200220
}
201221
} else {
202222
oldDom = newVNode._dom = mountElementNode(
203-
newVNode,
223+
internal,
204224
globalContext,
205225
namespace,
206226
excessDomChildren,
@@ -217,7 +237,7 @@ export function mount(
217237

218238
/**
219239
* Diff two virtual nodes representing DOM element
220-
* @param {VNode} newVNode The new virtual node
240+
* @param {Internal} internal The new virtual node
221241
* @param {object} globalContext The current context object
222242
* @param {string} namespace Current namespace of the DOM node (HTML, SVG, or MathML)
223243
* @param {Array<PreactElement>} excessDomChildren
@@ -228,19 +248,21 @@ export function mount(
228248
* @returns {PreactElement}
229249
*/
230250
function mountElementNode(
231-
newVNode,
251+
internal,
232252
globalContext,
233253
namespace,
234254
excessDomChildren,
235255
commitQueue,
236256
isHydrating,
237257
refQueue
238258
) {
259+
// @ts-expect-error
260+
const newVNode = internal.vnode;
239261
/** @type {PreactElement} */
240262
let dom;
241263
let oldProps = EMPTY_OBJ;
242-
let newProps = newVNode.props;
243-
let nodeType = /** @type {string} */ (newVNode.type);
264+
let newProps = internal.props;
265+
let nodeType = /** @type {string} */ (internal.type);
244266
/** @type {any} */
245267
let i;
246268
/** @type {{ __html?: string }} */
@@ -252,8 +274,8 @@ function mountElementNode(
252274
let checked;
253275

254276
// 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')
277+
if (internal.flags & MODE_SVG) namespace = 'http://www.w3.org/2000/svg';
278+
else if (internal.flags & MODE_MATH)
257279
namespace = 'http://www.w3.org/1998/Math/MathML';
258280
else if (!namespace) namespace = 'http://www.w3.org/1999/xhtml';
259281

@@ -277,7 +299,7 @@ function mountElementNode(
277299
}
278300

279301
if (dom == null) {
280-
if (nodeType === null) {
302+
if (internal.flags & TYPE_TEXT) {
281303
return document.createTextNode(newProps);
282304
}
283305

@@ -298,7 +320,7 @@ function mountElementNode(
298320
excessDomChildren = null;
299321
}
300322

301-
if (nodeType === null) {
323+
if (internal.flags & TYPE_TEXT) {
302324
// During hydration, we still have to split merged text from SSR'd HTML.
303325
dom.data = newProps;
304326
} else {
@@ -361,6 +383,7 @@ function mountElementNode(
361383
newVNode._children = [];
362384
} else {
363385
mountChildren(
386+
internal,
364387
dom,
365388
isArray(newChildren) ? newChildren : [newChildren],
366389
newVNode,
@@ -416,6 +439,7 @@ function doRender(props, _state, context) {
416439

417440
/**
418441
* Diff the children of a virtual node
442+
* @param {Internal} internal The DOM element whose children are being
419443
* @param {PreactElement} parentDom The DOM element whose children are being
420444
* diffed
421445
* @param {ComponentChildren[]} renderResult
@@ -435,6 +459,7 @@ function doRender(props, _state, context) {
435459
* @param {any[]} refQueue an array of elements needed to invoke refs
436460
*/
437461
function mountChildren(
462+
internal,
438463
parentDom,
439464
renderResult,
440465
newParentVNode,
@@ -517,10 +542,11 @@ function mountChildren(
517542
childVNode._parent = newParentVNode;
518543
childVNode._depth = newParentVNode._depth + 1;
519544

545+
const childInternal = createInternal(childVNode, internal);
520546
// Morph the old element into the new one, but don't append it to the dom yet
521547
const result = mount(
522548
parentDom,
523-
childVNode,
549+
childInternal,
524550
globalContext,
525551
namespace,
526552
excessDomChildren,
@@ -544,9 +570,13 @@ function mountChildren(
544570
firstChildDom = newDom;
545571
}
546572

547-
if (typeof childVNode.type != 'function') {
573+
if (childInternal.flags & TYPE_ELEMENT || childInternal.flags & TYPE_TEXT) {
548574
oldDom = insert(childVNode, oldDom, parentDom);
549-
} else if (typeof childVNode.type == 'function' && result !== UNDEFINED) {
575+
} else if (
576+
(childInternal.flags & TYPE_FUNCTION ||
577+
childInternal.flags & TYPE_CLASS) &&
578+
result !== UNDEFINED
579+
) {
550580
oldDom = result;
551581
} else if (newDom) {
552582
oldDom = newDom.nextSibling;

src/internal.d.ts

+37
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,43 @@ declare global {
159159
_flags: number;
160160
}
161161

162+
/**
163+
* An Internal is a persistent backing node within Preact's virtual DOM tree.
164+
* Think of an Internal like a long-lived VNode with stored data and tree linkages.
165+
*/
166+
export interface Internal<P = {}> {
167+
type: string | ComponentType<P>;
168+
/** The props object for Elements/Components, and the string contents for Text */
169+
props: (P & { children: ComponentChildren }) | string | number;
170+
key: any;
171+
ref: Ref<any> | null;
172+
173+
/** Bitfield containing information about the Internal or its component. */
174+
flags: number;
175+
/** Polymorphic property to store extensions like hooks on */
176+
data: object | PreactNode;
177+
/** The function that triggers in-place re-renders for an internal */
178+
// rerender: (internal: Internal) => void;
179+
180+
/** children Internal nodes */
181+
_children: Internal[];
182+
/** next sibling Internal node */
183+
_parent: Internal;
184+
/** most recent vnode ID */
185+
_vnodeId: number;
186+
/**
187+
* Associated DOM element for the Internal, or its nearest realized descendant.
188+
* For Fragments, this is the first DOM child.
189+
*/
190+
/** The component instance for which this is a backing Internal node */
191+
_component: Component | null;
192+
/** This Internal's distance from the tree root */
193+
_depth: number | null;
194+
/** Callbacks to invoke when this internal commits */
195+
_commitCallbacks: Array<() => void>;
196+
_stateCallbacks: Array<() => void>; // Only class components
197+
}
198+
162199
export interface Component<P = {}, S = {}> extends preact.Component<P, S> {
163200
// When component is functional component, this is reset to functional component
164201
constructor: ComponentType<P>;

src/render.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createElement, Fragment } from './create-element';
44
import options from './options';
55
import { slice } from './util';
66
import { mount } from './diff/mount';
7+
import { createInternal } from './tree';
78

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

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

3234
// List of effects that need to be called after diffing.
3335
let commitQueue = [],
@@ -51,9 +53,7 @@ export function render(vnode, parentDom, replaceNode) {
5153
} else {
5254
mount(
5355
parentDom,
54-
// Determine the new vnode tree and store it on the DOM element on
55-
// our custom `_children` property.
56-
vnode,
56+
internal,
5757
EMPTY_OBJ,
5858
parentDom.namespaceURI,
5959
parentDom.firstChild ? slice.call(parentDom.childNodes) : null,

src/tree.js

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { UNDEFINED } from './constants';
2+
3+
export const TYPE_TEXT = 1 << 0;
4+
export const TYPE_ELEMENT = 1 << 1;
5+
export const TYPE_CLASS = 1 << 2;
6+
export const TYPE_FUNCTION = 1 << 3;
7+
export const TYPE_INVALID = 1 << 6;
8+
export const TYPE_COMPONENT = TYPE_CLASS | TYPE_FUNCTION;
9+
10+
export const MODE_SVG = 1 << 4;
11+
export const MODE_MATH = 1 << 5;
12+
const INHERITED_MODES = MODE_MATH | MODE_SVG;
13+
14+
/**
15+
*
16+
* @param {VNode} vnode
17+
* @param {Internal | null} parentInternal
18+
* @returns {Internal}
19+
*/
20+
export function createInternal(vnode, parentInternal) {
21+
let flags = parentInternal ? parentInternal.flags & INHERITED_MODES : 0,
22+
type = vnode.type;
23+
24+
if (vnode.constructor !== UNDEFINED) {
25+
flags |= TYPE_INVALID;
26+
} else if (typeof vnode == 'string' || type == null) {
27+
// type = null;
28+
flags |= TYPE_TEXT;
29+
} else {
30+
// flags = typeof type === 'function' ? COMPONENT_NODE : ELEMENT_NODE;
31+
flags |=
32+
typeof type == 'function'
33+
? type.prototype && type.prototype.render
34+
? TYPE_CLASS
35+
: TYPE_FUNCTION
36+
: TYPE_ELEMENT;
37+
38+
if (flags & TYPE_ELEMENT && type === 'svg') {
39+
flags |= MODE_SVG;
40+
} else if (
41+
parentInternal &&
42+
parentInternal.flags & MODE_SVG &&
43+
parentInternal.type === 'foreignObject'
44+
) {
45+
flags &= ~MODE_SVG;
46+
} else if (flags & TYPE_ELEMENT && type === 'math') {
47+
flags |= MODE_MATH;
48+
}
49+
}
50+
51+
return {
52+
type,
53+
props: vnode.props,
54+
key: vnode.key,
55+
ref: vnode.ref,
56+
data:
57+
flags & TYPE_COMPONENT
58+
? { _commitCallbacks: [], _context: null, _stateCallbacks: [] }
59+
: null,
60+
flags,
61+
// @ts-expect-error
62+
vnode,
63+
// TODO: rerender
64+
_children: null,
65+
_parent: parentInternal,
66+
_vnodeId: vnode._original,
67+
_component: null,
68+
_depth: parentInternal ? parentInternal._depth + 1 : 0
69+
};
70+
}

0 commit comments

Comments
 (0)