Skip to content
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

Add .d.ts files for improved TypeScript support #33

Merged
merged 5 commits into from
Dec 7, 2023

Conversation

patricknelson
Copy link
Owner

Implement #30.

Copy link

vercel bot commented Dec 4, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
svelte-retag ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 7, 2023 8:52pm

@patricknelson
Copy link
Owner Author

patricknelson commented Dec 4, 2023

@baseplate-admin can you test this out for me please? Best method is going to be for you to clone locally and link it in your project. Here some quick guidance though

  1. Clone svelte-retag to your system
  2. Run npm i in that newly cloned folder
  3. Run yarn link somewhere inside the svelte-retag folder
  4. Go to your project and install svelte-retag (if not already)
  5. Run yarn link svelte-retag to symbolically link the project so you can edit it in-place from the originally cloned dir

Once done testing, reverse the link to your local copy via yarn unlink svelte-retag and then npm i (or yarn or whatever your local package manager is). I don't have a formal contribution guidelines doc yet that explains how to clone and run locally but hopefully you get the gist and I'm just overexplaining 😄 (so, sorry if this messes something up)

@patricknelson
Copy link
Owner Author

p.s. Since I didn’t mention it explicitly (sorry I forgot): Be sure to run npm run build to generate those .d.ts files in the root of the main svelte-retag folder.

@baseplate-admin
Copy link

Hi so i found a bug outside of this PR.

* @param {any} opts.component Svelte component instance to incorporate into your custom element.

This should be

import type { SvelteComponent } from "svelte";
 * @param {typeof SvelteComponent<{}>}       opts.component  Svelte component instance to incorporate into your custom element.

@patricknelson
Copy link
Owner Author

Hi so i found a bug outside of this PR.

Shouldn't that be {SvelteComponent<{}>} and not {typeof SvelteComponent<{}>}?

@param {SvelteComponent<{}>} opts.component Svelte component instance to incorporate into your custom element.

I could incorporate into this PR. If you want. Just ensure you are checked out to issue-30-typescript-dts-files and test it again if I push that update

@patricknelson
Copy link
Owner Author

patricknelson commented Dec 4, 2023

Alternatively (if easier) -- fork svelte-retag, checkout my branch issue-30-typescript-dts-files and issue your own alternative PR (just keep changes limited to this specific issue, please). Thanks!

@baseplate-admin
Copy link

baseplate-admin commented Dec 4, 2023

Hi so i found a bug outside of this PR.

Shouldn't that be {SvelteComponent<{}>} and not {typeof SvelteComponent<{}>}?

@param {SvelteComponent<{}>} opts.component Svelte component instance to incorporate into your custom element.

I could incorporate into this PR. If you want. Just ensure you are checked out to issue-30-typescript-dts-files and test it again if I push that update

Actually i did check the PR, i used this in all my projects.
As you can see here.


image

on another note i am looking into the repository

@patricknelson
Copy link
Owner Author

@baseplate-admin do me a favor please and give me a version of this branch that you see is ideal so I can try it out. This back/forth would probably be a bit more efficient if maybe you showed me a version I can check out (or merge).

You may include a version that commits .d.ts as well so I can give that a try.

@patricknelson
Copy link
Owner Author

patricknelson commented Dec 4, 2023

p.s. @baseplate-admin in my main TypeScript project, this is what I did. However, I am not 100% sure what would need to be done in a JS-only project like this one when using .d.ts files instead. So, your PR would be helpful (which is partly why I ask).

Just to give you an idea, here what I ended up doing (probably pretty amateur) put into types.ts:

// Generic type for anything that can be instantiated.  Copied and modified from https://github.com/microsoft/TypeScript/issues/17572#issue-247539885
export type Instantiable = {new(...args: any[]): any};

// Setup to type hint Svelte component constructors (i.e. the default export of *.svelte files).
export type SvelteComponent = Instantiable;

Then where I use svelteRetag I just pull that in, e.g.

import svelteRetag from 'svelte-retag';
import type { SvelteComponent } from './types';

/**
 * Defines a custom element for the given Svelte component and hyphenated tag name.
 *
 * @param {SvelteComponent}	component The svelte component constructor. Basically, the default export from your MyComponent.svelte file.
 *
 * @param {string}          tagName	  The name of the tag. Must contain a dash and be all lower case.
 *                                    e.g. 'my-component' (for <my-component> tag).
 *
 * @param {array}           syncProps For reactively synchronizing changes to the element attributes with the props on
 *                                    the Svelte component. Not necessary for initialization, just for changes
 *                                    performed after mount.
 */
