sveld 0.25.12 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,11 +8,7 @@
8
8
 
9
9
  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.
10
10
 
11
- [Carbon Components Svelte](https://github.com/carbon-design-system/carbon-components-svelte) uses this library to auto-generate component types and API metadata:
12
-
13
- - [TypeScript definitions](https://github.com/carbon-design-system/carbon-components-svelte/blob/master/types): Component TypeScript definitions
14
- - [Component Index](https://github.com/carbon-design-system/carbon-components-svelte/blob/master/COMPONENT_INDEX.md): Markdown file documenting component props, slots, and events
15
- - [Component API](https://github.com/carbon-design-system/carbon-components-svelte/blob/master/docs/src/COMPONENT_API.json): Component API metadata in JSON format
11
+ [Carbon Components Svelte](https://github.com/carbon-design-system/carbon-components-svelte) uses this library to auto-generate component types and API metadata.
16
12
 
17
13
  **Note:** `sveld` supports Svelte 3, 4, and 5, but does not support Svelte 5-specific syntax or runes-only usage. Components must use traditional Svelte syntax (e.g., `export let` for props, not `$props()`).
18
14
 
@@ -116,7 +112,7 @@ export default class Button extends SvelteComponentTyped<
116
112
  - [Approach](#approach)
117
113
  - [Usage](#usage)
118
114
  - [Installation](#installation)
119
- - [Rollup](#rollup)
115
+ - [Vite](#vite)
120
116
  - [Node.js](#nodejs)
121
117
  - [CLI](#cli)
122
118
  - [Publishing to NPM](#publishing-to-npm)
@@ -129,7 +125,7 @@ export default class Button extends SvelteComponentTyped<
129
125
  - [@event](#event)
130
126
  - [Context API](#context-api)
131
127
  - [@restProps](#restprops)
132
- - [@extends](#extends)
128
+ - [@extendProps](#extendprops)
133
129
  - [@generics](#generics)
134
130
  - [@component comments](#component-comments)
135
131
  - [Accessor Props](#accessor-props)
@@ -171,26 +167,23 @@ bun i -D sveld
171
167
  yarn add -D sveld
172
168
  ```
173
169
 
174
- ### Rollup
170
+ ### Vite
175
171
 
176
- Import and add `sveld` as a plugin to your `rollup.config.js`.
172
+ Import and add `sveld` as a plugin to your `vite.config.ts`. The plugin only runs during `vite build` (not the dev server).
177
173
 
178
- ```js
179
- // rollup.config.js
180
- import svelte from "rollup-plugin-svelte";
181
- import resolve from "@rollup/plugin-node-resolve";
174
+ ```ts
175
+ // vite.config.ts
176
+ import { svelte } from "@sveltejs/vite-plugin-svelte";
182
177
  import sveld from "sveld";
178
+ import { defineConfig } from "vite";
183
179
 
184
- export default {
185
- input: "src/index.js",
186
- output: {
187
- format: "es",
188
- file: "lib/index.mjs",
189
- },
190
- plugins: [svelte(), resolve(), sveld()],
191
- };
180
+ export default defineConfig({
181
+ plugins: [svelte(), sveld()],
182
+ });
192
183
  ```
193
184
 
185
+ Since Vite uses Rollup for production builds, `sveld` also works in Rollup configurations.
186
+
194
187
  By default, `sveld` will use the `"svelte"` field from your `package.json` to determine the entry point. You can override this by specifying an explicit `entry` option:
195
188
 
196
189
  ```js
@@ -213,16 +206,6 @@ sveld({
213
206
  })
214
207
  ```
215
208
 
216
- The [tests/e2e](tests/e2e) folder contains example set-ups:
217
-
218
- - [single-export](tests/e2e/single-export): library that exports one component
219
- - [single-export-default-only](tests/e2e/single-export-default-only): library that exports one component using the concise `export { default } ...` syntax
220
- - [multi-export](tests/e2e/multi-export): multi-component library without JSDoc annotations (types are inferred)
221
- - [multi-export-typed](tests/e2e/multi-export-typed): multi-component library with JSDoc annotations
222
- - [multi-export-typed-ts-only](tests/e2e/multi-export-typed-ts-only): multi-component library that only generates TS definitions
223
- - [glob](tests/e2e/glob): library that uses the glob strategy to collect/analyze \*.svelte files
224
- - [carbon](tests/e2e/carbon): full `carbon-components-svelte` example
225
-
226
209
  ### CLI
227
210
 
228
211
  The CLI uses the `"svelte"` field from your `package.json` as the entry point:
@@ -303,7 +286,7 @@ TypeScript definitions are outputted to the `types` folder by default. Don't for
303
286
 
304
287
  ## Available Options
305
288
 
306
- ### Rollup Plugin Options
289
+ ### Plugin Options
307
290
 
308
291
  - **`entry`** (string, optional): Specify the entry point to uncompiled Svelte source. If not provided, sveld will use the `"svelte"` field from `package.json`.
309
292
  - **`glob`** (boolean, optional): Enable glob mode to analyze all `*.svelte` files.
@@ -1003,22 +986,24 @@ Example:
1003
986
  {/if}
1004
987
  ```
1005
988
 
1006
- ### `@extends`
989
+ ### `@extendProps`
990
+
991
+ In some cases, a component may be based on another component. The `@extendProps` tag can be used to extend generated component props.
1007
992
 
1008
- In some cases, a component may be based on another component. The `@extends` tag can be used to extend generated component props.
993
+ > **Note:** `@extends` is supported as an alias but `@extendProps` is preferred to avoid conflicts with standard JSDoc `@extends` (used for class inheritance).
1009
994
 
1010
995
  Signature:
1011
996
 
1012
997
  ```js
1013
998
  /**
1014
- * @extends {<relative path to component>} ComponentProps
999
+ * @extendProps {<relative path to component>} ComponentProps
1015
1000
  */
1016
1001
  ```
1017
1002
 
1018
1003
  Example:
1019
1004
 
1020
1005
  ```js
1021
- /** @extends {"./Button.svelte"} ButtonProps */
1006
+ /** @extendProps {"./Button.svelte"} ButtonProps */
1022
1007
 
1023
1008
  export const secondary = true;
1024
1009
 
@@ -167,6 +167,8 @@ interface ComponentElement {
167
167
  * ```
168
168
  */
169
169
  thisValue?: string;
170
+ /** Inline or block description from the `@restProps` JSDoc tag */
171
+ description?: string;
170
172
  }
171
173
  type RestProps = undefined | ComponentInlineElement | ComponentElement;
172
174
  /**
@@ -510,6 +512,23 @@ export default class ComponentParser {
510
512
  * ```
511
513
  */
512
514
  private aliasType;
515
+ /**
516
+ * Extracts a property's type from an object type string.
517
+ *
518
+ * Parses type strings like `{ value: string; other: number }` and returns
519
+ * the type for the requested property name. Handles nested braces, generics,
520
+ * and optional properties.
521
+ *
522
+ * @returns The property type string, or undefined if not found
523
+ */
524
+ private extractPropertyType;
525
+ /**
526
+ * Resolves the type of a MemberExpression (e.g., `obj.value`) by looking up
527
+ * the object's type annotation and extracting the property type.
528
+ *
529
+ * @returns The resolved type string, or undefined if it cannot be resolved
530
+ */
531
+ private resolveMemberExpressionType;
513
532
  /**
514
533
  * Adds or merges a slot definition to the slots map.
515
534
  *
@@ -32,6 +32,8 @@ const VAR_DECLARATION_REGEX = /(?:const|let|function)\s+(\w+)\s*[=(]/;
32
32
  * ```
33
33
  */
34
34
  const DESCRIPTION_DASH_PREFIX_REGEX = /^-\s*/;
35
+ /** Matches a single word character (letter, digit, or underscore). Used for dotted prop access validation. */
36
+ const WORD_CHAR_REGEX = /\w/;
35
37
  /**
36
38
  * Extracts description text after the last dash from JSDoc comments.
37
39
  *
@@ -256,6 +258,7 @@ class ComponentParser {
256
258
  "returns",
257
259
  "return",
258
260
  "extends",
261
+ "extendProps",
259
262
  "restProps",
260
263
  "slot",
261
264
  "event",
@@ -732,6 +735,77 @@ class ComponentParser {
732
735
  return "any";
733
736
  return type.trim();
734
737
  }
738
+ /**
739
+ * Extracts a property's type from an object type string.
740
+ *
741
+ * Parses type strings like `{ value: string; other: number }` and returns
742
+ * the type for the requested property name. Handles nested braces, generics,
743
+ * and optional properties.
744
+ *
745
+ * @returns The property type string, or undefined if not found
746
+ */
747
+ extractPropertyType(typeStr, propName) {
748
+ const trimmed = typeStr.trim();
749
+ if (!trimmed.startsWith("{") || !trimmed.endsWith("}"))
750
+ return undefined;
751
+ const inner = trimmed.slice(1, -1);
752
+ const segments = [];
753
+ let depth = 0;
754
+ let current = "";
755
+ for (const char of inner) {
756
+ if (char === "{" || char === "<" || char === "(" || char === "[") {
757
+ depth++;
758
+ current += char;
759
+ }
760
+ else if (char === "}" || char === ">" || char === ")" || char === "]") {
761
+ depth--;
762
+ current += char;
763
+ }
764
+ else if ((char === ";" || char === ",") && depth === 0) {
765
+ segments.push(current.trim());
766
+ current = "";
767
+ }
768
+ else {
769
+ current += char;
770
+ }
771
+ }
772
+ if (current.trim())
773
+ segments.push(current.trim());
774
+ for (const segment of segments) {
775
+ if (!segment.startsWith(propName))
776
+ continue;
777
+ const afterName = segment.slice(propName.length);
778
+ if (afterName.length > 0 && WORD_CHAR_REGEX.test(afterName[0]))
779
+ continue;
780
+ let rest = afterName.trimStart();
781
+ if (rest.startsWith("?"))
782
+ rest = rest.slice(1).trimStart();
783
+ if (rest.startsWith(":")) {
784
+ return rest.slice(1).trim();
785
+ }
786
+ }
787
+ return undefined;
788
+ }
789
+ /**
790
+ * Resolves the type of a MemberExpression (e.g., `obj.value`) by looking up
791
+ * the object's type annotation and extracting the property type.
792
+ *
793
+ * @returns The resolved type string, or undefined if it cannot be resolved
794
+ */
795
+ resolveMemberExpressionType(expr) {
796
+ const memberExpr = expr;
797
+ if (memberExpr.computed || memberExpr.object?.type !== "Identifier" || memberExpr.property?.type !== "Identifier") {
798
+ return undefined;
799
+ }
800
+ const objName = memberExpr.object.name;
801
+ const propName = memberExpr.property.name;
802
+ if (!objName || !propName)
803
+ return undefined;
804
+ const objType = this.props.get(objName)?.type ?? this.findVariableTypeAndDescription(objName)?.type;
805
+ if (!objType)
806
+ return undefined;
807
+ return this.extractPropertyType(objType, propName);
808
+ }
735
809
  /**
736
810
  * Adds or merges a slot definition to the slots map.
737
811
  *
@@ -1116,6 +1190,7 @@ class ComponentParser {
1116
1190
  const precedingDescription = getPrecedingDescription(tagSource);
1117
1191
  switch (tag) {
1118
1192
  case "extends":
1193
+ case "extendProps":
1119
1194
  this.extends = {
1120
1195
  interface: name,
1121
1196
  import: type,
@@ -1123,14 +1198,31 @@ class ComponentParser {
1123
1198
  if (isFirstTag)
1124
1199
  isFirstTag = false;
1125
1200
  break;
1126
- case "restProps":
1201
+ case "restProps": {
1202
+ /**
1203
+ * Prefer inline description (e.g., "@restProps {type} description" or "@restProps {type} - description"),
1204
+ * fall back to preceding line description, then fall back to the
1205
+ * comment block description (only for first tag if not already used).
1206
+ *
1207
+ * Note: comment-parser treats the first word after the type as "name" and the rest as "description",
1208
+ * so we combine them to form the full inline description for @restProps.
1209
+ */
1210
+ const rawInlineDesc = name ? (description ? `${name} ${description}` : name) : description;
1211
+ const inlineRestPropsDesc = cleanDescription(rawInlineDesc);
1212
+ let restPropsDesc = inlineRestPropsDesc || precedingDescription;
1213
+ if (!restPropsDesc && isFirstTag && !commentDescriptionUsed && commentDescription) {
1214
+ restPropsDesc = commentDescription;
1215
+ commentDescriptionUsed = true;
1216
+ }
1127
1217
  this.rest_props = {
1128
1218
  type: "Element",
1129
1219
  name: type,
1220
+ description: restPropsDesc || undefined,
1130
1221
  };
1131
1222
  if (isFirstTag)
1132
1223
  isFirstTag = false;
1133
1224
  break;
1225
+ }
1134
1226
  case "slot": {
1135
1227
  /**
1136
1228
  * Prefer inline description (e.g., "@slot name - description"),
@@ -2298,14 +2390,14 @@ class ComponentParser {
2298
2390
  if (expression.type === "Literal" && "value" in expression) {
2299
2391
  slot_prop_value.value = String(expression.value);
2300
2392
  }
2393
+ else if (expression.type === "MemberExpression") {
2394
+ slot_prop_value.value = this.resolveMemberExpressionType(expression);
2395
+ }
2301
2396
  else if (expression.type !== "Identifier") {
2302
2397
  if (start !== undefined && end !== undefined) {
2303
2398
  if (expression.type === "ObjectExpression" || expression.type === "TemplateLiteral") {
2304
2399
  slot_prop_value.value = this.sourceAtPos(start + 1, end - 1);
2305
2400
  }
2306
- else {
2307
- slot_prop_value.value = this.sourceAtPos(start, end);
2308
- }
2309
2401
  }
2310
2402
  }
2311
2403
  }
package/lib/cli.js CHANGED
@@ -8,7 +8,7 @@ const plugin_node_resolve_1 = __importDefault(require("@rollup/plugin-node-resol
8
8
  const rollup_1 = require("rollup");
9
9
  const rollup_plugin_svelte_1 = __importDefault(require("rollup-plugin-svelte"));
10
10
  const get_svelte_entry_1 = require("./get-svelte-entry");
11
- const rollup_plugin_1 = require("./rollup-plugin");
11
+ const plugin_1 = require("./plugin");
12
12
  /**
13
13
  * Command-line interface for sveld.
14
14
  *
@@ -43,6 +43,6 @@ async function cli(process) {
43
43
  plugins: [(0, rollup_plugin_svelte_1.default)(), (0, plugin_node_resolve_1.default)()],
44
44
  });
45
45
  await rollup_bundle.generate({});
46
- const result = await (0, rollup_plugin_1.generateBundle)(input, options?.glob === true);
47
- (0, rollup_plugin_1.writeOutput)(result, options, input);
46
+ const result = await (0, plugin_1.generateBundle)(input, options?.glob === true);
47
+ (0, plugin_1.writeOutput)(result, options, input);
48
48
  }
package/lib/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { default as ComponentParser } from "./ComponentParser";
2
2
  export { cli } from "./cli";
3
- export { default } from "./rollup-plugin";
3
+ export { default } from "./plugin";
4
4
  export { sveld } from "./sveld";
package/lib/index.js CHANGED
@@ -8,7 +8,7 @@ var ComponentParser_1 = require("./ComponentParser");
8
8
  Object.defineProperty(exports, "ComponentParser", { enumerable: true, get: function () { return __importDefault(ComponentParser_1).default; } });
9
9
  var cli_1 = require("./cli");
10
10
  Object.defineProperty(exports, "cli", { enumerable: true, get: function () { return cli_1.cli; } });
11
- var rollup_plugin_1 = require("./rollup-plugin");
12
- Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(rollup_plugin_1).default; } });
11
+ var plugin_1 = require("./plugin");
12
+ Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(plugin_1).default; } });
13
13
  var sveld_1 = require("./sveld");
14
14
  Object.defineProperty(exports, "sveld", { enumerable: true, get: function () { return sveld_1.sveld; } });
@@ -23,12 +23,15 @@ export interface ComponentDocApi extends ParsedComponent {
23
23
  moduleName: string;
24
24
  }
25
25
  export type ComponentDocs = Map<ComponentModuleName, ComponentDocApi>;
26
- export default function pluginSveld(opts?: PluginSveldOptions): {
26
+ interface SveldPlugin {
27
27
  name: string;
28
+ apply?: "build" | "serve";
29
+ enforce?: "pre" | "post";
28
30
  buildStart(): void;
29
31
  generateBundle(): Promise<void>;
30
32
  writeBundle(): void;
31
- };
33
+ }
34
+ export default function pluginSveld(opts?: PluginSveldOptions): SveldPlugin;
32
35
  interface GenerateBundleResult {
33
36
  exports: ParsedExports;
34
37
  components: ComponentDocs;
@@ -25,7 +25,9 @@ function pluginSveld(opts) {
25
25
  let result;
26
26
  let input;
27
27
  return {
28
- name: "plugin-sveld",
28
+ name: "vite-plugin-sveld",
29
+ apply: "build",
30
+ enforce: "post",
29
31
  buildStart() {
30
32
  input = (0, get_svelte_entry_1.getSvelteEntry)(opts?.entry);
31
33
  },
package/lib/sveld.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type PluginSveldOptions } from "./rollup-plugin";
1
+ import { type PluginSveldOptions } from "./plugin";
2
2
  interface SveldOptions extends PluginSveldOptions {
3
3
  /**
4
4
  * Specify the input to the uncompiled Svelte source.
package/lib/sveld.js CHANGED
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sveld = sveld;
4
4
  const get_svelte_entry_1 = require("./get-svelte-entry");
5
- const rollup_plugin_1 = require("./rollup-plugin");
5
+ const plugin_1 = require("./plugin");
6
6
  /**
7
7
  * Main entry point for programmatic sveld usage.
8
8
  *
@@ -28,6 +28,6 @@ async function sveld(opts) {
28
28
  const input = (0, get_svelte_entry_1.getSvelteEntry)(opts?.input);
29
29
  if (input === null)
30
30
  return;
31
- const result = await (0, rollup_plugin_1.generateBundle)(input, opts?.glob === true);
32
- (0, rollup_plugin_1.writeOutput)(result, opts || {}, input);
31
+ const result = await (0, plugin_1.generateBundle)(input, opts?.glob === true);
32
+ (0, plugin_1.writeOutput)(result, opts || {}, input);
33
33
  }
@@ -1,4 +1,4 @@
1
- import type { ComponentDocs } from "../rollup-plugin";
1
+ import type { ComponentDocs } from "../plugin";
2
2
  import type { AppendType } from "./MarkdownWriterBase";
3
3
  /**
4
4
  * Interface for markdown documents that can be used for rendering.
@@ -1,4 +1,4 @@
1
- import type { ComponentDocs } from "../rollup-plugin";
1
+ import type { ComponentDocs } from "../plugin";
2
2
  export interface WriteJsonOptions {
3
3
  input: string;
4
4
  inputDir: string;
@@ -1,4 +1,4 @@
1
- import type { ComponentDocs } from "../rollup-plugin";
1
+ import type { ComponentDocs } from "../plugin";
2
2
  import { type AppendType, MarkdownWriterBaseImpl } from "./MarkdownWriterBase";
3
3
  export type { AppendType };
4
4
  type OnAppend = (type: AppendType, document: BrowserWriterMarkdown) => void;
@@ -1,4 +1,4 @@
1
- import type { ComponentDocs } from "../rollup-plugin";
1
+ import type { ComponentDocs } from "../plugin";
2
2
  import WriterMarkdown, { type AppendType } from "./WriterMarkdown";
3
3
  export interface WriteMarkdownOptions {
4
4
  write?: boolean;
@@ -1,4 +1,4 @@
1
- import type { ComponentDocApi } from "../rollup-plugin";
1
+ import type { ComponentDocApi } from "../plugin";
2
2
  export declare function formatTsProps(props?: string): string;
3
3
  export declare function getTypeDefs(def: Pick<ComponentDocApi, "typedefs">): string;
4
4
  /**
@@ -301,6 +301,15 @@ function genPropDef(def) {
301
301
  * Template literal is required for TypeScript's template literal type syntax.
302
302
  */
303
303
  const dataAttributes = "[key: `data-${string}`]: any;";
304
+ /**
305
+ * Generate JSDoc comment for $RestProps if description is provided.
306
+ * Use multiline format when description contains newlines.
307
+ */
308
+ const restPropsComment = def.rest_props.description
309
+ ? def.rest_props.description.includes("\n")
310
+ ? `${formatMultiLineComment(def.rest_props.description)}\n `
311
+ : `${formatSingleLineComment(def.rest_props.description)}\n `
312
+ : "";
304
313
  /**
305
314
  * When both `@extends` and `@restProps` are present, merge all three type sources:
306
315
  * 1. Rest props from element types (SvelteHTMLElements)
@@ -309,7 +318,7 @@ function genPropDef(def) {
309
318
  */
310
319
  if (def.extends !== undefined) {
311
320
  prop_def = `
312
- ${extend_tag_map ? `type $RestProps = ${extend_tag_map};\n` : ""}
321
+ ${restPropsComment}${extend_tag_map ? `type $RestProps = ${extend_tag_map};\n` : ""}
313
322
  type $Props${genericsName} = {
314
323
  ${props}
315
324
 
@@ -321,7 +330,7 @@ function genPropDef(def) {
321
330
  }
322
331
  else {
323
332
  prop_def = `
324
- ${extend_tag_map ? `type $RestProps = ${extend_tag_map};\n` : ""}
333
+ ${restPropsComment}${extend_tag_map ? `type $RestProps = ${extend_tag_map};\n` : ""}
325
334
  type $Props${genericsName} = {
326
335
  ${props}
327
336
 
@@ -1,5 +1,5 @@
1
1
  import type { ParsedExports } from "../parse-exports";
2
- import type { ComponentDocs } from "../rollup-plugin";
2
+ import type { ComponentDocs } from "../plugin";
3
3
  /**
4
4
  * Re-export browser-compatible functions from core module.
5
5
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sveld",
3
- "version": "0.25.12",
3
+ "version": "0.26.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Generate TypeScript definitions for your Svelte components.",
6
6
  "main": "./lib/index.js",
@@ -37,7 +37,9 @@
37
37
  "docgen",
38
38
  "typescript",
39
39
  "definitions",
40
- "JSDocs"
40
+ "JSDocs",
41
+ "vite",
42
+ "vite-plugin"
41
43
  ],
42
44
  "maintainers": [
43
45
  "Eric Liu (https://github.com/metonym)"