From 4a6316818cd586f32dd4d82a78c64cf9bd7f0734 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 30 Mar 2024 09:46:53 -0400 Subject: [PATCH] feat: faster HTML tags (#10986) * feat: faster HTML tags * move code to where it is used --- .changeset/swift-poets-carry.md | 5 ++ .../src/internal/client/dom/blocks/html.js | 48 ++++++++++++++++++- .../src/internal/client/dom/reconciler.js | 36 -------------- 3 files changed, 51 insertions(+), 38 deletions(-) create mode 100644 .changeset/swift-poets-carry.md diff --git a/.changeset/swift-poets-carry.md b/.changeset/swift-poets-carry.md new file mode 100644 index 0000000000..0cb65a9389 --- /dev/null +++ b/.changeset/swift-poets-carry.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: faster HTML tags diff --git a/packages/svelte/src/internal/client/dom/blocks/html.js b/packages/svelte/src/internal/client/dom/blocks/html.js index 31fcc3d2e2..ff96617885 100644 --- a/packages/svelte/src/internal/client/dom/blocks/html.js +++ b/packages/svelte/src/internal/client/dom/blocks/html.js @@ -1,7 +1,8 @@ import { derived } from '../../reactivity/deriveds.js'; import { render_effect } from '../../reactivity/effects.js'; import { get } from '../../runtime.js'; -import { reconcile_html, remove } from '../reconciler.js'; +import { hydrate_nodes, hydrating } from '../hydration.js'; +import { create_fragment_from_html, remove } from '../reconciler.js'; /** * @param {Element | Text | Comment} anchor @@ -13,10 +14,53 @@ export function html(anchor, get_value, svg) { let value = derived(get_value); render_effect(() => { - var dom = reconcile_html(anchor, get(value), svg); + var dom = html_to_dom(anchor, get(value), svg); if (dom) { return () => remove(dom); } }); } + +/** + * Creates the content for a `@html` tag from its string value, + * inserts it before the target anchor and returns the new nodes. + * @template V + * @param {Element | Text | Comment} target + * @param {V} value + * @param {boolean} svg + * @returns {Element | Comment | (Element | Comment | Text)[]} + */ +function html_to_dom(target, value, svg) { + if (hydrating) return hydrate_nodes; + + var html = value + ''; + if (svg) html = `${html}`; + + // Don't use create_fragment_with_script_from_html here because that would mean script tags are executed. + // @html is basically `.innerHTML = ...` and that doesn't execute scripts either due to security reasons. + /** @type {DocumentFragment | Element} */ + var node = create_fragment_from_html(html); + + if (svg) { + node = /** @type {Element} */ (node.firstChild); + } + + if (node.childNodes.length === 1) { + var child = /** @type {Text | Element | Comment} */ (node.firstChild); + target.before(child); + return child; + } + + var nodes = /** @type {Array} */ ([...node.childNodes]); + + if (svg) { + while (node.firstChild) { + target.before(node.firstChild); + } + } else { + target.before(node); + } + + return nodes; +} diff --git a/packages/svelte/src/internal/client/dom/reconciler.js b/packages/svelte/src/internal/client/dom/reconciler.js index 18fac51f96..5b9f246ed3 100644 --- a/packages/svelte/src/internal/client/dom/reconciler.js +++ b/packages/svelte/src/internal/client/dom/reconciler.js @@ -1,4 +1,3 @@ -import { hydrate_nodes, hydrating } from './hydration.js'; import { is_array } from '../utils.js'; /** @param {string} html */ @@ -23,38 +22,3 @@ export function remove(current) { current.remove(); } } - -/** - * Creates the content for a `@html` tag from its string value, - * inserts it before the target anchor and returns the new nodes. - * @template V - * @param {Element | Text | Comment} target - * @param {V} value - * @param {boolean} svg - * @returns {Element | Comment | (Element | Comment | Text)[]} - */ -export function reconcile_html(target, value, svg) { - if (hydrating) { - return hydrate_nodes; - } - var html = value + ''; - // Even if html is the empty string we need to continue to insert something or - // else the element ordering gets out of sync, resulting in subsequent values - // not getting inserted anymore. - var frag_nodes; - if (svg) { - html = `${html}`; - } - // Don't use create_fragment_with_script_from_html here because that would mean script tags are executed. - // @html is basically `.innerHTML = ...` and that doesn't execute scripts either due to security reasons. - var content = create_fragment_from_html(html); - if (svg) { - content = /** @type {DocumentFragment} */ (/** @type {unknown} */ (content.firstChild)); - } - var clone = content.cloneNode(true); - frag_nodes = [...clone.childNodes]; - frag_nodes.forEach((node) => { - target.before(node); - }); - return /** @type {Array} */ (frag_nodes); -}