From 208b0b9b6c43fc876ecf4db633677c6039c18dc7 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Wed, 13 Nov 2024 07:41:27 +0100 Subject: [PATCH] Fork types for TS 5.1 and beyond (#4548) --- package-lock.json | 23 ++- package.json | 12 +- src/index-5.d.ts | 398 +++++++++++++++++++++++++++++++++++++++++ src/index.d.ts | 4 +- test/ts/VNode-test.tsx | 11 +- test/ts/preact.tsx | 31 ++-- 6 files changed, 453 insertions(+), 26 deletions(-) create mode 100644 src/index-5.d.ts diff --git a/package-lock.json b/package-lock.json index 9ba9b5b7e6..f205a5e93d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ "sade": "^1.8.1", "sinon": "^9.2.3", "sinon-chai": "^3.7.0", - "typescript": "^4.9.5", + "typescript": "5.1.6", "undici": "^4.12.0" }, "funding": { @@ -7328,6 +7328,19 @@ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", "dev": true }, + "node_modules/microbundle/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/micromatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", @@ -10556,16 +10569,16 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/ua-parser-js": { diff --git a/package.json b/package.json index a605fe0ef1..88ce82bad5 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,16 @@ "umd:main": "dist/preact.umd.js", "unpkg": "dist/preact.min.js", "source": "src/index.js", + "typesVersions": { + "<=5.0": { + ".": ["./src/index-5.d.ts"] + } + }, "exports": { ".": { + "types@<=5.0": { + "types": "./src/index-5.d.ts" + }, "types": "./src/index.d.ts", "browser": "./dist/preact.module.js", "umd": "./dist/preact.umd.js", @@ -105,7 +113,7 @@ }, "types": "src/index.d.ts", "scripts": { - "prepare": "husky && run-s build && check-export-map && npm-merge-driver-install", + "prepare": "husky && run-s build && npm-merge-driver-install", "build": "npm-run-all --parallel build:*", "build:core": "microbundle build --raw --no-generateTypes -f cjs,esm,umd", "build:core-min": "microbundle build --raw --no-generateTypes -f cjs,esm,umd,iife src/cjs.js -o dist/preact.min.js", @@ -246,7 +254,7 @@ "sade": "^1.8.1", "sinon": "^9.2.3", "sinon-chai": "^3.7.0", - "typescript": "^4.9.5", + "typescript": "5.1.6", "undici": "^4.12.0" }, "volta": { diff --git a/src/index-5.d.ts b/src/index-5.d.ts new file mode 100644 index 0000000000..a2be9f081f --- /dev/null +++ b/src/index-5.d.ts @@ -0,0 +1,398 @@ +export as namespace preact; + +import { JSXInternal } from './jsx'; + +export import JSX = JSXInternal; + +// +// Preact Virtual DOM +// ----------------------------------- + +export interface VNode

{ + type: ComponentType

| string; + props: P & { children: ComponentChildren }; + key: Key; + /** + * ref is not guaranteed by React.ReactElement, for compatibility reasons + * with popular react libs we define it as optional too + */ + ref?: Ref | null; + /** + * The time this `vnode` started rendering. Will only be set when + * the devtools are attached. + * Default value: `0` + */ + startTime?: number; + /** + * The time that the rendering of this `vnode` was completed. Will only be + * set when the devtools are attached. + * Default value: `-1` + */ + endTime?: number; +} + +// +// Preact Component interface +// ----------------------------------- + +export type Key = string | number | any; + +export type RefObject = { current: T | null }; +export type RefCallback = (instance: T | null) => void; +export type Ref = RefObject | RefCallback | null; + +export type ComponentChild = + | VNode + | object + | string + | number + | bigint + | boolean + | null + | undefined; +export type ComponentChildren = ComponentChild[] | ComponentChild; + +export interface Attributes { + key?: Key | undefined; + jsx?: boolean | undefined; +} + +export interface ClassAttributes extends Attributes { + ref?: Ref; +} + +export interface PreactDOMAttributes { + children?: ComponentChildren; + dangerouslySetInnerHTML?: { + __html: string; + }; +} + +export interface ErrorInfo { + componentStack?: string; +} + +export type RenderableProps = P & + Readonly }>; + +export type ComponentType

= ComponentClass

| FunctionComponent

; +export type ComponentFactory

= ComponentType

; + +export type ComponentProps< + C extends ComponentType | keyof JSXInternal.IntrinsicElements +> = C extends ComponentType + ? P + : C extends keyof JSXInternal.IntrinsicElements + ? JSXInternal.IntrinsicElements[C] + : never; + +export interface FunctionComponent

{ + (props: RenderableProps

, context?: any): VNode | null; + displayName?: string; + defaultProps?: Partial

| undefined; +} +export interface FunctionalComponent

extends FunctionComponent

{} + +export interface ComponentClass

{ + new (props: P, context?: any): Component; + displayName?: string; + defaultProps?: Partial

; + contextType?: Context; + getDerivedStateFromProps?( + props: Readonly

, + state: Readonly + ): Partial | null; + getDerivedStateFromError?(error: any): Partial | null; +} +export interface ComponentConstructor

+ extends ComponentClass {} + +// Type alias for a component instance considered generally, whether stateless or stateful. +export type AnyComponent

= + | FunctionComponent

+ | ComponentConstructor; + +export interface Component

{ + componentWillMount?(): void; + componentDidMount?(): void; + componentWillUnmount?(): void; + getChildContext?(): object; + componentWillReceiveProps?(nextProps: Readonly

, nextContext: any): void; + shouldComponentUpdate?( + nextProps: Readonly

, + nextState: Readonly, + nextContext: any + ): boolean; + componentWillUpdate?( + nextProps: Readonly

, + nextState: Readonly, + nextContext: any + ): void; + getSnapshotBeforeUpdate?(oldProps: Readonly

, oldState: Readonly): any; + componentDidUpdate?( + previousProps: Readonly

, + previousState: Readonly, + snapshot: any + ): void; + componentDidCatch?(error: any, errorInfo: ErrorInfo): void; +} + +export abstract class Component { + constructor(props?: P, context?: any); + + static displayName?: string; + static defaultProps?: any; + static contextType?: Context; + + // Static members cannot reference class type parameters. This is not + // supported in TypeScript. Reusing the same type arguments from `Component` + // will lead to an impossible state where one cannot satisfy the type + // constraint under no circumstances, see #1356.In general type arguments + // seem to be a bit buggy and not supported well at the time of this + // writing with TS 3.3.3333. + static getDerivedStateFromProps?( + props: Readonly, + state: Readonly + ): object | null; + static getDerivedStateFromError?(error: any): object | null; + + state: Readonly; + props: RenderableProps

; + context: any; + base?: Element | Text; + + // From https://github.com/DefinitelyTyped/DefinitelyTyped/blob/e836acc75a78cf0655b5dfdbe81d69fdd4d8a252/types/react/index.d.ts#L402 + // // We MUST keep setState() as a unified signature because it allows proper checking of the method return type. + // // See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257 + setState( + state: + | (( + prevState: Readonly, + props: Readonly

+ ) => Pick | Partial | null) + | (Pick | Partial | null), + callback?: () => void + ): void; + + forceUpdate(callback?: () => void): void; + + abstract render( + props?: RenderableProps

, + state?: Readonly, + context?: any + ): ComponentChild; +} + +// +// Preact createElement +// ----------------------------------- + +export function createElement( + type: 'input', + props: + | (JSXInternal.DOMAttributes & + ClassAttributes) + | null, + ...children: ComponentChildren[] +): VNode< + JSXInternal.DOMAttributes & + ClassAttributes +>; +export function createElement< + P extends JSXInternal.HTMLAttributes, + T extends HTMLElement +>( + type: keyof JSXInternal.IntrinsicElements, + props: (ClassAttributes & P) | null, + ...children: ComponentChildren[] +): VNode & P>; +export function createElement< + P extends JSXInternal.SVGAttributes, + T extends HTMLElement +>( + type: keyof JSXInternal.IntrinsicElements, + props: (ClassAttributes & P) | null, + ...children: ComponentChildren[] +): VNode & P>; +export function createElement( + type: string, + props: + | (ClassAttributes & + JSXInternal.HTMLAttributes & + JSXInternal.SVGAttributes) + | null, + ...children: ComponentChildren[] +): VNode< + ClassAttributes & JSXInternal.HTMLAttributes & JSXInternal.SVGAttributes +>; +export function createElement

