move template code into its own module (#10773)

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/10776/head
Rich Harris 4 months ago committed by GitHub
parent 77f39ea988
commit d82d57e833
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,6 +1,6 @@
import { createClassComponent } from '../../legacy/legacy-client.js';
import { destroy_effect, render_effect } from './reactivity/effects.js';
import { open, close } from './render.js';
import { open, close } from './dom/template.js';
import { define_property } from './utils.js';
/**

@ -0,0 +1,214 @@
import { current_hydration_fragment, hydrate_block_anchor, hydrating } from '../hydration.js';
import { child, clone_node, empty } from '../operations.js';
import {
create_fragment_from_html,
create_fragment_with_script_from_html,
insert
} from '../reconciler.js';
import { current_block } from '../runtime.js';
import { is_array } from '../utils.js';
/**
* @param {string} html
* @param {boolean} return_fragment
* @returns {() => Node}
*/
/*#__NO_SIDE_EFFECTS__*/
export function template(html, return_fragment) {
/** @type {undefined | Node} */
let cached_content;
return () => {
if (cached_content === undefined) {
const content = create_fragment_from_html(html);
cached_content = return_fragment ? content : /** @type {Node} */ (child(content));
}
return cached_content;
};
}
/**
* @param {string} html
* @param {boolean} return_fragment
* @returns {() => Node}
*/
/*#__NO_SIDE_EFFECTS__*/
export function template_with_script(html, return_fragment) {
/** @type {undefined | Node} */
let cached_content;
return () => {
if (cached_content === undefined) {
const content = create_fragment_with_script_from_html(html);
cached_content = return_fragment ? content : /** @type {Node} */ (child(content));
}
return cached_content;
};
}
/**
* @param {string} svg
* @param {boolean} return_fragment
* @returns {() => Node}
*/
/*#__NO_SIDE_EFFECTS__*/
export function svg_template(svg, return_fragment) {
/** @type {undefined | Node} */
let cached_content;
return () => {
if (cached_content === undefined) {
const content = /** @type {Node} */ (child(create_fragment_from_html(`<svg>${svg}</svg>`)));
cached_content = return_fragment ? content : /** @type {Node} */ (child(content));
}
return cached_content;
};
}
/**
* @param {string} svg
* @param {boolean} return_fragment
* @returns {() => Node}
*/
/*#__NO_SIDE_EFFECTS__*/
export function svg_template_with_script(svg, return_fragment) {
/** @type {undefined | Node} */
let cached_content;
return () => {
if (cached_content === undefined) {
const content = /** @type {Node} */ (child(create_fragment_from_html(`<svg>${svg}</svg>`)));
cached_content = return_fragment ? content : /** @type {Node} */ (child(content));
}
return cached_content;
};
}
/**
* @param {boolean} is_fragment
* @param {boolean} use_clone_node
* @param {null | Text | Comment | Element} anchor
* @param {() => Node} [template_element_fn]
* @returns {Element | DocumentFragment | Node[]}
*/
function open_template(is_fragment, use_clone_node, anchor, template_element_fn) {
if (hydrating) {
if (anchor !== null) {
hydrate_block_anchor(anchor, false);
}
// In ssr+hydration optimization mode, we might remove the template_element,
// so we need to is_fragment flag to properly handle hydrated content accordingly.
const fragment = current_hydration_fragment;
if (fragment !== null) {
return is_fragment ? fragment : /** @type {Element} */ (fragment[0]);
}
}
return use_clone_node
? clone_node(/** @type {() => Element} */ (template_element_fn)(), true)
: document.importNode(/** @type {() => Element} */ (template_element_fn)(), true);
}
/**
* @param {null | Text | Comment | Element} anchor
* @param {boolean} use_clone_node
* @param {() => Node} [template_element_fn]
* @returns {Element | DocumentFragment | Node[]}
*/
/*#__NO_SIDE_EFFECTS__*/
export function open(anchor, use_clone_node, template_element_fn) {
return open_template(false, use_clone_node, anchor, template_element_fn);
}
/**
* @param {null | Text | Comment | Element} anchor
* @param {boolean} use_clone_node
* @param {() => Node} [template_element_fn]
* @returns {Element | DocumentFragment | Node[]}
*/
/*#__NO_SIDE_EFFECTS__*/
export function open_frag(anchor, use_clone_node, template_element_fn) {
return open_template(true, use_clone_node, anchor, template_element_fn);
}
const space_template = template(' ', false);
const comment_template = template('<!>', true);
/**
* @param {Text | Comment | Element | null} anchor
*/
/*#__NO_SIDE_EFFECTS__*/
export function space_frag(anchor) {
/** @type {Node | null} */
var node = /** @type {any} */ (open(anchor, true, space_template));
// if an {expression} is empty during SSR, there might be no
// text node to hydrate (or an anchor comment is falsely detected instead)
// — we must therefore create one
if (hydrating && node?.nodeType !== 3) {
node = empty();
// @ts-ignore in this case the anchor should always be a comment,
// if not something more fundamental is wrong and throwing here is better to bail out early
anchor.before(node);
}
return node;
}
/**
* @param {Text | Comment | Element} anchor
*/
/*#__NO_SIDE_EFFECTS__*/
export function space(anchor) {
// if an {expression} is empty during SSR, there might be no
// text node to hydrate (or an anchor comment is falsely detected instead)
// — we must therefore create one
if (hydrating && anchor.nodeType !== 3) {
const node = empty();
anchor.before(node);
return node;
}
return anchor;
}
/**
* @param {null | Text | Comment | Element} anchor
*/
/*#__NO_SIDE_EFFECTS__*/
export function comment(anchor) {
return open_frag(anchor, true, comment_template);
}
/**
* Assign the created (or in hydration mode, traversed) dom elements to the current block
* and insert the elements into the dom (in client mode).
* @param {Element | Text} dom
* @param {boolean} is_fragment
* @param {null | Text | Comment | Element} anchor
* @returns {void}
*/
function close_template(dom, is_fragment, anchor) {
const block = /** @type {import('#client').Block} */ (current_block);
/** @type {import('#client').TemplateNode | Array<import('#client').TemplateNode>} */
const current = is_fragment
? is_array(dom)
? dom
: /** @type {import('#client').TemplateNode[]} */ (Array.from(dom.childNodes))
: dom;
if (!hydrating && anchor !== null) {
insert(current, null, anchor);
}
block.d = current;
}
/**
* @param {null | Text | Comment | Element} anchor
* @param {Element | Text} dom
* @returns {void}
*/
export function close(anchor, dom) {
close_template(dom, false, anchor);
}
/**
* @param {null | Text | Comment | Element} anchor
* @param {Element | Text} dom
* @returns {void}
*/
export function close_frag(anchor, dom) {
close_template(dom, true, anchor);
}

@ -1,8 +1,6 @@
import { DEV } from 'esm-env';
import {
append_child,
child,
clone_node,
create_element,
empty,
init_operations,
@ -20,12 +18,7 @@ import {
PROPS_IS_UPDATED,
PROPS_IS_LAZY_INITIAL
} from '../../constants.js';
import {
create_fragment_from_html,
create_fragment_with_script_from_html,
insert,
remove
} from './reconciler.js';
import { remove } from './reconciler.js';
import {
untrack,
flush_sync,
@ -75,78 +68,6 @@ const all_registered_events = new Set();
/** @type {Set<(events: Array<string>) => void>} */
const root_event_handles = new Set();
/**
* @param {string} html
* @param {boolean} return_fragment
* @returns {() => Node}
*/
/*#__NO_SIDE_EFFECTS__*/
export function template(html, return_fragment) {
/** @type {undefined | Node} */
let cached_content;
return () => {
if (cached_content === undefined) {
const content = create_fragment_from_html(html);
cached_content = return_fragment ? content : /** @type {Node} */ (child(content));
}
return cached_content;
};
}
/**
* @param {string} html
* @param {boolean} return_fragment
* @returns {() => Node}
*/
/*#__NO_SIDE_EFFECTS__*/
export function template_with_script(html, return_fragment) {
/** @type {undefined | Node} */
let cached_content;
return () => {
if (cached_content === undefined) {
const content = create_fragment_with_script_from_html(html);
cached_content = return_fragment ? content : /** @type {Node} */ (child(content));
}
return cached_content;
};
}
/**
* @param {string} svg
* @param {boolean} return_fragment
* @returns {() => Node}
*/
/*#__NO_SIDE_EFFECTS__*/
export function svg_template(svg, return_fragment) {
/** @type {undefined | Node} */
let cached_content;
return () => {
if (cached_content === undefined) {
const content = /** @type {Node} */ (child(create_fragment_from_html(`<svg>${svg}</svg>`)));
cached_content = return_fragment ? content : /** @type {Node} */ (child(content));
}
return cached_content;
};
}
/**
* @param {string} svg
* @param {boolean} return_fragment
* @returns {() => Node}
*/
/*#__NO_SIDE_EFFECTS__*/
export function svg_template_with_script(svg, return_fragment) {
/** @type {undefined | Node} */
let cached_content;
return () => {
if (cached_content === undefined) {
const content = /** @type {Node} */ (child(create_fragment_from_html(`<svg>${svg}</svg>`)));
cached_content = return_fragment ? content : /** @type {Node} */ (child(content));
}
return cached_content;
};
}
/**
* @param {Element} node
* @returns {Element}
@ -157,139 +78,6 @@ export function svg_replace(node) {
return first_child;
}
/**
* @param {boolean} is_fragment
* @param {boolean} use_clone_node
* @param {null | Text | Comment | Element} anchor
* @param {() => Node} [template_element_fn]
* @returns {Element | DocumentFragment | Node[]}
*/
function open_template(is_fragment, use_clone_node, anchor, template_element_fn) {
if (hydrating) {
if (anchor !== null) {
hydrate_block_anchor(anchor, false);
}
// In ssr+hydration optimization mode, we might remove the template_element,
// so we need to is_fragment flag to properly handle hydrated content accordingly.
const fragment = current_hydration_fragment;
if (fragment !== null) {
return is_fragment ? fragment : /** @type {Element} */ (fragment[0]);
}
}
return use_clone_node
? clone_node(/** @type {() => Element} */ (template_element_fn)(), true)
: document.importNode(/** @type {() => Element} */ (template_element_fn)(), true);
}
/**
* @param {null | Text | Comment | Element} anchor
* @param {boolean} use_clone_node
* @param {() => Node} [template_element_fn]
* @returns {Element | DocumentFragment | Node[]}
*/
/*#__NO_SIDE_EFFECTS__*/
export function open(anchor, use_clone_node, template_element_fn) {
return open_template(false, use_clone_node, anchor, template_element_fn);
}
/**
* @param {null | Text | Comment | Element} anchor
* @param {boolean} use_clone_node
* @param {() => Node} [template_element_fn]
* @returns {Element | DocumentFragment | Node[]}
*/
/*#__NO_SIDE_EFFECTS__*/
export function open_frag(anchor, use_clone_node, template_element_fn) {
return open_template(true, use_clone_node, anchor, template_element_fn);
}
const space_template = template(' ', false);
const comment_template = template('<!>', true);
/**
* @param {Text | Comment | Element | null} anchor
*/
/*#__NO_SIDE_EFFECTS__*/
export function space_frag(anchor) {
/** @type {Node | null} */
var node = /** @type {any} */ (open(anchor, true, space_template));
// if an {expression} is empty during SSR, there might be no
// text node to hydrate (or an anchor comment is falsely detected instead)
// — we must therefore create one
if (hydrating && node?.nodeType !== 3) {
node = empty();
// @ts-ignore in this case the anchor should always be a comment,
// if not something more fundamental is wrong and throwing here is better to bail out early
anchor.before(node);
}
return node;
}
/**
* @param {Text | Comment | Element} anchor
*/
/*#__NO_SIDE_EFFECTS__*/
export function space(anchor) {
// if an {expression} is empty during SSR, there might be no
// text node to hydrate (or an anchor comment is falsely detected instead)
// — we must therefore create one
if (hydrating && anchor.nodeType !== 3) {
const node = empty();
anchor.before(node);
return node;
}
return anchor;
}
/**
* @param {null | Text | Comment | Element} anchor
*/
/*#__NO_SIDE_EFFECTS__*/
export function comment(anchor) {
return open_frag(anchor, true, comment_template);
}
/**
* Assign the created (or in hydration mode, traversed) dom elements to the current block
* and insert the elements into the dom (in client mode).
* @param {Element | Text} dom
* @param {boolean} is_fragment
* @param {null | Text | Comment | Element} anchor
* @returns {void}
*/
function close_template(dom, is_fragment, anchor) {
const block = /** @type {import('./types.js').Block} */ (current_block);
/** @type {import('./types.js').TemplateNode | Array<import('./types.js').TemplateNode>} */
const current = is_fragment
? is_array(dom)
? dom
: /** @type {import('./types.js').TemplateNode[]} */ (Array.from(dom.childNodes))
: dom;
if (!hydrating && anchor !== null) {
insert(current, null, anchor);
}
block.d = current;
}
/**
* @param {null | Text | Comment | Element} anchor
* @param {Element | Text} dom
* @returns {void}
*/
export function close(anchor, dom) {
close_template(dom, false, anchor);
}
/**
* @param {null | Text | Comment | Element} anchor
* @param {Element | Text} dom
* @returns {void}
*/
export function close_frag(anchor, dom) {
close_template(dom, true, anchor);
}
/**
* @param {(event: Event, ...args: Array<unknown>) => void} fn
* @returns {(event: Event, ...args: unknown[]) => void}

@ -40,6 +40,7 @@ export * from './client/dom/blocks/snippet.js';
export * from './client/dom/blocks/svelte-component.js';
export * from './client/dom/blocks/svelte-element.js';
export * from './client/dom/blocks/svelte-head.js';
export * from './client/dom/template.js';
export * from './client/reactivity/deriveds.js';
export * from './client/reactivity/effects.js';
export * from './client/reactivity/sources.js';

Loading…
Cancel
Save