diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts
index acc2f9c82..66d191902 100644
--- a/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts
+++ b/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts
@@ -40,6 +40,7 @@ import {
} from '../svelte2tsx/nodes/handleScopeAndResolveForSlot';
import { EventHandler } from '../svelte2tsx/nodes/event-handler';
import { ComponentEvents } from '../svelte2tsx/nodes/ComponentEvents';
+import { analyze } from 'periscopic';
export interface TemplateProcessResult {
/**
@@ -53,7 +54,7 @@ export interface TemplateProcessResult {
scriptTag: BaseNode;
moduleScriptTag: BaseNode;
/** Start/end positions of snippets that should be moved to the instance script or possibly even module script */
- rootSnippets: Array<[number, number]>;
+ rootSnippets: Array<[start: number, end: number, globals: Map]>;
/** To be added later as a comment on the default class export */
componentDocumentation: ComponentDocumentation;
events: ComponentEvents;
@@ -92,7 +93,7 @@ export function convertHtmlxToJsx(
stripDoctype(str);
- const rootSnippets: Array<[number, number]> = [];
+ const rootSnippets: Array<[number, number, Map]> = [];
let element: Element | InlineComponent | undefined;
const pendingSnippetHoistCheck = new Set();
@@ -249,7 +250,21 @@ export function convertHtmlxToJsx(
);
if (parent === ast) {
// root snippet -> move to instance script or possibly even module script
- rootSnippets.push([node.start, node.end]);
+ const result = analyze({
+ type: 'FunctionDeclaration',
+ start: -1,
+ end: -1,
+ id: node.expression,
+ params: node.parameters ?? [],
+ body: {
+ type: 'BlockStatement',
+ start: -1,
+ end: -1,
+ body: node.children
+ }
+ });
+
+ rootSnippets.push([node.start, node.end, result.globals]);
} else {
pendingSnippetHoistCheck.add(parent);
}
diff --git a/packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts b/packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts
index ab568ce0e..6a929fe94 100644
--- a/packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts
@@ -8,7 +8,6 @@ export interface CreateRenderFunctionPara extends InstanceScriptProcessResult {
str: MagicString;
scriptTag: Node;
scriptDestination: number;
- rootSnippets: Array<[number, number]>;
slots: Map>;
events: ComponentEvents;
uses$$SlotsInterface: boolean;
@@ -20,7 +19,6 @@ export function createRenderFunction({
str,
scriptTag,
scriptDestination,
- rootSnippets,
slots,
events,
exportedNames,
@@ -82,10 +80,6 @@ export function createRenderFunction({
);
}
- for (const rootSnippet of rootSnippets) {
- str.move(rootSnippet[0], rootSnippet[1], scriptTagEnd);
- }
-
const scriptEndTagStart = htmlx.lastIndexOf('<', scriptTag.end - 1);
// wrap template with callback
str.overwrite(scriptEndTagStart, scriptTag.end, `${slotsDeclaration};\nasync () => {`, {
diff --git a/packages/svelte2tsx/src/svelte2tsx/index.ts b/packages/svelte2tsx/src/svelte2tsx/index.ts
index 06f343ac4..0b48cc607 100644
--- a/packages/svelte2tsx/src/svelte2tsx/index.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/index.ts
@@ -139,7 +139,6 @@ export function svelte2tsx(
str,
scriptTag,
scriptDestination: instanceScriptTarget,
- rootSnippets,
slots,
events,
exportedNames,
@@ -164,6 +163,32 @@ export function svelte2tsx(
),
moduleAst
);
+ if (!scriptTag) {
+ moduleAst.tsAst.forEachChild((node) =>
+ exportedNames.hoistableInterfaces.analyzeModuleScriptNode(node)
+ );
+ }
+ }
+
+ if (moduleScriptTag || scriptTag) {
+ const allowed = exportedNames.hoistableInterfaces.getAllowedValues();
+ for (const [start, end, globals] of rootSnippets) {
+ const hoist_to_module =
+ moduleScriptTag &&
+ (globals.size === 0 || [...globals.keys()].every((id) => allowed.has(id)));
+
+ if (hoist_to_module) {
+ str.move(
+ start,
+ end,
+ scriptTag
+ ? scriptTag.start + 1 // +1 because imports are also moved at that position, and we want to move interfaces after imports
+ : moduleScriptTag.end
+ );
+ } else if (scriptTag) {
+ str.move(start, end, renderFunctionStart);
+ }
+ }
}
addComponentExport({
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts
index 647926ed0..093a4a132 100644
--- a/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts
@@ -358,6 +358,10 @@ export class HoistableInterfaces {
}
}
+ getAllowedValues() {
+ return this.import_value_set;
+ }
+
/**
* Collects type and value dependencies from a given TypeNode.
* @param type_node The TypeNode to analyze.
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-1.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-1.v5/expectedv2.ts
new file mode 100644
index 000000000..69587fe79
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-1.v5/expectedv2.ts
@@ -0,0 +1,50 @@
+///
+;
+ let module = true;
+;;
+
+import { imported } from './x';
+ const hoistable1/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
+ { svelteHTML.createElement("div", {}); }
+};return __sveltets_2_any(0)}; const hoistable2/*Ωignore_positionΩ*/ = (bar)/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
+ { svelteHTML.createElement("div", {});bar; }
+};return __sveltets_2_any(0)}; const hoistable3/*Ωignore_positionΩ*/ = (bar: string)/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
+ { svelteHTML.createElement("div", {});bar; }
+};return __sveltets_2_any(0)}; const hoistable4/*Ωignore_positionΩ*/ = (foo)/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
+ { svelteHTML.createElement("div", {});foo; }
+};return __sveltets_2_any(0)}; const hoistable5/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
+ { svelteHTML.createElement("button", { "onclick":e => e,}); }
+};return __sveltets_2_any(0)}; const hoistable6/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
+ { svelteHTML.createElement("div", {});module; }
+};return __sveltets_2_any(0)}; const hoistable7/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
+ { svelteHTML.createElement("div", {});imported; }
+};return __sveltets_2_any(0)};function render() {
+ const not_hoistable/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
+ { svelteHTML.createElement("div", {});foo; }
+};return __sveltets_2_any(0)};
+
+ let foo = true;
+;
+async () => {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+};
+return { props: /** @type {Record} */ ({}), exports: {}, bindings: "", slots: {}, events: {} }}
+const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_partial(__sveltets_2_with_any_event(render())));
+/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType;
+/*Ωignore_endΩ*/export default Input__SvelteComponent_;
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-1.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-1.v5/input.svelte
new file mode 100644
index 000000000..924dc61aa
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-1.v5/input.svelte
@@ -0,0 +1,40 @@
+
+
+
+
+{#snippet hoistable1()}
+ hello
+{/snippet}
+
+{#snippet hoistable2(bar)}
+ {bar}
+{/snippet}
+
+{#snippet hoistable3(bar: string)}
+ {bar}
+{/snippet}
+
+{#snippet hoistable4(foo)}
+ {foo}
+{/snippet}
+
+{#snippet hoistable5()}
+
+{/snippet}
+
+{#snippet hoistable6()}
+ {module}
+{/snippet}
+
+{#snippet hoistable7()}
+ {imported}
+{/snippet}
+
+{#snippet not_hoistable()}
+ {foo}
+{/snippet}
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-2.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-2.v5/expectedv2.ts
new file mode 100644
index 000000000..90505a1e5
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-2.v5/expectedv2.ts
@@ -0,0 +1,21 @@
+///
+;
+import { imported } from './x';
+function render() {
+ const hoistable/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
+ { svelteHTML.createElement("div", {}); }
+};return __sveltets_2_any(0)}; const not_hoistable/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
+ { svelteHTML.createElement("div", {});foo; }
+};return __sveltets_2_any(0)};
+
+ let foo = true;
+;
+async () => {
+
+
+
+};
+return { props: /** @type {Record} */ ({}), exports: {}, bindings: "", slots: {}, events: {} }}
+const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_partial(__sveltets_2_with_any_event(render())));
+/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType;
+/*Ωignore_endΩ*/export default Input__SvelteComponent_;
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-2.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-2.v5/input.svelte
new file mode 100644
index 000000000..dd221c944
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-2.v5/input.svelte
@@ -0,0 +1,12 @@
+
+
+{#snippet hoistable()}
+ hello
+{/snippet}
+
+{#snippet not_hoistable()}
+ {foo}
+{/snippet}
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-3.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-3.v5/expectedv2.ts
new file mode 100644
index 000000000..a48aa0ae9
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-3.v5/expectedv2.ts
@@ -0,0 +1,17 @@
+///
+;
+ let foo = true;
+; const hoistable1/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
+ { svelteHTML.createElement("div", {}); }
+};return __sveltets_2_any(0)}; const hoistable2/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
+ { svelteHTML.createElement("div", {});foo; }
+};return __sveltets_2_any(0)};;function render() {
+async () => {
+
+
+
+};
+return { props: /** @type {Record} */ ({}), exports: {}, bindings: "", slots: {}, events: {} }}
+const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_partial(__sveltets_2_with_any_event(render())));
+/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType;
+/*Ωignore_endΩ*/export default Input__SvelteComponent_;
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-3.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-3.v5/input.svelte
new file mode 100644
index 000000000..fb72eb302
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-3.v5/input.svelte
@@ -0,0 +1,11 @@
+
+
+{#snippet hoistable1()}
+ hello
+{/snippet}
+
+{#snippet hoistable2()}
+ {foo}
+{/snippet}