export function defineTag(component: SvelteComponent, tagName: string, syncProps = []) {
	svelteRetag({
		component: component,
		tagname: tagName,
		attributes: syncProps,
	});
}

@baseplate-admin
Copy link

Fair enough, give me some time to get personal life sorted then i will send a PR

@baseplate-admin
Copy link

baseplate-admin commented Dec 6, 2023

Hi, Sorry for late reply. This works fine for my project :)

Lemme fix the remaining issues.
Aweosme work.

@patricknelson
Copy link
Owner Author

Ok @baseplate-admin I think I know what you meant -- was it that when you added that Instantiable TypeScript type into your application code when it started working? If so, then I think I find the fix in JSDoc on my end here. Can you try this on your end as well and see if it works?


Essentially:

  1. Define a Newable (figure this is more user friendly than Instantiable) type:

    export type Newable = new (...args: any[]) => any;
  2. Map a custom name to it (instead of importing from Svelte) of SvelteComponent just for clarity/visibility in the Intellisense pop-up that you see, i.e.

    export type SvelteComponent = Newable;

Via JSDoc:

/**
 * @typedef {new(...args: any[]) => any} Newable
 * @typedef {Newable} SvelteComponent
 */

And that effectively generates this index.d.ts:

/**
 * @typedef {new(...args: any[]) => any} Newable         Type alias for a really generic class constructor
 * @typedef {Newable}                    SvelteComponent Svelte component class constructor (basically a "newable" object)
 */
/**
 * Please see README.md for usage information.
 *
 * @param {object} opts Custom element options
 *
 * @param {SvelteComponent} opts.component       The Svelte component *class* constructor to incorporate into your custom element (this is the imported component class, *not* an instance)
 * @param {string}          opts.tagname         Name of the custom element tag you'd like to define.
 * @param {string[]}        [opts.attributes=[]] Optional array of attributes that should be reactively forwarded to the component when modified.
 * @param {boolean}         [opts.shadow=false]  Indicates if we should build the component in the shadow root instead of in the regular ("light") DOM.
 * @param {string}          [opts.href=""]       URL to the CSS stylesheet to incorporate into the shadow DOM (if enabled).
 *
 * Experimental:
 * @param {boolean}  [opts.hydratable=false]   Light DOM slot hydration (specific to svelte-retag): Enables pre-rendering of the
 *                                     web component (e.g. SSR) by adding extra markers (attributes & wrappers) during
 *                                     rendering to enable svelte-retag to find and restore light DOM slots when
 *                                     restoring interactivity.
 *
 * @param {boolean|string} [opts.debugMode=false] Hidden option to enable debugging for package development purposes.
 */
export default function svelteRetag(opts: {
    component: SvelteComponent;
    tagname: string;
    attributes?: string[];
    shadow?: boolean;
    href?: string;
    hydratable?: boolean;
    debugMode?: boolean | string;
}): void;
/**
 * Type alias for a really generic class constructor
 */
export type Newable = new (...args: any[]) => any;
/**
 * Svelte component class constructor (basically a "newable" object)
 */
export type SvelteComponent = Newable;

@patricknelson
Copy link
Owner Author

Fair warning @baseplate-admin main has changed a lot, so I'm rebasing, so you'll need to do a hard reset (or also rebase) your local version if you're still testing it locally. Also just to summarize for you:

  1. Rebased (see above)
  2. Updated type definitions and using an "instantiable" (or "newable") type to define the component constructor, which is what we're taking (not a component instance per se).
  3. New: I forgot to mention that, in this branch, I fixed type definitions so that you don't see errors for including optional flags like href or hydratable.
  4. New: I added automatic attribute forwarding in [email protected] which means you no longer need to define every damned attribute. 😅 Give that a whirl (just set attributes: true). This means you can use import.meta.glob() to just automatically define everything without having to specify individual attributes. If you are feeling risky, check out the example autoDefine function I setup here https://github.com/patricknelson/svelte-retag/blob/main/demo/vercel/src/main.js#L39-L88 (but use at your own risk, I'd suggest just modifying it to suit your needs for now). However, it does take an options callback as a third parameter which can accept tagName and options object that you can adjust as needed, if needed.

