diff --git a/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts b/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts index b0343c868..3de132df3 100644 --- a/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts +++ b/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts @@ -85,7 +85,7 @@ class __sveltets_Render${genericsDef} { * Remove this once Svelte typings no longer do that (Svelte 6 or 7) */ let customConstructor = ''; - if (exportedNames.usesRunes()) { + if (exportedNames.hasPropsRune()) { customConstructor = `\n constructor(options: import('svelte').ComponentConstructorOptions<__sveltets_2_PropsWithChildren<${returnType('props')}, ${returnType('slots')}>>) { super(options); }`; } @@ -145,7 +145,7 @@ function addSimpleComponentExport({ * Remove this once Svelte typings no longer do that (Svelte 6 or 7) */ let customConstructor = ''; - if (exportedNames.usesRunes()) { + if (exportedNames.hasPropsRune()) { customConstructor = `\n constructor(options = __sveltets_2_runes_constructor(${propDef})) { super(options); }`; } diff --git a/packages/svelte2tsx/src/svelte2tsx/index.ts b/packages/svelte2tsx/src/svelte2tsx/index.ts index e903ef974..181f8da18 100644 --- a/packages/svelte2tsx/src/svelte2tsx/index.ts +++ b/packages/svelte2tsx/src/svelte2tsx/index.ts @@ -395,6 +395,10 @@ export function svelte2tsx( ({ exportedNames, events, generics, uses$$SlotsInterface } = res); } + if (svelte5Plus) { + exportedNames.checkGlobalsForRunes(implicitStoreValues.getGlobals()); + } + //wrap the script tag and template content in a function returning the slot and exports createRenderFunction({ str, diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts index 6c1abe8f6..209533261 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts @@ -24,6 +24,10 @@ export class ExportedNames { * Uses the `$$Props` type */ public uses$$Props = false; + /** + * Component contains globals that have a rune name + */ + private hasRunesGlobals = false; /** * The `$props()` rune's type info as a string, if it exists. * If using TS, this returns the generic string, if using JS, returns the `@type {..}` string. @@ -33,6 +37,7 @@ export class ExportedNames { type: '', bindings: [] as string[] }; + /** Map of all props and exports. Exposing it publicly is no longer necessary for runes mode */ private exports = new Map(); private possibleExports = new Map< string, @@ -646,6 +651,13 @@ export class ExportedNames { return result; } + if (this.usesRunes()) { + // Necessary, because {} roughly equals to any + return this.isTsFile + ? '{} as Record' + : '/** @type {Record} */ ({})'; + } + if (this.uses$$Props) { const lets = names.filter(([, { isLet }]) => isLet); const others = names.filter(([, { isLet }]) => !isLet); @@ -741,17 +753,29 @@ export class ExportedNames { } createOptionalPropsArray(): string[] { - return Array.from(this.exports.entries()) - .filter(([_, entry]) => !entry.required) - .map(([name, entry]) => `'${entry.identifierText || name}'`); + if (this.usesRunes()) { + return []; + } else { + return Array.from(this.exports.entries()) + .filter(([_, entry]) => !entry.required) + .map(([name, entry]) => `'${entry.identifierText || name}'`); + } } getExportsMap() { return this.exports; } - usesRunes() { - // TODO runes mode can also be given if there's no $props() (but $state() etc) + hasPropsRune() { return this.$props.type || this.$props.comment; } + + checkGlobalsForRunes(globals: string[]) { + const runes = ['$state', '$derived', '$effect']; // no need to check for props, already handled through other means in here + this.hasRunesGlobals = globals.some((global) => runes.includes(global)); + } + + private usesRunes() { + return this.hasRunesGlobals || this.hasPropsRune(); + } } diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitStoreValues.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitStoreValues.ts index 881053fcd..43c1afc3b 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitStoreValues.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitStoreValues.ts @@ -7,7 +7,7 @@ import { extractIdentifiers, getNamesFromLabeledStatement } from '../utils/tsAst /** * Tracks all store-usages as well as all variable declarations and imports in the component. * - * In the modification-step at the end, all variable declartaions and imports which + * In the modification-step at the end, all variable declarations and imports which * were used as stores are appended with `let $xx = __sveltets_2_store_get(xx)` to create the store variables. */ export class ImplicitStoreValues { @@ -49,6 +49,18 @@ export class ImplicitStoreValues { return [...this.accessedStores.keys()]; } + public getGlobals(): string[] { + const globals = new Set(this.accessedStores); + this.variableDeclarations.forEach((node) => + extractIdentifiers(node.name).forEach((id) => globals.delete(id.text)) + ); + this.reactiveDeclarations.forEach((node) => + getNamesFromLabeledStatement(node).forEach((name) => globals.delete(name)) + ); + this.importStatements.forEach(({ name }) => name && globals.delete(name.getText())); + return [...globals].map((name) => `$${name}`); + } + private attachStoreValueDeclarationToDecl( node: ts.VariableDeclaration, astOffset: number, diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/runes-only-export/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/runes-only-export/expectedv2.ts new file mode 100644 index 000000000..91d154a36 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/runes-only-export/expectedv2.ts @@ -0,0 +1,14 @@ +/// +;function render() { + + let x = $state(); + function foo() { return true; } +; +async () => { + +x;}; +return { props: /** @type {Record} */ ({}), exports: /** @type {foo: typeof foo} */ ({}), slots: {}, events: {} }} + +export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(__sveltets_2_with_any_event(render()))) { + get foo() { return render().exports.foo } +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/runes-only-export/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/runes-only-export/input.svelte new file mode 100644 index 000000000..2b21731e2 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/runes-only-export/input.svelte @@ -0,0 +1,6 @@ + + +{x} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune-no-changes/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune-no-changes/expectedv2.ts index c84109940..f1d1c1d07 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune-no-changes/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune-no-changes/expectedv2.ts @@ -9,7 +9,7 @@ async () => {}; return { props: /** @type {$$ComponentProps} */({}), exports: /** @type {snapshot: typeof snapshot} */ ({}), slots: {}, events: {} }} -export default class Page__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(['snapshot'], __sveltets_2_with_any_event(render()))) { - constructor(options = __sveltets_2_runes_constructor(__sveltets_2_partial(['snapshot'], __sveltets_2_with_any_event(render())))) { super(options); } +export default class Page__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(__sveltets_2_with_any_event(render()))) { + constructor(options = __sveltets_2_runes_constructor(__sveltets_2_partial(__sveltets_2_with_any_event(render())))) { super(options); } get snapshot() { return render().exports.snapshot } } \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune/expectedv2.ts index e90443c57..e49b9dd31 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune/expectedv2.ts @@ -7,7 +7,7 @@ async () => {}; return { props: /** @type {$$ComponentProps} */({}), exports: /** @type {snapshot: typeof snapshot} */ ({}), slots: {}, events: {} }} -export default class Page__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(['snapshot'], __sveltets_2_with_any_event(render()))) { - constructor(options = __sveltets_2_runes_constructor(__sveltets_2_partial(['snapshot'], __sveltets_2_with_any_event(render())))) { super(options); } +export default class Page__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(__sveltets_2_with_any_event(render()))) { + constructor(options = __sveltets_2_runes_constructor(__sveltets_2_partial(__sveltets_2_with_any_event(render())))) { super(options); } get snapshot() { return render().exports.snapshot } } \ No newline at end of file