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}