fix: reorder reactive statements during migration (#12329)

fixes #12183
pull/12342/head
Simon H 4 months ago committed by GitHub
parent bbf489504d
commit 2ffb55ce7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: reorder reactive statements during migration

@ -145,6 +145,47 @@ export function migrate(source) {
}
}
/**
* If true, then we need to move all reactive statements to the end of the script block,
* in their correct order. Svelte 4 reordered reactive statements, $derived/$effect.pre
* don't have this behavior.
*/
let needs_reordering = false;
for (const [node, { dependencies }] of state.analysis.reactive_statements) {
/** @type {Compiler.Binding[]} */
let ids = [];
if (
node.body.type === 'ExpressionStatement' &&
node.body.expression.type === 'AssignmentExpression'
) {
ids = extract_identifiers(node.body.expression.left)
.map((id) => state.scope.get(id.name))
.filter((id) => !!id);
}
if (
dependencies.some(
(dep) =>
!ids.includes(dep) &&
/** @type {number} */ (dep.node.start) > /** @type {number} */ (node.start)
)
) {
needs_reordering = true;
break;
}
}
if (needs_reordering) {
const nodes = Array.from(state.analysis.reactive_statements.keys());
for (const node of nodes) {
const { start, end } = get_node_range(source, node);
str.appendLeft(end, '\n');
str.move(start, end, /** @type {number} */ (parsed.instance?.content.end));
str.remove(start - (source[start - 2] === '\r' ? 2 : 1), start);
}
}
if (state.needs_run && !added_legacy_import) {
if (parsed.instance) {
str.appendRight(
@ -377,7 +418,7 @@ const instance_script = {
/** @type {number} */ (node.body.expression.start),
'let '
);
state.str.prependLeft(
state.str.prependRight(
/** @type {number} */ (node.body.expression.right.start),
'$derived('
);
@ -388,14 +429,14 @@ const instance_script = {
');'
);
} else {
state.str.appendRight(/** @type {number} */ (node.end), ');');
state.str.appendLeft(/** @type {number} */ (node.end), ');');
}
return;
} else {
for (const binding of reassigned_bindings) {
if (binding && ids.includes(binding.node)) {
// implicitly-declared variable which we need to make explicit
state.str.prependLeft(
state.str.prependRight(
/** @type {number} */ (node.start),
`let ${binding.node.name}${binding.kind === 'state' ? ' = $state()' : ''};\n${state.indent}`
);
@ -428,7 +469,7 @@ const instance_script = {
[/** @type {number} */ (node.body.end), state.end]
]
});
state.str.appendRight(/** @type {number} */ (node.end), `\n${state.indent}});`);
state.str.appendLeft(/** @type {number} */ (node.end), `\n${state.indent}});`);
}
}
};
@ -808,6 +849,30 @@ function get_indent(state, ...nodes) {
return indent;
}
/**
* Returns start and end of the node. If the start is preceeded with white-space-only before a line break,
* the start will be the start of the line.
* @param {string} source
* @param {Node} node
*/
function get_node_range(source, node) {
let start = /** @type {number} */ (node.start);
let end = /** @type {number} */ (node.end);
let idx = start;
while (source[idx - 1] !== '\n' && source[idx - 1] !== '\r') {
idx--;
if (source[idx] !== ' ' && source[idx] !== '\t') {
idx = start;
break;
}
}
start = idx;
return { start, end };
}
/**
* @param {Compiler.OnDirective} last
* @param {State} state

@ -0,0 +1,7 @@
<script>
$: mobile = width < 640;
$: x = !mobile;
let width = 0;
</script>
{width / mobile / x}

@ -0,0 +1,7 @@
<script>
let width = 0;
let mobile = $derived(width < 640);
let x = $derived(!mobile);
</script>
{width / mobile / x}

@ -0,0 +1,7 @@
<script>
let width = 0;
$: console.log(mobile);
$: mobile = width < 640;
</script>
{width / mobile}

@ -0,0 +1,11 @@
<script>
import { run } from 'svelte/legacy';
let width = 0;
let mobile = $derived(width < 640);
run(() => {
console.log(mobile);
});
</script>
{width / mobile}
Loading…
Cancel
Save