Giter Site home page Giter Site logo

carbon-design-system / sveld Goto Github PK

View Code? Open in Web Editor NEW
387.0 9.0 22.0 1.04 MB

Generate TypeScript definitions for your Svelte components

Home Page: https://sveld.onrender.com

License: Apache License 2.0

JavaScript 7.25% Svelte 74.90% TypeScript 17.71% HTML 0.14%
svelte svelte-component documentation docgen typescript-definitions jsdoc

sveld's Introduction

sveld

NPM GitHub npm downloads to date

sveld generates TypeScript definitions for Svelte components by statically analyzing their props, events, slots and more. Prop types and signatures can be defined using JSDoc notation. This documentation generator can also emit component documentation in Markdown and JSON output formats.

The purpose of this project is to make third party Svelte component libraries compatible with the Svelte Language Server and TypeScript with minimal effort required by the author. For example, TypeScript definitions may be used during development via intelligent code completion in Integrated Development Environments (IDE) like VSCode.

Carbon Components Svelte uses this library to auto-generate component types and API metadata:

Please note that the generated TypeScript definitions require Svelte version 3.55 or greater.


Given a Svelte component, sveld can infer basic prop types to generate TypeScript definitions compatible with the Svelte Language Server:

Button.svelte

<script>
  export let type = "button";
  export let primary = false;
</script>

<button {...$$restProps} {type} class:primary on:click>
  <slot>Click me</slot>
</button>

The generated definition extends the official SvelteComponentTyped interface exported from Svelte.

Button.svelte.d.ts

import type { SvelteComponentTyped } from "svelte";
import type { SvelteHTMLElements } from "svelte/elements";

type RestProps = SvelteHTMLElements["button"];

export interface ButtonProps extends RestProps {
  /**
   * @default "button"
   */
  type?: string;

  /**
   * @default false
   */
  primary?: boolean;
}

export default class Button extends SvelteComponentTyped<
  ButtonProps,
  { click: WindowEventMap["click"] },
  { default: {} }
> {}

Sometimes, inferring prop types is insufficient.

Prop/event/slot types and signatures can be augmented using JSDoc notations.

/** @type {"button" | "submit" | "reset"} */
export let type = "button";

/**
 * Set to `true` to use the primary variant
 */
export let primary = false;

The accompanying JSDoc annotations would generate the following:

import type { SvelteHTMLElements } from "svelte/elements";

type RestProps = SvelteHTMLElements["button"];

export interface ButtonProps extends RestProps {
  /**
   * @default "button"
   */
  type?: "button" | "submit" | "reset";

  /**
   * Set to `true` to use the primary variant
   * @default false
   */
  primary?: boolean;
}

export default class Button extends SvelteComponentTyped<
  ButtonProps,
  { click: WindowEventMap["click"] },
  { default: {} }
> {}

Table of Contents

Approach

sveld uses the Svelte compiler to statically analyze Svelte components exported from a library to generate documentation useful to the end user.

Extracted metadata include:

  • props
  • slots
  • forwarded events
  • dispatched events
  • $$restProps

This library adopts a progressively enhanced approach. Any property type that cannot be inferred (e.g., "hello" is a string) falls back to "any" to minimize incorrectly typed properties or signatures. To mitigate this, the library author can add JSDoc annotations to specify types that cannot be reliably inferred. This represents a progressively enhanced approach because JSDocs are comments that can be ignored by the compiler.

Usage

Installation

Install sveld as a development dependency.

yarn add -D sveld
# OR
npm i -D sveld
# OR
pnpm i -D sveld

Rollup

Import and add sveld as a plugin to your rollup.config.js.

// rollup.config.js
import svelte from "rollup-plugin-svelte";
import resolve from "@rollup/plugin-node-resolve";
import sveld from "sveld";

export default {
  input: "src/index.js",
  output: {
    format: "es",
    file: "lib/index.mjs",
  },
  plugins: [svelte(), resolve(), sveld()],
};

When building the library, TypeScript definitions are emitted to the types folder by default.

Customize the output folder using the typesOptions.outDir option.

The following example emits the output to the dist folder:

sveld({
+  typesOptions: {
+    outDir: 'dist'
+  }
})

The tests/e2e folder contains example set-ups:

CLI

The CLI wraps the Rollup plugin and uses the "svelte" field defined in your package.json as the entry point.

npx sveld

Append --json or --markdown flags to generate documentation in JSON/Markdown formats, respectively.

npx sveld --json --markdown

Node.js

