Skip to content
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

fix: @html mismatch in hydration #9063

Draft
wants to merge 2 commits into
base: svelte-4
Choose a base branch
from
Draft
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
8 changes: 1 addition & 7 deletions packages/playground/src/App.svelte
Original file line number Diff line number Diff line change
@@ -1,7 1 @@
<script>
import Counter from "./lib/Counter.svelte";
</script>

<div>
Hello world!
</div>
{@html false ? 'foo' : 'bar'}
3 changes: 2 additions & 1 deletion packages/playground/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 69,12 @@ const watcher = watch([
async generateBundle(_, bundle) {
const result = bundle['entry-server.js'];
const mod = (0, eval)(result.code);
const { html } = mod.render();
const { html, head } = mod.render();

writeFileSync(
'dist/index.html',
readFileSync('src/template.html', 'utf-8')
.replace('<!--app-head-->', head)
.replace('<!--app-html-->', html)
.replace('<!--app-title-->', svelte.VERSION)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 47,7 @@ export default class RawMustacheTagWrapper extends Tag {
block.chunks.create.push(b`${html_tag} = new @HtmlTag(${is_svg ? 'true' : 'false'});`);
if (this.renderer.options.hydratable) {
block.chunks.claim.push(
b`${html_tag} = @claim_html_tag(${_parent_nodes}, ${is_svg ? 'true' : 'false'});`
b`${html_tag} = @claim_html_tag(${_parent_nodes}, ${is_svg ? 'true' : 'false'}, ${init});`
);
}
block.chunks.hydrate.push(b`${html_tag}.a = ${update_anchor};`);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 1,19 @@
import { x } from 'code-red';

/**
* @param {import('../../nodes/RawMustacheTag.js').default} node
* @param {import('../Renderer.js').default} renderer
* @param {import('../private.js').RenderOptions} options
*/
export default function (node, renderer, options) {
if (options.hydratable) renderer.add_string('<!-- HTML_TAG_START -->');
renderer.add_expression(/** @type {import('estree').Expression} */ (node.expression.node));
if (options.hydratable) renderer.add_string('<!-- HTML_TAG_END -->');
if (!options.hydratable) {
renderer.add_expression(/** @type {import('estree').Expression} */ (node.expression.node));
} else {
renderer.add_expression(x`(() => {
const #html_string = ${node.expression.node} '';
const #hash = /* @__PURE__ */ @hash(#html_string);

return \`<!-- HTML_\${#hash}_START -->\${#html_string}<!-- HTML_\${#hash}_END -->\`;
})()`);
}
}
11 changes: 7 additions & 4 deletions packages/svelte/src/runtime/internal/dom.js
Original file line number Diff line number Diff line change
@@ -1,5 1,5 @@
import { ResizeObserverSingleton } from './ResizeObserverSingleton.js';
import { contenteditable_truthy_values, has_prop } from './utils.js';
import { contenteditable_truthy_values, has_prop, hash } from './utils.js';
// Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM
// at the end of hydration without touching the remaining nodes.
let is_hydrating = false;
Expand Down Expand Up @@ -789,11 789,14 @@ function get_comment_idx(nodes, text, start) {
* @param {boolean} is_svg
* @returns {HtmlTagHydration}
*/
export function claim_html_tag(nodes, is_svg) {
export function claim_html_tag(nodes, is_svg, content) {
// find html opening tag
const start_index = get_comment_idx(nodes, 'HTML_TAG_START', 0);
const end_index = get_comment_idx(nodes, 'HTML_TAG_END', start_index 1);
const content_hash = hash(content '');
const start_index = get_comment_idx(nodes, `HTML_${content_hash}_START`, 0);
const end_index = get_comment_idx(nodes, `HTML_${content_hash}_END`, start_index 1);

if (start_index === -1 || end_index === -1) {
// Content mismatch, recreate
return new HtmlTagHydration(is_svg);
}

Expand Down
13 changes: 1 addition & 12 deletions packages/svelte/src/runtime/internal/style_manager.js
Original file line number Diff line number Diff line change
@@ -1,5 1,6 @@
import { append_empty_stylesheet, detach, get_root_for_style } from './dom.js';
import { raf } from './environment.js';
import { hash } from './utils.js';

// we need to store the information for multiple documents because a Svelte application could also contain iframes
// https://github.com/sveltejs/svelte/issues/3624
Expand All @@ -8,18 9,6 @@ const managed_styles = new Map();

let active = 0;

// https://github.com/darkskyapp/string-hash/blob/master/index.js
/**
* @param {string} str
* @returns {number}
*/
function hash(str) {
let hash = 5381;
let i = str.length;
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
return hash >>> 0;
}

/**
* @param {Document | ShadowRoot} doc
* @param {Element & ElementCSSInlineStyle} node
Expand Down
12 changes: 12 additions & 0 deletions packages/svelte/src/runtime/internal/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 289,15 @@ export function split_css_unit(value) {
}

export const contenteditable_truthy_values = ['', true, 1, 'true', 'contenteditable'];

// https://github.com/darkskyapp/string-hash/blob/master/index.js
/**
* @param {string} str
* @returns {number}
*/
export function hash(str) {
let hash = 5381;
let i = str.length;
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
return hash >>> 0;
}
Original file line number Diff line number Diff line change
@@ -1,8 1,8 @@
<p><!-- HTML_TAG_START --></p>
<p><!-- HTML_2920328455_START --></p>
<p>invalid</p>
<!-- HTML_TAG_END -->
<!-- HTML_2920328455_END -->
<p></p>
<p><!-- HTML_TAG_START --></p>
<p><!-- HTML_2920328455_START --></p>
<p>invalid</p>
<!-- HTML_TAG_END -->
<!-- HTML_2920328455_END -->
<p></p>
4 changes: 2 additions & 2 deletions packages/svelte/test/hydration/samples/raw-svg/_before.html
Original file line number Diff line number Diff line change
@@ -1,5 1,5 @@
<svg>
<!-- HTML_TAG_START -->
<!-- HTML_1284501889_START -->
<circle cx="200" cy="500" r="200"></circle>
<!-- HTML_TAG_END -->
<!-- HTML_1284501889_END -->
</svg>
4 changes: 2 additions & 2 deletions packages/svelte/test/hydration/samples/raw/_before.html
Original file line number Diff line number Diff line change
@@ -1,4 1,4 @@
<!-- HTML_TAG_START -->
<!-- HTML_2526333745_START -->
<p>this is some html</p>
<p>and so is this</p>
<!-- HTML_TAG_END -->
<!-- HTML_2526333745_END -->
2 changes: 1 addition & 1 deletion sites/svelte.dev/src/app.html
Original file line number Diff line number Diff line change
@@ -1,4 1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en" class="theme-default typo-default">
<head>
<meta charset="utf-8" />
Expand Down