Skip to content

Commit fbe46ec

Browse files
committed
feat: migrate svelte:self
1 parent aa3f002 commit fbe46ec

File tree

8 files changed

+127
-12
lines changed

8 files changed

+127
-12
lines changed

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

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { regex_valid_component_name } from '../phases/1-parse/state/element.js';
1010
import { analyze_component } from '../phases/2-analyze/index.js';
1111
import { get_rune } from '../phases/scope.js';
1212
import { reset, reset_warning_filter } from '../state.js';
13-
import { extract_identifiers, extract_all_identifiers_from_expression } from '../utils/ast.js';
13+
import { extract_all_identifiers_from_expression, extract_identifiers } from '../utils/ast.js';
1414
import { migrate_svelte_ignore } from '../utils/extract_svelte_ignore.js';
1515
import { validate_component_options } from '../validate-options.js';
1616
import { is_svg, is_void } from '../../utils.js';
@@ -23,9 +23,10 @@ const style_placeholder = '/*$$__STYLE_CONTENT__$$*/';
2323
* May throw an error if the code is too complex to migrate automatically.
2424
*
2525
* @param {string} source
26+
* @param {string} [filename]
2627
* @returns {{ code: string; }}
2728
*/
28-
export function migrate(source) {
29+
export function migrate(source, filename) {
2930
try {
3031
// Blank CSS, could contain SCSS or similar that needs a preprocessor.
3132
// Since we don't care about CSS in this migration, we'll just ignore it.
@@ -37,7 +38,7 @@ export function migrate(source) {
3738
});
3839

3940
reset_warning_filter(() => false);
40-
reset(source, { filename: 'migrate.svelte' });
41+
reset(source, { filename: filename ?? 'migrate.svelte' });
4142

4243
let parsed = parse(source);
4344

@@ -64,6 +65,7 @@ export function migrate(source) {
6465
let state = {
6566
scope: analysis.instance.scope,
6667
analysis,
68+
filename,
6769
str,
6870
indent,
6971
props: [],
@@ -86,12 +88,14 @@ export function migrate(source) {
8688
createBubbler: analysis.root.unique('createBubbler').name,
8789
bubble: analysis.root.unique('bubble').name,
8890
passive: analysis.root.unique('passive').name,
89-
nonpassive: analysis.root.unique('nonpassive').name
91+
nonpassive: analysis.root.unique('nonpassive').name,
92+
svelte_self: analysis.root.unique('SvelteSelf').name
9093
},
9194
legacy_imports: new Set(),
9295
script_insertions: new Set(),
9396
derived_components: new Map(),
94-
derived_labeled_statements: new Set()
97+
derived_labeled_statements: new Set(),
98+
has_svelte_self: false
9599
};
96100

97101
if (parsed.module) {
@@ -118,12 +122,21 @@ export function migrate(source) {
118122
state.script_insertions.size > 0 ||
119123
state.props.length > 0 ||
120124
analysis.uses_rest_props ||
121-
analysis.uses_props;
125+
analysis.uses_props ||
126+
state.has_svelte_self;
122127

123128
if (!parsed.instance && need_script) {
124129
str.appendRight(0, '<script>');
125130
}
126131

132+
if (state.has_svelte_self && filename) {
133+
const file = filename.split('/').pop();
134+
str.appendRight(
135+
insertion_point,
136+
`\n${indent}import ${state.names.svelte_self} from './${file}';`
137+
);
138+
}
139+
127140
const specifiers = [...state.legacy_imports].map((imported) => {
128141
const local = state.names[imported];
129142
return imported === local ? imported : `${imported} as ${local}`;
@@ -294,6 +307,7 @@ export function migrate(source) {
294307
* scope: Scope;
295308
* str: MagicString;
296309
* analysis: ComponentAnalysis;
310+
* filename?: string;
297311
* indent: string;
298312
* 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; }>;
299313
* props_insertion_point: number;
@@ -302,8 +316,9 @@ export function migrate(source) {
302316
* names: Record<string, string>;
303317
* legacy_imports: Set<string>;
304318
* script_insertions: Set<string>;
305-
* derived_components: Map<string, string>,
306-
* derived_labeled_statements: Set<LabeledStatement>
319+
* derived_components: Map<string, string>;
320+
* derived_labeled_statements: Set<LabeledStatement>;
321+
* has_svelte_self: boolean;
307322
* }} State
308323
*/
309324

@@ -724,6 +739,32 @@ const template = {
724739
}
725740
next();
726741
},
742+
SvelteSelf(node, { state, next }) {
743+
if (!state.filename) {
744+
const indent = guess_indent(state.str.original.substring(node.start, node.end));
745+
state.str.prependRight(
746+
node.start,
747+
`<!-- @migration-task: migrate this by hand or call the migrate function with the filename of this file -->\n${indent}`
748+
);
749+
return;
750+
}
751+
let end = node.end - 1;
752+
if (node.fragment.nodes.length > 0) {
753+
end = node.fragment.nodes[0].start;
754+
}
755+
if (node.attributes.length > 0) {
756+
end = node.attributes[0].start;
757+
}
758+
state.str.overwrite(node.start + 1, end - 1, `${state.names.svelte_self}`);
759+
if (node.fragment.nodes.length > 0) {
760+
const start_closing =
761+
state.str.original.indexOf('<', node.fragment.nodes[node.fragment.nodes.length - 1].end) +
762+
2;
763+
state.str.overwrite(start_closing, node.end - 1, `${state.names.svelte_self}`);
764+
}
765+
state.has_svelte_self = true;
766+
next();
767+
},
727768
SvelteElement(node, { state, next }) {
728769
if (node.tag.type === 'Literal') {
729770
let is_static = true;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
{/if}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
{/if}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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+
{/if}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
{/if}

packages/svelte/tests/migrate/test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const { test, run } = suite<ParserTest>(async (config, cwd) => {
1212
.replace(/\s+$/, '')
1313
.replace(/\r/g, '');
1414

15-
const actual = migrate(input).code;
15+
const actual = migrate(input, `${cwd}/output.svelte`).code;
1616

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

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)