hybridsjs / hybrids

Extraordinary JavaScript UI framework with unique declarative and functional architecture
https://hybrids.js.org
MIT License
3.03k stars 85 forks source link

Example of TypeScript Types in file index.d.ts for a web-component. #173

Closed riccardoscalco closed 2 years ago

riccardoscalco commented 3 years ago

Hi, recently I wrote a web component with hybrids. The package score in skypack.dev shows that TypeScript Types are missing in the package.

I am very new to TypeScript and I do not know exactly how to create the index.d.ts file for the web component. I found this page in hybrids docs, where there is an example of how to use TypeScript with hybrids.

However, I prefer to keep the code in plain JS and create the file index.d.ts for the user-base that works with TypeScript.

Could you please provide an example of the file index.d.ts related to a web component created with hybrids? Thanks in advance.

smalluban commented 3 years ago

Hi @riccardoscalco. Your question is more general, and not strictly related to hybrids, but to defining custom elements for the TS. I suppose, that there is no one the best way to do that, but I can recommend looking for some examples. One of the time elements from GitHub produces following d.ts file:

export default class LocalTimeElement extends ExtendedTimeElement {
    attributeChangedCallback(attrName: string, oldValue: string, newValue: string): void;
    getFormattedDate(): string | undefined;
}
declare global {
    interface Window {
        LocalTimeElement: typeof LocalTimeElement;
    }
    interface HTMLElementTagNameMap {
        'local-time': LocalTimeElement;
    }
}

The above definition assumes, that this custom element is also registered in window.LocalTimeElement. For proper recongnision with calls like document.createElement("local-time") they put a tag name into the special HTMLElementTagNameMap.

For the component built with hybrids, follow typescript guides for putting properties into the class, and create something like this:

class MyElement extends HTMLElement {
  propOne: string;
  propTwo: string;
}

declare global {
    interface Window {
        MyElement: typeof MyElement; // if you register your element in global scope
    }
    interface HTMLElementTagNameMap {
        'my-element': LocalTimeElement;
    }
}

There is also a tool https://github.com/runem/web-component-analyzer, which describes how you can add comments/metadata for better IDE completion.

smalluban commented 3 years ago

I'm not sure, but it might be required to add a module statement wrapper, as you expose it as an npm module:

declare module "@nextbitlabs/trend-line" {
  ...
}

For testing the best way is to set up a simple project, which uses TS and import your module by the relative path from the disk npm i ../trend-line to check if d.ts is correct.

riccardoscalco commented 3 years ago

Thanks for the precious suggestions, I give it a try!

smalluban commented 3 years ago

Share your result when it will be ready :)

riccardoscalco commented 3 years ago

An update: I managed to automatically generate file index.d.ts from a slightly modified version of the example in the docs. Note that I used import from URL and plain JS similarly to the mentioned web-component I am working on.

File index.ts is the following:

// @ts-ignore
import { define, html } from 'https://cdn.skypack.dev/hybrids@6';

function increaseCount(host) {
  host.count += 1;
}

export const SimpleCounter = {
  count: 0,
  render: ({ count }) => html`
    <button onclick="${increaseCount}">
      Count: ${count}
    </button>
  `,
};

define('simple-counter', SimpleCounter);

The automatically generated index.d.ts is:

export declare const SimpleCounter: {
    count: number;
    render: ({ count }: {
        count: any;
    }) => any;
};

I am not sure this index.d.ts is useful at all.

smalluban commented 3 years ago

Your automatically generated index.d.ts won't be useful for code completion. It defines the hybrids definition, but users of your library interact with the custom element. You need an interface of your properties and extend global HTMLElementTagNameMap to allow the TS server to find the definition when using document.createElement("my-element") APIs.

Try to follow what I wrote in the last comments.

smalluban commented 2 years ago

Feel free to re-open if you need more help with the subject.