( + type: ComponentType

, + props: (Attributes & P) | null, + ...children: ComponentChildren[] +): VNode

; +export namespace createElement { + export import JSX = JSXInternal; +} + +export function h( + type: 'input', + props: + | (JSXInternal.DOMAttributes & + ClassAttributes) + | null, + ...children: ComponentChildren[] +): VNode< + JSXInternal.DOMAttributes & + ClassAttributes +>; +export function h< + P extends JSXInternal.HTMLAttributes, + T extends HTMLElement +>( + type: keyof JSXInternal.IntrinsicElements, + props: (ClassAttributes & P) | null, + ...children: ComponentChildren[] +): VNode & P>; +export function h< + P extends JSXInternal.SVGAttributes, + T extends HTMLElement +>( + type: keyof JSXInternal.IntrinsicElements, + props: (ClassAttributes & P) | null, + ...children: ComponentChildren[] +): VNode & P>; +export function h( + type: string, + props: + | (ClassAttributes & + JSXInternal.HTMLAttributes & + JSXInternal.SVGAttributes) + | null, + ...children: ComponentChildren[] +): VNode< + | (ClassAttributes & + JSXInternal.HTMLAttributes & + JSXInternal.SVGAttributes) + | null +>; +export function h

( + type: ComponentType

, + props: (Attributes & P) | null, + ...children: ComponentChildren[] +): VNode; +export namespace h { + export import JSX = JSXInternal; +} + +// +// Preact render +// ----------------------------------- +interface ContainerNode { + readonly nodeType: number; + readonly parentNode: ContainerNode | null; + readonly firstChild: ContainerNode | null; + readonly childNodes: ArrayLike; + + contains(other: ContainerNode | null): boolean; + insertBefore(node: ContainerNode, child: ContainerNode | null): ContainerNode; + appendChild(node: ContainerNode): ContainerNode; + removeChild(child: ContainerNode): ContainerNode; +} + +export function render(vnode: ComponentChild, parent: ContainerNode): void; +/** + * @deprecated Will be removed in v11. + * + * Replacement Preact 10+ implementation can be found here: https://gist.github.com/developit/f4c67a2ede71dc2fab7f357f39cff28c + */ +export function render( + vnode: ComponentChild, + parent: ContainerNode, + replaceNode?: Element | Text +): void; +export function hydrate(vnode: ComponentChild, parent: ContainerNode): void; +export function cloneElement( + vnode: VNode, + props?: any, + ...children: ComponentChildren[] +): VNode; +export function cloneElement

