From ba7c20b056c59b44c69b3d3be26a94b9ffe86599 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 1 Apr 2024 15:15:26 -0400 Subject: [PATCH 1/4] remove comments --- .../3-transform/server/transform-server.js | 22 ++++++------------- .../src/internal/client/dom/blocks/if.js | 14 ++++-------- .../src/internal/client/dom/hydration.js | 2 +- .../surrounding-whitespace/_override.html | 2 +- 4 files changed, 13 insertions(+), 27 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index ac71edfd01d8..51138ba190fa 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -26,6 +26,7 @@ import { binding_properties } from '../../bindings.js'; import { regex_starts_with_newline, regex_whitespaces_strict } from '../../patterns.js'; import { DOMBooleanAttributes, HYDRATION_END, HYDRATION_START } from '../../../../constants.js'; import { sanitize_template_string } from '../../../utils/sanitize_template_string.js'; +import { BLOCK_CLOSE } from '../../../../internal/server/hydration.js'; export const block_open = t_string(``); export const block_close = t_string(``); @@ -1522,32 +1523,23 @@ const template_visitors = { const state = context.state; state.template.push(block_open); - // Insert ssr:if:true/false anchors in addition to the other anchors so that - // the if block can catch hydration mismatches (false on the server, true on the client and vice versa) - // and continue hydration without having to re-render everything from scratch. - const consequent = create_block(node, node.consequent.nodes, context); - consequent.unshift( - b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(''))) - ); + const alternate = node.alternate ? create_block(node, node.alternate.nodes, context) : []; - const alternate = node.alternate - ? /** @type {import('estree').BlockStatement} */ (context.visit(node.alternate)) - : b.block([]); - alternate.body.unshift( - b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(''))) + consequent.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE)))); + alternate.push( + b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(``))) ); state.template.push( t_statement( b.if( /** @type {import('estree').Expression} */ (context.visit(node.test)), - b.block(/** @type {import('estree').Statement[]} */ (consequent)), - alternate + b.block(consequent), + b.block(alternate) ) ) ); - state.template.push(block_close); }, AwaitBlock(node, context) { const state = context.state; diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js index 27accd8d61bf..7446596d0324 100644 --- a/packages/svelte/src/internal/client/dom/blocks/if.js +++ b/packages/svelte/src/internal/client/dom/blocks/if.js @@ -2,6 +2,7 @@ import { IS_ELSEIF } from '../../constants.js'; import { hydrate_nodes, hydrating, set_hydrating } from '../hydration.js'; import { remove } from '../reconciler.js'; import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js'; +import { HYDRATION_END } from '../../../../constants.js'; /** * @param {Comment} anchor @@ -34,21 +35,14 @@ export function if_block( let mismatch = false; if (hydrating) { - const comment_text = /** @type {Comment} */ (hydrate_nodes?.[0])?.data; + const is_else = anchor.data === `${HYDRATION_END}!`; - if ( - !comment_text || - (comment_text === 'ssr:if:true' && !condition) || - (comment_text === 'ssr:if:false' && condition) - ) { + if (condition === is_else) { // Hydration mismatch: remove everything inside the anchor and start fresh. - // This could happen using when `{#if browser} .. {/if}` in SvelteKit. + // This could happen with `{#if browser}...{/if}`, for example remove(hydrate_nodes); set_hydrating(false); mismatch = true; - } else { - // Remove the ssr:if comment node or else it will confuse the subsequent hydration algorithm - hydrate_nodes.shift(); } } diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index ef157f8344c8..5d3c98f2f034 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -53,7 +53,7 @@ export function hydrate_anchor(node) { if (data === HYDRATION_START) { depth += 1; - } else if (data === HYDRATION_END) { + } else if (data[0] === HYDRATION_END) { if (depth === 0) { hydrate_nodes = /** @type {import('#client').TemplateNode[]} */ (nodes); return current; diff --git a/packages/svelte/tests/hydration/samples/surrounding-whitespace/_override.html b/packages/svelte/tests/hydration/samples/surrounding-whitespace/_override.html index a4b8d9e17cdf..e728b682d0cd 100644 --- a/packages/svelte/tests/hydration/samples/surrounding-whitespace/_override.html +++ b/packages/svelte/tests/hydration/samples/surrounding-whitespace/_override.html @@ -1,2 +1,2 @@ - hello + hello From e45d91b4b3ca7516d102f7099598520d1f7794e4 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 1 Apr 2024 15:35:50 -0400 Subject: [PATCH 2/4] remove comments --- .../3-transform/server/transform-server.js | 18 +++++++++++------- .../src/internal/client/dom/blocks/each.js | 6 ++---- .../_expected/server/index.svelte.js | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 51138ba190fa..1a8056073ef4 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -1500,24 +1500,28 @@ const template_visitors = { b.update('++', index, false), b.block(each) ); + + const close = b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE))); + if (node.fallback) { - const fallback_stmts = create_block(node, node.fallback.nodes, context); - fallback_stmts.unshift( - b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(''))) + const fallback = create_block(node, node.fallback.nodes, context); + + fallback.push( + b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(``))) ); + state.template.push( t_statement( b.if( b.binary('!==', b.member(array_id, b.id('length')), b.literal(0)), - for_loop, - b.block(fallback_stmts) + b.block([for_loop, close]), + b.block(fallback) ) ) ); } else { - state.template.push(t_statement(for_loop)); + state.template.push(t_statement(for_loop), t_statement(close)); } - state.template.push(block_close); }, IfBlock(node, context) { const state = context.state; diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 8ad815a7a796..b636837e8628 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -5,6 +5,7 @@ import { EACH_IS_STRICT_EQUALS, EACH_ITEM_REACTIVE, EACH_KEYED, + HYDRATION_END, HYDRATION_START } from '../../../../constants.js'; import { hydrate_anchor, hydrate_nodes, hydrating, set_hydrating } from '../hydration.js'; @@ -97,16 +98,13 @@ function each(anchor, flags, get_collection, get_key, render_fn, fallback_fn, re let mismatch = false; if (hydrating) { - var is_else = /** @type {Comment} */ (hydrate_nodes?.[0])?.data === 'ssr:each_else'; + var is_else = /** @type {Comment} */ (anchor).data === `${HYDRATION_END}!`; if (is_else !== (length === 0)) { // hydration mismatch — remove the server-rendered DOM and start over remove(hydrate_nodes); set_hydrating(false); mismatch = true; - } else if (is_else) { - // Remove the each_else comment node or else it will confuse the subsequent hydration algorithm - /** @type {import('#client').TemplateNode[]} */ (hydrate_nodes).shift(); } } diff --git a/packages/svelte/tests/snapshot/samples/each-string-template/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/each-string-template/_expected/server/index.svelte.js index baabeb63e077..afb1daa750cb 100644 --- a/packages/svelte/tests/snapshot/samples/each-string-template/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/each-string-template/_expected/server/index.svelte.js @@ -17,6 +17,6 @@ export default function Each_string_template($$payload, $$props) { $$payload.out += ""; } - $$payload.out += ``; + $$payload.out += ""; $.pop(); } \ No newline at end of file From 732b1453ff134fe20591d4ede43f627573bcd3ff Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 1 Apr 2024 15:36:26 -0400 Subject: [PATCH 3/4] changeset --- .changeset/dry-pillows-exist.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dry-pillows-exist.md diff --git a/.changeset/dry-pillows-exist.md b/.changeset/dry-pillows-exist.md new file mode 100644 index 000000000000..dab22ccef1b5 --- /dev/null +++ b/.changeset/dry-pillows-exist.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: more efficient hydration markers From 9700ee5201422ec26ef4fc956b15e62c88a127b8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 1 Apr 2024 16:24:00 -0400 Subject: [PATCH 4/4] tidy up --- .../3-transform/server/transform-server.js | 17 +++++++++-------- packages/svelte/src/constants.js | 1 + .../src/internal/client/dom/blocks/each.js | 4 ++-- .../svelte/src/internal/client/dom/blocks/if.js | 4 ++-- .../svelte/src/internal/server/hydration.js | 3 ++- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 1a8056073ef4..19c626ae58a9 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -24,9 +24,14 @@ import { create_attribute, is_custom_element_node, is_element_node } from '../.. import { error } from '../../../errors.js'; import { binding_properties } from '../../bindings.js'; import { regex_starts_with_newline, regex_whitespaces_strict } from '../../patterns.js'; -import { DOMBooleanAttributes, HYDRATION_END, HYDRATION_START } from '../../../../constants.js'; +import { + DOMBooleanAttributes, + HYDRATION_END, + HYDRATION_END_ELSE, + HYDRATION_START +} from '../../../../constants.js'; import { sanitize_template_string } from '../../../utils/sanitize_template_string.js'; -import { BLOCK_CLOSE } from '../../../../internal/server/hydration.js'; +import { BLOCK_CLOSE, BLOCK_CLOSE_ELSE } from '../../../../internal/server/hydration.js'; export const block_open = t_string(``); export const block_close = t_string(``); @@ -1506,9 +1511,7 @@ const template_visitors = { if (node.fallback) { const fallback = create_block(node, node.fallback.nodes, context); - fallback.push( - b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(``))) - ); + fallback.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE_ELSE)))); state.template.push( t_statement( @@ -1531,9 +1534,7 @@ const template_visitors = { const alternate = node.alternate ? create_block(node, node.alternate.nodes, context) : []; consequent.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE)))); - alternate.push( - b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(``))) - ); + alternate.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE_ELSE)))); state.template.push( t_statement( diff --git a/packages/svelte/src/constants.js b/packages/svelte/src/constants.js index 5c9408d31471..159cbfb7b6af 100644 --- a/packages/svelte/src/constants.js +++ b/packages/svelte/src/constants.js @@ -21,6 +21,7 @@ export const TEMPLATE_USE_IMPORT_NODE = 1 << 1; export const HYDRATION_START = '['; export const HYDRATION_END = ']'; +export const HYDRATION_END_ELSE = `${HYDRATION_END}!`; // used to indicate that an `{:else}...` block was rendered export const UNINITIALIZED = Symbol(); diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index b636837e8628..092f600c4317 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -5,7 +5,7 @@ import { EACH_IS_STRICT_EQUALS, EACH_ITEM_REACTIVE, EACH_KEYED, - HYDRATION_END, + HYDRATION_END_ELSE, HYDRATION_START } from '../../../../constants.js'; import { hydrate_anchor, hydrate_nodes, hydrating, set_hydrating } from '../hydration.js'; @@ -98,7 +98,7 @@ function each(anchor, flags, get_collection, get_key, render_fn, fallback_fn, re let mismatch = false; if (hydrating) { - var is_else = /** @type {Comment} */ (anchor).data === `${HYDRATION_END}!`; + var is_else = /** @type {Comment} */ (anchor).data === HYDRATION_END_ELSE; if (is_else !== (length === 0)) { // hydration mismatch — remove the server-rendered DOM and start over diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js index 7446596d0324..277abacfbd7f 100644 --- a/packages/svelte/src/internal/client/dom/blocks/if.js +++ b/packages/svelte/src/internal/client/dom/blocks/if.js @@ -2,7 +2,7 @@ import { IS_ELSEIF } from '../../constants.js'; import { hydrate_nodes, hydrating, set_hydrating } from '../hydration.js'; import { remove } from '../reconciler.js'; import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js'; -import { HYDRATION_END } from '../../../../constants.js'; +import { HYDRATION_END_ELSE } from '../../../../constants.js'; /** * @param {Comment} anchor @@ -35,7 +35,7 @@ export function if_block( let mismatch = false; if (hydrating) { - const is_else = anchor.data === `${HYDRATION_END}!`; + const is_else = anchor.data === HYDRATION_END_ELSE; if (condition === is_else) { // Hydration mismatch: remove everything inside the anchor and start fresh. diff --git a/packages/svelte/src/internal/server/hydration.js b/packages/svelte/src/internal/server/hydration.js index 941918456b95..20913cd54ced 100644 --- a/packages/svelte/src/internal/server/hydration.js +++ b/packages/svelte/src/internal/server/hydration.js @@ -1,4 +1,5 @@ -import { HYDRATION_END, HYDRATION_START } from '../../constants.js'; +import { HYDRATION_END, HYDRATION_END_ELSE, HYDRATION_START } from '../../constants.js'; export const BLOCK_OPEN = ``; export const BLOCK_CLOSE = ``; +export const BLOCK_CLOSE_ELSE = ``;