From 26b95e4daec57532701366fcd011c26d979cca31 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 8 Apr 2024 19:57:05 +0100 Subject: [PATCH 1/3] fix: improve hydration of svelte head blocks --- .changeset/proud-queens-sniff.md | 5 +++ .../internal/client/dom/blocks/svelte-head.js | 38 ++++++++++++++++--- packages/svelte/src/internal/client/render.js | 2 + packages/svelte/src/internal/server/index.js | 7 ++-- .../samples/multiple-head/MetaTag.svelte | 9 +++++ .../samples/multiple-head/_config.js | 12 ++++++ .../samples/multiple-head/main.svelte | 9 +++++ .../_expected-head.html | 2 +- playgrounds/demo/src/entry-client.ts | 4 +- 9 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 .changeset/proud-queens-sniff.md create mode 100644 packages/svelte/tests/runtime-runes/samples/multiple-head/MetaTag.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/multiple-head/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/multiple-head/main.svelte diff --git a/.changeset/proud-queens-sniff.md b/.changeset/proud-queens-sniff.md new file mode 100644 index 000000000000..e2970fd4bc41 --- /dev/null +++ b/.changeset/proud-queens-sniff.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: improve hydration of svelte head blocks diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-head.js b/packages/svelte/src/internal/client/dom/blocks/svelte-head.js index 70606cd32fd4..89d3bb631114 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-head.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-head.js @@ -1,7 +1,16 @@ import { hydrate_anchor, hydrate_nodes, hydrating, set_hydrate_nodes } from '../hydration.js'; import { empty } from '../operations.js'; import { block } from '../../reactivity/effects.js'; -import { HYDRATION_START } from '../../../../constants.js'; +import { HYDRATION_END, HYDRATION_START } from '../../../../constants.js'; + +/** + * @type {Node | undefined} + */ +let head_anchor; + +export function reset_head_anchor() { + head_anchor = undefined; +} /** * @param {(anchor: Node) => import('#client').Dom | void} render_fn @@ -19,12 +28,31 @@ export function head(render_fn) { if (hydrating) { previous_hydrate_nodes = hydrate_nodes; - let anchor = /** @type {import('#client').TemplateNode} */ (document.head.firstChild); - while (anchor.nodeType !== 8 || /** @type {Comment} */ (anchor).data !== HYDRATION_START) { - anchor = /** @type {import('#client').TemplateNode} */ (anchor.nextSibling); + // There might be multiple head blocks in our app, so we need to account for each one needing independent hydration. + if (head_anchor === undefined) { + head_anchor = /** @type {import('#client').TemplateNode} */ (document.head.firstChild); + } + + var depth = 0; + + while (head_anchor !== null) { + if (head_anchor.nodeType === 8) { + var data = /** @type {Comment} */ (head_anchor).data; + if (data === HYDRATION_START) { + if (depth === 0) { + break; + } + depth++; + } + if (data === HYDRATION_END) { + depth--; + } + } + head_anchor = /** @type {import('#client').TemplateNode} */ (head_anchor.nextSibling); } - anchor = /** @type {import('#client').TemplateNode} */ (hydrate_anchor(anchor)); + head_anchor = /** @type {import('#client').TemplateNode} */ (hydrate_anchor(head_anchor)); + head_anchor = /** @type {import('#client').TemplateNode} */ (head_anchor.nextSibling); } else { anchor = document.head.appendChild(empty()); } diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 421e30caad4e..833070925ed0 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -18,6 +18,7 @@ import { } from './dom/hydration.js'; import { array_from } from './utils.js'; import { handle_event_propagation } from './dom/elements/events.js'; +import { reset_head_anchor } from './dom/blocks/svelte-head.js'; /** @type {Set} */ export const all_registered_events = new Set(); @@ -175,6 +176,7 @@ export function hydrate(component, options) { } finally { set_hydrating(!!previous_hydrate_nodes); set_hydrate_nodes(previous_hydrate_nodes); + reset_head_anchor(); } } diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index b6d10cfca8cd..6a5e8aa1770c 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -207,10 +207,7 @@ export function render(component, options) { on_destroy = prev_on_destroy; return { - head: - payload.head.out || payload.head.title - ? payload.head.title + BLOCK_OPEN + payload.head.out + BLOCK_CLOSE - : '', + head: payload.head.out || payload.head.title ? payload.head.out + payload.head.title : '', html: payload.out }; } @@ -247,7 +244,9 @@ export function escape(value, is_attr = false) { */ export function head(payload, fn) { const head_payload = payload.head; + payload.head.out += BLOCK_OPEN; fn(head_payload); + payload.head.out += BLOCK_CLOSE; } /** diff --git a/packages/svelte/tests/runtime-runes/samples/multiple-head/MetaTag.svelte b/packages/svelte/tests/runtime-runes/samples/multiple-head/MetaTag.svelte new file mode 100644 index 000000000000..b19d8a77db25 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/multiple-head/MetaTag.svelte @@ -0,0 +1,9 @@ + + + {title} + + + diff --git a/packages/svelte/tests/runtime-runes/samples/multiple-head/_config.js b/packages/svelte/tests/runtime-runes/samples/multiple-head/_config.js new file mode 100644 index 000000000000..5c9395a73c49 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/multiple-head/_config.js @@ -0,0 +1,12 @@ +import { test } from '../../test'; + +export default test({ + html: `
Hello
`, + + async test({ assert, target }) { + assert.htmlEqual( + target.ownerDocument.head.innerHTML, + `Hello world` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/multiple-head/main.svelte b/packages/svelte/tests/runtime-runes/samples/multiple-head/main.svelte new file mode 100644 index 000000000000..c5dcbb912991 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/multiple-head/main.svelte @@ -0,0 +1,9 @@ + + + + + + +
Hello
diff --git a/packages/svelte/tests/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected-head.html b/packages/svelte/tests/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected-head.html index 0e12a4a9de64..c20e3600ff0f 100644 --- a/packages/svelte/tests/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected-head.html +++ b/packages/svelte/tests/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected-head.html @@ -1,6 +1,6 @@ -Some Title +Some Title diff --git a/playgrounds/demo/src/entry-client.ts b/playgrounds/demo/src/entry-client.ts index 1cb573735cfc..f6affe53c43b 100644 --- a/playgrounds/demo/src/entry-client.ts +++ b/playgrounds/demo/src/entry-client.ts @@ -1,8 +1,8 @@ // @ts-ignore -import { mount, unmount } from 'svelte'; +import { hydrate, mount, unmount } from 'svelte'; // @ts-ignore you need to create this file import App from './App.svelte'; -const component = mount(App, { +const component = hydrate(App, { target: document.getElementById('root')! }); // @ts-ignore From 7ffdcaab1d2417671b919fee77737466ee4c755b Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 8 Apr 2024 19:57:55 +0100 Subject: [PATCH 2/3] revert sandbox --- playgrounds/demo/src/entry-client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playgrounds/demo/src/entry-client.ts b/playgrounds/demo/src/entry-client.ts index f6affe53c43b..1cb573735cfc 100644 --- a/playgrounds/demo/src/entry-client.ts +++ b/playgrounds/demo/src/entry-client.ts @@ -1,8 +1,8 @@ // @ts-ignore -import { hydrate, mount, unmount } from 'svelte'; +import { mount, unmount } from 'svelte'; // @ts-ignore you need to create this file import App from './App.svelte'; -const component = hydrate(App, { +const component = mount(App, { target: document.getElementById('root')! }); // @ts-ignore From 1e19135d057ddcfe9da2b764baafeced57c064d9 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 9 Apr 2024 11:03:58 +0100 Subject: [PATCH 3/3] simplify --- .../internal/client/dom/blocks/svelte-head.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-head.js b/packages/svelte/src/internal/client/dom/blocks/svelte-head.js index 89d3bb631114..b00a3a242b1e 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-head.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-head.js @@ -33,21 +33,10 @@ export function head(render_fn) { head_anchor = /** @type {import('#client').TemplateNode} */ (document.head.firstChild); } - var depth = 0; - - while (head_anchor !== null) { - if (head_anchor.nodeType === 8) { - var data = /** @type {Comment} */ (head_anchor).data; - if (data === HYDRATION_START) { - if (depth === 0) { - break; - } - depth++; - } - if (data === HYDRATION_END) { - depth--; - } - } + while ( + head_anchor.nodeType !== 8 || + /** @type {Comment} */ (head_anchor).data !== HYDRATION_START + ) { head_anchor = /** @type {import('#client').TemplateNode} */ (head_anchor.nextSibling); }