# sveld **Repository Path**: mirrors_ibm/sveld ## Basic Information - **Project Name**: sveld - **Description**: Generate TypeScript definitions for your Svelte components - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-11-23 - **Last Updated**: 2026-07-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # sveld [![NPM][npm]][npm-url] ![npm downloads to date](https://img.shields.io/npm/dt/sveld?color=262626&style=for-the-badge) `sveld` generates TypeScript definitions and component documentation (Markdown/JSON) for Svelte components. It statically analyzes props, events, slots, module exports, context, and `$$restProps`. Add types with [JSDoc](https://jsdoc.app/) when inference is not enough. The goal is to get third-party Svelte libraries working with the Svelte Language Server and TypeScript with minimal effort from the author. Generated `.d.ts` files give you autocomplete in VS Code and other IDEs. [Carbon Components Svelte](https://github.com/carbon-design-system/carbon-components-svelte) uses this library to auto-generate component types and API metadata. `sveld` uses the Svelte 5 compiler to parse `.svelte` files. That single parse path powers docgen and TypeScript output for Svelte 3, Svelte 4, and Svelte 5 without runes (`export let`, ``, `$$restProps`, …). It also covers Svelte 5 Runes (`$props()`, `$bindable()`, `{@render ...}`, callback props such as `onclick`, …). For `lang="ts"` components, `sveld` keeps source-level prop type annotations when it can, instead of forcing JSDoc. That covers legacy `export let` props, typed `$props()` destructuring, typed whole-object `$props()` captures, local `interface`/`type` declarations, and imported type references in emitted `.d.ts` files. By default, generated `.d.ts` files extend `SvelteComponentTyped` from `svelte`, so TypeScript and the Svelte Language Server work whether consumers use Svelte 3, Svelte 4, or Svelte 5. Set `typesOptions.format: "component"` to instead emit the Svelte 5 `Component` type; see [`typesOptions.format`](#typesoptionsformat). ## When to use sveld SvelteKit's library tooling (`svelte-package`) and `svelte2tsx` already emit `.d.ts` files for SvelteKit-based projects, so start there if that's your setup. `sveld` targets JS-first Svelte libraries: components authored in plain JavaScript with JSDoc rather than `lang="ts"`, where you still want full editor types without adopting TypeScript. It also covers cases `svelte-package` doesn't: generating JSON and Markdown component docs from the same source as the types, checking for API drift between releases (`--check`), and deriving richer types from JSDoc tags (`@typedef`, `@callback`, `@slot`, context types) than plain type inference produces. --- From a Svelte component, `sveld` can infer basic prop types and emit definitions the [Svelte Language Server](https://github.com/sveltejs/language-tools) understands: **Button.svelte** ```svelte ``` The following generated `.d.ts` extends `SvelteComponentTyped`: **Button.svelte.d.ts** ```ts import { SvelteComponentTyped } from "svelte"; import type { SvelteHTMLElements } from "svelte/elements"; type $RestProps = SvelteHTMLElements["button"]; type $Props = { /** * @default "button" */ type?: string; /** * @default false */ primary?: boolean; [key: `data-${string}`]: unknown; }; export type ButtonProps = Omit<$RestProps, keyof $Props> & $Props; export default class Button extends SvelteComponentTyped< ButtonProps, { click: WindowEventMap["click"] }, { default: Record } > {} ``` `sveld` adds the `[key: \`data-${string}\`]: unknown;` index signature whenever `$$restProps` is spread onto an element, so callers can pass arbitrary `data-*` attributes. Inference only gets you so far. Use [JSDoc](https://jsdoc.app/) to document prop, event, and slot types when you need more precision. ```js /** @type {"button" | "submit" | "reset"} */ export let type = "button"; /** * Set to `true` to use the primary variant */ export let primary = false; ``` With JSDoc, the output looks like this: ```ts import { SvelteComponentTyped } from "svelte"; import type { SvelteHTMLElements } from "svelte/elements"; type $RestProps = SvelteHTMLElements["button"]; type $Props = { /** * @default "button" */ type?: "button" | "submit" | "reset"; /** * Set to `true` to use the primary variant * @default false */ primary?: boolean; }; export type ButtonProps = Omit<$RestProps, keyof $Props> & $Props; export default class Button extends SvelteComponentTyped< ButtonProps, { click: WindowEventMap["click"] }, { default: Record } > {} ``` --- ## Table of Contents - [When to use sveld](#when-to-use-sveld) - [Approach](#approach) - [Features](#features) - [`.d.ts` output format (`typesOptions.format`)](#dts-output-format-typesoptionsformat) - [Opt-in semantic resolution (`resolveTypes`)](#opt-in-semantic-resolution-resolvetypes) - [Persistent parse cache (`cache`)](#persistent-parse-cache-cache) - [Compile-checked `@example` blocks (`checkExamples`)](#compile-checked-example-blocks-checkexamples) - [Type inference diagnostics](#type-inference-diagnostics) - [Requirements](#requirements) - [Usage](#usage) - [Installation](#installation) - [Vite](#vite) - [CLI](#cli) - [Exit codes](#exit-codes) - [CI: API-drift checks (`--check`)](#ci-api-drift-checks---check) - [Node.js](#nodejs) - [Browser](#browser) - [Config File](#config-file) - [Publishing to NPM](#publishing-to-npm) - [Available Options](#available-options) - [Documenting Entry Exports](#documenting-entry-exports) - [JSON Output](#json-output) - [Custom Elements Manifest](#custom-elements-manifest) - [Consuming the manifest](#consuming-the-manifest) - [API Reference](#api-reference) - [reactive](#reactive) - [binding](#binding) - [@type](#type) - [@default](#default) - [@typedef](#typedef) - [@callback](#callback) - [@slot / @snippet](#slot--snippet) - [Extra JSDoc tags before `@slot`](#extra-jsdoc-tags-before-slot) - [Svelte 5 Snippet Compatibility](#svelte-5-snippet-compatibility) - [@event](#event) - [@deprecated](#deprecated) - [Context API](#context-api) - [@restProps](#restprops) - [@extendProps](#extendprops) - [@template](#template) - [@generics](#generics) - [@component comments](#component-comments) - [Accessor Props](#accessor-props) - [Troubleshooting](#troubleshooting) - [Contributing](#contributing) - [License](#license) ## Approach `sveld` uses the Svelte compiler to statically analyze exported components and emit docs for consumers. It extracts: - props - slots - forwarded events - dispatched events - context (setContext/getContext) - `$$restProps` When inference fails, props fall back to `any` rather than guessing wrong. Authors can tighten types with JSDoc. Comments are optional from the compiler's point of view, so plain JavaScript components still parse. When both TypeScript syntax and JSDoc are present, `sveld` resolves prop types in this order: 1. explicit TypeScript annotation 2. explicit JSDoc annotation 3. initializer inference 4. `any` `sveld` stays AST-only. It copies imported and local type text into generated `.d.ts` output but does not run project-wide semantic resolution with the TypeScript compiler. Opaque imported whole-object `$props()` types can therefore stay in declarations without being fully expanded into JSON metadata. ## Features ### `.d.ts` output format (`typesOptions.format`) `typesOptions.format` controls the shape of generated `.d.ts` files: `"class"` (the default) or `"component"`. `"class"` extends `SvelteComponentTyped`, deprecated in Svelte 5 and plausibly removed in Svelte 6: ```ts import { SvelteComponentTyped } from "svelte"; export type ButtonProps = { label?: string }; export default class Button extends SvelteComponentTyped< ButtonProps, { click: WindowEventMap["click"] }, { default: Record } > {} ``` `"component"` emits the Svelte 5 `Component` type instead: ```ts import type { Component } from "svelte"; export type ButtonProps = { label?: string; onclick?: (event: WindowEventMap["click"]) => void }; export type ButtonExports = Record; declare const Button: Component; export default Button; ``` Publish `"component"` if you target Svelte 5+ consumers. `"class"` remains compatible with Svelte 3, 4, and 5. ```ts await sveld({ types: true, typesOptions: { format: "component" } }); ``` Also available as `--types-format=component` on the CLI. `"component"`'s three type parameters: - **Props**: the same `$Props`/`Props` type as `"class"`. Runes components already declare callback props (e.g. `onclick`) as regular props. Legacy (non-runes) components additionally get `on?: (event: Type) => void` callback props for every dispatched and forwarded event, so Svelte 5 consumers can attach handlers as props instead of `on:event`. - **Exports**: the component's accessor props (exported `function`/`const` members), the same members that render as class members under `"class"`. - **Bindings**: a union of string literals for props declared with `$bindable(...)` (runes) or marked `@bindable writable` ([see `binding`](#binding)) (legacy) — e.g. `"value"`, or `"value" | "open"` for more than one. `""` when the component declares none, matching Svelte's own convention for "no bindings." **Generic components.** A `declare const X: Component<...>` value can't itself carry a generic type parameter the way a class can, so generic components (`@template`/`generics`) get a per-component interface instead of `Component<...>` directly: ```ts interface GenericListComponent { new ( options: ComponentConstructorOptions>, ): SvelteComponent> & GenericListExports; ( this: void, internals: ComponentInternals, props: GenericListProps, ): { $on?(...): () => void; $set?(...): void } & GenericListExports; z_$$bindings?: ""; } declare const GenericList: GenericListComponent; export default GenericList; ``` Both signatures carry their own generic parameter (not the `const` itself), so `Item` is inferred per usage site, e.g. `` infers `Item` from `numbers`. The `new` signature is the one place `"component"` format still touches a legacy type (`SvelteComponent`/`ComponentConstructorOptions`, not the deprecated `SvelteComponentTyped`): the Svelte language server resolves generic inference for `` template usage through `new`, not the plain call signature, confirmed by comparing against `@sveltejs/package`'s own generated output for the same component. Omitting it silently breaks inference instead of erroring, so it stays in even though it means one legacy import for generic components. ### Opt-in semantic resolution (`resolveTypes`) Imported whole-object `$props()` types stay opaque in JSON by default (`"props": []`). Turn on `resolveTypes` when a docs site or prop table needs the individual fields. ```ts await sveld({ json: true, resolveTypes: true }); ``` ```svelte ``` Without `resolveTypes`, JSON lists no props. With it, each field shows up with `"typeSource": "typescript"`: ```jsonc { "props": [ { "name": "disabled", "type": "boolean", "isRequired": false, "typeSource": "typescript" }, { "name": "href", "type": "string", "isRequired": true, "typeSource": "typescript" }, { "name": "variant", "type": "\"primary\" | \"secondary\"", "isRequired": true, "typeSource": "typescript" } ] } ``` **Performance.** Off by default. This is the only path that loads TypeScript. It needs `typescript` and a `tsconfig.json`, runs slower than the AST-only pipeline, and gets slower as your types grow. Use it only when you need expanded JSON. `.d.ts` output is unchanged. ### Persistent parse cache (`cache`) Parsed output is written to disk and reused when the source file has not changed, on by default. That applies across runs, including CI on a fresh checkout. ```ts await sveld({ json: true }); ``` By default this writes to `node_modules/.cache/sveld/parse-cache.json`. Pass a string to use a different location, e.g. `cache: ".cache/sveld.json"`, or `cache: false` to disable it. Also available as `--cache` / `--cache=` / `--cache=false` on the CLI. If a component [`@extendProps`](#extendprops) / [`@extends`](#extendprops) another file, it is re-parsed when that dependency changes, same as in [`watch`](#available-options) mode. Bumping the `sveld` or Svelte version clears the cache. ### Compile-checked `@example` blocks (`checkExamples`) `@example` blocks are just text. Rename a prop and the sample code can sit there broken for months. Set `checkExamples: true` to run plain TS/JS `@example` blocks through the TypeScript program. Broken examples show up as `example-compile-error` diagnostics. ```ts await sveld({ json: true, checkExamples: true }); ``` ```svelte ``` If `formatValue` is later renamed and the example is never updated, `checkExamples` reports it: ``` @example blocks that failed to compile (1): ./Component.svelte - Line 1: Cannot find name 'formatValue'. ``` Plain TS/JS only. Examples fenced as `svelte` or `html`, or bare markup like ` {:else} {cell.value} {/if} {/snippet} ``` Generated output includes both the snippet prop and the traditional slot definition: ```ts type DataTableProps = { // ... other props // Snippet prop for Svelte 5 compatibility cell?: ( this: void, ...args: [ { row: Row; cell: DataTableCell; rowIndex: number; cellIndex: number; }, ] ) => void; // Default slot as children prop children?: (this: void) => void; }; export default class DataTable extends SvelteComponentTyped< DataTableProps, { /* events */ }, { // Traditional slot definition (Svelte 3/4) default: Record; cell: { row: Row; cell: DataTableCell; rowIndex: number; cellIndex: number; }; } > {} ``` ### `@event` Use the `@event` tag to type dispatched events. An event name is required and a description optional. In Svelte 5 runes components, callback props like `onclick` are props, not events. The `events` output stays reserved for dispatched events and legacy forwarded events. If a runes component documents `@event foo` and exposes a matching callback prop like `onfoo` without actually dispatching or forwarding `foo`, `sveld` aliases that documentation onto the callback prop instead of synthesizing an emitted event. Use `null` as the value if no event detail is provided. **Signature:** ```js /** * Optional event description * @event {EventDetail} eventname [inline description] */ ``` **Example:** ```svelte ``` **Svelte 5 Runes with dispatched events:** ```svelte ```
Svelte 3/4 (legacy) syntax ```svelte ```
Output is identical for both syntax modes. Output: ```ts export default class Component extends SvelteComponentTyped< ComponentProps, { "button:key": CustomEvent<{ key: string }>; /** Fired when `key` changes. */ key: CustomEvent; }, Record > {} ``` #### Using `@property` for complex event details For events with complex object payloads, use `@property` to document individual fields. The main comment becomes the event description. This is the idiomatic way to describe each field of an event detail. An inline object literal such as `@event {{ items: string[]; added: string[] }} change` types the payload but cannot carry per-field descriptions, since a nested block comment would terminate the host JSDoc. Declare the detail with `@type {object}` and `@property` instead to document every field. **Signature:** ```js /** * Event description * @event eventname * @type {object} * @property {Type} propertyName - Property description */ ``` **Example:** ```svelte ```
Svelte 3/4 (legacy) syntax ```svelte ```
Output is identical for both syntax modes. Output: ```ts export default class Component extends SvelteComponentTyped< ComponentProps, { /** Fired when the user submits the form */ submit: CustomEvent<{ /** The user's name */ name: string; /** The user's email address */ email: string; /** Whether the user opted into the newsletter */ newsletter: boolean; }>; }, Record > {} ``` #### Optional properties in event details Like typedefs, you can mark event detail properties as optional with square brackets when they are not always in the payload. **Example:** ```svelte ```
Svelte 3/4 (legacy) syntax ```svelte ```
Output is identical for both syntax modes. Output: ```ts export default class Component extends SvelteComponentTyped< ComponentProps, { /** Snowball event fired when throwing a snowball */ snowball: CustomEvent<{ /** Indicates whether the snowball is tightly packed */ isPacked: boolean; /** The speed of the snowball in mph */ speed: number; /** Optional color of the snowball */ color?: string; /** Optional density with default value @default 0.9 */ density?: number; }>; }, Record > {} ``` #### Discriminated unions in event details When the event detail is a union (or any non-object shape), use `@type` to declare it directly. An explicit `@type` wins over `@property` tags, so the union is copied verbatim into the emitted `.d.ts` instead of being flattened into independent property unions. The only exception is `@type {object}`, which tells `sveld` to build the shape from `@property` tags (as shown above). **Example:** ```svelte ``` Output: ```ts export default class Component extends SvelteComponentTyped< ComponentProps, { /** Dispatched when a sortable column header would change the active sort. */ sort: CustomEvent<{ key: null; direction: "none" } | { key: string; direction: "ascending" | "descending" }>; }, Record > {} ``` Any free-text prose after the tags is attached to the event description, not to a property doc. ### `@deprecated` Add `@deprecated` to a prop, event, slot, or exported accessor. An optional message after the tag can explain why or name a replacement. ```svelte ``` For slots, put `@deprecated` before the `@slot` / `@snippet` line, alongside the description and any other [extra tags](#extra-jsdoc-tags-before-slot). For events, put it after the `@event` line. Generated `.d.ts` files include an `@deprecated` JSDoc line so editors strike the symbol through. JSON adds a `deprecated` field (the message string, or `true` when the tag has no message). Markdown strikes through the name and adds a **Deprecated** badge with the message when present. ```ts /** * The visible label. * @deprecated Use the `text` prop instead. */ label?: string; ``` ```json { "name": "label", "deprecated": "Use the `text` prop instead." } ``` ### Context API `sveld` generates TypeScript definitions for Svelte's `setContext`/`getContext` by extracting types from JSDoc on context values. #### How it works When you call `setContext` in a component, `sveld`: 1. Detects the `setContext` call 2. Resolves the context key (see [Supported context keys](#supported-context-keys)) 3. Finds JSDoc `@type` annotations on the variables being passed 4. Generates a TypeScript type export for the context #### Supported context keys The key becomes the `{PascalCase}Context` type name. `sveld` can resolve: | Key form | Example | Generated type | | --- | --- | --- | | String literal | `setContext("simple-modal", …)` | `SimpleModalContext` | | Static template literal | `` setContext(`simple-modal`, …) `` | `SimpleModalContext` | | `const`-bound string | `const KEY = "simple-modal";`
`setContext(KEY, …)` | `SimpleModalContext` | | `Symbol()` / `Symbol.for()` | `setContext(Symbol("tabs"), …)` | `TabsContext` | `const` identifiers are followed up to 5 levels deep (`const A = "x"; const B = A;`). Only `const` bindings count. `let`, `var`, and props are skipped because they can change at runtime. Symbol keys take their name from the description: `Symbol("tabs")` and `Symbol.for("tabs")` both become `TabsContext`. For `const ModalKey = Symbol()` with no description, the binding name wins: `ModalKeyContext`. Anything else (dynamic identifiers, template interpolation, other function calls) logs a warning. No context type is generated. #### Example **Modal.svelte** ```svelte ```
Svelte 3/4 (legacy) syntax ```svelte ```
Output is identical for both syntax modes. **Generated TypeScript definition:** ```ts export type SimpleModalContext = { /** Open the modal with content */ open: (component: any, props?: any) => void; /** Close the modal */ close: () => void; }; export type ModalProps = {}; export default class Modal extends SvelteComponentTyped< ModalProps, Record, { default: Record } > {} ``` **Consumer usage:** ```svelte ``` #### Explicitly typing contexts There are several ways to type contexts: **Option 1: Inline JSDoc on variables (recommended)** ```svelte ``` **Option 2: Using @typedef for complex types** ```svelte ``` **Option 3: Referencing imported types** ```svelte ``` **Option 4: Direct object literal with inline functions** ```svelte ``` > Inline functions without `@type` annotations get generic inferred signatures. Add explicit JSDoc when you care about the shape. #### Notes - Context keys must be statically resolvable: a string literal, a static template literal, a `const`-bound string, or a `Symbol()` / `Symbol.for()` call with a static description. Dynamic expressions (runtime identifiers, template interpolation, other function calls) are skipped with a warning. - Variables passed to `setContext` should have JSDoc `@type` annotations for accurate types - The generated type name follows the pattern: `{PascalCase}Context`. Separators (hyphens, underscores, dots, colons, slashes, spaces) are stripped and each segment is capitalized: | Context Key | Generated Type Name | | --- | --- | | `"simple-modal"` | `SimpleModalContext` | | `"user_settings"` | `UserSettingsContext` | | `"Carbon.Modal"` | `CarbonModalContext` | | `"Carbon:Modal"` | `CarbonModalContext` | | `"app/modal"` | `AppModalContext` | | `"My Context"` | `MyContextContext` | | `"Tabs"` | `TabsContext` | - If no type annotation is found, the type defaults to `any` with a warning ### `@restProps` `sveld` can detect inline HTML elements that `$$restProps` is forwarded to. It cannot infer the underlying element for instantiated components. Use `@restProps` to name the element tags `$$restProps` is forwarded to. **Signature:** ```js /** * Single element * @restProps {tagname} * * Multiple elements * @restProps {tagname-1 | tagname-2 | tagname-3} */ ``` **Example:** ```svelte {#if edit} ```
Svelte 3/4 (legacy) syntax ```svelte ```
Output is identical for both syntax modes. Output: ```ts /** * @example * */ export default class Button extends SvelteComponentTyped< ButtonProps, Record, { default: Record } > {} ``` ### Accessor Props Exported functions and consts become accessor props in generated TypeScript definitions. Use `@type` for function signatures, or `@param` and `@returns` (or `@return`) for richer docs. `@type` wins over `@param`/`@returns` when both are present. **Signature:** ```js /** * Function description * @param {Type} paramName - Parameter description * @param {Type} [optionalParam] - Optional parameter * @returns {ReturnType} Return value description */ ``` **Example:** ```svelte
{@render children?.()}
```
Svelte 3/4 (legacy) syntax ```svelte ```
Output is identical for both syntax modes. Output: ```ts export type NotificationData = { /** Optional id for deduplication */ id?: string; kind?: "error" | "info" | "success"; }; export type ComponentProps = Record; export default class Component extends SvelteComponentTyped< ComponentProps, Record, Record > { /** * Add a notification to the queue. */ add: (notification: NotificationData) => string; /** * Remove a notification by id. */ remove: (id: string) => boolean; /** * Get notification count. */ getCount: () => number; } ``` When only `@param` tags are present without `@returns`, the return type defaults to `any`. When only `@returns` is present without `@param`, the function signature is `() => returnType`. ## Troubleshooting **A prop came out `any`.** Enable [`reportDiagnostics`](#type-inference-diagnostics) (or `strict` to fail CI) to see which props sveld couldn't infer, then tighten them with `@type` or a native TypeScript annotation. **Generated types don't appear for consumers.** Check that the `types` folder is listed in `exports` and `files` in `package.json`. See [Publishing to NPM](#publishing-to-npm). **Output differs in CI.** Commit `COMPONENT_API.json` and run [`--check`](#ci-api-drift-checks---check) in CI so API drift fails the build instead of silently diverging. ## Contributing See [contributing guidelines](CONTRIBUTING.md). ## License [Apache-2.0](LICENSE) [npm]: https://img.shields.io/npm/v/sveld.svg?color=262626&style=for-the-badge [npm-url]: https://npmjs.com/package/sveld