diff --git a/.changeset/green-inputs-ssr.md b/.changeset/green-inputs-ssr.md
new file mode 100644
index 0000000000..e034d0e79a
--- /dev/null
+++ b/.changeset/green-inputs-ssr.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: serialize input default values during server rendering
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js
index b4f4facb5d..c19d760f48 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js
@@ -80,6 +80,14 @@ export function build_element_attributes(node, context, transform) {
) {
events_to_capture.add(attribute.name);
}
+ } else if (
+ node.type === 'RegularElement' &&
+ node.name === 'input' &&
+ (attribute.name === 'defaultValue' || attribute.name === 'defaultChecked')
+ ) {
+ attributes.push(attribute);
+ // deopt to spread at runtime, where we can handle interaction of value/defaultValue etc
+ has_spread = true;
// the defaultValue/defaultChecked properties don't exist as attributes
} else if (attribute.name !== 'defaultValue' && attribute.name !== 'defaultChecked') {
if (attribute.name === 'class') {
diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js
index 3d8ec5fe5e..20bef78306 100644
--- a/packages/svelte/src/internal/server/index.js
+++ b/packages/svelte/src/internal/server/index.js
@@ -146,8 +146,10 @@ export function attributes(attrs, css_hash, classes, styles, flags = 0) {
const is_html = (flags & ELEMENT_IS_NAMESPACED) === 0;
const lowercase = (flags & ELEMENT_PRESERVE_ATTRIBUTE_CASE) === 0;
const is_input = (flags & ELEMENT_IS_INPUT) !== 0;
+ const names = Object.keys(attrs);
- for (name of Object.keys(attrs)) {
+ outer: for (let i = 0; i < names.length; i++) {
+ name = names[i];
// omit functions, internal svelte properties and invalid attribute names
if (typeof attrs[name] === 'function') continue;
if (name[0] === '$' && name[1] === '$') continue; // faster than name.startsWith('$$')
@@ -163,8 +165,13 @@ export function attributes(attrs, css_hash, classes, styles, flags = 0) {
if (is_input) {
if (name === 'defaultvalue' || name === 'defaultchecked') {
+ // value/checked takes precedence over defaultValue/defaultChecked
name = name === 'defaultvalue' ? 'value' : 'checked';
- if (attrs[name]) continue;
+ if (name in attrs) continue;
+ // We're checking prior entries aswell because "name in attrs" is not enough as the attributes may have different casing
+ for (let j = 0; j < names.length; j++) {
+ if (names[j].toLowerCase() === name) continue outer;
+ }
}
}
diff --git a/packages/svelte/tests/server-side-rendering/samples/input-default-value/_config.js b/packages/svelte/tests/server-side-rendering/samples/input-default-value/_config.js
new file mode 100644
index 0000000000..f47bee71df
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/input-default-value/_config.js
@@ -0,0 +1,3 @@
+import { test } from '../../test';
+
+export default test({});
diff --git a/packages/svelte/tests/server-side-rendering/samples/input-default-value/_expected.html b/packages/svelte/tests/server-side-rendering/samples/input-default-value/_expected.html
new file mode 100644
index 0000000000..87635c9545
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/input-default-value/_expected.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/packages/svelte/tests/server-side-rendering/samples/input-default-value/main.svelte b/packages/svelte/tests/server-side-rendering/samples/input-default-value/main.svelte
new file mode 100644
index 0000000000..e1ad517850
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/input-default-value/main.svelte
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+