Skip to content

fix: use fine grained for template if the component is not explicitly in legacy mode #16232

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cold-dingos-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: use fine grained for template if the component is not explicitly in legacy mode
23 changes: 23 additions & 0 deletions packages/svelte/src/compiler/phases/2-analyze/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,29 @@ export function analyze_component(root, source, options) {
template,
elements: [],
runes,
// if we are not in runes mode but we have no reserved references ($$props, $$restProps)
// and no `export let` we might be in a wannabe runes component that is using runes in an external
// module...we need to fallback to the runic behavior
maybe_runes:
!runes &&
// if they explicitly disabled runes, use the legacy behavior
options.runes !== false &&
![...module.scope.references.keys()].some((name) =>
['$$props', '$$restProps'].includes(name)
) &&
!instance.ast.body.some(
(node) =>
node.type === 'LabeledStatement' ||
(node.type === 'ExportNamedDeclaration' &&
((node.declaration &&
node.declaration.type === 'VariableDeclaration' &&
node.declaration.kind === 'let') ||
node.specifiers.some(
(specifier) =>
specifier.local.type === 'Identifier' &&
instance.scope.get(specifier.local.name)?.declaration_kind === 'let'
)))
),
tracing: false,
classes: new Map(),
immutable: runes || options.immutable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,10 @@ export function validate_mutation(node, context, expression) {
export function build_expression(context, expression, metadata, state = context.state) {
const value = /** @type {Expression} */ (context.visit(expression, state));

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

Expand Down
1 change: 1 addition & 0 deletions packages/svelte/src/compiler/phases/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export interface ComponentAnalysis extends Analysis {
/** Used for CSS pruning and scoping */
elements: Array<AST.RegularElement | AST.SvelteElement>;
runes: boolean;
maybe_runes: boolean;
tracing: boolean;
exports: Array<{ name: string; alias: string | null }>;
/** Whether the component uses `$$props` */
Expand Down
5 changes: 4 additions & 1 deletion packages/svelte/tests/runtime-legacy/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,10 @@ async function common_setup(cwd: string, runes: boolean | undefined, config: Run
...config.compileOptions,
immutable: config.immutable,
accessors: 'accessors' in config ? config.accessors : true,
runes
runes:
config.compileOptions && 'runes' in config.compileOptions
? config.compileOptions.runes
: runes
};

// load_compiled can be used for debugging a test. It means the compiler will not run on the input
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { flushSync } from 'svelte';
import { test } from '../../test';

export default test({
mode: ['client'],
compileOptions: {
runes: undefined
},
async test({ assert, target }) {
const p = target.querySelector('p');
const btn = target.querySelector('button');
flushSync(() => {
btn?.click();
});
assert.equal(p?.innerHTML, '0');
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<svelte:options runes={false} />
<script>
import { get, set } from "./test.svelte.js";
</script>

<p>{get()}</p>

<button onclick={()=>set()}></button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let count = $state(0);

export function get() {
return count;
}

export function set() {
count++;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { flushSync } from 'svelte';
import { test } from '../../test';

export default test({
mode: ['client'],
compileOptions: {
runes: undefined
},
async test({ assert, target }) {
const p = target.querySelector('p');
const btn = target.querySelector('button');
flushSync(() => {
btn?.click();
});
assert.equal(p?.innerHTML, '0');
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script>
import { get, set } from "./test.svelte.js";

$$props;
</script>

<p>{get()}</p>

<button onclick={()=>set()}></button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let count = $state(0);

export function get() {
return count;
}

export function set() {
count++;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { flushSync } from 'svelte';
import { test } from '../../test';

export default test({
mode: ['client'],
compileOptions: {
runes: undefined
},
async test({ assert, target }) {
const p = target.querySelector('p');
const btn = target.querySelector('button');
flushSync(() => {
btn?.click();
});
assert.equal(p?.innerHTML, '0');
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script>
import { get, set } from "./test.svelte.js";

$$restProps;
</script>

<p>{get()}</p>

<button onclick={()=>set()}></button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let count = $state(0);

export function get() {
return count;
}

export function set() {
count++;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { flushSync } from 'svelte';
import { test } from '../../test';

export default test({
mode: ['client'],
compileOptions: {
runes: undefined
},
async test({ assert, target }) {
const p = target.querySelector('p');
const btn = target.querySelector('button');
flushSync(() => {
btn?.click();
});
assert.equal(p?.innerHTML, '1');
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script>
import { get, set } from "./test.svelte.js";

export const x = 42;
</script>

<p>{get()}</p>

<button onclick={()=>set()}></button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let count = $state(0);

export function get() {
return count;
}

export function set() {
count++;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { flushSync } from 'svelte';
import { test } from '../../test';

export default test({
mode: ['client'],
compileOptions: {
runes: undefined
},
async test({ assert, target }) {
const p = target.querySelector('p');
const btn = target.querySelector('button');
flushSync(() => {
btn?.click();
});
assert.equal(p?.innerHTML, '0');
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script>
import { get, set } from "./test.svelte.js";

$: console.log("");
</script>

<p>{get()}</p>

<button onclick={()=>set()}></button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let count = $state(0);

export function get() {
return count;
}

export function set() {
count++;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { flushSync } from 'svelte';
import { test } from '../../test';

export default test({
mode: ['client'],
compileOptions: {
runes: undefined
},
async test({ assert, target }) {
const p = target.querySelector('p');
const btn = target.querySelector('button');
flushSync(() => {
btn?.click();
});
assert.equal(p?.innerHTML, '0');
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
import { get, set } from "./test.svelte.js";

export let x = 42;
</script>

{x}
<p>{get()}</p>

<button onclick={()=>set()}></button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let count = $state(0);

export function get() {
return count;
}

export function set() {
count++;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { flushSync } from 'svelte';
import { test } from '../../test';

export default test({
mode: ['client'],
compileOptions: {
runes: undefined
},
async test({ assert, target }) {
const p = target.querySelector('p');
const btn = target.querySelector('button');
flushSync(() => {
btn?.click();
});
assert.equal(p?.innerHTML, '0');
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script>
import { get, set } from "./test.svelte.js";

let x = 42;

export { x };
</script>

{x}
<p>{get()}</p>

<button onclick={()=>set()}></button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let count = $state(0);

export function get() {
return count;
}

export function set() {
count++;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { flushSync } from 'svelte';
import { test } from '../../test';

export default test({
mode: ['client'],
compileOptions: {
runes: undefined
},
async test({ assert, target }) {
const p = target.querySelector('p');
const btn = target.querySelector('button');
flushSync(() => {
btn?.click();
});
assert.equal(p?.innerHTML, '1');
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
import { get, set } from "./test.svelte.js";
</script>

<p>{get()}</p>

<button onclick={()=>set()}></button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let count = $state(0);

export function get() {
return count;
}

export function set() {
count++;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@ export default function Purity($$anchor) {
var fragment = root();
var p = $.first_child(fragment);

p.textContent = (
$.untrack(() => Math.max(0, Math.min(0, 100)))
);
p.textContent = '0';

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

p_1.textContent = ($.untrack(() => location.href));
p_1.textContent = location.href;

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

Expand Down