From 38acabfa6089ab8ac469c12b5f55022fb96935e5 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 23 Aug 2021 16:46:06 -0300 Subject: added web vendors --- preact/src/diff/index.js | 514 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 514 insertions(+) create mode 100644 preact/src/diff/index.js (limited to 'preact/src/diff/index.js') 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} excessDomChildren + * @param {Array} 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} 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} 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 -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); +} -- cgit v1.2.3