@poveste/plugin-svelte 0.19.1 → 0.21.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
@@ -55,6 +55,8 @@ and your content reads it from the snippet:
55
55
  >
56
56
  > [How, and why](https://poveste.dev/guide/migration-from-histoire#svelte-story-state-moves-to-initstate)
57
57
 
58
+ A story's Docs tab shows a props, slots and events table for each component it imports, read from the type on the component's `$props()`. It needs `typescript` 6 or earlier and `svelte2tsx` in the book: `npm i -D typescript@6 svelte2tsx`. See [Component docs](https://poveste.dev/guide/svelte/getting-started#component-docs).
59
+
58
60
  [Svelte guide](https://poveste.dev/guide/svelte/getting-started) ·
59
61
  [SvelteKit](https://poveste.dev/guide/sveltekit/getting-started) ·
60
62
  [Documentation](https://poveste.dev)
@@ -1 +1 @@
1
- import{watch as e}from"@poveste/vendors/vue";import{applyState as t,clone as n}from"@poveste/shared";function r(e){let t={};for(let n in e){if(n===`Hst`)continue;let r=e[n];typeof r!=`function`&&r!==void 0&&(r instanceof HTMLElement||typeof r==`object`&&r?.$$||(t[n]=r))}return t}function i(i,a){let o=!1,s=e(()=>i,e=>{if(e!=null){if(o){o=!1;return}o=!0,a(r(e))}},{deep:!0,immediate:!0});function c(e){if(e!=null){if(o){o=!1;return}o=t(i,n(r(e)))}}return{apply:c,stop(){s()}}}export{i as syncState};
1
+ import{watch as e}from"@poveste/vendors/vue";import{applyState as t,clone as n}from"@poveste/shared";function r(e){let t={};for(let n in e){if(n===`Hst`)continue;let r=e[n];typeof r!=`function`&&r!==void 0&&(r instanceof HTMLElement||typeof r==`object`&&r?.$$||(t[n]=r))}return t}function i(i,a){let o=!1,s=e(()=>i,e=>{e!=null&&(o?o=!1:(o=!0,a(r(e))))},{deep:!0,immediate:!0});function c(e){e!=null&&(o=!o&&t(i,n(r(e))))}return{apply:c,stop(){s()}}}export{i as syncState};
@@ -1 +1 @@
1
- import{addStoryContext as e,addVariantContext as t,collectStoryContext as n,storyFileContext as r}from"../contexts.js";import"svelte/internal/disclose-version";import"svelte/internal/flags/legacy";import*as i from"svelte/internal/client";function a(a,o){i.push(o,!1);let s=i.prop(o,`title`,8,null),c=i.prop(o,`id`,8,null),l=i.prop(o,`group`,8,null),u=i.prop(o,`layout`,8,null),d=i.prop(o,`icon`,8,null),f=i.prop(o,`iconColor`,8,null),p=i.prop(o,`docsOnly`,8,!1),m=i.prop(o,`autoPropsDisabled`,8,null),h=i.prop(o,`initState`,8,null),g=i.prop(o,`meta`,8,null),_=e.getOptional(),v=r.getOptional(),y=c()??v?.id,b=s()??v?.fileName;if(!y||b===void 0)throw Error("[poveste] a <Story> collected outside a story file needs an `id` and a `title`");let x={id:y,title:b,group:l(),layout:u(),icon:d(),iconColor:f(),docsOnly:p(),autoPropsDisabled:m(),...g()?{meta:g()}:{},variants:[]};_?.(x);let S=h()?h()():{};n.set(x),t.set(e=>{x.variants.push(e)}),i.init();var C=i.comment(),w=i.first_child(C);i.slot(w,o,`default`,{get state(){return S}},null),i.append(a,C),i.pop()}export{a as default};
1
+ import{addStoryContext as e,addVariantContext as t,collectStoryContext as n,storyFileContext as r}from"../contexts.js";import"svelte/internal/disclose-version";import"svelte/internal/flags/legacy";import*as i from"svelte/internal/client";function a(a,o){i.push(o,!1);let s=i.prop(o,`title`,8,null),c=i.prop(o,`id`,8,null),l=i.prop(o,`group`,8,null),u=i.prop(o,`layout`,8,null),d=i.prop(o,`icon`,8,null),f=i.prop(o,`iconColor`,8,null),p=i.prop(o,`docsOnly`,8,!1),m=i.prop(o,`autoDocs`,8);i.prop(o,`component`,8);let h=i.prop(o,`autoPropsDisabled`,8,null),g=i.prop(o,`initState`,8,null),_=i.prop(o,`meta`,8,null),v=e.getOptional(),y=r.getOptional(),b=c()??y?.id,x=s()??y?.fileName;if(!b||x===void 0)throw Error("[poveste] a <Story> collected outside a story file needs an `id` and a `title`");let S={id:b,title:x,group:l(),layout:u(),icon:d(),iconColor:f(),docsOnly:p(),...m()===void 0?{}:{autoDocs:m()},autoPropsDisabled:h(),..._()?{meta:_()}:{},variants:[]};v?.(S);let C=g()?g()():{};n.set(S),t.set(e=>{S.variants.push(e)}),i.init();var w=i.comment(),T=i.first_child(w);i.slot(T,o,`default`,{get state(){return C}},null),i.append(a,w),i.pop()}export{a as default};
@@ -8,6 +8,10 @@
8
8
  export let icon = null
9
9
  export let iconColor = null
10
10
  export let docsOnly = false
11
+ export let autoDocs
12
+ // Read from the source by auto-docs, not here (#1432).
13
+ // svelte-ignore export_let_unused
14
+ export let component
11
15
  export let autoPropsDisabled = null
12
16
  export let initState = null
13
17
  export let meta = null
@@ -33,6 +37,7 @@
33
37
  icon,
34
38
  iconColor,
35
39
  docsOnly,
40
+ ...autoDocs !== undefined ? { autoDocs } : {},
36
41
  // Collected rather than inherited down: `MountStory` forwards story props
37
42
  // only to an implicit variant (#466). Left undefined when unset, or the
38
43
  // collector's `??=` merge could never apply `defaultStoryProps`.
@@ -1 +1 @@
1
- import{addStoryContext as e,storyFileContext as t}from"../contexts.js";import{callSetupFunctions as n,mountSvelteComponent as r}from"../util/svelte.js";import{collectHst as i}from"./hst.js";import*as a from"virtual:$poveste-generated-global-setup";import*as o from"virtual:$poveste-setup";import{tick as s}from"svelte";async function c({file:c,el:l,storyData:u}){let{default:d}=await import(/* @vite-ignore */c.moduleId),f=await r(d,{target:l,props:{Hst:i()},context:new Map([[e.key,e=>{u.push(e)}],[t.key,c]])},`client`),p={app:f.app,story:void 0,variant:void 0};if(await n(a,o,p),await s(),!u[0]){f.destroy();return}u[0].variants.length||u[0].variants.push({id:`_default`,title:`default`}),f.destroy()}export{c as run};
1
+ import{addStoryContext as e,storyFileContext as t}from"../contexts.js";import{callSetupFunctions as n,mountSvelteComponent as r}from"../util/svelte.js";import{collectHst as i}from"./hst.js";import*as a from"virtual:$poveste-generated-global-setup";import*as o from"virtual:$poveste-setup";import{tick as s}from"svelte";async function c({file:c,el:l,storyData:u}){let{default:d}=await import(/* @vite-ignore */c.moduleId),f=await r(d,{target:l,props:{Hst:i()},context:new Map([[e.key,e=>{u.push(e)}],[t.key,c]])},`client`),p={app:f.app,story:void 0,variant:void 0};await n(a,o,p),await s(),u[0]&&(u[0].variants.length||u[0].variants.push({id:`_default`,title:`default`})),f.destroy()}export{c as run};
@@ -0,0 +1,14 @@
1
+ type Parse = typeof import('svelte/compiler').parse;
2
+ export interface ScriptReading {
3
+ /** The defaults the component's own code applies, as written. */
4
+ defaults: Map<string, string>;
5
+ /** The props its `$props()` destructuring names. */
6
+ taken: Set<string>;
7
+ }
8
+ /**
9
+ * What a component's script says about its props: a `$props()` destructuring
10
+ * default, through `$bindable(...)`, or a Svelte 4 `export let`, and which props
11
+ * the destructuring takes.
12
+ */
13
+ export declare function readScript(parse: Parse, source: string): ScriptReading;
14
+ export {};
@@ -0,0 +1,57 @@
1
+ function keyOf(property) {
2
+ return property.key?.name ?? property.key?.value;
3
+ }
4
+ /**
5
+ * What a component's script says about its props: a `$props()` destructuring
6
+ * default, through `$bindable(...)`, or a Svelte 4 `export let`, and which props
7
+ * the destructuring takes.
8
+ */
9
+ export function readScript(parse, source) {
10
+ const defaults = new Map();
11
+ const taken = new Set();
12
+ const ast = parse(source, { modern: true });
13
+ const text = (node) => source.slice(node.start, node.end);
14
+ function visit(node) {
15
+ if (!node || typeof node !== 'object') {
16
+ return;
17
+ }
18
+ if (node.type === 'VariableDeclarator' && node.init?.type === 'CallExpression' && node.init.callee?.name === '$props' && node.id?.type === 'ObjectPattern') {
19
+ for (const property of node.id.properties ?? []) {
20
+ const name = keyOf(property);
21
+ if (property.type === 'Property' && name) {
22
+ taken.add(name);
23
+ }
24
+ if (property.type !== 'Property' || property.value?.type !== 'AssignmentPattern' || !name) {
25
+ continue;
26
+ }
27
+ let value = property.value.right;
28
+ if (value?.type === 'CallExpression' && value.callee?.name === '$bindable') {
29
+ value = value.arguments?.[0];
30
+ }
31
+ if (value) {
32
+ defaults.set(name, text(value));
33
+ }
34
+ }
35
+ }
36
+ if (node.type === 'ExportNamedDeclaration' && node.declaration?.type === 'VariableDeclaration' && node.declaration.kind === 'let') {
37
+ for (const declarator of node.declaration.declarations ?? []) {
38
+ if (declarator.id?.type === 'Identifier' && declarator.id.name && declarator.init) {
39
+ defaults.set(declarator.id.name, text(declarator.init));
40
+ }
41
+ }
42
+ }
43
+ for (const [key, value] of Object.entries(node)) {
44
+ if (key === 'parent') {
45
+ continue;
46
+ }
47
+ if (Array.isArray(value)) {
48
+ value.forEach(visit);
49
+ }
50
+ else if (value && typeof value === 'object' && 'type' in value) {
51
+ visit(value);
52
+ }
53
+ }
54
+ }
55
+ visit(ast.instance?.content);
56
+ return { defaults, taken };
57
+ }
@@ -0,0 +1,8 @@
1
+ import type { DocgenExtractor, DocgenExtractorContext } from '@poveste/shared';
2
+ /**
3
+ * Runs in the docgen worker. One language service over the book's tsconfig, in
4
+ * which a `.svelte` file is its `svelte2tsx` output; a probe file names each
5
+ * requested component's `ComponentProps`, which the checker resolves through
6
+ * imported and extended types the way the book's editor does (#501, #1160).
7
+ */
8
+ export declare function createExtractor(context: DocgenExtractorContext): DocgenExtractor;
@@ -0,0 +1,172 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { basename, dirname, isAbsolute, join, resolve } from 'pathe';
4
+ import { ambientFiles, documentProps, loadFromBook, readTsconfig } from 'poveste/docgen';
5
+ import { readScript } from './defaults.js';
6
+ // A Svelte 5 `Snippet<...>`, and Svelte 4's slot shape as `svelte2tsx` types it.
7
+ const SLOT_TYPE = /\bSnippet\b/;
8
+ // Svelte 5's DOM-style `onclick` as well as a library's `onCheckedChange`.
9
+ const EVENT_NAME = /^on[a-z]/i;
10
+ // Each `.svelte` file is read as its `svelte2tsx` output under this name. Nothing on
11
+ // disk carries it, unlike `x.svelte.ts`, which is a rune module a book may have.
12
+ const VIRTUAL = '.tsx';
13
+ function isVirtual(file) {
14
+ return file.endsWith(`.svelte${VIRTUAL}`) && !file.includes('/node_modules/');
15
+ }
16
+ function realOf(file) {
17
+ return isVirtual(file) ? file.slice(0, -VIRTUAL.length) : file;
18
+ }
19
+ /**
20
+ * Runs in the docgen worker. One language service over the book's tsconfig, in
21
+ * which a `.svelte` file is its `svelte2tsx` output; a probe file names each
22
+ * requested component's `ComponentProps`, which the checker resolves through
23
+ * imported and extended types the way the book's editor does (#501, #1160).
24
+ */
25
+ export function createExtractor(context) {
26
+ const root = context.root;
27
+ const options = (context.options ?? {});
28
+ const typescript = loadFromBook(root, 'typescript', 'reads Svelte components\' types');
29
+ const { VERSION, parse } = loadFromBook(root, 'svelte/compiler', 'reads Svelte components\' source');
30
+ const { svelte2tsx } = loadFromBook(root, 'svelte2tsx', 'reads Svelte components\' types through it');
31
+ const shims = ['svelte-shims-v4.d.ts', 'svelte-jsx-v4.d.ts'].map(file => join(dirname(createRequire(join(root, 'package.json')).resolve('svelte2tsx/package.json')), file));
32
+ const parsed = readTsconfig(typescript, root);
33
+ // The book's `app.d.ts` and other globals, which the probe's imports would not reach.
34
+ const ambient = ambientFiles(parsed);
35
+ const compilerOptions = {
36
+ module: typescript.ModuleKind.ESNext,
37
+ moduleResolution: typescript.ModuleResolutionKind.Bundler,
38
+ target: typescript.ScriptTarget.ESNext,
39
+ strict: true,
40
+ skipLibCheck: true,
41
+ ...parsed ? parsed.options : {},
42
+ // What `svelte2tsx` output needs whatever the book says: it is TSX, and the probe is never emitted.
43
+ jsx: typescript.JsxEmit.Preserve,
44
+ allowJs: true,
45
+ noEmit: true,
46
+ };
47
+ const probe = join(root, '__poveste_docgen__.ts');
48
+ const requested = [];
49
+ const versions = new Map();
50
+ const generated = new Map();
51
+ let projectVersion = 0;
52
+ function bump(file) {
53
+ versions.set(file, (versions.get(file) ?? 0) + 1);
54
+ projectVersion++;
55
+ }
56
+ function probeText() {
57
+ return requested.map((file, index) => `import C${index} from ${JSON.stringify(file)}\nexport type P${index} = import('svelte').ComponentProps<typeof C${index}>\n`).join('');
58
+ }
59
+ // Whether a component's script is TypeScript. One without `lang="ts"` comes out of
60
+ // `svelte2tsx` typed in JSDoc, which only a JS source file reads.
61
+ const typed = new Map();
62
+ function tsx(file) {
63
+ let code = generated.get(file);
64
+ if (code === undefined) {
65
+ const source = readFileSync(file, 'utf8');
66
+ const isTsFile = /<script\s[^>]*lang=["']ts["']/.test(source);
67
+ typed.set(file, isTsFile);
68
+ code = svelte2tsx(source, { filename: file, isTsFile, mode: 'ts', version: VERSION }).code;
69
+ generated.set(file, code);
70
+ }
71
+ return code;
72
+ }
73
+ function scriptKind(file) {
74
+ if (!isVirtual(file)) {
75
+ return typescript.ScriptKind.Unknown;
76
+ }
77
+ tsx(realOf(file));
78
+ return typed.get(realOf(file)) ? typescript.ScriptKind.TSX : typescript.ScriptKind.JSX;
79
+ }
80
+ function read(file) {
81
+ if (file === probe) {
82
+ return probeText();
83
+ }
84
+ if (isVirtual(file) && existsSync(realOf(file))) {
85
+ return tsx(realOf(file));
86
+ }
87
+ return typescript.sys.readFile(file);
88
+ }
89
+ function exists(file) {
90
+ return file === probe || (isVirtual(file) ? existsSync(realOf(file)) : typescript.sys.fileExists(file));
91
+ }
92
+ const moduleHost = { ...typescript.sys, fileExists: exists, readFile: read };
93
+ const cache = typescript.createModuleResolutionCache(root, name => name, compilerOptions);
94
+ /**
95
+ * A `.svelte` specifier goes to the virtual file when the book has the component,
96
+ * through the book's own `paths`, and otherwise resolves as TypeScript would: to a
97
+ * package's `.svelte.d.ts`, or to the ambient `*.svelte` module.
98
+ */
99
+ function resolveOne(specifier, containingFile) {
100
+ if (specifier.endsWith('.svelte')) {
101
+ const direct = specifier.startsWith('.') || isAbsolute(specifier) ? resolve(dirname(containingFile), specifier) + VIRTUAL : undefined;
102
+ // Through `paths` (`$lib/x.svelte`), the mapped location is all that is kept: TypeScript marks
103
+ // that resolution as made with a `.tsx` extension, which the specifier does not have, and the
104
+ // checker then fails an assertion looking for it.
105
+ const target = direct ?? typescript.resolveModuleName(specifier + VIRTUAL, containingFile, compilerOptions, moduleHost, cache).resolvedModule?.resolvedFileName;
106
+ if (target && isVirtual(target) && existsSync(realOf(target))) {
107
+ return { resolvedModule: { resolvedFileName: target, extension: typescript.Extension.Tsx, isExternalLibraryImport: false } };
108
+ }
109
+ }
110
+ return typescript.resolveModuleName(specifier, containingFile, compilerOptions, moduleHost, cache);
111
+ }
112
+ const service = typescript.createLanguageService({
113
+ getProjectVersion: () => String(projectVersion),
114
+ getCompilationSettings: () => compilerOptions,
115
+ getScriptFileNames: () => [probe, ...shims, ...ambient],
116
+ getScriptVersion: file => String(versions.get(file) ?? 0),
117
+ getScriptSnapshot(file) {
118
+ const text = read(file);
119
+ return text === undefined ? undefined : typescript.ScriptSnapshot.fromString(text);
120
+ },
121
+ getScriptKind: scriptKind,
122
+ getCurrentDirectory: () => root,
123
+ getDefaultLibFileName: settings => typescript.getDefaultLibFilePath(settings),
124
+ fileExists: exists,
125
+ readFile: read,
126
+ readDirectory: typescript.sys.readDirectory,
127
+ directoryExists: typescript.sys.directoryExists,
128
+ getDirectories: typescript.sys.getDirectories,
129
+ ...typescript.sys.realpath ? { realpath: typescript.sys.realpath } : {},
130
+ resolveModuleNameLiterals: (literals, containingFile) => literals.map(literal => resolveOne(literal.text, containingFile)),
131
+ });
132
+ return {
133
+ extract(file) {
134
+ let index = requested.indexOf(file);
135
+ if (index === -1) {
136
+ index = requested.push(file) - 1;
137
+ bump(probe);
138
+ }
139
+ const program = service.getProgram();
140
+ const alias = program?.getSourceFile(probe)?.statements.find((statement) => typescript.isTypeAliasDeclaration(statement) && statement.name.text === `P${index}`);
141
+ if (!program || !alias) {
142
+ throw new Error('TypeScript built no program for the book');
143
+ }
144
+ const checker = program.getTypeChecker();
145
+ const props = checker.getTypeAtLocation(alias.name);
146
+ if (props.flags & typescript.TypeFlags.Any) {
147
+ throw new Error('its props type did not resolve');
148
+ }
149
+ const script = readScript(parse, readFileSync(file, 'utf8'));
150
+ return documentProps(typescript, checker, props, {
151
+ location: alias,
152
+ name: basename(file).replace(/\.svelte$/, ''),
153
+ defaults: script.defaults,
154
+ slotType: SLOT_TYPE,
155
+ eventName: EVENT_NAME,
156
+ takesChildren: script.taken.has('children'),
157
+ }, options);
158
+ },
159
+ update(file) {
160
+ if (file.endsWith('.svelte')) {
161
+ generated.delete(file);
162
+ typed.delete(file);
163
+ bump(file + VIRTUAL);
164
+ }
165
+ else {
166
+ bump(file);
167
+ }
168
+ },
169
+ sources: () => service.getProgram()?.getSourceFiles().map(sourceFile => realOf(sourceFile.fileName)).filter(file => file !== probe && !file.includes('/node_modules/')) ?? [],
170
+ dispose: () => service.dispose(),
171
+ };
172
+ }
package/dist/helpers.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { HstControlOption } from '@poveste/controls';
2
- import type { Story, StoryMeta, StoryProps, Variant, VariantMeta, VariantProps } from '@poveste/shared';
2
+ import type { Awaitable, Story, StoryComponentOptions, StoryMeta, StoryProps, Variant, VariantMeta, VariantProps } from '@poveste/shared';
3
3
  import type { Component, Snippet } from 'svelte';
4
4
  export interface SvelteStorySetupApi {
5
5
  app: any;
@@ -44,7 +44,7 @@ type WithState<P> = Omit<P, 'source'> & {
44
44
  source?: string | ((state: StoryState) => string);
45
45
  };
46
46
  export interface Hst {
47
- Story: Component<WithState<WithChildren<StoryProps>> & {
47
+ Story: Component<WithState<WithChildren<StoryProps>> & StoryComponentOptions<Component<any> | Component<any>[]> & {
48
48
  meta?: StoryMeta;
49
49
  }>;
50
50
  Variant: Component<WithState<WithChildren<VariantProps>> & {
@@ -108,7 +108,7 @@ export interface Hst {
108
108
  value: unknown;
109
109
  title: string;
110
110
  }>;
111
- Shades: Component<{
111
+ ColorShades: Component<{
112
112
  shades: Record<string, any>;
113
113
  getName?: (key: string, color: string) => string;
114
114
  search?: string;
@@ -123,7 +123,8 @@ export interface Hst {
123
123
  colSize?: number;
124
124
  }>;
125
125
  CopyIcon: Component<{
126
- content: string;
126
+ content: string | undefined | (() => Awaitable<string | undefined>);
127
+ loading?: boolean;
127
128
  }>;
128
129
  ColorSelect: Component<{
129
130
  value?: string;
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { createRequire } from 'node:module';
3
3
  import process from 'node:process';
4
+ import { fileURLToPath } from 'node:url';
4
5
  import { dirname, join } from 'pathe';
5
6
  import { defaultColors } from 'poveste';
6
7
  import generateStoryCommand from './commands/generate-story.server.js';
@@ -11,9 +12,38 @@ import { withoutSvelteKitServer } from './util/kit-server.js';
11
12
  import { syncSvelteKit } from './util/kit-sync.js';
12
13
  import { listComponentFiles } from './util/list-components.js';
13
14
  import { disableStoryComponentHmr } from './util/story-hmr.js';
15
+ /**
16
+ * `svelte2tsx` is an optional peer, loaded from the book like TypeScript, so a book
17
+ * with auto-docs off installs neither. Looked for up the book's own `node_modules`,
18
+ * where a check that passes means the extractor's load finds it, or resolved from
19
+ * the book under Yarn PnP, which has none (#1508).
20
+ */
21
+ function svelte2tsxProblem(root) {
22
+ if (process.versions['pnp']) {
23
+ try {
24
+ createRequire(join(root, 'package.json')).resolve('svelte2tsx/package.json');
25
+ return undefined;
26
+ }
27
+ catch { }
28
+ }
29
+ for (let dir = root; dir !== dirname(dir); dir = dirname(dir)) {
30
+ if (existsSync(join(dir, 'node_modules', 'svelte2tsx', 'package.json'))) {
31
+ return undefined;
32
+ }
33
+ }
34
+ return 'Auto-docs is off: it reads Svelte components\' types through `svelte2tsx`, which this book does not have. Install `svelte2tsx` in the book to turn it on, or set `autoDocs: false` to turn it off without this message.';
35
+ }
14
36
  export function HstSvelte() {
15
37
  return {
16
38
  name: '@poveste/plugin-svelte',
39
+ // Props through `svelte2tsx` and the book's own TypeScript, read in the docgen worker (#501).
40
+ docgen: {
41
+ match: file => file.endsWith('.svelte') && !file.endsWith('.story.svelte'),
42
+ module: fileURLToPath(new URL('./docgen/extractor.js', import.meta.url)),
43
+ // `svelte2tsx`'s output is read with the book's TypeScript (#1401).
44
+ typescript: 'book',
45
+ unavailable: svelte2tsxProblem,
46
+ },
17
47
  async defaultConfig() {
18
48
  const syncProblem = syncSvelteKit(process.cwd());
19
49
  if (syncProblem) {
@@ -2,6 +2,9 @@
2
2
  export declare function svelteAutoProps(): {
3
3
  name: string;
4
4
  enforce: "pre";
5
+ watchChange(_id: string, change: {
6
+ event: "create" | "update" | "delete";
7
+ }): void;
5
8
  transform(this: any, code: string, id: string): Promise<{
6
9
  code: string;
7
10
  map: null;
@@ -1,27 +1,52 @@
1
1
  import { readFile } from 'node:fs/promises';
2
+ import { dirname } from 'node:path';
2
3
  import { transformStoryAutoProps } from './auto-props.js';
3
4
  import { extractPropDefs } from './props.js';
4
5
  import { isSvelteStory } from './story-source.js';
5
6
  const IMPORTS_A_COMPONENT = /from\s*['"][^'"]*\.svelte['"]/;
6
7
  /** Runs before `vite-plugin-svelte`: after it compiles there is nothing left to rewrite (#233). */
7
8
  export function svelteAutoProps() {
9
+ // Shared by every story and by both passes a build makes: each story re-resolved
10
+ // and re-parsed the components it imports, 30 times for 12 files in the Svelte
11
+ // example (#476). A resolution holds until a file is created or deleted; props
12
+ // hold while the component's source is unchanged.
13
+ const resolutions = new Map();
14
+ const parsed = new Map();
8
15
  return {
9
16
  name: 'poveste:svelte-auto-props',
10
17
  enforce: 'pre',
18
+ watchChange(_id, change) {
19
+ if (change.event !== 'update') {
20
+ resolutions.clear();
21
+ }
22
+ },
11
23
  async transform(code, id) {
12
24
  if (!isSvelteStory(id, code) || !IMPORTS_A_COMPONENT.test(code)) {
13
25
  return undefined;
14
26
  }
15
27
  const result = await transformStoryAutoProps(code, async (specifier) => {
16
- const resolved = await this.resolve(specifier, id);
17
- if (!resolved) {
28
+ const key = `${dirname(id)}\0${specifier}`;
29
+ if (!resolutions.has(key)) {
30
+ resolutions.set(key, this.resolve(specifier, id).then((resolved) => resolved?.id.split('?')[0], (error) => {
31
+ resolutions.delete(key);
32
+ throw error;
33
+ }));
34
+ }
35
+ const file = await resolutions.get(key);
36
+ if (!file) {
18
37
  return undefined;
19
38
  }
20
- const file = resolved.id.split('?')[0];
21
39
  // Baked into this module, so Vite has to know it depends on the source.
22
40
  this.addWatchFile(file);
23
41
  try {
24
- return extractPropDefs(await readFile(file, 'utf8'));
42
+ const source = await readFile(file, 'utf8');
43
+ const cached = parsed.get(file);
44
+ if (cached?.source === source) {
45
+ return cached.props;
46
+ }
47
+ const props = extractPropDefs(source);
48
+ parsed.set(file, { source, props });
49
+ return props;
25
50
  }
26
51
  catch (error) {
27
52
  // Not a failed build, but silence would look like a component that
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@poveste/plugin-svelte",
3
3
  "type": "module",
4
- "version": "0.19.1",
4
+ "version": "0.21.0",
5
5
  "description": "Poveste plugin for Svelte 5 and SvelteKit support",
6
6
  "author": {
7
7
  "name": "Sorin Gitlan",
@@ -69,36 +69,41 @@
69
69
  "peerDependencies": {
70
70
  "@sveltejs/kit": "^2.53.0 || ^3.0.0",
71
71
  "@sveltejs/vite-plugin-svelte": "^7.3.0",
72
- "poveste": "^0.19.1",
73
- "svelte": "^5.46.4"
72
+ "poveste": "^0.21.0",
73
+ "svelte": "^5.46.4",
74
+ "svelte2tsx": "^0.7.61"
74
75
  },
75
76
  "peerDependenciesMeta": {
76
77
  "@sveltejs/kit": {
77
78
  "optional": true
79
+ },
80
+ "svelte2tsx": {
81
+ "optional": true
78
82
  }
79
83
  },
80
84
  "dependencies": {
81
- "@poveste/controls": "^0.19.1",
82
- "@poveste/shared": "^0.19.1",
83
- "@poveste/vendors": "^0.19.1",
85
+ "@poveste/controls": "^0.21.0",
86
+ "@poveste/shared": "^0.21.0",
87
+ "@poveste/vendors": "^0.21.0",
84
88
  "change-case": "^5.4.4",
85
89
  "ignore": "^7.0.10",
86
- "launch-editor": "^2.9.1",
90
+ "launch-editor": "^2.14.2",
87
91
  "pathe": "^2.0.3",
88
92
  "tinyglobby": "^0.2.17"
89
93
  },
90
94
  "devDependencies": {
91
95
  "@sveltejs/vite-plugin-svelte": "^7.3.1",
92
- "@types/node": "^26.6.3",
93
- "concurrently": "^10.0.5",
96
+ "@types/node": "^26.6.5",
97
+ "concurrently": "^10.0.6",
94
98
  "fs-extra": "^11.4.1",
95
- "jsdom": "^30.1.1",
96
- "poveste": "0.19.1",
97
- "svelte": "5.57.1",
99
+ "jsdom": "^30.1.3",
100
+ "poveste": "0.21.0",
101
+ "svelte": "5.57.2",
98
102
  "svelte-preprocess": "^6.0.5",
103
+ "svelte2tsx": "^0.7.62",
99
104
  "typescript": "6.0.3",
100
- "vite": "^8.3.1",
101
- "vitest": "^5.0.2"
105
+ "vite": "^8.3.4",
106
+ "vitest": "^5.0.5"
102
107
  },
103
108
  "scripts": {
104
109
  "build": "rimraf dist && vite build && tsc -d -P tsconfig.build.json && pnpm run build:types",