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(``)));
+ 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(``)));
+ 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(``)));
- 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(``)));
- 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';