Skip to content

Commit 14ecedf

Browse files
feat: migrate svelte:self (#13504)
* feat: migrate `svelte:self` * chore: regenerate types * fix: special case `<svelte:self></svelte:self>` * chore: add special case to tests * chore: add no filename test * chore: better migration task message Co-authored-by: Simon H <[email protected]> * chore: make filename an options object to futureproof it * chore: simplify open tag `svelte:self` * chore: simplify migration comment test * chore: generate types * chore: apply smart suggestion * chore: changeset --------- Co-authored-by: Simon H <[email protected]>
1 parent 687d9db commit 14ecedf

File tree

13 files changed

+165
-14
lines changed

13 files changed

+165
-14
lines changed

.changeset/gold-pens-sell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
feat: support migrating `svelte:self`

packages/svelte/src/compiler/migrate/index.js

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ const style_placeholder = '/*$$__STYLE_CONTENT__$$*/';
2727
* May throw an error if the code is too complex to migrate automatically.
2828
*
2929
* @param {string} source
30+
* @param {{filename?: string}} [options]
3031
* @returns {{ code: string; }}
3132
*/
32-
export function migrate(source) {
33+
export function migrate(source, { filename } = {}) {
3334
try {
3435
// Blank CSS, could contain SCSS or similar that needs a preprocessor.
3536
// Since we don't care about CSS in this migration, we'll just ignore it.
@@ -41,7 +42,7 @@ export function migrate(source) {
4142
});
4243

4344
reset_warning_filter(() => false);
44-
reset(source, { filename: 'migrate.svelte' });
45+
reset(source, { filename: filename ?? 'migrate.svelte' });
4546

4647
let parsed = parse(source);
4748

@@ -68,6 +69,7 @@ export function migrate(source) {
6869
let state = {
6970
scope: analysis.instance.scope,
7071
analysis,
72+
filename,
7173
str,
7274
indent,
7375
props: [],
@@ -90,12 +92,14 @@ export function migrate(source) {
9092
createBubbler: analysis.root.unique('createBubbler').name,
9193
bubble: analysis.root.unique('bubble').name,
9294
passive: analysis.root.unique('passive').name,
93-
nonpassive: analysis.root.unique('nonpassive').name
95+
nonpassive: analysis.root.unique('nonpassive').name,
96+
svelte_self: analysis.root.unique('SvelteSelf').name
9497
},
9598
legacy_imports: new Set(),
9699
script_insertions: new Set(),
97100
derived_components: new Map(),
98-
derived_labeled_statements: new Set()
101+
derived_labeled_statements: new Set(),
102+
has_svelte_self: false
99103
};
100104

101105
if (parsed.module) {
@@ -122,12 +126,21 @@ export function migrate(source) {
122126
state.script_insertions.size > 0 ||
123127
state.props.length > 0 ||
124128
analysis.uses_rest_props ||
125-
analysis.uses_props;
129+
analysis.uses_props ||
130+
state.has_svelte_self;
126131

127132
if (!parsed.instance && need_script) {
128133
str.appendRight(0, '<script>');
129134
}
130135

136+
if (state.has_svelte_self && filename) {
137+
const file = filename.split('/').pop();
138+
str.appendRight(
139+
insertion_point,
140+
`\n${indent}import ${state.names.svelte_self} from './${file}';`
141+
);
142+
}
143+
131144
const specifiers = [...state.legacy_imports].map((imported) => {
132145
const local = state.names[imported];
133146
return imported === local ? imported : `${imported} as ${local}`;
@@ -298,6 +311,7 @@ export function migrate(source) {
298311
* scope: Scope;
299312
* str: MagicString;
300313
* analysis: ComponentAnalysis;
314+
* filename?: string;
301315
* indent: string;
302316
* props: Array<{ local: string; exported: string; init: string; bindable: boolean; slot_name?: string; optional: boolean; type: string; comment?: string; type_only?: boolean; needs_refine_type?: boolean; }>;
303317
* props_insertion_point: number;
@@ -306,8 +320,9 @@ export function migrate(source) {
306320
* names: Record<string, string>;
307321
* legacy_imports: Set<string>;
308322
* script_insertions: Set<string>;
309-
* derived_components: Map<string, string>,
310-
* derived_labeled_statements: Set<LabeledStatement>
323+
* derived_components: Map<string, string>;
324+
* derived_labeled_statements: Set<LabeledStatement>;
325+
* has_svelte_self: boolean;
311326
* }} State
312327
*/
313328

@@ -729,9 +744,44 @@ const template = {
729744
}
730745
next();
731746
},
747+
SvelteSelf(node, { state, next }) {
748+
const source = state.str.original.substring(node.start, node.end);
749+
if (!state.filename) {
750+
const indent = guess_indent(source);
751+
state.str.prependRight(
752+
node.start,
753+
`<!-- @migration-task: svelte:self is deprecated, import this Svelte file into itself instead -->\n${indent}`
754+
);
755+
next();
756+
return;
757+
}
758+
// overwrite the open tag
759+
state.str.overwrite(
760+
node.start + 1,
761+
node.start + 1 + 'svelte:self'.length,
762+
`${state.names.svelte_self}`
763+
);
764+
// if it has a fragment we need to overwrite the closing tag too
765+
if (node.fragment.nodes.length > 0) {
766+
state.str.overwrite(
767+
state.str.original.lastIndexOf('<', node.end) + 2,
768+
node.end - 1,
769+
`${state.names.svelte_self}`
770+
);
771+
} else if (!source.endsWith('/>')) {
772+
// special case for case `<svelte:self></svelte:self>` it has no fragment but
773+
// we still need to overwrite the end tag
774+
state.str.overwrite(
775+
node.start + source.lastIndexOf('</', node.end) + 2,
776+
node.end - 1,
777+
`${state.names.svelte_self}`
778+
);
779+
}
780+
state.has_svelte_self = true;
781+
next();
782+
},
732783
SvelteElement(node, { state, path, next }) {
733784
migrate_slot_usage(node, path, state);
734-
735785
if (node.tag.type === 'Literal') {
736786
let is_static = true;
737787

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script>
2+
let SvelteSelf;
3+
</script>
4+
5+
{#if false}
6+
<svelte:self />
7+
<svelte:self with_attributes/>
8+
<svelte:self count={count+1}/>
9+
<svelte:self>
10+
child
11+
</svelte:self>
12+
<svelte:self count={count+1}>
13+
child
14+
</svelte:self>
15+
<svelte:self count={$$props.count} >
16+
child
17+
</svelte:self>
18+
<svelte:self></svelte:self>
19+
{/if}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script>
2+
import SvelteSelf_1 from './output.svelte';
3+
/** @type {Record<string, any>} */
4+
let { ...props } = $props();
5+
let SvelteSelf;
6+
</script>
7+
8+
{#if false}
9+
<SvelteSelf_1 />
10+
<SvelteSelf_1 with_attributes/>
11+
<SvelteSelf_1 count={count+1}/>
12+
<SvelteSelf_1>
13+
child
14+
</SvelteSelf_1>
15+
<SvelteSelf_1 count={count+1}>
16+
child
17+
</SvelteSelf_1>
18+
<SvelteSelf_1 count={props.count} >
19+
child
20+
</SvelteSelf_1>
21+
<SvelteSelf_1></SvelteSelf_1>
22+
{/if}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
skip_filename: true
5+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{#if false}
2+
<svelte:self />
3+
{/if}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{#if false}
2+
<!-- @migration-task: svelte:self is deprecated, import this Svelte file into itself instead -->
3+
<svelte:self />
4+
{/if}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{#if false}
2+
<svelte:self />
3+
<svelte:self with_attributes/>
4+
<svelte:self count={count+1}/>
5+
<svelte:self>
6+
child
7+
</svelte:self>
8+
<svelte:self count={count+1}>
9+
child
10+
</svelte:self>
11+
<svelte:self count={$$props.count} >
12+
child
13+
</svelte:self>
14+
<svelte:self></svelte:self>
15+
{/if}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script>
2+
import SvelteSelf from './output.svelte';
3+
/** @type {Record<string, any>} */
4+
let { ...props } = $props();
5+
</script>
6+
7+
{#if false}
8+
<SvelteSelf />
9+
<SvelteSelf with_attributes/>
10+
<SvelteSelf count={count+1}/>
11+
<SvelteSelf>
12+
child
13+
</SvelteSelf>
14+
<SvelteSelf count={count+1}>
15+
child
16+
</SvelteSelf>
17+
<SvelteSelf count={props.count} >
18+
child
19+
</SvelteSelf>
20+
<SvelteSelf></SvelteSelf>
21+
{/if}

packages/svelte/tests/migrate/test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@ import { migrate } from 'svelte/compiler';
44
import { try_read_file } from '../helpers.js';
55
import { suite, type BaseTest } from '../suite.js';
66

7-
interface ParserTest extends BaseTest {}
7+
interface ParserTest extends BaseTest {
8+
skip_filename?: boolean;
9+
}
810

911
const { test, run } = suite<ParserTest>(async (config, cwd) => {
1012
const input = fs
1113
.readFileSync(`${cwd}/input.svelte`, 'utf-8')
1214
.replace(/\s+$/, '')
1315
.replace(/\r/g, '');
1416

15-
const actual = migrate(input).code;
17+
const actual = migrate(input, {
18+
filename: config.skip_filename ? undefined : `${cwd}/output.svelte`
19+
}).code;
1620

1721
// run `UPDATE_SNAPSHOTS=true pnpm test migrate` to update parser tests
1822
if (process.env.UPDATE_SNAPSHOTS || !fs.existsSync(`${cwd}/output.svelte`)) {

packages/svelte/types/index.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1264,7 +1264,9 @@ declare module 'svelte/compiler' {
12641264
* May throw an error if the code is too complex to migrate automatically.
12651265
*
12661266
* */
1267-
export function migrate(source: string): {
1267+
export function migrate(source: string, { filename }?: {
1268+
filename?: string;
1269+
} | undefined): {
12681270
code: string;
12691271
};
12701272
namespace Css {

sites/svelte-5-preview/src/lib/Output/Compiler.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ export default class Compiler {
8080
this.worker.postMessage({
8181
id,
8282
type: 'migrate',
83-
source: file.source
83+
source: file.source,
84+
filename: `${file.name}.${file.type}`
8485
});
8586
});
8687
}

sites/svelte-5-preview/src/lib/workers/compiler/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,9 @@ function compile({ id, source, options, return_ast }) {
133133
}
134134

135135
/** @param {import("../workers").MigrateMessageData} param0 */
136-
function migrate({ id, source }) {
136+
function migrate({ id, source, filename }) {
137137
try {
138-
const result = svelte.migrate(source);
138+
const result = svelte.migrate(source, { filename });
139139

140140
return {
141141
id,

0 commit comments

Comments
 (0)