diff --git a/.changeset/eighty-days-cheat.md b/.changeset/eighty-days-cheat.md
new file mode 100644
index 0000000000..1dab9a948f
--- /dev/null
+++ b/.changeset/eighty-days-cheat.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: improve template literal expression output generation
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
index 2ecec2eeb3..19fef4e5bd 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
@@ -615,6 +615,7 @@ export function should_proxy_or_freeze(node) {
if (
!node ||
node.type === 'Literal' ||
+ node.type === 'TemplateLiteral' ||
node.type === 'ArrowFunctionExpression' ||
node.type === 'FunctionExpression' ||
node.type === 'UnaryExpression' ||
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js
index 8c3cb748c4..b6708bc851 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js
@@ -1663,6 +1663,11 @@ function serialize_template_literal(values, visit, state) {
if (node.type === 'Text') {
const last = /** @type {import('estree').TemplateElement} */ (quasis.at(-1));
last.value.raw += sanitize_template_string(node.data);
+ } else if (node.type === 'ExpressionTag' && node.expression.type === 'Literal') {
+ const last = /** @type {import('estree').TemplateElement} */ (quasis.at(-1));
+ if (node.expression.value != null) {
+ last.value.raw += sanitize_template_string(node.expression.value + '');
+ }
} else {
if (node.type === 'ExpressionTag' && node.metadata.contains_call_expression) {
contains_call_expression = true;
diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js
index 30da7f35bb..2062095235 100644
--- a/packages/svelte/src/internal/client/render.js
+++ b/packages/svelte/src/internal/client/render.js
@@ -2803,7 +2803,7 @@ export function mount(component, options) {
).c = options.context;
}
// @ts-expect-error the public typings are not what the actual function looks like
- accessors = component(anchor, options.props || {}, options.events || {});
+ accessors = component(anchor, options.props || {});
if (options.context) {
pop();
}
diff --git a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js
index e233a9d51e..f0960dc93d 100644
--- a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js
@@ -45,4 +45,4 @@ export default function Main($$anchor, $$props) {
$.close_frag($$anchor, fragment);
$.pop();
-}
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/each-string-template/_config.js b/packages/svelte/tests/snapshot/samples/each-string-template/_config.js
new file mode 100644
index 0000000000..f47bee71df
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/each-string-template/_config.js
@@ -0,0 +1,3 @@
+import { test } from '../../test';
+
+export default test({});
diff --git a/packages/svelte/tests/snapshot/samples/each-string-template/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/each-string-template/_expected/client/index.svelte.js
new file mode 100644
index 0000000000..5680d12e63
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/each-string-template/_expected/client/index.svelte.js
@@ -0,0 +1,30 @@
+// index.svelte (Svelte VERSION)
+// Note: compiler output will change before 5.0 is released!
+import "svelte/internal/disclose-version";
+import * as $ from "svelte/internal";
+
+export default function Each_string_template($$anchor, $$props) {
+ $.push($$props, false);
+
+ /* Init */
+ var fragment = $.comment($$anchor);
+ var node = $.child_frag(fragment);
+
+ $.each_indexed(
+ node,
+ () => ['foo', 'bar', 'baz'],
+ 1,
+ ($$anchor, thing, $$index) => {
+ /* Init */
+ var node_1 = $.space($$anchor);
+
+ /* Update */
+ $.text_effect(node_1, () => `${$.stringify($.unwrap(thing))}, `);
+ $.close($$anchor, node_1);
+ },
+ null
+ );
+
+ $.close_frag($$anchor, fragment);
+ $.pop();
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/each-string-template/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/each-string-template/_expected/server/index.svelte.js
new file mode 100644
index 0000000000..a4bbf5a982
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/each-string-template/_expected/server/index.svelte.js
@@ -0,0 +1,22 @@
+// index.svelte (Svelte VERSION)
+// Note: compiler output will change before 5.0 is released!
+import * as $ from "svelte/internal/server";
+
+export default function Each_string_template($$payload, $$props) {
+ $.push(false);
+
+ const anchor = $.create_anchor($$payload);
+ const each_array = $.ensure_array_like(['foo', 'bar', 'baz']);
+
+ $$payload.out += `${anchor}`;
+
+ for (let $$index = 0; $$index < each_array.length; $$index++) {
+ const thing = each_array[$$index];
+ const anchor_1 = $.create_anchor($$payload);
+
+ $$payload.out += `${anchor_1}${$.escape(thing)},${$.escape(' ')}${anchor_1}`;
+ }
+
+ $$payload.out += `${anchor}`;
+ $.pop();
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/each-string-template/index.svelte b/packages/svelte/tests/snapshot/samples/each-string-template/index.svelte
new file mode 100644
index 0000000000..f2ab129af6
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/each-string-template/index.svelte
@@ -0,0 +1,3 @@
+{#each ['foo', 'bar', 'baz'] as thing}
+ {thing},{' '}
+{/each}
diff --git a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_config.js b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_config.js
new file mode 100644
index 0000000000..f47bee71df
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_config.js
@@ -0,0 +1,3 @@
+import { test } from '../../test';
+
+export default test({});
diff --git a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js
new file mode 100644
index 0000000000..f1a5a0ee42
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js
@@ -0,0 +1,39 @@
+// index.svelte (Svelte VERSION)
+// Note: compiler output will change before 5.0 is released!
+import "svelte/internal/disclose-version";
+import * as $ from "svelte/internal";
+
+function reset(_, str, tpl) {
+ $.set(str, '');
+ $.set(str, ``);
+ $.set(tpl, '');
+ $.set(tpl, ``);
+}
+
+var frag = $.template(` `, true);
+
+export default function State_proxy_literal($$anchor, $$props) {
+ $.push($$props, true);
+
+ let str = $.source('');
+ let tpl = $.source(``);
+ /* Init */
+ var fragment = $.open_frag($$anchor, true, frag);
+ var node = $.child_frag(fragment);
+
+ $.remove_input_attr_defaults(node);
+
+ var input = $.sibling($.sibling(node));
+
+ $.remove_input_attr_defaults(input);
+
+ var button = $.sibling($.sibling(input));
+
+ $.bind_value(node, () => $.get(str), ($$value) => $.set(str, $.proxy($$value)));
+ $.bind_value(input, () => $.get(tpl), ($$value) => $.set(tpl, $.proxy($$value)));
+ button.__click = [reset, str, tpl];
+ $.close_frag($$anchor, fragment);
+ $.pop();
+}
+
+$.delegate(["click"]);
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/server/index.svelte.js
new file mode 100644
index 0000000000..c541299e36
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/server/index.svelte.js
@@ -0,0 +1,20 @@
+// index.svelte (Svelte VERSION)
+// Note: compiler output will change before 5.0 is released!
+import * as $ from "svelte/internal/server";
+
+export default function State_proxy_literal($$payload, $$props) {
+ $.push(true);
+
+ let str = '';
+ let tpl = ``;
+
+ function reset() {
+ str = '';
+ str = ``;
+ tpl = '';
+ tpl = ``;
+ }
+
+ $$payload.out += ` `;
+ $.pop();
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/state-proxy-literal/index.svelte b/packages/svelte/tests/snapshot/samples/state-proxy-literal/index.svelte
new file mode 100644
index 0000000000..155682f955
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/state-proxy-literal/index.svelte
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js
index 937e22410f..6fcc18c0c4 100644
--- a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js
@@ -14,4 +14,4 @@ export default function Svelte_element($$anchor, $$props) {
$.element(node, tag);
$.close_frag($$anchor, fragment);
$.pop();
-}
+}
\ No newline at end of file