From 42e7b450933134d66a871bb5370c702ef18cf3cc Mon Sep 17 00:00:00 2001 From: Ryan Christian <33403762+rschristian@users.noreply.github.com> Date: Fri, 29 Nov 2024 18:11:48 -0600 Subject: [PATCH] refactor: Remove `declare global` from internal types (#4583) * refactor: Remove `declare global` from internal types * refactor: Fix hook types --- hooks/src/index.js | 2 +- hooks/src/internal.d.ts | 12 +- src/clone-element.js | 6 +- src/component.js | 18 +- src/create-context.js | 6 +- src/create-element.js | 12 +- src/diff/catch-error.js | 10 +- src/diff/children.js | 7 + src/diff/index.js | 12 ++ src/diff/props.js | 4 +- src/internal.d.ts | 372 ++++++++++++++++++++-------------------- src/options.js | 2 +- src/render.js | 10 +- src/util.js | 2 +- 14 files changed, 249 insertions(+), 226 deletions(-) diff --git a/hooks/src/index.js b/hooks/src/index.js index 92a05f0a54..d6c434e400 100644 --- a/hooks/src/index.js +++ b/hooks/src/index.js @@ -423,7 +423,7 @@ export function useId() { const state = getHookState(currentIndex++, 11); if (!state._value) { // Grab either the root node or the nearest async boundary node. - /** @type {import('./internal.d').VNode} */ + /** @type {import('./internal').VNode} */ let root = currentComponent._vnode; while (root !== null && !root._mask && root._parent !== null) { root = root._parent; diff --git a/hooks/src/internal.d.ts b/hooks/src/internal.d.ts index 5b612dbda6..e828b198d7 100644 --- a/hooks/src/internal.d.ts +++ b/hooks/src/internal.d.ts @@ -1,8 +1,14 @@ +import { + Options as PreactOptions, + Component as PreactComponent, + VNode as PreactVNode, + Context as PreactContext, +} from '../../src/internal'; import { Reducer, StateUpdater } from '.'; export { PreactContext }; -export interface Options extends globalThis.Options { +export interface Options extends PreactOptions { /** Attach a hook that is invoked before a vnode is diffed. */ _diff?(vnode: VNode): void; diffed?(vnode: VNode): void; @@ -24,14 +30,14 @@ export interface ComponentHooks { _pendingEffects: EffectHookState[]; } -export interface Component extends globalThis.Component { +export interface Component extends PreactComponent { __hooks?: ComponentHooks; // Extend to include HookStates _renderCallbacks?: Array void)>; _hasScuFromHooks?: boolean; } -export interface VNode extends globalThis.VNode { +export interface VNode extends PreactVNode { _mask?: [number, number]; _component?: Component; // Override with our specific Component type } diff --git a/src/clone-element.js b/src/clone-element.js index e037defd3c..9998095344 100644 --- a/src/clone-element.js +++ b/src/clone-element.js @@ -5,11 +5,11 @@ import { UNDEFINED } from './constants'; /** * Clones the given VNode, optionally adding attributes/props and replacing its * children. - * @param {VNode} vnode The virtual DOM element to clone + * @param {import('./internal').VNode} vnode The virtual DOM element to clone * @param {object} props Attributes/props to add when cloning - * @param {Array} rest Any additional arguments will be used + * @param {Array} rest Any additional arguments will be used * as replacement children. - * @returns {VNode} + * @returns {import('./internal').VNode} */ export function cloneElement(vnode, props, children) { let normalizedProps = assign({}, vnode.props), diff --git a/src/component.js b/src/component.js index 287f3233f4..9fb5178bc5 100644 --- a/src/component.js +++ b/src/component.js @@ -18,7 +18,7 @@ export function BaseComponent(props, context) { /** * Update component state and schedule a re-render. - * @this {Component} + * @this {import('./internal').Component} * @param {object | ((s: object, p: object) => object)} update A hash of state * properties to update with new values or a function that given the current * state and props returns a new partial state @@ -57,7 +57,7 @@ BaseComponent.prototype.setState = function (update, callback) { /** * Immediately perform a synchronous re-render of the component - * @this {Component} + * @this {import('./internal').Component} * @param {() => void} [callback] A function to be called after component is * re-rendered */ @@ -85,7 +85,7 @@ BaseComponent.prototype.forceUpdate = function (callback) { BaseComponent.prototype.render = Fragment; /** - * @param {VNode} vnode + * @param {import('./internal').VNode} vnode * @param {number | null} [childIndex] */ export function getDomSibling(vnode, childIndex) { @@ -118,7 +118,7 @@ export function getDomSibling(vnode, childIndex) { /** * Trigger in-place re-rendering of a component. - * @param {Component} component The component to rerender + * @param {import('./internal').Component} component The component to rerender */ function renderComponent(component) { let oldVNode = component._vnode, @@ -155,7 +155,7 @@ function renderComponent(component) { } /** - * @param {VNode} vnode + * @param {import('./internal').VNode} vnode */ function updateParentDomPointers(vnode) { if ((vnode = vnode._parent) != null && vnode._component != null) { @@ -174,7 +174,7 @@ function updateParentDomPointers(vnode) { /** * The render queue - * @type {Array} + * @type {Array} */ let rerenderQueue = []; @@ -196,7 +196,7 @@ const defer = /** * Enqueue a rerender of a component - * @param {Component} c The component to rerender + * @param {import('./internal').Component} c The component to rerender */ export function enqueueRender(c) { if ( @@ -212,8 +212,8 @@ export function enqueueRender(c) { } /** - * @param {Component} a - * @param {Component} b + * @param {import('./internal').Component} a + * @param {import('./internal').Component} b */ const depthSort = (a, b) => a._vnode._depth - b._vnode._depth; diff --git a/src/create-context.js b/src/create-context.js index 400d7716b3..1bb9868f6d 100644 --- a/src/create-context.js +++ b/src/create-context.js @@ -8,17 +8,17 @@ export function createContext(defaultValue, contextId) { const context = { _id: contextId, _defaultValue: defaultValue, - /** @type {FunctionComponent} */ + /** @type {import('./internal').FunctionComponent} */ Consumer(props, contextValue) { // return props.children( // context[contextId] ? context[contextId].props.value : defaultValue // ); return props.children(contextValue); }, - /** @type {FunctionComponent} */ + /** @type {import('./internal').FunctionComponent} */ Provider(props) { if (!this.getChildContext) { - /** @type {Set | null} */ + /** @type {Set | null} */ let subs = new Set(); let ctx = {}; ctx[contextId] = this; diff --git a/src/create-element.js b/src/create-element.js index 0360f8bfee..c843e0dc51 100644 --- a/src/create-element.js +++ b/src/create-element.js @@ -6,12 +6,12 @@ let vnodeId = 0; /** * Create an virtual node (used for JSX) - * @param {VNode["type"]} type The node name or Component constructor for this + * @param {import('./internal').VNode["type"]} type The node name or Component constructor for this * virtual node * @param {object | null | undefined} [props] The properties of the virtual node * @param {Array} [children] The children of the * virtual node - * @returns {VNode} + * @returns {import('./internal').VNode} */ export function createElement(type, props, children) { let normalizedProps = {}, @@ -44,20 +44,20 @@ export function createElement(type, props, children) { /** * Create a VNode (used internally by Preact) - * @param {VNode["type"]} type The node name or Component + * @param {import('./internal').VNode["type"]} type The node name or Component * Constructor for this virtual node * @param {object | string | number | null} props The properties of this virtual node. * If this virtual node represents a text node, this is the text of the node (string or number). * @param {string | number | null} key The key for this virtual node, used when * diffing it against its children - * @param {VNode["ref"]} ref The ref property that will + * @param {import('./internal').VNode["ref"]} ref The ref property that will * receive a reference to its created child - * @returns {VNode} + * @returns {import('./internal').VNode} */ export function createVNode(type, props, key, ref, original) { // V8 seems to be better at detecting type shapes if the object is allocated from the same call site // Do not inline into createElement and coerceToVNode! - /** @type {VNode} */ + /** @type {import('./internal').VNode} */ const vnode = { type, props, diff --git a/src/diff/catch-error.js b/src/diff/catch-error.js index 1dd63dbab1..2bd607f797 100644 --- a/src/diff/catch-error.js +++ b/src/diff/catch-error.js @@ -1,16 +1,16 @@ /** * Find the closest error boundary to a thrown error and call it * @param {object} error The thrown value - * @param {VNode} vnode The vnode that threw the error that was caught (except + * @param {import('../internal').VNode} vnode The vnode that threw the error that was caught (except * for unmounting when this parameter is the highest parent that was being * unmounted) - * @param {VNode} [oldVNode] - * @param {ErrorInfo} [errorInfo] + * @param {import('../internal').VNode} [oldVNode] + * @param {import('../internal').ErrorInfo} [errorInfo] */ export function _catchError(error, vnode, oldVNode, errorInfo) { - /** @type {Component} */ + /** @type {import('../internal').Component} */ let component, - /** @type {ComponentType} */ + /** @type {import('../internal').ComponentType} */ ctor, /** @type {boolean} */ handled; diff --git a/src/diff/children.js b/src/diff/children.js index 08cfbe28f8..6f5059751e 100644 --- a/src/diff/children.js +++ b/src/diff/children.js @@ -10,6 +10,13 @@ import { import { isArray } from '../util'; import { getDomSibling } from '../component'; +/** + * @typedef {import('../internal').ComponentChildren} ComponentChildren + * @typedef {import('../internal').Component} Component + * @typedef {import('../internal').PreactElement} PreactElement + * @typedef {import('../internal').VNode} VNode + */ + /** * Diff the children of a virtual node * @param {PreactElement} parentDom The DOM element whose children are being diff --git a/src/diff/index.js b/src/diff/index.js index 84d9b2900e..6e139a6094 100644 --- a/src/diff/index.js +++ b/src/diff/index.js @@ -15,6 +15,18 @@ import { setProperty } from './props'; import { assign, isArray, removeNode, slice } from '../util'; import options from '../options'; +/** + * @typedef {import('../internal').ComponentChildren} ComponentChildren + * @typedef {import('../internal').Component} Component + * @typedef {import('../internal').PreactElement} PreactElement + * @typedef {import('../internal').VNode} VNode + */ + +/** + * @template {any} T + * @typedef {import('../internal').Ref} Ref + */ + /** * Diff two virtual nodes and apply proper changes to the DOM * @param {PreactElement} parentDom The parent of the DOM element diff --git a/src/diff/props.js b/src/diff/props.js index 54bf66876e..56a37f591d 100644 --- a/src/diff/props.js +++ b/src/diff/props.js @@ -30,7 +30,7 @@ let eventClock = 0; /** * Set a property value on a DOM node - * @param {PreactElement} dom The DOM node to modify + * @param {import('../internal').PreactElement} dom The DOM node to modify * @param {string} name The name of the property to set * @param {*} value The value to set the property to * @param {*} oldValue The old value the property had @@ -152,7 +152,7 @@ export function setProperty(dom, name, value, oldValue, namespace) { function createEventProxy(useCapture) { /** * Proxy an event to hooked event handlers - * @param {PreactEvent} e The event object from the browser + * @param {import('../internal').PreactEvent} e The event object from the browser * @private */ return function (e) { diff --git a/src/internal.d.ts b/src/internal.d.ts index dfa4989408..0abfd37ab2 100644 --- a/src/internal.d.ts +++ b/src/internal.d.ts @@ -1,190 +1,188 @@ import * as preact from './index'; -declare global { - export enum HookType { - useState = 1, - useReducer = 2, - useEffect = 3, - useLayoutEffect = 4, - useRef = 5, - useImperativeHandle = 6, - useMemo = 7, - useCallback = 8, - useContext = 9, - useErrorBoundary = 10, - // Not a real hook, but the devtools treat is as such - useDebugvalue = 11 - } - - export interface DevSource { - fileName: string; - lineNumber: number; - } - - export interface ErrorInfo { - componentStack?: string; - } - - export interface Options extends preact.Options { - /** Attach a hook that is invoked before render, mainly to check the arguments. */ - _root?(vnode: ComponentChild, parent: preact.ContainerNode): void; - /** Attach a hook that is invoked before a vnode is diffed. */ - _diff?(vnode: VNode): void; - /** Attach a hook that is invoked after a tree was mounted or was updated. */ - _commit?(vnode: VNode, commitQueue: Component[]): void; - /** Attach a hook that is invoked before a vnode has rendered. */ - _render?(vnode: VNode): void; - /** Attach a hook that is invoked before a hook's state is queried. */ - _hook?(component: Component, index: number, type: HookType): void; - /** Bypass effect execution. Currenty only used in devtools for hooks inspection */ - _skipEffects?: boolean; - /** Attach a hook that is invoked after an error is caught in a component but before calling lifecycle hooks */ - _catchError( - error: any, - vnode: VNode, - oldVNode?: VNode | undefined, - errorInfo?: ErrorInfo | undefined - ): void; - /** Attach a hook that fires when hydration can't find a proper DOM-node to match with */ - _hydrationMismatch?( - vnode: VNode, - excessDomChildren: Array - ): void; - } - - export type ComponentChild = - | VNode - | string - | number - | boolean - | null - | undefined; - export type ComponentChildren = ComponentChild[] | ComponentChild; - - export interface FunctionComponent

- extends preact.FunctionComponent

{ - // Internally, createContext uses `contextType` on a Function component to - // implement the Consumer component - contextType?: PreactContext; - - // Internally, createContext stores a ref to the context object on the Provider - // Function component to help devtools - _contextRef?: PreactContext; - - // Define these properties as undefined on FunctionComponent to get rid of - // some errors in `diff()` - getDerivedStateFromProps?: undefined; - getDerivedStateFromError?: undefined; - } - - export interface ComponentClass

extends preact.ComponentClass

{ - _contextRef?: any; - - // Override public contextType with internal PreactContext type - contextType?: PreactContext; - } - - // Redefine ComponentType using our new internal FunctionComponent interface above - export type ComponentType

= ComponentClass

| FunctionComponent

; - - export interface PreactElement extends preact.ContainerNode { - // Namespace detection - readonly namespaceURI?: string; - // Property used to update Text nodes - data?: CharacterData['data']; - // Property to set __dangerouslySetInnerHTML - innerHTML?: Element['innerHTML']; - - // Attribute reading and setting - readonly attributes?: Element['attributes']; - setAttribute?: Element['setAttribute']; - removeAttribute?: Element['removeAttribute']; - - // Event listeners - addEventListener?: Element['addEventListener']; - removeEventListener?: Element['removeEventListener']; - - // Setting styles - readonly style?: CSSStyleDeclaration; - - // nextSibling required for inserting nodes - readonly nextSibling: ContainerNode | null; - - // Used to match DOM nodes to VNodes during hydration. Note: doesn't exist - // on Text nodes - readonly localName?: string; - - // Input handling - value?: HTMLInputElement['value']; - checked?: HTMLInputElement['checked']; - - // Internal properties - _children?: VNode | null; - /** Event listeners to support event delegation */ - _listeners?: Record void>; - } - - export interface PreactEvent extends Event { - _dispatched?: number; - } - - // We use the `current` property to differentiate between the two kinds of Refs so - // internally we'll define `current` on both to make TypeScript happy - type RefObject = { current: T | null }; - type RefCallback = { - (instance: T | null): void | (() => void); - current: undefined; - }; - type Ref = RefObject | RefCallback; - - export interface VNode

extends preact.VNode

{ - // Redefine type here using our internal ComponentType type, and specify - // string has an undefined `defaultProps` property to make TS happy - type: (string & { defaultProps: undefined }) | ComponentType

; - props: P & { children: ComponentChildren }; - ref?: Ref | null; - _children: Array> | null; - _parent: VNode | null; - _depth: number | null; - /** - * The [first (for Fragments)] DOM child of a VNode - */ - _dom: PreactElement | null; - _component: Component | null; - constructor: undefined; - _original: number; - _index: number; - _flags: number; - } - - export interface Component

extends preact.Component { - // When component is functional component, this is reset to functional component - constructor: ComponentType

; - state: S; // Override Component["state"] to not be readonly for internal use, specifically Hooks - base?: PreactElement; - - _dirty: boolean; - _force?: boolean; - _renderCallbacks: Array<() => void>; // Only class components - _stateCallbacks: Array<() => void>; // Only class components - _globalContext?: any; - _vnode?: VNode

| null; - _nextState?: S | null; // Only class components - /** Only used in the devtools to later dirty check if state has changed */ - _prevState?: S | null; - /** - * Pointer to the parent dom node. This is only needed for top-level Fragment - * components or array returns. - */ - _parentDom?: PreactElement | null; - // Always read, set only when handling error - _processingException?: Component | null; - // Always read, set only when handling error. This is used to indicate at diffTime to set _processingException - _pendingError?: Component | null; - } - - export interface PreactContext extends preact.Context { - _id: string; - _defaultValue: any; - } +export enum HookType { + useState = 1, + useReducer = 2, + useEffect = 3, + useLayoutEffect = 4, + useRef = 5, + useImperativeHandle = 6, + useMemo = 7, + useCallback = 8, + useContext = 9, + useErrorBoundary = 10, + // Not a real hook, but the devtools treat is as such + useDebugvalue = 11 +} + +export interface DevSource { + fileName: string; + lineNumber: number; +} + +export interface ErrorInfo { + componentStack?: string; +} + +export interface Options extends preact.Options { + /** Attach a hook that is invoked before render, mainly to check the arguments. */ + _root?(vnode: ComponentChild, parent: preact.ContainerNode): void; + /** Attach a hook that is invoked before a vnode is diffed. */ + _diff?(vnode: VNode): void; + /** Attach a hook that is invoked after a tree was mounted or was updated. */ + _commit?(vnode: VNode, commitQueue: Component[]): void; + /** Attach a hook that is invoked before a vnode has rendered. */ + _render?(vnode: VNode): void; + /** Attach a hook that is invoked before a hook's state is queried. */ + _hook?(component: Component, index: number, type: HookType): void; + /** Bypass effect execution. Currenty only used in devtools for hooks inspection */ + _skipEffects?: boolean; + /** Attach a hook that is invoked after an error is caught in a component but before calling lifecycle hooks */ + _catchError( + error: any, + vnode: VNode, + oldVNode?: VNode | undefined, + errorInfo?: ErrorInfo | undefined + ): void; + /** Attach a hook that fires when hydration can't find a proper DOM-node to match with */ + _hydrationMismatch?( + vnode: VNode, + excessDomChildren: Array + ): void; +} + +export type ComponentChild = + | VNode + | string + | number + | boolean + | null + | undefined; +export type ComponentChildren = ComponentChild[] | ComponentChild; + +export interface FunctionComponent

+ extends preact.FunctionComponent

{ + // Internally, createContext uses `contextType` on a Function component to + // implement the Consumer component + contextType?: PreactContext; + + // Internally, createContext stores a ref to the context object on the Provider + // Function component to help devtools + _contextRef?: PreactContext; + + // Define these properties as undefined on FunctionComponent to get rid of + // some errors in `diff()` + getDerivedStateFromProps?: undefined; + getDerivedStateFromError?: undefined; +} + +export interface ComponentClass

extends preact.ComponentClass

{ + _contextRef?: any; + + // Override public contextType with internal PreactContext type + contextType?: PreactContext; +} + +// Redefine ComponentType using our new internal FunctionComponent interface above +export type ComponentType

= ComponentClass

| FunctionComponent

; + +export interface PreactElement extends preact.ContainerNode { + // Namespace detection + readonly namespaceURI?: string; + // Property used to update Text nodes + data?: CharacterData['data']; + // Property to set __dangerouslySetInnerHTML + innerHTML?: Element['innerHTML']; + + // Attribute reading and setting + readonly attributes?: Element['attributes']; + setAttribute?: Element['setAttribute']; + removeAttribute?: Element['removeAttribute']; + + // Event listeners + addEventListener?: Element['addEventListener']; + removeEventListener?: Element['removeEventListener']; + + // Setting styles + readonly style?: CSSStyleDeclaration; + + // nextSibling required for inserting nodes + readonly nextSibling: ContainerNode | null; + + // Used to match DOM nodes to VNodes during hydration. Note: doesn't exist + // on Text nodes + readonly localName?: string; + + // Input handling + value?: HTMLInputElement['value']; + checked?: HTMLInputElement['checked']; + + // Internal properties + _children?: VNode | null; + /** Event listeners to support event delegation */ + _listeners?: Record void>; +} + +export interface PreactEvent extends Event { + _dispatched?: number; +} + +// We use the `current` property to differentiate between the two kinds of Refs so +// internally we'll define `current` on both to make TypeScript happy +type RefObject = { current: T | null }; +type RefCallback = { + (instance: T | null): void | (() => void); + current: undefined; +}; +export type Ref = RefObject | RefCallback; + +export interface VNode

extends preact.VNode

{ + // Redefine type here using our internal ComponentType type, and specify + // string has an undefined `defaultProps` property to make TS happy + type: (string & { defaultProps: undefined }) | ComponentType

; + props: P & { children: ComponentChildren }; + ref?: Ref | null; + _children: Array> | null; + _parent: VNode | null; + _depth: number | null; + /** + * The [first (for Fragments)] DOM child of a VNode + */ + _dom: PreactElement | null; + _component: Component | null; + constructor: undefined; + _original: number; + _index: number; + _flags: number; +} + +export interface Component

extends preact.Component { + // When component is functional component, this is reset to functional component + constructor: ComponentType

; + state: S; // Override Component["state"] to not be readonly for internal use, specifically Hooks + base?: PreactElement; + + _dirty: boolean; + _force?: boolean; + _renderCallbacks: Array<() => void>; // Only class components + _stateCallbacks: Array<() => void>; // Only class components + _globalContext?: any; + _vnode?: VNode

| null; + _nextState?: S | null; // Only class components + /** Only used in the devtools to later dirty check if state has changed */ + _prevState?: S | null; + /** + * Pointer to the parent dom node. This is only needed for top-level Fragment + * components or array returns. + */ + _parentDom?: PreactElement | null; + // Always read, set only when handling error + _processingException?: Component | null; + // Always read, set only when handling error. This is used to indicate at diffTime to set _processingException + _pendingError?: Component | null; +} + +export interface PreactContext extends preact.Context { + _id: string; + _defaultValue: any; } diff --git a/src/options.js b/src/options.js index d37ffb3d7c..174f322705 100644 --- a/src/options.js +++ b/src/options.js @@ -7,7 +7,7 @@ import { _catchError } from './diff/catch-error'; * and `preact/hooks` are based on. See the `Options` type in `internal.d.ts` * for a full list of available option hooks (most editors/IDEs allow you to * ctrl+click or cmd+click on mac the type definition below). - * @type {Options} + * @type {import('./internal').Options} */ const options = { _catchError diff --git a/src/render.js b/src/render.js index 2443b0e363..4478d3ac7a 100644 --- a/src/render.js +++ b/src/render.js @@ -6,9 +6,9 @@ import { slice } from './util'; /** * Render a Preact virtual node into a DOM element - * @param {ComponentChild} vnode The virtual node to render - * @param {PreactElement} parentDom The DOM element to render into - * @param {PreactElement | object} [replaceNode] Optional: Attempt to re-use an + * @param {import('./internal').ComponentChild} vnode The virtual node to render + * @param {import('./internal').PreactElement} parentDom The DOM element to render into + * @param {import('./internal').PreactElement | object} [replaceNode] Optional: Attempt to re-use an * existing DOM tree rooted at `replaceNode` */ export function render(vnode, parentDom, replaceNode) { @@ -70,8 +70,8 @@ export function render(vnode, parentDom, replaceNode) { /** * Update an existing DOM element with data from a Preact virtual node - * @param {ComponentChild} vnode The virtual node to render - * @param {PreactElement} parentDom The DOM element to update + * @param {import('./internal').ComponentChild} vnode The virtual node to render + * @param {import('./internal').PreactElement} parentDom The DOM element to update */ export function hydrate(vnode, parentDom) { render(vnode, parentDom, hydrate); diff --git a/src/util.js b/src/util.js index 293c56e52a..647519175f 100644 --- a/src/util.js +++ b/src/util.js @@ -19,7 +19,7 @@ export function assign(obj, props) { * Remove a child node from its parent if attached. This is a workaround for * IE11 which doesn't support `Element.prototype.remove()`. Using this function * is smaller than including a dedicated polyfill. - * @param {preact.ContainerNode} node The node to remove + * @param {import('./index').ContainerNode} node The node to remove */ export function removeNode(node) { if (node && node.parentNode) node.parentNode.removeChild(node);