fix: @html mismatch in hydration

raw-hydration-mismatch
gtmnayan 2 years ago
parent abf257306b
commit 508e30c681

@ -1,7 +1 @@
<script> {@html false ? 'foo' : 'bar'}
import Counter from "./lib/Counter.svelte";
</script>
<div>
Hello world!
</div>

@ -69,11 +69,12 @@ const watcher = watch([
async generateBundle(_, bundle) { async generateBundle(_, bundle) {
const result = bundle['entry-server.js']; const result = bundle['entry-server.js'];
const mod = (0, eval)(result.code); const mod = (0, eval)(result.code);
const { html } = mod.render(); const { html, head } = mod.render();
writeFileSync( writeFileSync(
'dist/index.html', 'dist/index.html',
readFileSync('src/template.html', 'utf-8') readFileSync('src/template.html', 'utf-8')
.replace('<!--app-head-->', head)
.replace('<!--app-html-->', html) .replace('<!--app-html-->', html)
.replace('<!--app-title-->', svelte.VERSION) .replace('<!--app-title-->', svelte.VERSION)
); );

@ -47,7 +47,7 @@ export default class RawMustacheTagWrapper extends Tag {
block.chunks.create.push(b`${html_tag} = new @HtmlTag(${is_svg ? 'true' : 'false'});`); block.chunks.create.push(b`${html_tag} = new @HtmlTag(${is_svg ? 'true' : 'false'});`);
if (this.renderer.options.hydratable) { if (this.renderer.options.hydratable) {
block.chunks.claim.push( 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};`); block.chunks.hydrate.push(b`${html_tag}.a = ${update_anchor};`);

@ -1,10 +1,19 @@
import { x } from 'code-red';
/** /**
* @param {import('../../nodes/RawMustacheTag.js').default} node * @param {import('../../nodes/RawMustacheTag.js').default} node
* @param {import('../Renderer.js').default} renderer * @param {import('../Renderer.js').default} renderer
* @param {import('../private.js').RenderOptions} options * @param {import('../private.js').RenderOptions} options
*/ */
export default function (node, renderer, options) { export default function (node, renderer, options) {
if (options.hydratable) renderer.add_string('<!-- HTML_TAG_START -->'); if (!options.hydratable) {
renderer.add_expression(/** @type {import('estree').Expression} */ (node.expression.node)); renderer.add_expression(/** @type {import('estree').Expression} */ (node.expression.node));
if (options.hydratable) renderer.add_string('<!-- HTML_TAG_END -->'); } 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 -->\`;
})()`);
}
} }

@ -1,5 +1,5 @@
import { ResizeObserverSingleton } from './ResizeObserverSingleton.js'; 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 // 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. // at the end of hydration without touching the remaining nodes.
let is_hydrating = false; let is_hydrating = false;
@ -789,11 +789,13 @@ function get_comment_idx(nodes, text, start) {
* @param {boolean} is_svg * @param {boolean} is_svg
* @returns {HtmlTagHydration} * @returns {HtmlTagHydration}
*/ */
export function claim_html_tag(nodes, is_svg) { export function claim_html_tag(nodes, is_svg, content) {
// find html opening tag // find html opening tag
const start_index = get_comment_idx(nodes, 'HTML_TAG_START', 0); const content_hash = hash(content + '');
const end_index = get_comment_idx(nodes, 'HTML_TAG_END', start_index + 1); const start_index = get_comment_idx(nodes, `HTML_${content_hash}_START`, 0);
if (start_index === -1 || end_index === -1) { 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); return new HtmlTagHydration(is_svg);
} }

@ -1,5 +1,6 @@
import { append_empty_stylesheet, detach, get_root_for_style } from './dom.js'; import { append_empty_stylesheet, detach, get_root_for_style } from './dom.js';
import { raf } from './environment.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 // we need to store the information for multiple documents because a Svelte application could also contain iframes
// https://github.com/sveltejs/svelte/issues/3624 // https://github.com/sveltejs/svelte/issues/3624
@ -8,18 +9,6 @@ const managed_styles = new Map();
let active = 0; 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 {Document | ShadowRoot} doc
* @param {Element & ElementCSSInlineStyle} node * @param {Element & ElementCSSInlineStyle} node

@ -289,3 +289,15 @@ export function split_css_unit(value) {
} }
export const contenteditable_truthy_values = ['', true, 1, 'true', 'contenteditable']; 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;
}

@ -1,8 +1,8 @@
<p><!-- HTML_TAG_START --></p> <p><!-- HTML_2920328455_START --></p>
<p>invalid</p> <p>invalid</p>
<!-- HTML_TAG_END --> <!-- HTML_2920328455_END -->
<p></p> <p></p>
<p><!-- HTML_TAG_START --></p> <p><!-- HTML_2920328455_START --></p>
<p>invalid</p> <p>invalid</p>
<!-- HTML_TAG_END --> <!-- HTML_2920328455_END -->
<p></p> <p></p>

@ -1,5 +1,5 @@
<svg> <svg>
<!-- HTML_TAG_START --> <!-- HTML_1284501889_START -->
<circle cx="200" cy="500" r="200"></circle> <circle cx="200" cy="500" r="200"></circle>
<!-- HTML_TAG_END --> <!-- HTML_1284501889_END -->
</svg> </svg>

Before

Width:  |  Height:  |  Size: 106 B

After

Width:  |  Height:  |  Size: 120 B

@ -1,4 +1,4 @@
<!-- HTML_TAG_START --> <!-- HTML_2526333745_START -->
<p>this is some html</p> <p>this is some html</p>
<p>and so is this</p> <p>and so is this</p>
<!-- HTML_TAG_END --> <!-- HTML_2526333745_END -->

Loading…
Cancel
Save