From 508e30c681f6b9e4d562bf3ab6e1e2ee4e75f2f6 Mon Sep 17 00:00:00 2001 From: gtmnayan Date: Wed, 2 Aug 2023 12:42:12 +0545 Subject: [PATCH] fix: @html mismatch in hydration --- packages/playground/src/App.svelte | 8 +------- packages/playground/start.js | 3 ++- .../compile/render_dom/wrappers/RawMustacheTag.js | 2 +- .../compile/render_ssr/handlers/HtmlTag.js | 15 ++++++++++++--- packages/svelte/src/runtime/internal/dom.js | 12 +++++++----- .../svelte/src/runtime/internal/style_manager.js | 13 +------------ packages/svelte/src/runtime/internal/utils.js | 12 ++++++++++++ .../hydration/samples/raw-repair/_before.html | 8 ++++---- .../test/hydration/samples/raw-svg/_before.html | 4 ++-- .../test/hydration/samples/raw/_before.html | 4 ++-- 10 files changed, 44 insertions(+), 37 deletions(-) diff --git a/packages/playground/src/App.svelte b/packages/playground/src/App.svelte index b3c16eec67..156287852c 100644 --- a/packages/playground/src/App.svelte +++ b/packages/playground/src/App.svelte @@ -1,7 +1 @@ - - -
- Hello world! -
\ No newline at end of file +{@html false ? 'foo' : 'bar'} diff --git a/packages/playground/start.js b/packages/playground/start.js index af37a40e26..ace479ac9f 100644 --- a/packages/playground/start.js +++ b/packages/playground/start.js @@ -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('', head) .replace('', html) .replace('', svelte.VERSION) ); diff --git a/packages/svelte/src/compiler/compile/render_dom/wrappers/RawMustacheTag.js b/packages/svelte/src/compiler/compile/render_dom/wrappers/RawMustacheTag.js index cc382a9b0b..c381321dec 100644 --- a/packages/svelte/src/compiler/compile/render_dom/wrappers/RawMustacheTag.js +++ b/packages/svelte/src/compiler/compile/render_dom/wrappers/RawMustacheTag.js @@ -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};`); diff --git a/packages/svelte/src/compiler/compile/render_ssr/handlers/HtmlTag.js b/packages/svelte/src/compiler/compile/render_ssr/handlers/HtmlTag.js index 1a210ec00d..094dc6d6bb 100644 --- a/packages/svelte/src/compiler/compile/render_ssr/handlers/HtmlTag.js +++ b/packages/svelte/src/compiler/compile/render_ssr/handlers/HtmlTag.js @@ -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(''); - renderer.add_expression(/** @type {import('estree').Expression} */ (node.expression.node)); - if (options.hydratable) renderer.add_string(''); + 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_string}\`; + })()`); + } } diff --git a/packages/svelte/src/runtime/internal/dom.js b/packages/svelte/src/runtime/internal/dom.js index 18a5d5d278..003a4578fd 100644 --- a/packages/svelte/src/runtime/internal/dom.js +++ b/packages/svelte/src/runtime/internal/dom.js @@ -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; @@ -789,11 +789,13 @@ 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); - if (start_index === -1 || end_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); } diff --git a/packages/svelte/src/runtime/internal/style_manager.js b/packages/svelte/src/runtime/internal/style_manager.js index a98984788f..c8c725599c 100644 --- a/packages/svelte/src/runtime/internal/style_manager.js +++ b/packages/svelte/src/runtime/internal/style_manager.js @@ -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 @@ -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 diff --git a/packages/svelte/src/runtime/internal/utils.js b/packages/svelte/src/runtime/internal/utils.js index ec39c0d9e4..9c42d90377 100644 --- a/packages/svelte/src/runtime/internal/utils.js +++ b/packages/svelte/src/runtime/internal/utils.js @@ -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; +} diff --git a/packages/svelte/test/hydration/samples/raw-repair/_before.html b/packages/svelte/test/hydration/samples/raw-repair/_before.html index fe958bece5..8cf8b3e954 100644 --- a/packages/svelte/test/hydration/samples/raw-repair/_before.html +++ b/packages/svelte/test/hydration/samples/raw-repair/_before.html @@ -1,8 +1,8 @@ -

+

invalid

- +

-

+

invalid

- +

diff --git a/packages/svelte/test/hydration/samples/raw-svg/_before.html b/packages/svelte/test/hydration/samples/raw-svg/_before.html index 2a1285938d..884540a056 100644 --- a/packages/svelte/test/hydration/samples/raw-svg/_before.html +++ b/packages/svelte/test/hydration/samples/raw-svg/_before.html @@ -1,5 +1,5 @@ - + - + diff --git a/packages/svelte/test/hydration/samples/raw/_before.html b/packages/svelte/test/hydration/samples/raw/_before.html index de20b90f6c..8fec75a411 100644 --- a/packages/svelte/test/hydration/samples/raw/_before.html +++ b/packages/svelte/test/hydration/samples/raw/_before.html @@ -1,4 +1,4 @@ - +

this is some html

and so is this

- +