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 7225bcfc62..55be4dcb81 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 @@ -4,8 +4,6 @@ /** @import { Node } from './types.js' */ import { dev, locator } from '../../../../state.js'; import * as b from '../../../../utils/builders.js'; -import { template_to_functions } from './to-functions.js'; -import { template_to_string } from './to-string.js'; /** * @@ -60,8 +58,8 @@ function build_locations(nodes) { export function transform_template(state, namespace, flags) { const expression = state.options.templatingMode === 'functional' - ? template_to_functions(state.template.nodes) - : template_to_string(state.template.nodes); + ? state.template.as_objects() + : state.template.as_string(); let call = b.call( get_template_function(namespace, state), 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 3a706ee897..e3d64342f4 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 @@ -1,5 +1,10 @@ /** @import { AST } from '#compiler' */ /** @import { Node, Element } from './types'; */ +import { escape_html } from '../../../../../escaping.js'; +import { is_void } from '../../../../../utils.js'; +import * as b from '#compiler/builders'; +import fix_attribute_casing from './fix-attribute-casing.js'; +import { regex_starts_with_newline } from '../../../patterns.js'; export class Template { /** @@ -65,4 +70,94 @@ export class Template { set_prop(key, value) { /** @type {Element} */ (this.#element).attributes[key] = value; } + + as_string() { + return b.template([b.quasi(this.nodes.map(stringify).join(''), true)], []); + } + + as_objects() { + // if the first item is a comment we need to add another comment for effect.start + if (this.nodes[0].type === 'anchor') { + this.nodes.unshift({ type: 'anchor', data: undefined }); + } + + return b.array(this.nodes.map(objectify)); + } +} + +/** + * @param {Node} item + */ +function stringify(item) { + if (item.type === 'text') { + return item.nodes.map((node) => node.raw).join(''); + } + + if (item.type === 'anchor') { + return item.data ? `` : ''; + } + + let str = `<${item.name}`; + + for (const key in item.attributes) { + const value = item.attributes[key]; + + str += ` ${key}`; + if (value !== undefined) str += `="${escape_html(value, true)}"`; + } + + str += `>`; + str += item.children.map(stringify).join(''); + + if (!is_void(item.name)) { + str += `${item.name}>`; + } + + return str; +} + +/** @param {Node} item */ +function objectify(item) { + if (item.type === 'text') { + return b.literal(item.nodes.map((node) => node.data).join('')); + } + + if (item.type === 'anchor') { + return item.data ? b.array([b.literal(`// ${item.data}`)]) : null; + } + + const element = b.array([b.literal(item.name)]); + + const attributes = b.object([]); + + for (const key in item.attributes) { + const value = item.attributes[key]; + + attributes.properties.push( + b.prop( + 'init', + b.key(fix_attribute_casing(key)), + value === undefined ? b.void0 : b.literal(value) + ) + ); + } + + if (attributes.properties.length > 0 || item.children.length > 0) { + element.elements.push(attributes.properties.length > 0 ? attributes : b.null); + } + + if (item.children.length > 0) { + const children = item.children.map(objectify); + element.elements.push(...children); + + // special case — strip leading newline from `
` and `