summaryrefslogtreecommitdiff
path: root/preact/src/diff/index.js
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-08-23 16:46:06 -0300
committerSebastian <sebasjm@gmail.com>2021-08-23 16:48:30 -0300
commit38acabfa6089ab8ac469c12b5f55022fb96935e5 (patch)
tree453dbf70000cc5e338b06201af1eaca8343f8f73 /preact/src/diff/index.js
parentf26125e039143b92dc0d84e7775f508ab0cdcaa8 (diff)
downloadnode-vendor-38acabfa6089ab8ac469c12b5f55022fb96935e5.tar.gz
node-vendor-38acabfa6089ab8ac469c12b5f55022fb96935e5.tar.bz2
node-vendor-38acabfa6089ab8ac469c12b5f55022fb96935e5.zip
added web vendorsHEADmaster
Diffstat (limited to 'preact/src/diff/index.js')
-rw-r--r--preact/src/diff/index.js514
1 files changed, 514 insertions, 0 deletions
diff --git a/preact/src/diff/index.js b/preact/src/diff/index.js
new file mode 100644
index 0000000..de63843
--- /dev/null
+++ b/preact/src/diff/index.js
@@ -0,0 +1,514 @@
+import { EMPTY_OBJ } from '../constants';
+import { Component, getDomSibling } from '../component';
+import { Fragment } from '../create-element';
+import { diffChildren } from './children';
+import { diffProps, setProperty } from './props';
+import { assign, removeNode, slice } from '../util';
+import options from '../options';
+
+/**
+ * Diff two virtual nodes and apply proper changes to the DOM
+ * @param {import('../internal').PreactElement} parentDom The parent of the DOM element
+ * @param {import('../internal').VNode} newVNode The new virtual node
+ * @param {import('../internal').VNode} oldVNode The old virtual node
+ * @param {object} globalContext The current context object. Modified by getChildContext
+ * @param {boolean} isSvg Whether or not this element is an SVG node
+ * @param {Array<import('../internal').PreactElement>} excessDomChildren
+ * @param {Array<import('../internal').Component>} commitQueue List of components
+ * which have callbacks to invoke in commitRoot
+ * @param {import('../internal').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
+ */
+export function diff(
+ parentDom,
+ newVNode,
+ oldVNode,
+ globalContext,
+ isSvg,
+ excessDomChildren,
+ commitQueue,
+ oldDom,
+ isHydrating
+) {
+ let tmp,
+ newType = newVNode.type;
+
+ // When passing through createElement it assigns the object
+ // constructor as undefined. This to prevent JSON-injection.
+ if (newVNode.constructor !== undefined) return null;
+
+ // If the previous diff bailed out, resume creating/hydrating.
+ if (oldVNode._hydrating != null) {
+ isHydrating = oldVNode._hydrating;
+ oldDom = newVNode._dom = oldVNode._dom;
+ // if we resume, we want the tree to be "unlocked"
+ newVNode._hydrating = null;
+ excessDomChildren = [oldDom];
+ }
+
+ if ((tmp = options._diff)) tmp(newVNode);
+
+ try {
+ outer: if (typeof newType == 'function') {
+ let c, isNew, oldProps, oldState, snapshot, clearProcessingException;
+ let newProps = newVNode.props;
+
+ // Necessary for createContext api. Setting this property will pass
+ // the context value as `this.context` just for this component.
+ tmp = newType.contextType;
+ let provider = tmp && globalContext[tmp._id];
+ let componentContext = tmp
+ ? provider
+ ? provider.props.value
+ : tmp._defaultValue
+ : globalContext;
+
+ // Get component and set it to `c`
+ if (oldVNode._component) {
+ c = newVNode._component = oldVNode._component;
+ clearProcessingException = c._processingException = c._pendingError;
+ } else {
+ // Instantiate the new component
+ if ('prototype' in newType && newType.prototype.render) {
+ // @ts-ignore The check above verifies that newType is suppose to be constructed
+ newVNode._component = c = new newType(newProps, componentContext); // eslint-disable-line new-cap
+ } else {
+ // @ts-ignore Trust me, Component implements the interface we want
+ newVNode._component = c = new Component(newProps, componentContext);
+ c.constructor = newType;
+ c.render = doRender;
+ }
+ if (provider) provider.sub(c);
+
+ c.props = newProps;
+ if (!c.state) c.state = {};
+ c.context = componentContext;
+ c._globalContext = globalContext;
+ isNew = c._dirty = true;
+ c._renderCallbacks = [];
+ }
+
+ // Invoke getDerivedStateFromProps
+ if (c._nextState == null) {
+ c._nextState = c.state;
+ }
+ if (newType.getDerivedStateFromProps != null) {
+ if (c._nextState == c.state) {
+ c._nextState = assign({}, c._nextState);
+ }
+
+ assign(
+ c._nextState,
+ newType.getDerivedStateFromProps(newProps, c._nextState)
+ );
+ }
+
+ oldProps = c.props;
+ oldState = c.state;
+
+ // Invoke pre-render lifecycle methods
+ if (isNew) {
+ if (
+ newType.getDerivedStateFromProps == null &&
+ c.componentWillMount != null
+ ) {
+ c.componentWillMount();
+ }
+
+ if (c.componentDidMount != null) {
+ c._renderCallbacks.push(c.componentDidMount);
+ }
+ } else {
+ if (
+ newType.getDerivedStateFromProps == null &&
+ newProps !== oldProps &&
+ c.componentWillReceiveProps != null
+ ) {
+ c.componentWillReceiveProps(newProps, componentContext);
+ }
+
+ if (
+ (!c._force &&
+ c.shouldComponentUpdate != null &&
+ c.shouldComponentUpdate(
+ newProps,
+ c._nextState,
+ componentContext
+ ) === false) ||
+ newVNode._original === oldVNode._original
+ ) {
+ c.props = newProps;
+ c.state = c._nextState;
+ // More info about this here: https://gist.github.com/JoviDeCroock/bec5f2ce93544d2e6070ef8e0036e4e8
+ if (newVNode._original !== oldVNode._original) c._dirty = false;
+ c._vnode = newVNode;
+ newVNode._dom = oldVNode._dom;
+ newVNode._children = oldVNode._children;
+ newVNode._children.forEach(vnode => {
+ if (vnode) vnode._parent = newVNode;
+ });
+ if (c._renderCallbacks.length) {
+ commitQueue.push(c);
+ }
+
+ break outer;
+ }
+
+ if (c.componentWillUpdate != null) {
+ c.componentWillUpdate(newProps, c._nextState, componentContext);
+ }
+
+ if (c.componentDidUpdate != null) {
+ c._renderCallbacks.push(() => {
+ c.componentDidUpdate(oldProps, oldState, snapshot);
+ });
+ }
+ }
+
+ c.context = componentContext;
+ c.props = newProps;
+ c.state = c._nextState;
+
+ if ((tmp = options._render)) tmp(newVNode);
+
+ c._dirty = false;
+ c._vnode = newVNode;
+ c._parentDom = parentDom;
+
+ tmp = c.render(c.props, c.state, c.context);
+
+ // Handle setState called in render, see #2553
+ c.state = c._nextState;
+
+ if (c.getChildContext != null) {
+ globalContext = assign(assign({}, globalContext), c.getChildContext());
+ }
+
+ if (!isNew && c.getSnapshotBeforeUpdate != null) {
+ snapshot = c.getSnapshotBeforeUpdate(oldProps, oldState);
+ }
+
+ let isTopLevelFragment =
+ tmp != null && tmp.type === Fragment && tmp.key == null;
+ let renderResult = isTopLevelFragment ? tmp.props.children : tmp;
+
+ diffChildren(
+ parentDom,
+ Array.isArray(renderResult) ? renderResult : [renderResult],
+ newVNode,
+ oldVNode,
+ globalContext,
+ isSvg,
+ excessDomChildren,
+ commitQueue,
+ oldDom,
+ isHydrating
+ );
+
+ c.base = newVNode._dom;
+
+ // We successfully rendered this VNode, unset any stored hydration/bailout state:
+ newVNode._hydrating = null;
+
+ if (c._renderCallbacks.length) {
+ commitQueue.push(c);
+ }
+
+ if (clearProcessingException) {
+ c._pendingError = c._processingException = null;
+ }
+
+ c._force = false;
+ } else if (
+ excessDomChildren == null &&
+ newVNode._original === oldVNode._original
+ ) {
+ newVNode._children = oldVNode._children;
+ newVNode._dom = oldVNode._dom;
+ } else {
+ newVNode._dom = diffElementNodes(
+ oldVNode._dom,
+ newVNode,
+ oldVNode,
+ globalContext,
+ isSvg,
+ excessDomChildren,
+ commitQueue,
+ isHydrating
+ );
+ }
+
+ if ((tmp = options.diffed)) tmp(newVNode);
+ } catch (e) {
+ newVNode._original = null;
+ // if hydrating or creating initial tree, bailout preserves DOM:
+ if (isHydrating || excessDomChildren != null) {
+ newVNode._dom = oldDom;
+ newVNode._hydrating = !!isHydrating;
+ excessDomChildren[excessDomChildren.indexOf(oldDom)] = null;
+ // ^ could possibly be simplified to:
+ // excessDomChildren.length = 0;
+ }
+ options._catchError(e, newVNode, oldVNode);
+ }
+}
+
+/**
+ * @param {Array<import('../internal').Component>} commitQueue List of components
+ * which have callbacks to invoke in commitRoot
+ * @param {import('../internal').VNode} root
+ */
+export function commitRoot(commitQueue, root) {
+ if (options._commit) options._commit(root, commitQueue);
+
+ commitQueue.some(c => {
+ try {
+ // @ts-ignore Reuse the commitQueue variable here so the type changes
+ commitQueue = c._renderCallbacks;
+ c._renderCallbacks = [];
+ commitQueue.some(cb => {
+ // @ts-ignore See above ts-ignore on commitQueue
+ cb.call(c);
+ });
+ } catch (e) {
+ options._catchError(e, c._vnode);
+ }
+ });
+}
+
+/**
+ * Diff two virtual nodes representing DOM element
+ * @param {import('../internal').PreactElement} dom The DOM element representing
+ * the virtual nodes being diffed
+ * @param {import('../internal').VNode} newVNode The new virtual node
+ * @param {import('../internal').VNode} oldVNode The old virtual node
+ * @param {object} globalContext The current context object
+ * @param {boolean} isSvg Whether or not this DOM node is an SVG node
+ * @param {*} excessDomChildren
+ * @param {Array<import('../internal').Component>} commitQueue List of components
+ * which have callbacks to invoke in commitRoot
+ * @param {boolean} isHydrating Whether or not we are in hydration
+ * @returns {import('../internal').PreactElement}
+ */
+function diffElementNodes(
+ dom,
+ newVNode,
+ oldVNode,
+ globalContext,
+ isSvg,
+ excessDomChildren,
+ commitQueue,
+ isHydrating
+) {
+ let oldProps = oldVNode.props;
+ let newProps = newVNode.props;
+ let nodeType = newVNode.type;
+ let i = 0;
+
+ // Tracks entering and exiting SVG namespace when descending through the tree.
+ if (nodeType === 'svg') isSvg = true;
+
+ if (excessDomChildren != null) {
+ for (; i < excessDomChildren.length; i++) {
+ const child = excessDomChildren[i];
+
+ // if newVNode matches an element in excessDomChildren or the `dom`
+ // argument matches an element in excessDomChildren, remove it from
+ // excessDomChildren so it isn't later removed in diffChildren
+ if (
+ child &&
+ (child === dom ||
+ (nodeType ? child.localName == nodeType : child.nodeType == 3))
+ ) {
+ dom = child;
+ excessDomChildren[i] = null;
+ break;
+ }
+ }
+ }
+
+ if (dom == null) {
+ if (nodeType === null) {
+ // @ts-ignore createTextNode returns Text, we expect PreactElement
+ return document.createTextNode(newProps);
+ }
+
+ if (isSvg) {
+ dom = document.createElementNS(
+ 'http://www.w3.org/2000/svg',
+ // @ts-ignore We know `newVNode.type` is a string
+ nodeType
+ );
+ } else {
+ dom = document.createElement(
+ // @ts-ignore We know `newVNode.type` is a string
+ nodeType,
+ newProps.is && newProps
+ );
+ }
+
+ // we created a new parent, so none of the previously attached children can be reused:
+ excessDomChildren = null;
+ // we are creating a new node, so we can assume this is a new subtree (in case we are hydrating), this deopts the hydrate
+ isHydrating = false;
+ }
+
+ if (nodeType === null) {
+ // During hydration, we still have to split merged text from SSR'd HTML.
+ if (oldProps !== newProps && (!isHydrating || dom.data !== newProps)) {
+ dom.data = newProps;
+ }
+ } else {
+ // If excessDomChildren was not null, repopulate it with the current element's children:
+ excessDomChildren = excessDomChildren && slice.call(dom.childNodes);
+
+ oldProps = oldVNode.props || EMPTY_OBJ;
+
+ let oldHtml = oldProps.dangerouslySetInnerHTML;
+ let newHtml = newProps.dangerouslySetInnerHTML;
+
+ // During hydration, props are not diffed at all (including dangerouslySetInnerHTML)
+ // @TODO we should warn in debug mode when props don't match here.
+ if (!isHydrating) {
+ // But, if we are in a situation where we are using existing DOM (e.g. replaceNode)
+ // we should read the existing DOM attributes to diff them
+ if (excessDomChildren != null) {
+ oldProps = {};
+ for (i = 0; i < dom.attributes.length; i++) {
+ oldProps[dom.attributes[i].name] = dom.attributes[i].value;
+ }
+ }
+
+ if (newHtml || oldHtml) {
+ // Avoid re-applying the same '__html' if it did not changed between re-render
+ if (
+ !newHtml ||
+ ((!oldHtml || newHtml.__html != oldHtml.__html) &&
+ newHtml.__html !== dom.innerHTML)
+ ) {
+ dom.innerHTML = (newHtml && newHtml.__html) || '';
+ }
+ }
+ }
+
+ diffProps(dom, newProps, oldProps, isSvg, isHydrating);
+
+ // If the new vnode didn't have dangerouslySetInnerHTML, diff its children
+ if (newHtml) {
+ newVNode._children = [];
+ } else {
+ i = newVNode.props.children;
+ diffChildren(
+ dom,
+ Array.isArray(i) ? i : [i],
+ newVNode,
+ oldVNode,
+ globalContext,
+ isSvg && nodeType !== 'foreignObject',
+ excessDomChildren,
+ commitQueue,
+ excessDomChildren
+ ? excessDomChildren[0]
+ : oldVNode._children && getDomSibling(oldVNode, 0),
+ isHydrating
+ );
+
+ // Remove children that are not part of any vnode.
+ if (excessDomChildren != null) {
+ for (i = excessDomChildren.length; i--; ) {
+ if (excessDomChildren[i] != null) removeNode(excessDomChildren[i]);
+ }
+ }
+ }
+
+ // (as above, don't diff props during hydration)
+ if (!isHydrating) {
+ if (
+ 'value' in newProps &&
+ (i = newProps.value) !== undefined &&
+ // #2756 For the <progress>-element the initial value is 0,
+ // despite the attribute not being present. When the attribute
+ // is missing the progress bar is treated as indeterminate.
+ // To fix that we'll always update it when it is 0 for progress elements
+ (i !== dom.value || (nodeType === 'progress' && !i))
+ ) {
+ setProperty(dom, 'value', i, oldProps.value, false);
+ }
+ if (
+ 'checked' in newProps &&
+ (i = newProps.checked) !== undefined &&
+ i !== dom.checked
+ ) {
+ setProperty(dom, 'checked', i, oldProps.checked, false);
+ }
+ }
+ }
+
+ return dom;
+}
+
+/**
+ * Invoke or update a ref, depending on whether it is a function or object ref.
+ * @param {object|function} ref
+ * @param {any} value
+ * @param {import('../internal').VNode} vnode
+ */
+export function applyRef(ref, value, vnode) {
+ try {
+ if (typeof ref == 'function') ref(value);
+ else ref.current = value;
+ } catch (e) {
+ options._catchError(e, vnode);
+ }
+}
+
+/**
+ * Unmount a virtual node from the tree and apply DOM changes
+ * @param {import('../internal').VNode} vnode The virtual node to unmount
+ * @param {import('../internal').VNode} parentVNode The parent of the VNode that
+ * initiated the unmount
+ * @param {boolean} [skipRemove] Flag that indicates that a parent node of the
+ * current element is already detached from the DOM.
+ */
+export function unmount(vnode, parentVNode, skipRemove) {
+ let r;
+ if (options.unmount) options.unmount(vnode);
+
+ if ((r = vnode.ref)) {
+ if (!r.current || r.current === vnode._dom) applyRef(r, null, parentVNode);
+ }
+
+ if ((r = vnode._component) != null) {
+ if (r.componentWillUnmount) {
+ try {
+ r.componentWillUnmount();
+ } catch (e) {
+ options._catchError(e, parentVNode);
+ }
+ }
+
+ r.base = r._parentDom = null;
+ }
+
+ if ((r = vnode._children)) {
+ for (let i = 0; i < r.length; i++) {
+ if (r[i]) {
+ unmount(r[i], parentVNode, typeof vnode.type != 'function');
+ }
+ }
+ }
+
+ if (!skipRemove && vnode._dom != null) removeNode(vnode._dom);
+
+ // Must be set to `undefined` to properly clean up `_nextDom`
+ // for which `null` is a valid value. See comment in `create-element.js`
+ vnode._dom = vnode._nextDom = undefined;
+}
+
+/** The `.render()` method for a PFC backing instance. */
+function doRender(props, state, context) {
+ return this.constructor(props, context);
+}