You can also use sveld programmatically in Node.js.

If no input is specified, sveld will infer the entry point based on the package.json#svelte field.

const { sveld } = require("sveld");
const pkg = require("./package.json");

sveld({
  input: "./src/index.js",
  glob: true,
  markdown: true,
  markdownOptions: {
    onAppend: (type, document, components) => {
      if (type === "h1")
        document.append("quote", `${components.size} components exported from ${pkg.name}@${pkg.version}.`);
    },
  },
  json: true,
  jsonOptions: {
    outFile: "docs/src/COMPONENT_API.json",
  },
});

jsonOptions.outDir

If json is true, a COMPONENT_API.json file will be generated at the root of your project. This file contains documentation for all components.

Use the jsonOptions.outDir option to specify the folder for individual JSON files to be emitted.

sveld({
  json: true,
  jsonOptions: {
    // an individual JSON file will be generated for each component API
    // e.g. "docs/Button.api.json"
    outDir: "docs",
  },
});

Publishing to NPM

TypeScript definitions are outputted to the types folder by default. Don't forget to include the folder in your package.json when publishing the package to NPM.

{
  "svelte": "./src/index.js",
  "main": "./lib/index.mjs",
+ "types": "./types/index.d.ts",
  "files": [
    "src",
    "lib",
+   "types",
  ]
}

Available Options

By default, only TypeScript definitions are generated.

To generate documentation in Markdown and JSON formats, set markdown and json to true.

sveld({
+  markdown: true,
+  json: true,
})

API Reference

@type

Without a @type annotation, sveld will infer the primitive type for a prop:

export let kind = "primary";
// inferred type: "string"

Use the @type tag to explicitly document the type. In the following example, the kind property has an enumerated (enum) type.

Signature:

/**
 * Optional description
 * @type {Type}
 */

Example:

/**
 * Specify the kind of button
 * @type {"primary" | "secondary" | "tertiary"}
 */
export let kind = "primary";

/**
 * Specify the Carbon icon to render
 * @type {typeof import("carbon-icons-svelte").CarbonIcon}
 */
export let renderIcon = Close20;

@typedef

The @typedef tag can be used to define a common type that is used multiple times within a component. All typedefs defined in a component will be exported from the generated TypeScript definition file.

Signature:

/**
 * @typedef {Type} TypeName
 */

Example:

/**
 * @typedef {string} AuthorName
 * @typedef {{ name?: AuthorName; dob?: string; }} Author
 */

/** @type {Author} */
export let author = {};

/** @type {Author[]} */
export let authors = [];

@slot

Use the @slot tag for typing component slots. Note that @slot is a non-standard JSDoc tag.

Descriptions are optional for named slots. Currently, the default slot cannot have a description.

Signature:

/**
 * @slot {Type} slot-name [slot description]
 */

Omit the `slot-name` to type the default slot.

/**
 * @slot {Type}
 */

Example:

<script>
  /**
   * @slot {{ prop: number; doubled: number; }}
   * @slot {{}} title
   * @slot {{ prop: number }} body - Customize the paragraph text.
   */

  export let prop = 0;
</script>

<h1>
  <slot {prop} doubled={prop * 2} />
  <slot name="title" />
</h1>

<p>
  <slot name="body" {prop} />
</p>

@event

Use the @event tag to type dispatched events. An event name is required and a description optional.

Use null as the value if no event detail is provided.

Signature:

/**
 * @event {EventDetail} eventname [event description]
 */

Example:

/**
 * @event {{ key: string }} button:key
 * @event {null} key โ€“ Fired when `key` changes.
 */

export let key = "";

import { createEventDispatcher } from "svelte";

const dispatch = createEventDispatcher();

$: dispatch("button:key", { key });
$: if (key) dispatch("key");

Output:

export default class Component extends SvelteComponentTyped<
  ComponentProps,
  {
    "button:key": CustomEvent<{ key: string }>;
    /** Fired when `key` changes. */ key: CustomEvent<null>;
  },
  {}
> {}

@restProps

sveld can pick up inline HTML elements that $$restProps is forwarded to. However, it cannot infer the underlying element for instantiated components.

You can use the @restProps tag to specify the element tags that $$restProps is forwarded to.

Signature:

/**
 * Single element
 * @restProps {tagname}
 *
 * Multiple elements
 * @restProps {tagname-1 | tagname-2 | tagname-3}
 */

Example:

<script>
  /** @restProps {h1 | button} */
  export let edit = false;

  import Button from "../";
</script>

