fix: don't clone node if a renderer is available

pull/18058/head
paoloricciuti 5 months ago
parent 9ae3faa09a
commit c459824a88

@ -10,7 +10,18 @@ import {
create_fragment, create_fragment,
create_comment, create_comment,
set_attribute, set_attribute,
merge_text_nodes merge_text_nodes,
get_last_child,
import_node,
clone_node,
append_child,
insert_before,
set_text_content,
replace_with,
node_type,
query_selector_all,
get_node_value,
node_name
} from './operations.js'; } from './operations.js';
import { create_fragment_from_html } from './reconciler.js'; import { create_fragment_from_html } from './reconciler.js';
import { active_effect } from '../runtime.js'; import { active_effect } from '../runtime.js';
@ -29,6 +40,7 @@ import {
REACTION_RAN, REACTION_RAN,
TEXT_NODE TEXT_NODE
} from '#client/constants'; } from '#client/constants';
import { renderer } from '../custom-renderer/state.js';
const TEMPLATE_TAG = IS_XHTML ? 'template' : 'TEMPLATE'; const TEMPLATE_TAG = IS_XHTML ? 'template' : 'TEMPLATE';
const SCRIPT_TAG = IS_XHTML ? 'script' : 'SCRIPT'; const SCRIPT_TAG = IS_XHTML ? 'script' : 'SCRIPT';
@ -75,12 +87,12 @@ export function from_html(content, flags) {
} }
var clone = /** @type {TemplateNode} */ ( var clone = /** @type {TemplateNode} */ (
use_import_node || is_firefox ? document.importNode(node, true) : node.cloneNode(true) use_import_node || is_firefox ? import_node(node, true) : clone_node(node, true)
); );
if (is_fragment) { if (is_fragment) {
var start = /** @type {TemplateNode} */ (get_first_child(clone)); var start = /** @type {TemplateNode} */ (get_first_child(clone));
var end = /** @type {TemplateNode} */ (clone.lastChild); var end = /** @type {TemplateNode} */ (get_last_child(clone));
assign_nodes(start, end); assign_nodes(start, end);
} else { } else {
@ -122,20 +134,20 @@ function from_namespace(content, flags, ns = 'svg') {
var root = /** @type {Element} */ (get_first_child(fragment)); var root = /** @type {Element} */ (get_first_child(fragment));
if (is_fragment) { if (is_fragment) {
node = document.createDocumentFragment(); node = create_fragment();
while (get_first_child(root)) { while (get_first_child(root)) {
node.appendChild(/** @type {TemplateNode} */ (get_first_child(root))); append_child(node, /** @type {TemplateNode} */ (get_first_child(root)));
} }
} else { } else {
node = /** @type {Element} */ (get_first_child(root)); node = /** @type {Element} */ (get_first_child(root));
} }
} }
var clone = /** @type {TemplateNode} */ (node.cloneNode(true)); var clone = /** @type {TemplateNode} */ (clone_node(node, true));
if (is_fragment) { if (is_fragment) {
var start = /** @type {TemplateNode} */ (get_first_child(clone)); var start = /** @type {TemplateNode} */ (get_first_child(clone));
var end = /** @type {TemplateNode} */ (clone.lastChild); var end = /** @type {TemplateNode} */ (get_last_child(clone));
assign_nodes(start, end); assign_nodes(start, end);
} else { } else {
@ -173,13 +185,13 @@ function fragment_from_tree(structure, ns) {
for (var item of structure) { for (var item of structure) {
if (typeof item === 'string') { if (typeof item === 'string') {
fragment.append(create_text(item)); append_child(fragment, create_text(item));
continue; continue;
} }
// if `preserveComments === true`, comments are represented as `['// <data>']` // if `preserveComments === true`, comments are represented as `['// <data>']`
if (item === undefined || item[0][0] === '/') { if (item === undefined || item[0][0] === '/') {
fragment.append(create_comment(item ? item[0].slice(3) : '')); append_child(fragment, create_comment(item ? item[0].slice(3) : ''));
continue; continue;
} }
@ -195,16 +207,18 @@ function fragment_from_tree(structure, ns) {
if (children.length > 0) { if (children.length > 0) {
var target = var target =
element.nodeName === TEMPLATE_TAG node_name(element) === TEMPLATE_TAG
? /** @type {HTMLTemplateElement} */ (element).content ? // TODO: DOM access
/** @type {HTMLTemplateElement} */ (element).content
: element; : element;
target.append( append_child(
fragment_from_tree(children, element.nodeName === 'foreignObject' ? undefined : namespace) target,
fragment_from_tree(children, node_name(element) === 'foreignObject' ? undefined : namespace)
); );
} }
fragment.append(element); append_child(fragment, element);
} }
return fragment; return fragment;
@ -229,7 +243,10 @@ export function from_tree(structure, flags) {
return hydrate_node; return hydrate_node;
} }
if (node === undefined) { // for the custom renderer we skip the cloning and create new nodes every time...a bit less efficient
// but save custom renderers implementors from having to implement importNode or cloneNode
// which can be a pain
if (node === undefined || renderer != null) {
const ns = const ns =
(flags & TEMPLATE_USE_SVG) !== 0 (flags & TEMPLATE_USE_SVG) !== 0
? NAMESPACE_SVG ? NAMESPACE_SVG
@ -242,12 +259,16 @@ export function from_tree(structure, flags) {
} }
var clone = /** @type {TemplateNode} */ ( var clone = /** @type {TemplateNode} */ (
use_import_node || is_firefox ? document.importNode(node, true) : node.cloneNode(true) renderer != null
? node
: use_import_node || is_firefox
? import_node(node, true)
: clone_node(node, true)
); );
if (is_fragment) { if (is_fragment) {
var start = /** @type {TemplateNode} */ (get_first_child(clone)); var start = /** @type {TemplateNode} */ (get_first_child(clone));
var end = /** @type {TemplateNode} */ (clone.lastChild); var end = /** @type {TemplateNode} */ (get_last_child(clone));
assign_nodes(start, end); assign_nodes(start, end);
} else { } else {
@ -275,31 +296,34 @@ function run_scripts(node) {
// scripts were SSR'd, in which case they will run // scripts were SSR'd, in which case they will run
if (hydrating) return node; if (hydrating) return node;
const is_fragment = node.nodeType === DOCUMENT_FRAGMENT_NODE; const is_fragment = node_type(node) === DOCUMENT_FRAGMENT_NODE;
const scripts = const scripts =
/** @type {HTMLElement} */ (node).nodeName === SCRIPT_TAG // TODO RENDERER: figure out what to do here
node_name(node) === SCRIPT_TAG
? [/** @type {HTMLScriptElement} */ (node)] ? [/** @type {HTMLScriptElement} */ (node)]
: node.querySelectorAll('script'); : query_selector_all(node, 'script');
const effect = /** @type {Effect & { nodes: EffectNodes }} */ (active_effect); const effect = /** @type {Effect & { nodes: EffectNodes }} */ (active_effect);
for (const script of scripts) { for (const script of scripts) {
const clone = create_element('script'); const clone = create_element('script');
// TODO: DOM access
for (var attribute of script.attributes) { for (var attribute of script.attributes) {
clone.setAttribute(attribute.name, attribute.value); set_attribute(clone, attribute.name, attribute.value);
} }
clone.textContent = script.textContent; // TODO: DOM access
set_text_content(clone, script.textContent ?? '');
// The script has changed - if it's at the edges, the effect now points at dead nodes // The script has changed - if it's at the edges, the effect now points at dead nodes
if (is_fragment ? node.firstChild === script : node === script) { if (is_fragment ? get_first_child(node) === script : node === script) {
effect.nodes.start = clone; effect.nodes.start = clone;
} }
if (is_fragment ? node.lastChild === script : node === script) { if (is_fragment ? get_last_child(node) === script : node === script) {
effect.nodes.end = clone; effect.nodes.end = clone;
} }
script.replaceWith(clone); replace_with(script, clone);
} }
return node; return node;
} }
@ -317,9 +341,9 @@ export function text(value = '') {
var node = hydrate_node; var node = hydrate_node;
if (node.nodeType !== TEXT_NODE) { if (node_type(node) !== TEXT_NODE) {
// if an {expression} is empty during SSR, we need to insert an empty text node // if an {expression} is empty during SSR, we need to insert an empty text node
node.before((node = create_text())); insert_before(node, (node = create_text()));
set_hydrate_node(node); set_hydrate_node(node);
} else { } else {
merge_text_nodes(/** @type {Text} */ (node)); merge_text_nodes(/** @type {Text} */ (node));
@ -339,10 +363,11 @@ export function comment() {
return hydrate_node; return hydrate_node;
} }
var frag = document.createDocumentFragment(); var frag = create_fragment();
var start = document.createComment(''); var start = create_comment('');
var anchor = create_text(); var anchor = create_text();
frag.append(start, anchor); append_child(frag, start);
append_child(frag, anchor);
assign_nodes(start, anchor); assign_nodes(start, anchor);
@ -375,24 +400,26 @@ export function append(anchor, dom) {
return; return;
} }
anchor.before(/** @type {Node} */ (dom)); insert_before(anchor, /** @type {Node} */ (dom));
} }
/** /**
* Create (or hydrate) an unique UID for the component instance. * Create (or hydrate) an unique UID for the component instance.
*/ */
export function props_id() { export function props_id() {
let node_value;
if ( if (
hydrating && hydrating &&
hydrate_node && hydrate_node &&
hydrate_node.nodeType === COMMENT_NODE && node_type(hydrate_node) === COMMENT_NODE &&
hydrate_node.textContent?.startsWith(`$`) (node_value = get_node_value(hydrate_node))?.startsWith(`$`)
) { ) {
const id = hydrate_node.textContent.substring(1); const id = node_value.substring(1);
hydrate_next(); hydrate_next();
return id; return id;
} }
// TODO RENDERER: figure out what to do here
// @ts-expect-error This way we ensure the id is unique even across Svelte runtimes // @ts-expect-error This way we ensure the id is unique even across Svelte runtimes
(window.__svelte ??= {}).uid ??= 1; (window.__svelte ??= {}).uid ??= 1;

Loading…
Cancel
Save