Skip to content

Commit 922bee9

Browse files
fix: use fine grained for template if the component is not explicitly in legacy mode (#16232)
* fix: use fine grained for template if the component is not explicitly in legacy mode * chore: add comment Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * fix: add `LabeledStatement` to `instance.ast` check * fix: spread `keys` * fix: snapshots * fix: use `compileOption.runes` if defined + add other tests --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
1 parent 2af7ba2 commit 922bee9

File tree

30 files changed

+320
-6
lines changed

30 files changed

+320
-6
lines changed

.changeset/cold-dingos-dream.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+
fix: use fine grained for template if the component is not explicitly in legacy mode

packages/svelte/src/compiler/phases/2-analyze/index.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,29 @@ export function analyze_component(root, source, options) {
431431
template,
432432
elements: [],
433433
runes,
434+
// if we are not in runes mode but we have no reserved references ($$props, $$restProps)
435+
// and no `export let` we might be in a wannabe runes component that is using runes in an external
436+
// module...we need to fallback to the runic behavior
437+
maybe_runes:
438+
!runes &&
439+
// if they explicitly disabled runes, use the legacy behavior
440+
options.runes !== false &&
441+
![...module.scope.references.keys()].some((name) =>
442+
['$$props', '$$restProps'].includes(name)
443+
) &&
444+
!instance.ast.body.some(
445+
(node) =>
446+
node.type === 'LabeledStatement' ||
447+
(node.type === 'ExportNamedDeclaration' &&
448+
((node.declaration &&
449+
node.declaration.type === 'VariableDeclaration' &&
450+
node.declaration.kind === 'let') ||
451+
node.specifiers.some(
452+
(specifier) =>
453+
specifier.local.type === 'Identifier' &&
454+
instance.scope.get(specifier.local.name)?.declaration_kind === 'let'
455+
)))
456+
),
434457
tracing: false,
435458
classes: new Map(),
436459
immutable: runes || options.immutable,

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,10 @@ export function validate_mutation(node, context, expression) {
370370
export function build_expression(context, expression, metadata, state = context.state) {
371371
const value = /** @type {Expression} */ (context.visit(expression, state));
372372

373-
if (context.state.analysis.runes) {
373+
// Components not explicitly in legacy mode might be expected to be in runes mode (especially since we didn't
374+
// adjust this behavior until recently, which broke people's existing components), so we also bail in this case.
375+
// Kind of an in-between-mode.
376+
if (context.state.analysis.runes || context.state.analysis.maybe_runes) {
374377
return value;
375378
}
376379

packages/svelte/src/compiler/phases/types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export interface ComponentAnalysis extends Analysis {
5151
/** Used for CSS pruning and scoping */
5252
elements: Array<AST.RegularElement | AST.SvelteElement>;
5353
runes: boolean;
54+
maybe_runes: boolean;
5455
tracing: boolean;
5556
exports: Array<{ name: string; alias: string | null }>;
5657
/** Whether the component uses `$$props` */

packages/svelte/tests/runtime-legacy/shared.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,10 @@ async function common_setup(cwd: string, runes: boolean | undefined, config: Run
158158
...config.compileOptions,
159159
immutable: config.immutable,
160160
accessors: 'accessors' in config ? config.accessors : true,
161-
runes
161+
runes:
162+
config.compileOptions && 'runes' in config.compileOptions
163+
? config.compileOptions.runes
164+
: runes
162165
};
163166

164167
// load_compiled can be used for debugging a test. It means the compiler will not run on the input
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
mode: ['client'],
6+
compileOptions: {
7+
runes: undefined
8+
},
9+
async test({ assert, target }) {
10+
const p = target.querySelector('p');
11+
const btn = target.querySelector('button');
12+
flushSync(() => {
13+
btn?.click();
14+
});
15+
assert.equal(p?.innerHTML, '0');
16+
}
17+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<svelte:options runes={false} />
2+
<script>
3+
import { get, set } from "./test.svelte.js";
4+
</script>
5+
6+
<p>{get()}</p>
7+
8+
<button onclick={()=>set()}></button>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
let count = $state(0);
2+
3+
export function get() {
4+
return count;
5+
}
6+
7+
export function set() {
8+
count++;
9+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
mode: ['client'],
6+
compileOptions: {
7+
runes: undefined
8+
},
9+
async test({ assert, target }) {
10+
const p = target.querySelector('p');
11+
const btn = target.querySelector('button');
12+
flushSync(() => {
13+
btn?.click();
14+
});
15+
assert.equal(p?.innerHTML, '0');
16+
}
17+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import { get, set } from "./test.svelte.js";
3+
4+
$$props;
5+
</script>
6+
7+
<p>{get()}</p>
8+
9+
<button onclick={()=>set()}></button>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
let count = $state(0);
2+
3+
export function get() {
4+
return count;
5+
}
6+
7+
export function set() {
8+
count++;
9+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
mode: ['client'],
6+
compileOptions: {
7+
runes: undefined
8+
},
9+
async test({ assert, target }) {
10+
const p = target.querySelector('p');
11+
const btn = target.querySelector('button');
12+
flushSync(() => {
13+
btn?.click();
14+
});
15+
assert.equal(p?.innerHTML, '0');
16+
}
17+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import { get, set } from "./test.svelte.js";
3+
4+
$$restProps;
5+
</script>
6+
7+
<p>{get()}</p>
8+
9+
<button onclick={()=>set()}></button>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
let count = $state(0);
2+
3+
export function get() {
4+
return count;
5+
}
6+
7+
export function set() {
8+
count++;
9+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
mode: ['client'],
6+
compileOptions: {
7+
runes: undefined
8+
},
9+
async test({ assert, target }) {
10+
const p = target.querySelector('p');
11+
const btn = target.querySelector('button');
12+
flushSync(() => {
13+
btn?.click();
14+
});
15+
assert.equal(p?.innerHTML, '1');
16+
}
17+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import { get, set } from "./test.svelte.js";
3+
4+
export const x = 42;
5+
</script>
6+
7+
<p>{get()}</p>
8+
9+
<button onclick={()=>set()}></button>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
let count = $state(0);
2+
3+
export function get() {
4+
return count;
5+
}
6+
7+
export function set() {
8+
count++;
9+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
mode: ['client'],
6+
compileOptions: {
7+
runes: undefined
8+
},
9+
async test({ assert, target }) {
10+
const p = target.querySelector('p');
11+
const btn = target.querySelector('button');
12+
flushSync(() => {
13+
btn?.click();
14+
});
15+
assert.equal(p?.innerHTML, '0');
16+
}
17+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import { get, set } from "./test.svelte.js";
3+
4+
$: console.log("");
5+
</script>
6+
7+
<p>{get()}</p>
8+
9+
<button onclick={()=>set()}></button>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
let count = $state(0);
2+
3+
export function get() {
4+
return count;
5+
}
6+
7+
export function set() {
8+
count++;
9+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
mode: ['client'],
6+
compileOptions: {
7+
runes: undefined
8+
},
9+
async test({ assert, target }) {
10+
const p = target.querySelector('p');
11+
const btn = target.querySelector('button');
12+
flushSync(() => {
13+
btn?.click();
14+
});
15+
assert.equal(p?.innerHTML, '0');
16+
}
17+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script>
2+
import { get, set } from "./test.svelte.js";
3+
4+
export let x = 42;
5+
</script>
6+
7+
{x}
8+
<p>{get()}</p>
9+
10+
<button onclick={()=>set()}></button>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
let count = $state(0);
2+
3+
export function get() {
4+
return count;
5+
}
6+
7+
export function set() {
8+
count++;
9+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
mode: ['client'],
6+
compileOptions: {
7+
runes: undefined
8+
},
9+
async test({ assert, target }) {
10+
const p = target.querySelector('p');
11+
const btn = target.querySelector('button');
12+
flushSync(() => {
13+
btn?.click();
14+
});
15+
assert.equal(p?.innerHTML, '0');
16+
}
17+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script>
2+
import { get, set } from "./test.svelte.js";
3+
4+
let x = 42;
5+
6+
export { x };
7+
</script>
8+
9+
{x}
10+
<p>{get()}</p>
11+
12+
<button onclick={()=>set()}></button>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
let count = $state(0);
2+
3+
export function get() {
4+
return count;
5+
}
6+
7+
export function set() {
8+
count++;
9+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
mode: ['client'],
6+
compileOptions: {
7+
runes: undefined
8+
},
9+
async test({ assert, target }) {
10+
const p = target.querySelector('p');
11+
const btn = target.querySelector('button');
12+
flushSync(() => {
13+
btn?.click();
14+
});
15+
assert.equal(p?.innerHTML, '1');
16+
}
17+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
import { get, set } from "./test.svelte.js";
3+
</script>
4+
5+
<p>{get()}</p>
6+
7+
<button onclick={()=>set()}></button>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
let count = $state(0);
2+
3+
export function get() {
4+
return count;
5+
}
6+
7+
export function set() {
8+
count++;
9+
}

packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@ export default function Purity($$anchor) {
88
var fragment = root();
99
var p = $.first_child(fragment);
1010

11-
p.textContent = (
12-
$.untrack(() => Math.max(0, Math.min(0, 100)))
13-
);
11+
p.textContent = '0';
1412

1513
var p_1 = $.sibling(p, 2);
1614

17-
p_1.textContent = ($.untrack(() => location.href));
15+
p_1.textContent = location.href;
1816

1917
var node = $.sibling(p_1, 2);
2018

0 commit comments

Comments
 (0)