( + vnode: VNode

, + props?: any, + ...children: ComponentChildren[] +): VNode

; + +// +// Preact Built-in Components +// ----------------------------------- + +// TODO: Revisit what the public type of this is... +export const Fragment: FunctionComponent<{}>; + +// +// Preact options +// ----------------------------------- + +/** + * Global options for preact + */ +export interface Options { + /** Attach a hook that is invoked whenever a VNode is created. */ + vnode?(vnode: VNode): void; + /** Attach a hook that is invoked immediately before a vnode is unmounted. */ + unmount?(vnode: VNode): void; + /** Attach a hook that is invoked after a vnode has rendered. */ + diffed?(vnode: VNode): void; + event?(e: Event): any; + requestAnimationFrame?(callback: () => void): void; + debounceRendering?(cb: () => void): void; + useDebugValue?(value: string | number): void; + _addHookName?(name: string | number): void; + __suspenseDidResolve?(vnode: VNode, cb: () => void): void; + // __canSuspenseResolve?(vnode: VNode, cb: () => void): void; + + /** + * Customize attribute serialization when a precompiled JSX transform + * is used. + */ + attr?(name: string, value: any): string | void; +} + +export const options: Options; + +// +// Preact helpers +// ----------------------------------- +export function createRef(): RefObject; +export function toChildArray( + children: ComponentChildren +): Array; +export function isValidElement(vnode: any): vnode is VNode; + +// +// Context +// ----------------------------------- +export interface Consumer + extends FunctionComponent<{ + children: (value: T) => ComponentChildren; + }> {} +export interface PreactConsumer extends Consumer {} + +export interface Provider + extends FunctionComponent<{ + value: T; + children?: ComponentChildren; + }> {} +export interface PreactProvider extends Provider {} +export type ContextType> = C extends Context + ? T + : never; + +export interface Context { + Consumer: Consumer; + Provider: Provider; + displayName?: string; +} +export interface PreactContext extends Context {} + +export function createContext(defaultValue: T): Context; diff --git a/src/index.d.ts b/src/index.d.ts index ab372b3c2a..fb868f3b17 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -87,7 +87,7 @@ export type ComponentProps< : never; export interface FunctionComponent

{ - (props: RenderableProps

, context?: any): VNode | null; + (props: RenderableProps

, context?: any): ComponentChildren; displayName?: string; defaultProps?: Partial

| undefined; } @@ -180,7 +180,7 @@ export abstract class Component { props?: RenderableProps

, state?: Readonly, context?: any - ): ComponentChild; + ): ComponentChildren; } // diff --git a/test/ts/VNode-test.tsx b/test/ts/VNode-test.tsx index 9169183a36..19e7b7da7b 100644 --- a/test/ts/VNode-test.tsx +++ b/test/ts/VNode-test.tsx @@ -9,7 +9,8 @@ import { ComponentFactory, VNode, ComponentChildren, - cloneElement + cloneElement, + ComponentChild } from '../../'; function getDisplayType(vnode: VNode | string | number) { @@ -195,3 +196,11 @@ class ComponentWithNumberChildren extends Component<{ children: number[] }> { {1} {2} ; + +const ComponentReturningComponentChildren = ({ + children +}: { + children: ComponentChild; +}) => children; + +123; diff --git a/test/ts/preact.tsx b/test/ts/preact.tsx index 3713f9d8f5..ef647eeb9d 100644 --- a/test/ts/preact.tsx +++ b/test/ts/preact.tsx @@ -138,22 +138,21 @@ const UseOfComponentWithChildren = () => { ); }; -// TODO: make this work -// const DummyChildren: FunctionalComponent = ({ children }) => { -// return children; -// }; - -// function ReturnChildren(props: { children: preact.ComponentChildren }) { -// return props.children; -// } - -// function TestUndefinedChildren() { -// return ( -// -// Hello -// -// ); -// } +const DummyChildren: FunctionalComponent = ({ children }) => { + return children; +}; + +function ReturnChildren(props: { children: preact.ComponentChildren }) { + return props.children; +} + +function TestUndefinedChildren() { + return ( + + Hello + + ); +} // using ref and or jsx class ComponentUsingRef extends Component {