-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New typings broke custom elements for me #1180
Comments
That's a tough one to crack. Somehow the declare global {
namespace JSX {
interface IntrinsicElements {
// Overwrite children prop
["test-ce"]: Partial<TestCE & { children: any}>;
}
}
} FYI: Simpler test case, because the TypeScript tests are not executed inside a browser: declare global {
namespace JSX {
interface IntrinsicElements {
["test-ce"]: Partial<HTMLElement>;
}
}
}
const MyTestCustomElement = <test-ce><div>hai</div></test-ce>; |
@marvinhagemeister Thanks for the workaround! Yeah, no idea what to do about this one. |
... I just realized that |
@surma ohh good catch, didn't know about that one 👍 |
In this case, is |
I think this is an unfortunate clash of names between what JSX considers as children and the children property of HTML elements If you wanted to replace the type of children for the definition in type PreactCustomElement<T, Children = preact.ComponentChildren> = {
[P in keyof T]?: P extends "children" ? Children : T[P]
} It defaults (ie when using as This is still less than ideal since it will let you do things like pass addEventListener as a property, I'll think about a better solution for that |
Okay so I think I've wrapped my head around it a bit now. I believe that as far as The properties defined in an I would say the best way to type it, then, is to treat it the same way the other intrinsic elements are declare global {
namespace JSX {
interface IntrinsicElements {
["test-ce"]: HTMLAttributes;
}
}
} This will give you the same behaviour as say a regular div: it will accept the common attributes and declare global {
namespace JSX {
interface IntrinsicElements {
["color-picker"]: HTMLAttributes & {
// Required attribute
space: "rgb" | "hsl" | "hsv",
// Optional attribute
alpha?: boolean
};
}
}
} |
@Alexendoo Awesome, thanks for taking up the torch and getting to the bottom of it. That thought about attributes crossed my mind but somehow I didn't pursue it further. Reading your comment now it makes so much more sense to go that way. Kudos to you and thanks for the great explanation. That's cleared a few things up for me 👍 @surma If you feel like it isn't feel free to ping me to reopen this issue. EDIT: Perhaps we should add a note about it to our docs/Readme? cc @developit |
Definitely worth adding a note. Perhaps a page on the Preact site about usage with TypeScript? Or a .md here. |
Did such a note get added? I still can't figure out how to do this as of Preact X rc0. https://github.com/kuhe/cannot-extend-instrinsic-elements-demo |
Thanks for pinging me. I'll reopen this ticket and mark it as something for the docs. I'm working on the new ones for X and we should write a section about this in the next weeks. |
I put that into a My setup, now including |
@kuhe You have to make sure to include the file in your
Something like the above. |
I tried this include array and it doesn't make a difference. I know the Nevertheless, I updated my demo repo to include |
Strange, it seems as though the behaviour has changed since I last looked into this. Investigating further! |
I copied your screenshot's file contents and structure. Using
Using
|
Unfortunately, declaration merging of namespaces in namespaces is not really supported by typescript. |
Thanks, Peter. I will work around it with a slight local modification of Preact X for my team's project(s) so that we can upgrade from 8.x. |
To clarify: Is there currently a workaround for using CEs that does not involve monkey-patching Preact X? |
A definate workaround is to use h instead of jsx |
As per discussion with @surma, this likely circumvents the issue (but is not a solution): const TestCe = 'test-ce';
const jsx = <TestCe><div>hai</div></TestCe> (JSX turns any tagname with a leading uppercase character or dotted identifier into a variable reference) |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Hi, I had a similar problem today. I have a npm package with custom elements which is owned by me, and also a project which depends on that package. My solution was to declare only a interface inside of the web-components package:
export class UiTitle { /* ... */ }
export interface UiTitleAttributes {
title?: string;
}
import {UiTitle, UiTitleAttributes} from "./components/ui-title";
export const defineElements = () => {
customElements.define("ui-title", UiTitle);
};
export declare interface UiIntrinsicElements<T> {
"ui-title": UiTitleAttributes & T;
} this compiles to import { UiTitleAttributes } from "./components/ui-title";
export declare const defineElements: () => void;
export declare interface UiIntrinsicElements<T> {
"ui-title": UiTitleAttributes & T;
} Next I added import {JSXInternal as PreactJSX} from "preact/src/jsx";
import {UiIntrinsicElements} from "@gira/web-components";
declare module "preact" {
namespace JSXInternal {
interface IntrinsicElements extends PreactJSX.IntrinsicElements, UiIntrinsicElements<PreactJSX.HTMLAttributes> {}
}
} Now I can use the |
Edited: Better solution import { JSXInternal } from 'preact/src/jsx';
import { WhateverElementEvent, WhateverElement } from './custom-whatever';
declare module 'preact/src/jsx' {
namespace JSXInternal {
interface IntrinsicElements {
'custom-whatever': WhateveElAttributes;
}
}
}
interface WhateveElAttributes extends JSXInternal.HTMLAttributes {
someattribute?: string;
onsomeevent?: (this: WhateverElement, ev: WhateverElementEvent) => void;
} |
It seems that the way we extend the As an example the declare module "preact" {
namespace JSXInternal {
/* custom IntrinsicElements extension */
interface ElementChildrenAttribute extends PreactJSX.ElementChildrenAttribute {}
}
} Some of the types inside of preacts declare module "preact" {
namespace JSXInternal {
/* custom IntrinsicElements extension */
interface ElementChildrenAttribute extends PreactJSX.ElementChildrenAttribute {}
interface HTMLAttributes extends PreactJSX.HTMLAttributes {}
interface SVGAttributes extends PreactJSX.SVGAttributes {}
}
} And I wouldn't wonder if there are not more types which will be needed in other coding contexts. I don't think it is a good idea to add all types and interfaces to the custom definition only because namespaces can not extended. ( I believe we need a better solution to add custom elements to the typing. Any ideas? |
The answer was right here by @somarlyonks. Overwriting module import {JSXInternal} from "preact/src/jsx";
declare module "preact/src/jsx" {
namespace JSXInternal {
/* custom IntrinsicElements extension */
}
} |
Whoa, nice! You don't even need to import Edit: I'm wrong, you do need the import. An incremental build made things look ok, but they were not. |
I think this should go on the preact site, but I couldn't get the dev server working due to preactjs/preact-www#605 |
Uh oh - #2575 will break this workaround :( It moves our types from the |
It would be good to make Typescript + custom elements first class in preact. As in, documented, and only breaks backwards compatibility in major versions. |
Agreed. I thought we had a test for this in our TypeScript types tests but I seem to be wrong. We should add one to provably demonstrate it works and prevent regressions as well as add documentation like you suggested |
Hmm seems there was a recent fix in TypeScript that may have resolved some of the issues we were seeing that required Repo: https://github.com/andrewiggins/cannot-extend-instrinsic-elements-demo/tree/explorations // module.tsx
export interface ModuleCE {
instanceProp: string;
}
export interface ModuleCEEvent {
eventProp: string;
}
export interface ModuleCEAttributes extends preact.JSX.HTMLAttributes {
ceProp?: string;
onsomeevent?: (this: ModuleCE, ev: ModuleCEEvent) => void;
}
declare module "preact" {
namespace JSX {
interface IntrinsicElements {
"module-custom-element": ModuleCEAttributes;
}
}
} // app.tsx
import { createElement } from "preact";
export function App() {
return (
<div>
<module-custom-element
ceProp="ceProp"
dir="auto" // Inherited prop from HTMLAttributes
onsomeevent={function (e) {
console.log(e.eventProp, this.instanceProp);
}}
/>
</div>
);
} There are over 100 fixed issues between 3.9.0-beta and 3.9.1-rc so haven't yet figured out which contributed the fix but thought I'd share what I've found so far. |
Okay one other update. The pattern below compiles for me all the back to 3.5.3 (which I think is the min version for Preact's types anyway). Thinking this might be the best guidance for us going forward since I think I remember seeing somewhere the TS team want's to move JSX declarations to be based on the // Works with TypeScript 3.5.3, 3.6.5, 3.7.5, 3.8.3, 3.9.0-beta, 3.9.5
declare module "preact" {
namespace createElement.JSX {
interface IntrinsicElements {
"module-custom-element": ModuleCEAttributes;
}
}
} |
Would love people's thoughts about the patterns I used in |
Reopening until the TypeScript team has published their fix. |
If you want to embed the type with your component file, you can use import { h, render } from 'preact';
import type { FunctionComponent } from 'preact';
// This makes it recgonized by the JSX parts in rest of the application:
declare global {
namespace preact.createElement.JSX {
interface IntrinsicElements {
'oksidi-sharer': { 'share-url': string; };
}
}
}
export const OksidiSharer: FunctionComponent<{ shareUrl: string }> = (
props,
) => {
return <div>Share this {props.shareUrl}</div>;
};
customElements.define(
'oksidi-sharer',
class extends HTMLElement {
private attachRoot: HTMLElement | ShadowRoot;
constructor() {
super();
this.attachRoot = this.attachShadow({ mode: 'open' });
}
connectedCallback() {
let p = {
shareUrl: '' + this.attributes.getNamedItem('share-url')?.value,
};
render(h(OksidiSharer, p, []), this.attachRoot);
}
detachedCallback() {
render(null, this.attachRoot);
}
},
{},
); |
@andrewiggins that doesn't work for me. It means my preact imports stop working: import { h, Component } from 'preact'; Module '"preact"' has no exported member 'h'. Edit: This is with TypeScript 4.0.2 |
ffs, it works if the |
declare global {
namespace JSX {
interface IntrinsicElements {
['custom-element']: any;
}
}
} which works in React 18, fails in @preactjs 10: idea2app/React-MobX-Bootstrap-ts#12 |
@TechQuery Preact doesn't define the JSX in the declare global {
namespace preact.JSX {
interface IntrinsicElements {
'custom-element': { 'foo': string; };
}
}
} |
For library users, there is no multiple JSX configuration fields in single For library developers, they can't define JSX types for all kinds of JSX-compatible engines in the world, so this work must be done by users in their projects manually. It's not a good developer experience in my opinion. |
There's a pretty small handful of JSX libs that see much use, I don't think asking to add an entry per compatible framework is too much of an ask.
It's preferable to the (seemingly only) alternative of competing/fighting types, so I'm not quite sure what you'd like to see change. We've recently added some docs to our site covering this which should hopefully help out here. I'll close this on out as I don't think there's anything actionable for us here (nor does TS seem interested in changing their somewhat React-centric handling of JSX). |
The new typings introduced in #1116 broke custom elements for me. Is there a workaround, am I using
JSX.IntrinsicElements
wrong or do we need a fix for preact’s typings?Test case (attach this to
test/ts/VNode-test.s
):Error:
cc @marvinhagemeister
The text was updated successfully, but these errors were encountered: