node_modules/preact/compat/src/suspense.js in isomorfeus-preact-10.6.2 vs node_modules/preact/compat/src/suspense.js in isomorfeus-preact-10.6.3

- old
+ new

@@ -1,270 +1,270 @@ -import { Component, createElement, options, Fragment } from 'preact'; -import { assign } from './util'; - -const oldCatchError = options._catchError; -options._catchError = function(error, newVNode, oldVNode) { - if (error.then) { - /** @type {import('./internal').Component} */ - let component; - let vnode = newVNode; - - for (; (vnode = vnode._parent); ) { - if ((component = vnode._component) && component._childDidSuspend) { - if (newVNode._dom == null) { - newVNode._dom = oldVNode._dom; - newVNode._children = oldVNode._children; - } - // Don't call oldCatchError if we found a Suspense - return component._childDidSuspend(error, newVNode); - } - } - } - oldCatchError(error, newVNode, oldVNode); -}; - -const oldUnmount = options.unmount; -options.unmount = function(vnode) { - /** @type {import('./internal').Component} */ - const component = vnode._component; - if (component && component._onResolve) { - component._onResolve(); - } - - // if the component is still hydrating - // most likely it is because the component is suspended - // we set the vnode.type as `null` so that it is not a typeof function - // so the unmount will remove the vnode._dom - if (component && vnode._hydrating === true) { - vnode.type = null; - } - - if (oldUnmount) oldUnmount(vnode); -}; - -function detachedClone(vnode, detachedParent, parentDom) { - if (vnode) { - if (vnode._component && vnode._component.__hooks) { - vnode._component.__hooks._list.forEach(effect => { - if (typeof effect._cleanup == 'function') effect._cleanup(); - }); - - vnode._component.__hooks = null; - } - - vnode = assign({}, vnode); - if (vnode._component != null) { - if (vnode._component._parentDom === parentDom) { - vnode._component._parentDom = detachedParent; - } - vnode._component = null; - } - - vnode._children = - vnode._children && - vnode._children.map(child => - detachedClone(child, detachedParent, parentDom) - ); - } - - return vnode; -} - -function removeOriginal(vnode, detachedParent, originalParent) { - if (vnode) { - vnode._original = null; - vnode._children = - vnode._children && - vnode._children.map(child => - removeOriginal(child, detachedParent, originalParent) - ); - - if (vnode._component) { - if (vnode._component._parentDom === detachedParent) { - if (vnode._dom) { - originalParent.insertBefore(vnode._dom, vnode._nextDom); - } - vnode._component._force = true; - vnode._component._parentDom = originalParent; - } - } - } - - return vnode; -} - -// having custom inheritance instead of a class here saves a lot of bytes -export function Suspense() { - // we do not call super here to golf some bytes... - this._pendingSuspensionCount = 0; - this._suspenders = null; - this._detachOnNextRender = null; -} - -// Things we do here to save some bytes but are not proper JS inheritance: -// - call `new Component()` as the prototype -// - do not set `Suspense.prototype.constructor` to `Suspense` -Suspense.prototype = new Component(); - -/** - * @this {import('./internal').SuspenseComponent} - * @param {Promise} promise The thrown promise - * @param {import('./internal').VNode<any, any>} suspendingVNode The suspending component - */ -Suspense.prototype._childDidSuspend = function(promise, suspendingVNode) { - const suspendingComponent = suspendingVNode._component; - - /** @type {import('./internal').SuspenseComponent} */ - const c = this; - - if (c._suspenders == null) { - c._suspenders = []; - } - c._suspenders.push(suspendingComponent); - - const resolve = suspended(c._vnode); - - let resolved = false; - const onResolved = () => { - if (resolved) return; - - resolved = true; - suspendingComponent._onResolve = null; - - if (resolve) { - resolve(onSuspensionComplete); - } else { - onSuspensionComplete(); - } - }; - - suspendingComponent._onResolve = onResolved; - - const onSuspensionComplete = () => { - if (!--c._pendingSuspensionCount) { - // If the suspension was during hydration we don't need to restore the - // suspended children into the _children array - if (c.state._suspended) { - const suspendedVNode = c.state._suspended; - c._vnode._children[0] = removeOriginal( - suspendedVNode, - suspendedVNode._component._parentDom, - suspendedVNode._component._originalParentDom - ); - } - - c.setState({ _suspended: (c._detachOnNextRender = null) }); - - let suspended; - while ((suspended = c._suspenders.pop())) { - suspended.forceUpdate(); - } - } - }; - - /** - * We do not set `suspended: true` during hydration because we want the actual markup - * to remain on screen and hydrate it when the suspense actually gets resolved. - * While in non-hydration cases the usual fallback -> component flow would occour. - */ - const wasHydrating = suspendingVNode._hydrating === true; - if (!c._pendingSuspensionCount++ && !wasHydrating) { - c.setState({ _suspended: (c._detachOnNextRender = c._vnode._children[0]) }); - } - promise.then(onResolved, onResolved); -}; - -Suspense.prototype.componentWillUnmount = function() { - this._suspenders = []; -}; - -/** - * @this {import('./internal').SuspenseComponent} - * @param {import('./internal').SuspenseComponent["props"]} props - * @param {import('./internal').SuspenseState} state - */ -Suspense.prototype.render = function(props, state) { - if (this._detachOnNextRender) { - // When the Suspense's _vnode was created by a call to createVNode - // (i.e. due to a setState further up in the tree) - // it's _children prop is null, in this case we "forget" about the parked vnodes to detach - if (this._vnode._children) { - const detachedParent = document.createElement('div'); - const detachedComponent = this._vnode._children[0]._component; - this._vnode._children[0] = detachedClone( - this._detachOnNextRender, - detachedParent, - (detachedComponent._originalParentDom = detachedComponent._parentDom) - ); - } - - this._detachOnNextRender = null; - } - - // Wrap fallback tree in a VNode that prevents itself from being marked as aborting mid-hydration: - /** @type {import('./internal').VNode} */ - const fallback = - state._suspended && createElement(Fragment, null, props.fallback); - if (fallback) fallback._hydrating = null; - - return [ - createElement(Fragment, null, state._suspended ? null : props.children), - fallback - ]; -}; - -/** - * Checks and calls the parent component's _suspended method, passing in the - * suspended vnode. This is a way for a parent (e.g. SuspenseList) to get notified - * that one of its children/descendants suspended. - * - * The parent MAY return a callback. The callback will get called when the - * suspension resolves, notifying the parent of the fact. - * Moreover, the callback gets function `unsuspend` as a parameter. The resolved - * child descendant will not actually get unsuspended until `unsuspend` gets called. - * This is a way for the parent to delay unsuspending. - * - * If the parent does not return a callback then the resolved vnode - * gets unsuspended immediately when it resolves. - * - * @param {import('./internal').VNode} vnode - * @returns {((unsuspend: () => void) => void)?} - */ -export function suspended(vnode) { - /** @type {import('./internal').Component} */ - let component = vnode._parent._component; - return component && component._suspended && component._suspended(vnode); -} - -export function lazy(loader) { - let prom; - let component; - let error; - - function Lazy(props) { - if (!prom) { - prom = loader(); - prom.then( - exports => { - component = exports.default || exports; - }, - e => { - error = e; - } - ); - } - - if (error) { - throw error; - } - - if (!component) { - throw prom; - } - - return createElement(component, props); - } - - Lazy.displayName = 'Lazy'; - Lazy._forwarded = true; - return Lazy; -} +import { Component, createElement, options, Fragment } from 'preact'; +import { assign } from './util'; + +const oldCatchError = options._catchError; +options._catchError = function(error, newVNode, oldVNode) { + if (error.then) { + /** @type {import('./internal').Component} */ + let component; + let vnode = newVNode; + + for (; (vnode = vnode._parent); ) { + if ((component = vnode._component) && component._childDidSuspend) { + if (newVNode._dom == null) { + newVNode._dom = oldVNode._dom; + newVNode._children = oldVNode._children; + } + // Don't call oldCatchError if we found a Suspense + return component._childDidSuspend(error, newVNode); + } + } + } + oldCatchError(error, newVNode, oldVNode); +}; + +const oldUnmount = options.unmount; +options.unmount = function(vnode) { + /** @type {import('./internal').Component} */ + const component = vnode._component; + if (component && component._onResolve) { + component._onResolve(); + } + + // if the component is still hydrating + // most likely it is because the component is suspended + // we set the vnode.type as `null` so that it is not a typeof function + // so the unmount will remove the vnode._dom + if (component && vnode._hydrating === true) { + vnode.type = null; + } + + if (oldUnmount) oldUnmount(vnode); +}; + +function detachedClone(vnode, detachedParent, parentDom) { + if (vnode) { + if (vnode._component && vnode._component.__hooks) { + vnode._component.__hooks._list.forEach(effect => { + if (typeof effect._cleanup == 'function') effect._cleanup(); + }); + + vnode._component.__hooks = null; + } + + vnode = assign({}, vnode); + if (vnode._component != null) { + if (vnode._component._parentDom === parentDom) { + vnode._component._parentDom = detachedParent; + } + vnode._component = null; + } + + vnode._children = + vnode._children && + vnode._children.map(child => + detachedClone(child, detachedParent, parentDom) + ); + } + + return vnode; +} + +function removeOriginal(vnode, detachedParent, originalParent) { + if (vnode) { + vnode._original = null; + vnode._children = + vnode._children && + vnode._children.map(child => + removeOriginal(child, detachedParent, originalParent) + ); + + if (vnode._component) { + if (vnode._component._parentDom === detachedParent) { + if (vnode._dom) { + originalParent.insertBefore(vnode._dom, vnode._nextDom); + } + vnode._component._force = true; + vnode._component._parentDom = originalParent; + } + } + } + + return vnode; +} + +// having custom inheritance instead of a class here saves a lot of bytes +export function Suspense() { + // we do not call super here to golf some bytes... + this._pendingSuspensionCount = 0; + this._suspenders = null; + this._detachOnNextRender = null; +} + +// Things we do here to save some bytes but are not proper JS inheritance: +// - call `new Component()` as the prototype +// - do not set `Suspense.prototype.constructor` to `Suspense` +Suspense.prototype = new Component(); + +/** + * @this {import('./internal').SuspenseComponent} + * @param {Promise} promise The thrown promise + * @param {import('./internal').VNode<any, any>} suspendingVNode The suspending component + */ +Suspense.prototype._childDidSuspend = function(promise, suspendingVNode) { + const suspendingComponent = suspendingVNode._component; + + /** @type {import('./internal').SuspenseComponent} */ + const c = this; + + if (c._suspenders == null) { + c._suspenders = []; + } + c._suspenders.push(suspendingComponent); + + const resolve = suspended(c._vnode); + + let resolved = false; + const onResolved = () => { + if (resolved) return; + + resolved = true; + suspendingComponent._onResolve = null; + + if (resolve) { + resolve(onSuspensionComplete); + } else { + onSuspensionComplete(); + } + }; + + suspendingComponent._onResolve = onResolved; + + const onSuspensionComplete = () => { + if (!--c._pendingSuspensionCount) { + // If the suspension was during hydration we don't need to restore the + // suspended children into the _children array + if (c.state._suspended) { + const suspendedVNode = c.state._suspended; + c._vnode._children[0] = removeOriginal( + suspendedVNode, + suspendedVNode._component._parentDom, + suspendedVNode._component._originalParentDom + ); + } + + c.setState({ _suspended: (c._detachOnNextRender = null) }); + + let suspended; + while ((suspended = c._suspenders.pop())) { + suspended.forceUpdate(); + } + } + }; + + /** + * We do not set `suspended: true` during hydration because we want the actual markup + * to remain on screen and hydrate it when the suspense actually gets resolved. + * While in non-hydration cases the usual fallback -> component flow would occour. + */ + const wasHydrating = suspendingVNode._hydrating === true; + if (!c._pendingSuspensionCount++ && !wasHydrating) { + c.setState({ _suspended: (c._detachOnNextRender = c._vnode._children[0]) }); + } + promise.then(onResolved, onResolved); +}; + +Suspense.prototype.componentWillUnmount = function() { + this._suspenders = []; +}; + +/** + * @this {import('./internal').SuspenseComponent} + * @param {import('./internal').SuspenseComponent["props"]} props + * @param {import('./internal').SuspenseState} state + */ +Suspense.prototype.render = function(props, state) { + if (this._detachOnNextRender) { + // When the Suspense's _vnode was created by a call to createVNode + // (i.e. due to a setState further up in the tree) + // it's _children prop is null, in this case we "forget" about the parked vnodes to detach + if (this._vnode._children) { + const detachedParent = document.createElement('div'); + const detachedComponent = this._vnode._children[0]._component; + this._vnode._children[0] = detachedClone( + this._detachOnNextRender, + detachedParent, + (detachedComponent._originalParentDom = detachedComponent._parentDom) + ); + } + + this._detachOnNextRender = null; + } + + // Wrap fallback tree in a VNode that prevents itself from being marked as aborting mid-hydration: + /** @type {import('./internal').VNode} */ + const fallback = + state._suspended && createElement(Fragment, null, props.fallback); + if (fallback) fallback._hydrating = null; + + return [ + createElement(Fragment, null, state._suspended ? null : props.children), + fallback + ]; +}; + +/** + * Checks and calls the parent component's _suspended method, passing in the + * suspended vnode. This is a way for a parent (e.g. SuspenseList) to get notified + * that one of its children/descendants suspended. + * + * The parent MAY return a callback. The callback will get called when the + * suspension resolves, notifying the parent of the fact. + * Moreover, the callback gets function `unsuspend` as a parameter. The resolved + * child descendant will not actually get unsuspended until `unsuspend` gets called. + * This is a way for the parent to delay unsuspending. + * + * If the parent does not return a callback then the resolved vnode + * gets unsuspended immediately when it resolves. + * + * @param {import('./internal').VNode} vnode + * @returns {((unsuspend: () => void) => void)?} + */ +export function suspended(vnode) { + /** @type {import('./internal').Component} */ + let component = vnode._parent._component; + return component && component._suspended && component._suspended(vnode); +} + +export function lazy(loader) { + let prom; + let component; + let error; + + function Lazy(props) { + if (!prom) { + prom = loader(); + prom.then( + exports => { + component = exports.default || exports; + }, + e => { + error = e; + } + ); + } + + if (error) { + throw error; + } + + if (!component) { + throw prom; + } + + return createElement(component, props); + } + + Lazy.displayName = 'Lazy'; + Lazy._forwarded = true; + return Lazy; +}