Good luck.

…nt a "newable" constructor type and also fixing the optional types by defining their defaults, so the generated .d.ts properly suffixes the "?" at the end of each optional field.
@patricknelson
Copy link
Owner Author

patricknelson commented Dec 7, 2023

You can also test this as a beta release, too (what I'm doing in my project, since symlinks aren't great for Intellisense in my case on Windows):

npm install [email protected]

Edit: Then in your .ts, try:

import type { SvelteComponent } from 'svelte-retag/types';

🤔 Wonder if I should give that another name.

@baseplate-admin
Copy link

baseplate-admin commented Dec 7, 2023

import svelteRetag from "svelte-retag";

const mappings = [
    { tagname: "markdown", component: await import("$components/minor/Markdown/Index.svelte"), attributes: [`markdown`, `class`] },
    { tagname: "scroll-area", component: await import("$components/minor/ScrollArea/Index.svelte"), attributes: [`parent_class`, `offset_scrollbar`, `gradient_mask`, `class`] },
    { tagname: "comment", component: await import("$components/minor/Comment/Index.svelte"), attributes: [`api_url`] }
];

mappings.forEach((item) => {
    svelteRetag({
        component: item.component.default,
        tagname: `coreproject-${item.tagname}`,
        attributes: item.attributes, // Changes to these attributes will be reactively forwarded to your component
        shadow: false, // Use the light DOM
        hydratable: false
    });
});

This type of code works with [email protected]


import type { SvelteComponent } from 'svelte-retag/types';

This is not necessary


New: I added automatic attribute forwarding in [email protected] which means you no longer need to define every damned attribute. 😅 Give that a whirl (just set attributes: true). This means you can use import.meta.glob() to just automatically define everything without having to specify individual attributes. If you are feeling risky, check out the example autoDefine function I setup here https://github.com/patricknelson/svelte-retag/blob/main/demo/vercel/src/main.js#L39-L88 (but use at your own risk, I'd suggest just modifying it to suit your needs for now). However, it does take an options callback as a third parameter which can accept tagName and options object that you can adjust as needed, if needed.

My god this is a godsend, i cant thank you how much this helps. Thank you for this :D

@patricknelson
Copy link
Owner Author

Sweet. I’ll work on this tomorrow and probably have 1.5.0 out by then.

Also @baseplate-admin

  1. When 1.5.0 is out, you won’t need to set shadow or hydratable in TypeScript since this branch also fixes the issue of those options showing up as required.
  2. Have you considered grouping your components into folders and then using import.meta.glob to automate (without mapping)? That should now be feasible in 1.4.0 w/ the attributes: true option since you no longer need to cater each call to svelteRetag() to include the customized attribute list. 😊

@patricknelson
Copy link
Owner Author

p.s.

define every damned attribute. 😅 Give that a whirl (just set attributes: true). This means you can use import.meta.glob() to just automatically define everything without having to specify individual attributes. If you are feeling risky, check out the example autoDefine function I setup here https://github.com/patricknelson/svelte-retag/blob/main/demo/vercel/src/main.js#L39-L88 (but use at your own risk, I'd suggest just modifying it to suit your needs for now). However, it does take an options callback as a third parameter which can accept tagName and options object that you can adjust as needed, if needed.

My god this is a godsend, i cant thank you how much this helps. Thank you for this :D

You’re very welcome! To be fair, I’m obviously a big user of svelte-retag and we’re working on a huge project (redesigning the homepage of a site at my company) and we’re building out a ton of web components for it, so this has been a major pain point for us. I’m hoping you find it useful too!

index.js Outdated Show resolved Hide resolved
… custom type for 'component' to prevent any confusion with an actual Svelte type.
@patricknelson patricknelson merged commit ea40d27 into main Dec 7, 2023
3 checks passed
@patricknelson patricknelson deleted the issue-30-typescript-dts-files branch December 7, 2023 20:56
@patricknelson
Copy link
Owner Author

This is published now as [email protected] but noticing some issues in PhpStorm right now. Might work fine in VSCode though. Still need to figure out why the types aren't being properly inferred in my IDE. 🤕

@patricknelson
Copy link
Owner Author

Ah, never mind... false alarm. It was a caching issue on my end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add .d.ts files for improved TypeScript support
2 participants