8000 fix: throw runtime error when template returns different html · sveltejs/svelte@a9b26c3 · GitHub
[go: up one dir, main page]

Skip to content

Commit a9b26c3

Browse files
committed
fix: throw runtime error when template returns different html
1 parent 32ee6c1 commit a9b26c3

File tree

8 files changed

+68
-3
lines changed

8 files changed

+68
-3
lines changed

.changeset/fluffy-eggs-do.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: throw runtime error when template returns different html

documentation/docs/98-reference/.generated/client-errors.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ Maximum update depth exceeded. This can happen when a reactive block or effect r
8080
Failed to hydrate the application
8181
```
8282

83+
### invalid_html_structure
84+
85+
```
86+
This html structure `%html_input%` would be corrected like this `%html_output%` by the browser making this component impossible to hydrate properly
87+
```
88+
8389
### invalid_snippet
8490

8591
```

packages/svelte/messages/client-errors/errors.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-long
5252

5353
> Failed to hydrate the application
5454
55+
## invalid_html_structure
56+
57+
> This html structure `%html_input%` would be corrected like this `%html_output%` by the browser making this component impossible to hydrate properly
58+
5559
## invalid_snippet
5660

5761
> Could not `{@render}` snippet due to the expression being `null` or `undefined`. Consider using optional chaining `{@render snippet?.()}`

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export function html(node, get_value, svg, mathml, skip_warning) {
9999
// Don't use create_fragment_with_script_from_html here because that would mean script tags are executed.
100100
// @html is basically `.innerHTML = ...` and that doesn't execute scripts either due to security reasons.
101101
/** @type {DocumentFragment | Element} */
102-
var node = create_fragment_from_html(html);
102+
var node = create_fragment_from_html(html, false);
103103

104104
if (svg || mathml) {
105105
node = /** @type {Element} */ (get_first_child(node));
Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,29 @@
1-
/** @param {string} html */
2-
export function create_fragment_from_html(html) {
1+
import { DEV } from 'esm-env';
2+
import * as e from '../errors.js';
3+
4+
/**
5+
* @param {string} html
6+
* @param {boolean} [check_structure]
7+
*/
8+
export function create_fragment_from_html(html, check_structure = true) {
39
var elem = document.createElement('template');
410
elem.innerHTML = html;
11+
if (DEV && check_structure) {
12+
let replace_comments = html.replaceAll('<!>', '<!---->');
13+
let remove_attributes_and_text_input = replace_comments
14+
// we remove every attribute since the template automatically adds ="" after boolean attributes
15+
.replace(/<([a-z0-9]+)(\s+[^>]+?)?>/g, '<$1>')
16+
// we remove the text within the elements because the template change & to &amp; (and similar)
17+
.replace(/>([^<>]*)/g, '>');
18+
let remove_attributes_and_text_output = elem.innerHTML
19+
// we remove every attribute since the template automatically adds ="" after boolean attributes
20+
.replace(/<([a-z0-9]+)(\s+[^>]+?)?>/g, '<$1>')
21+
// we remove the text within the elements because the template change & to &amp; (and similar)
22+
.replace(/>([^<>]*)/g, '>');
23+
if (remove_attributes_and_text_input !== remove_attributes_and_text_output) {
24+
e.invalid_html_structure(remove_attributes_and_text_input, remove_attributes_and_text_output);
25+
}
26+
}
27+
528
return elem.content;
629
}

packages/svelte/src/internal/client/errors.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,23 @@ export function hydration_failed() {
198198
}
199199
}
200200

201+
/**
202+
* This html structure `%html_input%` would be corrected like this `%html_output%` by the browser making this component impossible to hydrate properly
203+
* @param {string} html_input
204+
* @param {string} html_output
205+
* @returns {never}
206+
*/
207+
export function invalid_html_structure(html_input, html_output) {
208+
if (DEV) {
209+
const error = new Error(`invalid_html_structure\nThis html structure \`${html_input}\` would be corrected like this \`${html_output}\` by the browser making this component impossible to hydrate properly\nhttps://svelte.dev/e/invalid_html_structure`);
210+
211+
error.name = 'Svelte error';
212+
throw error;
213+
} else {
214+
throw new Error(`https://svelte.dev/e/invalid_html_structure`);
215+
}
216+
}
217+
201218
/**
202219
* Could not `{@render}` snippet due to the expression being `null` or `undefined`. Consider using optional chaining `{@render snippet?.()}`
203220
* @returns {never}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
mode: ['client&# 6CC7 39;, 'hydrate'],
5+
recover: true,
6+
runtime_error: 'invalid_html_structure'
7+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<svelte:options runes />
2+
<p></p>
3+
<tr></tr>

0 commit comments

Comments
 (0)
0