feat: faster HTML tags (#10986)

* feat: faster HTML tags

* move code to where it is used
pull/10995/head
Rich Harris 1 year ago committed by GitHub
parent cd90a5d946
commit 4a6316818c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: faster HTML tags

@ -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 = `<svg>${html}</svg>`;
// 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<Text | Element | Comment>} */ ([...node.childNodes]);
if (svg) {
while (node.firstChild) {
target.before(node.firstChild);
}
} else {
target.before(node);
}
return nodes;
}

@ -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 = `<svg>${html}</svg>`;
}
// 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<Text | Comment | Element>} */ (frag_nodes);
}

Loading…
Cancel
Save