From d82d57e833f07f1cfcd1de84f4446625bd4f4cea Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 12 Mar 2024 12:26:31 -0400 Subject: [PATCH] move template code into its own module (#10773) Co-authored-by: Rich Harris --- .../src/internal/client/custom-element.js | 2 +- .../src/internal/client/dom/template.js | 214 ++++++++++++++++++ packages/svelte/src/internal/client/render.js | 214 +----------------- packages/svelte/src/internal/index.js | 1 + 4 files changed, 217 insertions(+), 214 deletions(-) create mode 100644 packages/svelte/src/internal/client/dom/template.js diff --git a/packages/svelte/src/internal/client/custom-element.js b/packages/svelte/src/internal/client/custom-element.js index 2d5a8fe08c..5a1318e6a9 100644 --- a/packages/svelte/src/internal/client/custom-element.js +++ b/packages/svelte/src/internal/client/custom-element.js @@ -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'; /** diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js new file mode 100644 index 0000000000..690609203e --- /dev/null +++ b/packages/svelte/src/internal/client/dom/template.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}`))); + 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}`))); + 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} */ + 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); +} diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index afe46975b7..0a2da60f02 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -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) => 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}`))); - 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}`))); - 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} */ - 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) => void} fn * @returns {(event: Event, ...args: unknown[]) => void} diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index c8a45bc725..b3b4c27db2 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -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';