# 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-09-20 ## 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` parses `.svelte` files with its own template parser (`src/template-parse/`), kept in parity with `svelte/compiler`'s parser by a differential test suite. 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 (whole-object and per-prop), local `interface`/`type`/`enum` declarations, and TypeScript signatures on accessor exports (`export function`). Any type a prop annotation or accessor signature depends on — whether imported with `import type` or as a plain value import used only in a type position — is re-emitted as an `import type` at the top of the generated `.d.ts`, and local `interface`/`type`/`enum` declarations it depends on are copied alongside it. Everything stays textual: no semantic expansion, and `satisfies`/`as` are treated alike. A `const enum` is widened to a literal union of its member values instead of being re-declared, since `const enum` isn't supported by common bundlers under `isolatedModules`. 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) - [Diagnostic codes](#diagnostic-codes) - [Severity and `--strict=errors`](#severity-and---stricterrors) - [Ignoring diagnostics](#ignoring-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) - [CI: strictness profiles (`--strict=ci`/`--strict=local`)](#ci-strictness-profiles---strictci---strictlocal) - [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) - [llms.txt Output](#llmstxt-output) - [Custom Writers](#custom-writers) - [The `OutputWriter` contract](#the-outputwriter-contract) - [Registering a writer](#registering-a-writer) - [Running it via the plugin](#running-it-via-the-plugin) - [Worked example: a `components.txt` name-list writer](#worked-example-a-componentstxt-name-list-writer) - [API Reference](#api-reference) - [reactive](#reactive) - [binding](#binding) - [@type](#type) - [@default](#default) - [@typedef](#typedef) - [@property](#property) - [@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) - [@ignore / @internal](#ignore--internal) - [@deprecated](#deprecated) - [@since](#since) - [@see](#see) - [@link](#link) - [@example](#example) - [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` statically analyzes exported components and emits docs for consumers. Template parsing runs through sveld's own parser rather than `svelte/compiler`; `svelte/compiler` is imported only for its types, and `svelte/package.json` is read for the installed Svelte version. A differential test (`tests/svelte-template-parse-shim.test.ts`) parses every fixture `.svelte` file with both parsers and asserts the resulting ASTs match, and a weekly `svelte-canary` workflow re-runs that comparison against `svelte@latest` ahead of the lockfile pin. 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 one of the two paths that load TypeScript. It needs `typescript` 7+ and a `tsconfig.json` (see [Requirements](#requirements)); if either is missing, `resolveTypes` fails the run instead of silently producing empty props. It also 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. Generated `.d.ts` text is cached the same way, but is only reused when the component source _and_ every `typesOptions` value that affects output (for example [`format`](#dts-output-format-typesoptionsformat)) are unchanged; changing any of them regenerates that component's `.d.ts` without invalidating its cached parse. ```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 check them: plain TS/JS bodies run through the TypeScript program, and `svelte`/`html` bodies run through sveld's own template parser. Broken examples show up as `example-compile-error` (TS/JS) or `example-syntax-error` (markup) 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'. ``` A `svelte`/`html`-fenced example is syntax-checked, not type-checked: sveld parses the markup and discards the AST, so it catches malformed markup (a mismatched closing tag, an unterminated attribute) but not a prop that doesn't exist or a type error inside an expression. Those still need `svelte-check` in the consumer's own tests. Bare unfenced markup (`