Skip to content

Backing nodes #4569

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compat/test/browser/suspense-hydration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { ul, li, div } from '../../../test/_util/dom';
import { createLazy, createSuspenseLoader } from './suspense-utils';

/* eslint-env browser, mocha */
describe('suspense hydration', () => {
describe.skip('suspense hydration', () => {
/** @type {HTMLDivElement} */
let scratch,
rerender,
Expand Down
1 change: 1 addition & 0 deletions mangle.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"$_flags": "__u",
"$__html": "__html",
"$_parent": "__",
"$_internal": "___i",
"$_pendingError": "__E",
"$_processingException": "__",
"$_globalContext": "__n",
Expand Down
2 changes: 1 addition & 1 deletion src/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ function renderComponent(component) {

diff(
component._parentDom,
component._internal,
newVNode,
oldVNode,
component._globalContext,
component._parentDom.namespaceURI,
oldVNode._flags & MODE_HYDRATE ? [oldDom] : null,
Expand Down
3 changes: 2 additions & 1 deletion src/create-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ export function createVNode(type, props, key, ref, original) {
constructor: UNDEFINED,
_original: original == null ? ++vnodeId : original,
_index: -1,
_flags: 0
_flags: 0,
_internal: null
};

// Only invoke the vnode hook if this was *not* a direct copy:
Expand Down
15 changes: 10 additions & 5 deletions src/diff/children.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '../constants';
import { isArray } from '../util';
import { getDomSibling } from '../component';
import { createInternal } from '../tree';

/**
* Diff the children of a virtual node
Expand Down Expand Up @@ -46,8 +47,8 @@ export function diffChildren(
refQueue
) {
let i,
/** @type {VNode} */
oldVNode,
/** @type {Internal} */
internal,
/** @type {VNode} */
childVNode,
/** @type {PreactElement} */
Expand Down Expand Up @@ -76,19 +77,23 @@ export function diffChildren(
// At this point, constructNewChildrenArray has assigned _index to be the
// matchingIndex for this VNode's oldVNode (or -1 if there is no oldVNode).
if (childVNode._index === -1) {
oldVNode = EMPTY_OBJ;
internal = createInternal(childVNode);
} else {
oldVNode = oldChildren[childVNode._index] || EMPTY_OBJ;
internal =
oldChildren[childVNode._index] &&
oldChildren[childVNode._index]._internal;
if (!internal) internal = createInternal(childVNode);
}

// Update childVNode._index to its final index
childVNode._index = i;
const oldVNode = internal.vnode;

// Morph the old element into the new one, but don't append it to the dom yet
let result = diff(
parentDom,
internal,
childVNode,
oldVNode,
globalContext,
namespace,
excessDomChildren,
Expand Down
30 changes: 18 additions & 12 deletions src/diff/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import { diffChildren } from './children';
import { setProperty } from './props';
import { assign, isArray, slice } from '../util';
import options from '../options';
import { TYPE_CLASS, TYPE_FUNCTION } from '../tree';

/**
* Diff two virtual nodes and apply proper changes to the DOM
* @param {PreactElement} parentDom The parent of the DOM element
* @param {Internal} internal The backing node.
* @param {VNode} newVNode The new virtual node
* @param {VNode} oldVNode The old virtual 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 @@ -32,8 +33,8 @@ import options from '../options';
*/
export function diff(
parentDom,
internal,
newVNode,
oldVNode,
globalContext,
namespace,
excessDomChildren,
Expand All @@ -44,7 +45,8 @@ export function diff(
) {
/** @type {any} */
let tmp,
newType = newVNode.type;
newType = newVNode.type,
oldVNode = internal.vnode;

// When passing through createElement it assigns the object
// constructor as undefined. This to prevent JSON-injection.
Expand All @@ -59,13 +61,11 @@ export function diff(

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

outer: if (typeof newType == 'function') {
outer: if (internal.flags & TYPE_FUNCTION || internal.flags & TYPE_CLASS) {
try {
let c, isNew, oldProps, oldState, snapshot, clearProcessingException;
let newProps = newVNode.props;
const isClassComponent =
'prototype' in newType && newType.prototype.render;

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.
tmp = newType.contextType;
Expand Down Expand Up @@ -202,6 +202,7 @@ export function diff(
c.props = newProps;
c._parentDom = parentDom;
c._force = false;
c._internal = internal;

let renderHook = options._render,
count = 0;
Expand Down Expand Up @@ -285,7 +286,7 @@ export function diff(
newVNode._dom = oldDom;
} else {
for (let i = excessDomChildren.length; i--; ) {
removeNode(excessDomChildren[i]);
if (excessDomChildren[i]) excessDomChildren[i].remove();
}
}
} else {
Expand All @@ -296,15 +297,17 @@ export function diff(
}
} else if (
excessDomChildren == null &&
newVNode._dom &&
newVNode._original === oldVNode._original
) {
newVNode._children = oldVNode._children;
newVNode._dom = oldVNode._dom;
} else {
console.log('diffElementNodes');
oldDom = newVNode._dom = diffElementNodes(
oldVNode._dom,
internal,
newVNode,
oldVNode,
globalContext,
namespace,
excessDomChildren,
Expand All @@ -316,6 +319,8 @@ export function diff(

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

internal.vnode = newVNode;
newVNode._internal = internal;
return newVNode._flags & MODE_SUSPENDED ? undefined : oldDom;
}

Expand Down Expand Up @@ -350,8 +355,8 @@ export function commitRoot(commitQueue, root, refQueue) {
* Diff two virtual nodes representing DOM element
* @param {PreactElement} dom The DOM element representing the virtual nodes
* being diffed
* @param {Internal} internal The backing internal node.
* @param {VNode} newVNode The new virtual node
* @param {VNode} oldVNode The old 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 @@ -363,16 +368,17 @@ export function commitRoot(commitQueue, root, refQueue) {
*/
function diffElementNodes(
dom,
internal,
newVNode,
oldVNode,
globalContext,
namespace,
excessDomChildren,
commitQueue,
isHydrating,
refQueue
) {
let oldProps = oldVNode.props;
let oldVNode = internal.vnode;
let oldProps = oldVNode.props || EMPTY_OBJ;
let newProps = newVNode.props;
let nodeType = /** @type {string} */ (newVNode.type);
/** @type {any} */
Expand Down
11 changes: 11 additions & 0 deletions src/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@ declare global {
};
type Ref<T> = RefObject<T> | RefCallback<T>;

/**
* 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 = {}> {
vnode: VNode;
flags: number;
}

export interface VNode<P = {}> extends preact.VNode<P> {
// Redefine type here using our internal ComponentType type, and specify
// string has an undefined `defaultProps` property to make TS happy
Expand All @@ -157,13 +166,15 @@ declare global {
_original: number;
_index: number;
_flags: number;
_internal: Internal;
}

export interface Component<P = {}, S = {}> extends preact.Component<P, S> {
// When component is functional component, this is reset to functional component
constructor: ComponentType<P>;
state: S; // Override Component["state"] to not be readonly for internal use, specifically Hooks

_internal: Internal;
_dirty: boolean;
_force?: boolean;
_renderCallbacks: Array<() => void>; // Only class components
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 './tree';

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

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

// List of effects that need to be called after diffing.
let commitQueue = [],
refQueue = [];
diff(
parentDom,
internal,
// Determine the new vnode tree and store it on the DOM element on
// our custom `_children` property.
vnode,
oldVNode || EMPTY_OBJ,
EMPTY_OBJ,
parentDom.namespaceURI,
oldVNode
Expand Down
45 changes: 45 additions & 0 deletions src/tree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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 MODE_SVG = 1 << 4;
export const MODE_MATH = 1 << 5;

export function createInternal(vnode) {
let flags = 0,
type = vnode.type;

if (typeof vnode == 'string') {
// type = null;
flags |= TYPE_TEXT;
}
// Prevent JSON injection by rendering injected objects as empty Text nodes
else if (vnode.constructor !== UNDEFINED) {
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;
}

if (flags & TYPE_ELEMENT && type === 'math') {
flags |= MODE_MATH;
}

// TODO: if parent has math or svg namespace, inherit it
}

return {
flags,
vnode
};
}
4 changes: 2 additions & 2 deletions test/browser/render.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function getAttributes(node) {

const isIE11 = /Trident\//.test(navigator.userAgent);

describe('render()', () => {
describe.only('render()', () => {
let scratch, rerender;

let resetAppendChild;
Expand Down Expand Up @@ -79,7 +79,7 @@ describe('render()', () => {
expect(scratch.innerHTML).to.eql(`<div>Good</div>`);
});

it('should render % width and height on img correctly', () => {
it.only('should render % width and height on img correctly', () => {
render(<img width="100%" height="100%" />, scratch);
expect(scratch.innerHTML).to.eql(`<img width="100%" height="100%">`);
});
Expand Down
Loading