diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-template/index.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-template/index.js index e99a076a5d..61ae9d9b27 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-template/index.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-template/index.js @@ -2,25 +2,10 @@ /** @import { Namespace } from '#compiler' */ /** @import { ComponentClientTransformState } from '../types.js' */ /** @import { Node } from './types.js' */ +import { TEMPLATE_USE_MATHML, TEMPLATE_USE_SVG } from '../../../../../constants.js'; import { dev, locator } from '../../../../state.js'; import * as b from '../../../../utils/builders.js'; -/** - * - * @param {Namespace} namespace - * @param {ComponentClientTransformState} state - * @returns - */ -function get_template_function(namespace, state) { - return ( - namespace === 'svg' - ? '$.ns_template' - : namespace === 'mathml' - ? '$.mathml_template' - : '$.template' - ).concat(state.options.templatingMode === 'functional' ? '_fn' : ''); -} - /** * @param {Node[]} nodes */ @@ -50,14 +35,18 @@ function build_locations(nodes) { * @param {Namespace} namespace * @param {number} [flags] */ -export function transform_template(state, namespace, flags) { - const expression = - state.options.templatingMode === 'functional' - ? state.template.as_objects() - : state.template.as_string(); +export function transform_template(state, namespace, flags = 0) { + const tree = state.options.templatingMode === 'functional'; + + const expression = tree ? state.template.as_tree() : state.template.as_html(); + + if (tree) { + if (namespace === 'svg') flags |= TEMPLATE_USE_SVG; + if (namespace === 'mathml') flags |= TEMPLATE_USE_MATHML; + } let call = b.call( - get_template_function(namespace, state), + tree ? `$.from_tree` : `$.from_${namespace}`, expression, flags ? b.literal(flags) : undefined ); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-template/template.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-template/template.js index 6b31bb15ed..bb9a653f8c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-template/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-template/template.js @@ -69,11 +69,11 @@ export class Template { /** @type {Element} */ (this.#element).attributes[key] = value; } - as_string() { + as_html() { return b.template([b.quasi(this.nodes.map(stringify).join(''), true)], []); } - as_objects() { + as_tree() { // if the first item is a comment we need to add another comment for effect.start if (this.nodes[0].type === 'comment') { this.nodes.unshift({ type: 'comment', data: undefined }); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js index 6125d5bc89..4825184d31 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js @@ -17,7 +17,7 @@ export function Fragment(node, context) { // Creates a new block which looks roughly like this: // ```js // // hoisted: - // const block_name = $.template(`...`); + // const block_name = $.from_html(`...`); // // // for the main block: // const id = block_name(); diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js index 5803c04947..1445ce3aa6 100644 --- a/packages/svelte/src/compiler/phases/3-transform/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/utils.js @@ -324,7 +324,7 @@ export function clean_nodes( } /** - * Infers the namespace for the children of a node that should be used when creating the `$.template(...)`. + * Infers the namespace for the children of a node that should be used when creating the fragment * @param {Namespace} namespace * @param {AST.SvelteNode} parent * @param {AST.SvelteNode[]} nodes diff --git a/packages/svelte/src/constants.js b/packages/svelte/src/constants.js index 2ecd4afee2..4e1785723c 100644 --- a/packages/svelte/src/constants.js +++ b/packages/svelte/src/constants.js @@ -17,6 +17,8 @@ export const TRANSITION_GLOBAL = 1 << 2; export const TEMPLATE_FRAGMENT = 1; export const TEMPLATE_USE_IMPORT_NODE = 1 << 1; +export const TEMPLATE_USE_SVG = 1 << 2; +export const TEMPLATE_USE_MATHML = 1 << 3; export const HYDRATION_START = '['; /** used to indicate that an `{:else}...` block was rendered */ diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 18f45bdd67..0b77ab1396 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -16,7 +16,9 @@ import { NAMESPACE_MATHML, NAMESPACE_SVG, TEMPLATE_FRAGMENT, - TEMPLATE_USE_IMPORT_NODE + TEMPLATE_USE_IMPORT_NODE, + TEMPLATE_USE_MATHML, + TEMPLATE_USE_SVG } from '../../../constants.js'; /** @@ -37,7 +39,7 @@ export function assign_nodes(start, end) { * @returns {() => Node | Node[]} */ /*#__NO_SIDE_EFFECTS__*/ -export function template(content, flags) { +export function from_html(content, flags) { var is_fragment = (flags & TEMPLATE_FRAGMENT) !== 0; var use_import_node = (flags & TEMPLATE_USE_IMPORT_NODE) !== 0; @@ -77,99 +79,6 @@ export function template(content, flags) { return clone; }; } -/** - * @param {TemplateStructure[]} structure - * @param {NAMESPACE_SVG | NAMESPACE_MATHML | undefined} [ns] - */ -function structure_to_fragment(structure, ns) { - var fragment = create_fragment(); - - for (var item of structure) { - if (typeof item === 'string') { - fragment.append(create_text(item)); - continue; - } - - // if `preserveComments === true`, comments are represented as `['// ']` - if (item === undefined || item[0][0] === '/') { - fragment.append(create_comment(item ? item[0].slice(3) : '')); - continue; - } - - const [name, attributes, ...children] = item; - - const namespace = name === 'svg' ? NAMESPACE_SVG : name === 'math' ? NAMESPACE_MATHML : ns; - - var element = create_element(name, namespace, attributes?.is); - - for (var key in attributes) { - set_attribute(element, key, attributes[key]); - } - - if (children.length > 0) { - var target = - element.tagName === 'TEMPLATE' - ? /** @type {HTMLTemplateElement} */ (element).content - : element; - - target.append( - structure_to_fragment(children, element.tagName === 'foreignObject' ? undefined : namespace) - ); - } - - fragment.append(element); - } - - return fragment; -} - -/** - * @param {Array} structure - * @param {number} flags - * @returns {() => Node | Node[]} - */ -/*#__NO_SIDE_EFFECTS__*/ -export function template_fn(structure, flags) { - var is_fragment = (flags & TEMPLATE_FRAGMENT) !== 0; - var use_import_node = (flags & TEMPLATE_USE_IMPORT_NODE) !== 0; - - /** @type {Node} */ - var node; - - return () => { - if (hydrating) { - assign_nodes(hydrate_node, null); - return hydrate_node; - } - - if (node === undefined) { - node = structure_to_fragment(structure); - if (!is_fragment) node = /** @type {Node} */ (get_first_child(node)); - } - - var clone = /** @type {TemplateNode} */ ( - use_import_node || is_firefox ? document.importNode(node, true) : node.cloneNode(true) - ); - - if (is_fragment) { - var start = /** @type {TemplateNode} */ (get_first_child(clone)); - var end = /** @type {TemplateNode} */ (clone.lastChild); - - assign_nodes(start, end); - } else { - assign_nodes(clone, clone); - } - - return clone; - }; -} - -/** - * @param {() => Element | DocumentFragment} fn - */ -export function with_script(fn) { - return () => run_scripts(fn()); -} /** * @param {string} content @@ -178,7 +87,7 @@ export function with_script(fn) { * @returns {() => Node | Node[]} */ /*#__NO_SIDE_EFFECTS__*/ -export function ns_template(content, flags, ns = 'svg') { +function from_namespace(content, flags, ns = 'svg') { /** * Whether or not the first item is a text/element node. If not, we need to * create an additional comment node to act as `effect.nodes.start` @@ -227,14 +136,78 @@ export function ns_template(content, flags, ns = 'svg') { } /** - * @param {Array} structure + * @param {string} content + * @param {number} flags + */ +/*#__NO_SIDE_EFFECTS__*/ +export function from_svg(content, flags) { + return from_namespace(content, flags, 'svg'); +} + +/** + * @param {string} content + * @param {number} flags + */ +/*#__NO_SIDE_EFFECTS__*/ +export function from_mathml(content, flags) { + return from_namespace(content, flags, 'math'); +} + +/** + * @param {TemplateStructure[]} structure + * @param {NAMESPACE_SVG | NAMESPACE_MATHML | undefined} [ns] + */ +function fragment_from_tree(structure, ns) { + var fragment = create_fragment(); + + for (var item of structure) { + if (typeof item === 'string') { + fragment.append(create_text(item)); + continue; + } + + // if `preserveComments === true`, comments are represented as `['// ']` + if (item === undefined || item[0][0] === '/') { + fragment.append(create_comment(item ? item[0].slice(3) : '')); + continue; + } + + const [name, attributes, ...children] = item; + + const namespace = name === 'svg' ? NAMESPACE_SVG : name === 'math' ? NAMESPACE_MATHML : ns; + + var element = create_element(name, namespace, attributes?.is); + + for (var key in attributes) { + set_attribute(element, key, attributes[key]); + } + + if (children.length > 0) { + var target = + element.tagName === 'TEMPLATE' + ? /** @type {HTMLTemplateElement} */ (element).content + : element; + + target.append( + fragment_from_tree(children, element.tagName === 'foreignObject' ? undefined : namespace) + ); + } + + fragment.append(element); + } + + return fragment; +} + +/** + * @param {TemplateStructure[]} structure * @param {number} flags - * @param {'svg' | 'math'} ns * @returns {() => Node | Node[]} */ /*#__NO_SIDE_EFFECTS__*/ -export function ns_template_fn(structure, flags, ns = 'svg') { +export function from_tree(structure, flags) { var is_fragment = (flags & TEMPLATE_FRAGMENT) !== 0; + var use_import_node = (flags & TEMPLATE_USE_IMPORT_NODE) !== 0; /** @type {Node} */ var node; @@ -246,11 +219,20 @@ export function ns_template_fn(structure, flags, ns = 'svg') { } if (node === undefined) { - node = structure_to_fragment(structure, ns === 'svg' ? NAMESPACE_SVG : NAMESPACE_MATHML); + const ns = + (flags & TEMPLATE_USE_SVG) !== 0 + ? NAMESPACE_SVG + : (flags & TEMPLATE_USE_MATHML) !== 0 + ? NAMESPACE_MATHML + : undefined; + + node = fragment_from_tree(structure, ns); if (!is_fragment) node = /** @type {Node} */ (get_first_child(node)); } - var clone = /** @type {TemplateNode} */ (node.cloneNode(true)); + var clone = /** @type {TemplateNode} */ ( + use_import_node || is_firefox ? document.importNode(node, true) : node.cloneNode(true) + ); if (is_fragment) { var start = /** @type {TemplateNode} */ (get_first_child(clone)); @@ -266,23 +248,10 @@ export function ns_template_fn(structure, flags, ns = 'svg') { } /** - * @param {string} content - * @param {number} flags - * @returns {() => Node | Node[]} - */ -/*#__NO_SIDE_EFFECTS__*/ -export function mathml_template(content, flags) { - return ns_template(content, flags, 'math'); -} - -/** - * @param {Array} structure - * @param {number} flags - * @returns {() => Node | Node[]} + * @param {() => Element | DocumentFragment} fn */ -/*#__NO_SIDE_EFFECTS__*/ -export function mathml_template_fn(structure, flags) { - return ns_template_fn(structure, flags, 'math'); +export function with_script(fn) { + return () => run_scripts(fn()); } /** diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index cfeb2bd0de..226d79a65f 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -88,12 +88,10 @@ export { export { append, comment, - ns_template, - ns_template_fn, - mathml_template, - mathml_template_fn, - template, - template_fn, + from_html, + from_mathml, + from_svg, + from_tree, text, props_id, with_script diff --git a/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/client/index.svelte.js index 3e5a12ed9d..9bb45ebf78 100644 --- a/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/client/index.svelte.js @@ -5,7 +5,7 @@ function increment(_, counter) { counter.count += 1; } -var root = $.template(` `, 1); +var root = $.from_html(` `, 1); export default function Await_block_scope($$anchor) { let counter = $.proxy({ count: 0 }); diff --git a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js index 390e86a351..ba3f4b155a 100644 --- a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js @@ -10,7 +10,7 @@ const snippet = ($$anchor) => { $.append($$anchor, text); }; -var root = $.template(` `, 1); +var root = $.from_html(` `, 1); export default function Bind_component_snippet($$anchor) { let value = $.state(''); diff --git a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js index 219db6ffd5..3a13fa7e15 100644 --- a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js +++ b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js @@ -1,7 +1,7 @@ import 'svelte/internal/disclose-version'; import * as $ from 'svelte/internal/client'; -var root = $.template(`
`, 3); +var root = $.from_html(`
`, 3); export default function Main($$anchor) { // needs to be a snapshot test because jsdom does auto-correct the attribute casing diff --git a/packages/svelte/tests/snapshot/samples/each-index-non-null/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/each-index-non-null/_expected/client/index.svelte.js index 3d46a679b8..804a7c26f1 100644 --- a/packages/svelte/tests/snapshot/samples/each-index-non-null/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/each-index-non-null/_expected/client/index.svelte.js @@ -2,7 +2,7 @@ import 'svelte/internal/disclose-version'; import 'svelte/internal/flags/legacy'; import * as $ from 'svelte/internal/client'; -var root_1 = $.template(`

`); +var root_1 = $.from_html(`

`); export default function Each_index_non_null($$anchor) { var fragment = $.comment(); diff --git a/packages/svelte/tests/snapshot/samples/functional-templating/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/functional-templating/_expected/client/index.svelte.js index 2c50ca9309..792d5421e1 100644 --- a/packages/svelte/tests/snapshot/samples/functional-templating/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/functional-templating/_expected/client/index.svelte.js @@ -2,7 +2,7 @@ import 'svelte/internal/disclose-version'; import 'svelte/internal/flags/legacy'; import * as $ from 'svelte/internal/client'; -var root = $.template_fn( +var root = $.from_tree( [ ['h1', null, 'hello'], ' ', diff --git a/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js index 899c126001..68fdaa4570 100644 --- a/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js @@ -2,7 +2,7 @@ import 'svelte/internal/disclose-version'; import 'svelte/internal/flags/legacy'; import * as $ from 'svelte/internal/client'; -var root = $.template(`

hello world

`); +var root = $.from_html(`

hello world

`); export default function Hello_world($$anchor) { var h1 = root(); diff --git a/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js index 3c8322500b..1fac1338c5 100644 --- a/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js @@ -2,7 +2,7 @@ import 'svelte/internal/disclose-version'; import 'svelte/internal/flags/legacy'; import * as $ from 'svelte/internal/client'; -var root = $.template(`

hello world

`); +var root = $.from_html(`

hello world

`); function Hmr($$anchor) { var h1 = root(); diff --git a/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/client/index.svelte.js index 21f6ed9680..b46acee82e 100644 --- a/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/client/index.svelte.js @@ -2,7 +2,7 @@ import 'svelte/internal/disclose-version'; import * as $ from 'svelte/internal/client'; var on_click = (_, count) => $.update(count); -var root = $.template(`

`, 1); +var root = $.from_html(`

`, 1); export default function Nullish_coallescence_omittance($$anchor) { let name = 'world'; diff --git a/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js index f661dbc01d..a351851875 100644 --- a/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js @@ -2,7 +2,7 @@ import 'svelte/internal/disclose-version'; import 'svelte/internal/flags/legacy'; import * as $ from 'svelte/internal/client'; -var root = $.template(`

`, 1); +var root = $.from_html(`

`, 1); export default function Purity($$anchor) { var fragment = root(); diff --git a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js index 541b56a407..8dc4bafb38 100644 --- a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js @@ -1,7 +1,7 @@ import 'svelte/internal/disclose-version'; import * as $ from 'svelte/internal/client'; -var root = $.template(`

we don't need to traverse these nodes

or

these

ones

these

trailing

nodes

can

be

completely

ignored

`, 3); +var root = $.from_html(`

we don't need to traverse these nodes

or

these

ones

these

trailing

nodes

can

be

completely

ignored

`, 3); export default function Skip_static_subtree($$anchor, $$props) { var fragment = root(); diff --git a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js index a67210e541..73f65ccb58 100644 --- a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js @@ -8,7 +8,7 @@ function reset(_, str, tpl) { $.set(tpl, ``); } -var root = $.template(` `, 1); +var root = $.from_html(` `, 1); export default function State_proxy_literal($$anchor) { let str = $.state(''); diff --git a/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/client/index.svelte.js index d520d1ef24..464435cb0a 100644 --- a/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/client/index.svelte.js @@ -1,7 +1,7 @@ import 'svelte/internal/disclose-version'; import * as $ from 'svelte/internal/client'; -var root = $.template(`

`); +var root = $.from_html(`

`); export default function Text_nodes_deriveds($$anchor) { let count1 = 0;