Skip to content

Commit 42a7a0e

Browse files
chore: document @html and <img src> hydration change (#12373)
* chore: document `@html` and `<img src>` hydration change Also add a test for it closes #12333 * add a test * Update sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md Co-authored-by: Rich Harris <[email protected]> * lint * update example and wording * update test * since it turns out we already had a test, we can delete the new one * fix test --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 4e8d1c8 commit 42a7a0e

File tree

12 files changed

+105
-34
lines changed

12 files changed

+105
-34
lines changed

packages/svelte/src/internal/client/dom/blocks/html.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function check_hash(element, server_hash, value) {
2323
const loc = element.__svelte_meta?.loc;
2424
if (loc) {
2525
location = `near ${loc.file}:${loc.line}:${loc.column}`;
26-
} else if (dev_current_component_function.filename) {
26+
} else if (dev_current_component_function?.filename) {
2727
location = `in ${dev_current_component_function.filename}`;
2828
}
2929

@@ -59,6 +59,8 @@ export function html(node, get_value, svg, mathml) {
5959

6060
effect = branch(() => {
6161
if (hydrating) {
62+
// We're deliberately not trying to repair mismatches between server and client,
63+
// as it's costly and error-prone (and it's an edge case to have a mismatch anyway)
6264
var hash = /** @type {Comment} */ (hydrate_node).data;
6365
var next = hydrate_next();
6466
var last = next;

packages/svelte/tests/helpers.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export async function compile_directory(
6969

7070
fs.rmSync(output_dir, { recursive: true, force: true });
7171

72-
for (const file of glob('**', { cwd, filesOnly: true })) {
72+
for (let file of glob('**', { cwd, filesOnly: true })) {
7373
if (file.startsWith('_')) continue;
7474

7575
let text = fs.readFileSync(`${cwd}/${file}`, 'utf-8').replace(/\r\n/g, '\n');
@@ -101,7 +101,17 @@ export async function compile_directory(
101101

102102
write(out, result);
103103
}
104-
} else if (file.endsWith('.svelte')) {
104+
} else if (
105+
file.endsWith('.svelte') &&
106+
// Make it possible to compile separate versions for client and server to simulate
107+
// cases where `{browser ? 'foo' : 'bar'}` is turning into `{'foo'}` on the server
108+
// and `{bar}` on the client, assuming we have sophisticated enough treeshaking
109+
// in the future to make this a thing.
110+
(!file.endsWith('.server.svelte') || generate === 'server') &&
111+
(!file.endsWith('.client.svelte') || generate === 'client')
112+
) {
113+
file = file.replace(/\.client\.svelte$/, '.svelte').replace(/\.server\.svelte$/, '.svelte');
114+
105115
if (preprocessor?.preprocess) {
106116
const preprocessed = await preprocess(
107117
text,

packages/svelte/tests/hydration/samples/html-tag-hydration-2/_config.js

Lines changed: 0 additions & 19 deletions
This file was deleted.

packages/svelte/tests/hydration/samples/html-tag-hydration-2/main.svelte

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
server_props: {
5+
src: 'server.jpg'
6+
},
7+
props: {
8+
src: 'client.jpg'
9+
},
10+
test(assert, target) {
11+
// We deliberately don't slow down hydration just for supporting this edge case mismatch.
12+
assert.htmlEqual(target.innerHTML, '<img src="server.jpg" alt="">');
13+
},
14+
errors: [
15+
'The `src` attribute on `...<img src="server.jpg" alt="">` changed its value between server and client renders. The client value, `client.jpg`, will be ignored in favour of the server value'
16+
]
17+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
let { src } = $props();
3+
</script>
4+
5+
<img {src} alt="" />
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
test(assert, target) {
5+
// This test case guards against a potential future bug where we could optimize html tags away for static content:
6+
// Even if the {@html } block seems static, it should be preserved as such, because it could be dynamic originally
7+
// (like {@html browser ? 'foo' : 'bar'} which is then different between client and server.
8+
// Also see https://github.com/sveltejs/svelte/issues/8683 where this happened for Svelte 4.
9+
assert.htmlEqual(target.innerHTML, 'Server');
10+
},
11+
12+
errors: [
13+
'The value of an `{@html ...}` block changed between server and client renders. The client value will be ignored in favour of the server value'
14+
]
15+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{@html '<p>Client</p> <span>has more nodes so if we would walk this because we think it is static we would get an error</span>'}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{@html 'Server'}
Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
import { test } from '../../test';
22

33
export default test({
4-
// Even if the {@html } block seems static, it should be preserved as such, because it could be dynamic originally
5-
// (like {@html browser ? 'foo' : 'bar'} which is then different between client and server.
6-
// Question is whether that's actually something someone would do in practise, and why, so it's probably better to not
7-
// slow down hydration just for supporting this edge case - so far we've said no. If someone really needs this we could
8-
// add something like {@html dynamic ...}
9-
skip: true
4+
server_props: {
5+
html: 'Server'
6+
},
7+
8+
props: {
9+
html: 'Client'
10+
},
11+
12+
test(assert, target) {
13+
// We deliberately don't slow down hydration just for supporting this edge case mismatch.
14+
// If someone really needs this and workarounds are insufficient we could add something like {@html dynamic ...}
15+
assert.htmlEqual(target.innerHTML, 'Server');
16+
},
17+
18+
errors: [
19+
'The value of an `{@html ...}` block changed between server and client renders. The client value will be ignored in favour of the server value'
20+
]
1021
});

0 commit comments

Comments
 (0)