{#if edit}
  <Button {...$$restProps} />
{:else}
  <h1 {...$$restProps}><slot /></h1>
{/if}

@extends

In some cases, a component may be based on another component. The @extends tag can be used to extend generated component props.

Signature:

/**
 * @extends {<relative path to component>} ComponentProps
 */

Example:

/** @extends {"./Button.svelte"} ButtonProps */

export const secondary = true;

import Button from "./Button.svelte";

@component comments

The Svelte Language Server supports component-level comments through the following syntax: <!-- @component [comment] -->.

sveld will copy these over to the exported default component in the TypeScript definition.

Example:

<!-- @component
@example
<Button>
  Text
</Button>
-->
<button>
  <slot />
</button>

Output:

/**
 * @example
 * <Button>
 *   Text
 * </Button>
 */
export default class Button extends SvelteComponentTyped<ButtonProps, {}, { default: {} }> {}

Contributing

Refer to the contributing guidelines.

License

Apache-2.0

sveld's People

Contributors

aabounegm avatar akkumar avatar berndfuhrmann avatar brunnerh avatar ethan-jones-vizio avatar jelib3an avatar mathisonian avatar mean2me avatar metonym avatar neelneelneel avatar severecloud avatar stevemar avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sveld's Issues

Support programmatic usage

I want to be able to use sveld programmatically in Node.js.

The CLI doesn't fit my use case as I want to tap into the onAppend callback when generating the Markdown file.

I also don't want it to be coupled with Rollup only.

import sveld from "sveld";

sveld({
  glob: true,
  markdown: true,
  markdownOptions: {
    onAppend: (type, document, components) => {
      if (type === "h1")
        document.append(
          "quote",
          `${components.size} components exported from ${pkg.name}@${pkg.version}.`
        );
    },
  },
  json: true,
  jsonOptions: {
    outFile: "docs/src/COMPONENT_API.json",
  },
});

Generics support

Would it be possible to introduce TypeScript generic type parameters support? TS recognizes @template tags from JSDoc annotations for type parameters, if that would be of any help.

From what I've tested, sveld would currently generate prop and slot typings with the type parameter(s) included, but because the exported class and interface do not have a type parameter list, it is invalid.

Allow specifying an entry point in programmatic usage

Ref: carbon-design-system/carbon-components-svelte#1636 (comment)

Currently, the getSvelteEntry relies purely on package.json for the Svelte entry point. It will throw an error if the "svelte" field is absent in the root package.json.

The plugin that allows programmatic usage should allow the entry point to be specified.

E.g.,

sveld({
  entry: "src/index.js",
  glob: true,
  markdown: true,
  markdownOptions: {
    onAppend: (type, document, components) => {
      if (type === "h1")
        document.append(
          "quote",
          `${components.size} components exported from ${pkg.name}@${pkg.version}.`
        );
    },
  },
  json: true,
  jsonOptions: {
    outFile: "docs/src/COMPONENT_API.json",
  },
});

@slot and @event description bugs

Hey, my name is Chris and I'm the project lead and core contributor to Skeleton:
https://skeleton.dev/

We're in the process of integrating Sveld to automatically document our various components in our library. Unfortunately we're running into a couple issues with the new @slot and @event description fields introduced here:

Here's how we're defining our JSDoc comments for a component. This includes 3x Slots and 1x forwarded Event:

Screen Shot 2022-10-24 at 5 34 24 PM

Issue 1 - First Slot Always Dropped

NOTE we do not use the default slot on this particular component.

Per our JSDoc definitions above you'll see we're adding an empty @slot definition, which should come out to a total of 4x slots. But we've noted that regardless of how we structure or order the comments, the first @slot definition is always pruned. The only work around we've discovered is to insert an empty slot to be "sacrificed".

Screen Shot 2022-10-24 at 5 35 00 PM

Issue 2 - @event changes types

We've applied a single on:click event to our template. This should be forwarded up. Here's what that looks like if we do not specify a JSDocs comment, just allow Sveld to auto-document it:

Screen Shot 2022-10-24 at 5 38 57 PM

The above is correct, though missing the description obviously since one isn't specified.

However, when we do specify a description, we see two issues:

  • The type information incorrectly changes from forwarded -> dispatched
  • We lose the element value - though I believe this is due to the fact it's being treated as "dispatched"

Screen Shot 2022-10-24 at 5 38 27 PM

FYI we're still testing, so we might update this post or create another if we discover other issues. However, these are the two most pressing we've encountered.

Missing type def for tooltipBodyId attr

Tooltip's tooltipBodyId does not appear in type definition.

The cause may be here:

aria-describedby="{$$props['tooltipBodyId']}"

export let ... style is not used.

When I use tooltipBodyId, error is reported by svelte language server in lang=ts.

It'll also issued by svelte-check.

Error: Type '{ open: boolean; tooltipBodyId: string; hideIcon: true; }' is not assignable to type 'IntrinsicAttributes & TooltipProps'.
  Property 'tooltipBodyId' does not exist on type 'IntrinsicAttributes & TooltipProps'. (ts)

<Tooltip {open} tooltipBodyId="tooltip-body" hideIcon>
  <p id="tooltip-body">Copied!</p>

Parse typescript components

It would be wonderful if Sveld could generate proper typescript declarations for typescript components (ie: lang="ts), rather than only JS-annotated types.

Grabbing other tags

It would be handy if Sveld could grab any other present @tags in the JSDoc comment block and add them to the props exported per entity found. We would like to use things like @see to point to external docs and also use things like custom @Childof and @Parentof to help show relationships.

FYI we are using Sveld through vite-plugin-sveld on a svelte kit project and we don't see any extra tags come through when adding them (just in case the Sveld CLI behaves differently.

Type definition for exported module functions is incorrect

Follow #70, it is now possible to generate type definition for exported module functions. E.g.:

<script context="module">
  /**
   * Log something
   * @type {(message: string) => void}
   */
  export function log(message) {
    console.log(message);
  }
</script>

is now turned into the following type definition:

/**
 * Log something
 */
export type log = (message: string) => void;

The problem is that this type definition will throw log only refers to a type, but is being used as a value here:

<script lang="ts">
  import { log } from 'my-component';

  function logNow() {
    log('Hi');
  }
</script>

The issue is that we actually want log() to be declared as a function:

declare function log(message: string): void;

Ideally, the JS doc string would look as follows but I assume this isn't supported yet (given the static () => any type):

<script context="module">
  /**
   * Log something
   * @function log
   * @param {string} message
   * @return {void}
   */
  export function log(message) {
    console.log(message);
  }
</script>

Is there any way sveld supports creating function declaration at the moment or would you be open to adding it?

CompileError [ParseError]: <script> must have a closing tag

When creating a const String variable inside <script></script> which contains the string <style> (e.g. while injecting a css style into an iframe later on), I receive CompileError [ParseError]: <script> must have a closing tag as an error and sveld stops generating.

The code works fine under normal Svelte conditions. Wondering if another preprocessor must run before rollup-plugin.js:164

Example:

<script>
const css=`<style>
		body{ color: white; }
		a:link { color: #ccc; }
		ins { background: green; text-decoration: none; }
		del { background:red;text-decoration: none; }
</style>`
....
</script>

Output:


CompileError [ParseError]: <script> must have a closing tag
    at error (/Users/xyz/node_modules/sveld/node_modules/svelte/compiler.js:13213:20)
    at Parser$1.error (/Users/xyz/code/improve-carbon/node_modules/sveld/node_modules/svelte/compiler.js:13291:10)
    at Object.read_script [as read] (/Users/xyz/code/improve-carbon/node_modules/sveld/node_modules/svelte/compiler.js:9001:17)
    at tag (/Users/xyz/code//node_modules/sveld/node_modules/svelte/compiler.js:12237:34)
    at new Parser$1 (/Users/xyz/code/node_modules/sveld/node_modules/svelte/compiler.js:13250:22)
    at parse$b (/Users/xyz/code//node_modules/sveld/node_modules/svelte/compiler.js:13390:21)
    at compile (/Users/xyz/code/node_modules/sveld/node_modules/svelte/compiler.js:44085:18)
    at ComponentParser.parseSvelteComponent (/Users//code//node_modules/sveld/lib/ComponentParser.js:240:48)
    at /Users/xyz//node_modules/sveld/lib/rollup-plugin.js:164:112
    at step (/Users/xyz//node_modules/sveld/lib/rollup-plugin.js:67:23) {
  code: 'unclosed-script',
  start: { line: 35, column: 13, character: 1064 },
  end: { line: 35, column: 13, character: 1064 },
  pos: 1064,
  filename: undefined,
  frame: '33: \n' +
    "34:   if (themeValue === 'g90' || themeValue === 'g80' || themeValue === 'g100') { //dark mode\n" +
    '35:     const css="\n' +

SvelteKit Support (Vite compatibility?)

I'm currently creating a component library using SvelteKit's package feature, and am interested in using sveld to automatically generate markdown tables for my component API docs. When attempting to initialize sveld as a vite plugin, the build fails stating that sveld is not a function.

While Vite does offer loose compatibility with rollup plugins, it seems that either I misconfigured something, or sveld uses a function specific to rollup only.

$lib imports won't result in correct path - no such file or directory

When parsing a Svelte component that imports paths with an alias (paths defined in tsconfig.json) such as $lib, Sveld cannot substitute the correct path.

Example:
Given an import path:
import ExampleComponent from '$lib/components/ExampleComponent.svelte';
results into error:

[Error: ENOENT: no such file or directory, open '/Users/xyz/Documents/example-project/src/lib/$lib/components/ExampleComponent.svelte']

QuickFix:
I added the following two lines to line 108 in sveld/lib/parse-export.js

node.source.value = node.source.value.replace('$lib/','')
console.log("-->parsing ImportDeclaration",node.source.value)

Generated `filePath` in API is platform dependent

I tried to implement something in carbon-components-svelte but on build it caused changes like this all over COMPONENT_API.json:

   "components": [
     {
       "moduleName": "Accordion",
-      "filePath": "src/Accordion/Accordion.svelte",
+      "filePath": "src\\Accordion\\Accordion.svelte",
       "props": [
         {
           "name": "align",

Because Windows ๐Ÿ˜‘

backslashes in js export

After some debugging, I found out that there seems to be a bug here:
https://github.com/IBM/sveld/blob/d54c27b31cb8eb7e6d6085b254eb09833f5b6b77/src/rollup-plugin.ts#L65
path.relative is used to relativize the import paths, but under Microsoft Windows, path.relative will use backslashes instead of slashes. This is undesired for import paths, which always uses slashes.
If needed, I can provide a test project, will take me a couple of hours to prepare though.
sveld version: 0.8.0

multi-export example not working properly

Hello. I have been trying to use sveld for my package but failed to do so, so I tried cloning this repo and running the multi-export example, but it did not work as expected.

Reproduction steps:

  • Clone the repo
  • cd sveld/integration/multi-export
  • Run yarn install and yarn add sveld
  • Run rm -r types and rm COMPONENT_* (to remove the existing generated types and generate them again)
  • yarn build

Results:
It produces the component types in the root (multi-export) directory and the following index.d.ts in the types directory:

export { default as Button } from "./..Button";
export { default as Link } from "./..Link";
export { default as Quote } from "./..Quote";

Environment:

  • Node v12.18.2
  • Yarn v1.22.4
  • Windows 10 (20H2 update, Build 19042)

The required props are generated as optional

If we have the following component:

<script>
  /**
   * @type {string}
   */
  export let a;
</script>

it has a required prop a, but the generated prop types mark it as optional:

export interface ComponentProps {
  a?: string;
}

Is this inteded behaviour? I imagine it may be misleading for the users of typings to not know which props are required.

event description/detail?

Hello,

how do I get more information about an event in Markdown/JSON?

    /**
     * Will be fired if value has been changed
     * @event change
     */
    function handleChange(e) {
        dispatch("change", {
            value: value //passing argument
        })
    }

I also tried: https://jsdoc.app/tags-event.html
/**

  • Snowball event.
  • @event snowball
  • @type {object}
  • @Property {boolean} isPacked - Indicates whether the snowball is tightly packed.
    */

But there is no description and detail is always empty.

Thank you!

Exported functions are detected as props

The export function myFunc() {} syntax is treated the same way as export let myFunc = () => {}, even though it shouldn't be. As mentioned in the Svelte docs, export function () {} makes it a read-only export that exposes that function to the parent component (to be used with bind:this). However, I have the following component:

<script>
  export function showSnackbar(options) {
    // some code
  }
</script>

<slot {showSnackbar} />

(irrelevant code removed - full component can be found here)
And this generates typings as if showSnackbar is a prop that accepts a function, and uses the full implementation code as the default value. .d.ts:

/// <reference types="svelte" />
import { SvelteComponentTyped } from "svelte";

export interface SnackbarContainerProps {
  /**
   * @default () => { const { component = Snackbar, props = {}, duration = 4000 } = options; const key = { component, props }; key.props.closeCallback = function close() { clearTimeout(key.timeoutID); removeSnackbar(key, true); }; key.timeoutID = setTimeout(removeSnackbar, duration, key, false); registeredSnackbars.add(key); registeredSnackbars = registeredSnackbars; return { close: key.props.closeCallback, expired: new Promise(resolve => (key.resolveExpiredPromise = resolve)), }; }
   */
  showSnackbar?: () => any;
}

export default class SnackbarContainer extends SvelteComponentTyped<
  SnackbarContainerProps,
  {},
  { default: { showSnackbar: () => any } }
> {}

I think this might be a Svelte limitation (as I don't see how can this be properly represented with SvelteComponentTyped), but even then maybe it's best to ignore function exports.

The non-literal defaults aren't recognized correctly

Imagine you have a JS file which exports some enum:

export default Object.freeze({
  TOP_LEFT: 'top left',
  TOP_RIGHT: 'top right',
});

You want your prop to be one of the values from that enum, so you write the following in your Svelte component:

<script>
  import ThatEnum from 'path/to/enum';
  /**
   * @type {typeof import('path/to/enum').default}
   */
  export let position = ThatEnum.TOP_LEFT;
</script>

The generated typings lack the information on the default value of this prop:

export interface ComponentProps {
  position?: typeof import("path/to/enum").default;
}

Extend from SvelteComponentTyped, not SvelteComponent

Unfortunately the change to introduce a typed SvelteComponent was an accidental breaking change for people doing this: const foo: typeof SvelteComponent = SomeSpecificComponent because there was a type error now. Therefore the typing was reverted on SvelteComponent, the strongly typed component is instead exported through a new extendable class SvelteComponentTyped. This should become extends SvelteComponentTyped<...>.
Sorry for the inconvenience.

When generating d.ts, is it possible to export Props as well?

The current output looks like this.

export { default as Button } from "./components/Buttons/Button.svelte";
export { default as LinkButton } from "./components/Buttons/LinkButton.svelte";
export { default as JellyButton } from "./components/Buttons/JellyButton.svelte";
...

Is it possible to make it like this?

export { default as Button, ButtonProps } from "./components/Buttons/Button.svelte";
export { default as LinkButton, LinkButtonProps } from "./components/Buttons/LinkButton.svelte";
export { default as JellyButton, JellyButtonProps } from "./components/Buttons/JellyButton.svelte";

Or some other solution..

Type annotations for module="context" scripts exports

First of all, huge thanks for this library! ๐ŸŽ‰ It's a joy to work with.

I'm running into an issue with global exports defined in <script context="module" /> blocks. The generated type annotation does not properly reflect the static export.

Say for example I have the following single component:

<!-- welcome.svelte -->
<script context="module">
  /**
   * Log something
   * @type {(message: string) => void}
   */
  export function log(message) {
    console.log(message);
  }
</script>

<script>
  /**
   * Welcome message
   * @type string
   */
  export let message = 'there';
</script>

<h1>Hi {message}</h1>

To generate type definitions with Sveld I'd add the following index.js file:

export { default, default as Welcome, log } from './welcome.svelte';

Say I publish this package as my-cool-welcome-svelte-component, then in another typescript-powered Svelte app I can import the component and log() as expected:

<script lang="ts">
  import Welcome, { log } from 'my-cool-welcome-svelte-component';
  log('something interesting');
</script>

<Welcome message="cool user" />

However, TS will error and say that log is not exported by my-cool-welcome-svelte-component. I am not a TypeScript expert, so please forgive if my thinking is incorrect, but the issue appears to be that log will be typed as a static class method rather than a normal export.

Currently the types/welcome.svelte.d.ts looks as follows:

/// <reference types="svelte" />
import { SvelteComponentTyped } from "svelte";

export interface WelcomeProps, {
  /**
   * Welcome message
   * @default 'there'
   */
  message: string;
}

export default class Welcome extends SvelteComponentTyped<
  WelcomeProps,
  { },
  { default: {} }
> {
  /**
   * Log something
   */
  log: (message: string) => void;
}

Is there an issue with my setup or is this a limitation of Sveld? If it's a limitation, do you guys by chance have an idea how one could still make log() appear as a normal export type log = (message: string) => void;?

custom element issue

Hi,

I am getting:

npx sveld --json --markdown The 'tag' option is used when generating a custom element. Did you forget the 'customElement: true' compile option? .[..]/node_modules/rollup/dist/shared/rollup.js:158

yes it is a custom element and it works fine and it is defined with "true" in rollup.

Components containing Sass

I get a parse error if one of my components contains sass/scss. Is there a way to have either sveld preprocess or just ignore the styles section?

Dies when a function is exported as a prop

In the example svelte file Test.svelte with the contents:

<script>
    export const x = () => console.log('works')
</script>

it generates

  /// <reference types="svelte" />
  import { SvelteComponentTyped } from "svelte";
  
  
  
    export interface TestProps  {
      
    }
  

  export default class Test extends SvelteComponentTyped<
      TestProps,
      {},
      {}
    > {
      
    
    x: () => console.log('works');
    }
    ```

Copy Component-level doc comments to generated definitions

Svelte supports component doc comments, however this documentation is not shown when sveld generated types are present. To fix this, I propose copying that documentation to the typescript class.

Example:

Button.svelte

<script>
  export let type = "button";
  export let primary = false;
</script>

<!-- @component A button component -->

<button {...$$restProps} {type} class:primary on:click>
  <slot>Click me</slot>
</button>

Generated Interface (diff from current state):

 import { SvelteComponentTyped } from "svelte";

 export interface ButtonProps extends svelte.JSX.HTMLAttributes<HTMLElementTagNameMap["button"]> {
   /**
    * @default "button"
    */
   type?: string;

   /**
    * @default false
    */
   primary?: boolean;
 }

+/** A button component */
 export default class Button extends SvelteComponentTyped<
   ButtonProps,
   { click: WindowEventMap["click"] },
   { default: {} }
 > {}

Can't handle all slot types

We have a component that returns props, using the spread syntax:

<slot {...returnProps} />

It fails to build with this error:

[!] (plugin plugin-sveld) TypeError: Cannot read property '0' of undefined
TypeError: Cannot read property '0' of undefined
    at /home/ghost/Desktop/polykit/node_modules/sveld/lib/ComponentParser.js:281:34

[QUESTION] Clarification for purpose of Sveld

Hi everyone,

I'm still new to Svelte and typescript, and have been looking for the best way to type-check props objects I use to construct my components, as well as validate the props passed to my components.

I'm not sure if this is what Sveld does, and I don't know how to ask the right question as I'm still a beginner. What I'm hoping Sveld does is give me some single way to define an interface for data I would pass as component props that would also be used to validate my component props. I apologize if I don't understand the purpose of the project, I've taken a look at the carbon components usage of sveld and seen the generated types folder, but am still unsure of how things works as alot of internal functions are called. Are we supposed to include the types from the types folder and use that exported default class as an interface?

I'll describe my problem with Svelte in hopes that either Sveld is the solution I'm looking for, or someone can point me in the right direction :)

For example, if I had a Todo component, then I want to make sure that the 'text' prop passed to it is a string (this I have achieved using the prop-types library from React), but I also want to check that any Todo "items" I have saved in a list are of type Todo, which isn't possible using prop-types, I would need a typescript interface for this.

// App.svelte
let todos: TodoInterface[] = [ {'text': 'test'}]; // Need to statically type check data I pass as props
...
{#each todos as todo}
<Todo text={todo.text} />  // But also want to validate props at runtime
...

Now I have to define an interface, and then write the same type 'string' for prop-types to validate props passed to my component at runtime.

// Todo.svelte
const propTypes = {
      text: PropTypes.string.isRequired = ""
 };

  // Interface for Todo items
 export interface TodoInterface {
      text: string;
  }

PropTypes.checkPropTypes(propTypes, $$props, "prop", "Todo");

Would Sveld solve this issue? If not, is there a way to resolve it? All I'm trying to accomplish is write better code.
Again, I apologize if my misunderstanding of Sveld annoys anyone.

Thanks.

This but for typed web components

Have you considered (or tried) generating typed web components instead of Svelte components?
i.e. generating types that extend from regular HTMLAttributes not svelte.JSX.HTMLAttributes etc.

I just wanted to check to see if this was a route you'd explored before running into roadblocks.
(I'm not overly familiar with SvelteJS so you might be able to tell me this is a bad idea!)

how to remove code of function in documentation?

Hi,
I try to make documentation for a function:

    /**
     * get Theme object
     * @param {string} status
     * @param {string} size
     * @param {string} disabled
     * @param {string} theme
     * @returns {object}
     */
    export function getTheme(status,size,disabled,theme) {

In Markdown and JSON I get the whole code from inside the function inside the markdown/JSON (value attribute) in "Prop" list.
How to avoid this?

Thank you!

Unable to use .glob option

As per docs :

sveld({
  input: "./src/index.js",
  glob: true,
  markdown: true,
  markdownOptions: {
    onAppend: (type, document, components) => {
      if (type === "h1")
        document.append("quote", `${components.size} components exported from ${pkg.name}@${pkg.version}.`);
    },
  },
  json: true,
  jsonOptions: {
    outFile: "docs/src/COMPONENT_API.json",
  },
});

But

const entry = fs.readFileSync(input, "utf-8");

  const dir = fs.lstatSync(input).isFile() ? path.dirname(input) : input;
  const entry = fs.readFileSync(input, "utf-8");

throws error as fs.readFileSync fails on a directory

Type definitions for setContext

I am wondering if it's possible to annotate setContext calls such that the exposed variables are typed.

For example:

<!-- MyLogger.svelte -->
<script context="module">
  import { setContext } from 'svelte';
  /**
   * Log something
   * @type {(message: string) => void}
   */
  function log(message) {
    console.log(message);
  }

  setContext('my-logger', log);
</script>

My problem is that even though log has type annotations, when someone gets the function via getContext('my-logger') the type is unknown.

I wonder if there's a way to add type annotations to setContext.

Slot props are not properly typed

In the slot props, the values seem to be copied as they are instead of detecting their type. For example, I have the following snippet:

<script>
const selfControl = {
  toggle() {
    // implementation redacted for brevity
  },
};
</script>

<li>
  <slot name="handle" toggle={selfControl.toggle}>
    <Button on:click={selfControl.toggle}>{label}</Button>
  </slot>
</li>

The generated .d.ts file contains the following:

// Props definition excluded
  export default class AccordionSection extends SvelteComponentTyped<
      AccordionSectionProps,
      {},
      {default: {}
;
handle: { toggle: {selfControl.toggle} }
;}
    > {}

(The formatting gets messed up if there is any error in the generation)
It also prints this error in the console:

SyntaxError: Property or signature expected. (26:20)
  24 |       {default: {}
  25 | ;
> 26 | handle: { toggle: {selfControl.toggle} }
     |                    ^
  27 | ;}
  28 |     > {}

A similar thing happens when forwarding the slots. We make use of slot forwarding as can be seen in all of the slots of this component. The generated .d.ts file has the following:

// Props definition excluded
  export default class Autocomplete extends SvelteComponentTyped<
      AutocompleteProps,
      {change: WindowEventMap["change"];},
      {["loading-options"]: { slot: loading-options }
;
["more-options"]: { loadMoreOptions: any }
;
["not-enough-input"]: { slot: not-enough-input }
;
["too-many-options"]: { slot: too-many-options }
;}
    > {}

As expected, it prints the following error:

SyntaxError: ';' expected. (38:44)
  36 |       AutocompleteProps,
  37 |       {change: WindowEventMap["change"];},
> 38 |       {["loading-options"]: { slot: loading-options }
     |                                            ^
  39 | ;
  40 | ["more-options"]: { loadMoreOptions: any }
  41 | ;

In this case, slot is not even a prop that this slot accepts.

//ts-ignore after JSDoc block wipes out description on entity

/**
* Provide a writable store to maintain list selection.
* @type { Writable<T> | Writable<T[]> }
* @default undefined
* 
*/
//@ts-ignore
export let selected: Writable<T> = getContext('selected'); 

This results in the following JSON coming through to us from site-plugin-sveld.

{
"name": "selected",
"kind": "let",
"isFunction": false,
"isFunctionDeclaration": false,
"constant": false,
"reactive": false
},

Removing the //@ts-ignore brings back the description.
It would be great if we could still get the @type emitted even if it's not used for creating the types. Obviously related to #49 though.

Support for webpack

Great work on the plugin. Just wondering if webpack would be supported in the near future?

ComponentParser: Cannot read properties of null (reading 'type')

If you re-export an imported component in context="module", it currently throws this error: Cannot read properties of null (reading 'type').

I do this in the Chart component of LayerChart to simplify accessing layercake's Svg and Html components (which LayerChart is built upon).

<script context="module" lang="ts">
  import { LayerCake, Svg, Html } from 'layercake';
  export { Svg, Html };
</script>

This can be tested on https://sveld.onrender.com/ with this snippet.

as a workaround, I found you can redeclare them:

<script context="module" lang="ts">
  import { LayerCake, Svg as _Svg, Html as _Html } from 'layercake';

  export const Svg = _Svg;
  export const Html = _Html;
</script>

Note: this used to work, but I recently worked on upgrading all my dependencies, and upgrading vite-plugin-sveld from 1.0.3 to 1.1.0, which bumped sveld from 0.8.3 to 0.18.0 (based on package-lock.json), is when the problem started.

Thanks for the awesome project btw.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.