From f184ceb6fe196c85eb8f8a7c6a2756765d06cb6c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 29 May 2026 15:38:58 -0400 Subject: [PATCH] simplify --- .../client/transform-template/index.js | 72 ++++++------------- .../3-transform/client/visitors/Fragment.js | 8 +-- .../client/visitors/RegularElement.js | 10 ++- 3 files changed, 33 insertions(+), 57 deletions(-) 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 78d3e85103..161e742871 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 @@ -1,4 +1,4 @@ -/** @import { Expression, Identifier } from 'estree' */ +/** @import { TemplateLiteral } from 'estree' */ /** @import { Namespace } from '#compiler' */ /** @import { ComponentClientTransformState } from '../types.js' */ /** @import { Node } from './types.js' */ @@ -32,14 +32,29 @@ function build_locations(nodes) { /** * @param {ComponentClientTransformState} state + * @param {string} name * @param {Namespace} namespace * @param {number} [flags] */ -export function transform_template(state, namespace, flags = 0) { +export function transform_template(state, name, namespace, flags = 0) { const tree = state.options.fragments === 'tree'; const expression = tree ? state.template.as_tree() : state.template.as_html(); + const key = + tree || dev + ? null + : get_template_key( + /** @type {TemplateLiteral} */ (expression), + state.metadata.namespace, + flags + ); + + if (key !== null) { + const existing = state.templates.get(key); + if (existing !== undefined) return existing; + } + if (tree) { if (namespace === 'svg') flags |= TEMPLATE_USE_SVG; if (namespace === 'mathml') flags |= TEMPLATE_USE_MATHML; @@ -64,27 +79,8 @@ export function transform_template(state, namespace, flags = 0) { ); } - return call; -} - -/** - * Hoists a template factory (`$.from_html(...)` etc.) to module scope, reusing an - * existing identical template within the same component rather than emitting a duplicate. - * @param {ComponentClientTransformState} state - * @param {string} name - the base name for a newly hoisted template - * @param {Expression} template - * @returns {Identifier} - */ -export function hoist_template(state, name, template) { - const key = get_template_key(template); - - if (key !== null) { - const existing = state.templates.get(key); - if (existing !== undefined) return existing; - } - const id = state.scope.root.unique(name); - state.hoisted.push(b.var(id, template)); + state.hoisted.push(b.var(id, call)); if (key !== null) { state.templates.set(key, id); @@ -98,33 +94,11 @@ export function hoist_template(state, name, template) { * `$.from_html`/`from_svg`/`from_mathml` factories with literal arguments - or `null` * for anything else. Dev-mode templates are wrapped in `$.add_locations(...)`, which * embeds per-call-site locations, so they never produce a key and are never shared. - * @param {Expression} template + * @param {TemplateLiteral} template + * @param {Namespace} namespace + * @param {number} flags * @returns {string | null} */ -function get_template_key(template) { - if (template.type !== 'CallExpression' || template.callee.type !== 'Identifier') { - return null; - } - - const name = template.callee.name; - - if (name !== '$.from_html' && name !== '$.from_svg' && name !== '$.from_mathml') { - return null; - } - - const [content, flags] = template.arguments; - - if ( - content?.type !== 'TemplateLiteral' || - content.expressions.length !== 0 || - content.quasis.length !== 1 - ) { - return null; - } - - if (flags !== undefined && (flags.type !== 'Literal' || typeof flags.value !== 'number')) { - return null; - } - - return `${name} ${flags ? flags.value : 0} ${content.quasis[0].value.raw}`; +function get_template_key(template, namespace, flags) { + return `${namespace} ${flags} ${template.quasis[0].value.raw}`; } 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 8df4ceb3a7..379e779248 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 @@ -4,7 +4,7 @@ import * as b from '#compiler/builders'; import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../../../constants.js'; import { clean_nodes, infer_namespace } from '../../utils.js'; -import { hoist_template, transform_template } from '../transform-template/index.js'; +import { transform_template } from '../transform-template/index.js'; import { Template } from '../transform-template/template.js'; import { process_children } from './shared/fragment.js'; import { build_render_statement, Memoizer } from './shared/utils.js'; @@ -95,8 +95,7 @@ export function Fragment(node, context) { let flags = state.template.needs_import_node ? TEMPLATE_USE_IMPORT_NODE : undefined; - const template = transform_template(state, namespace, flags); - const template_name = hoist_template(state, 'root', template); + const template_name = transform_template(state, 'root', namespace, flags); state.init.unshift(b.var(id, b.call(template_name))); close = b.stmt(b.call('$.append', b.id('$$anchor'), id)); @@ -146,8 +145,7 @@ export function Fragment(node, context) { // special case — we can use `$.comment` instead of creating a unique template state.init.unshift(b.var(id, b.call('$.comment'))); } else { - const template = transform_template(state, namespace, flags); - const template_name = hoist_template(state, 'root', template); + const template_name = transform_template(state, 'root', namespace, flags); state.init.unshift(b.var(id, b.call(template_name))); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index c1bcbe1dbb..34fb1e6e4d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -30,7 +30,7 @@ import { process_children, is_static_element } from './shared/fragment.js'; import { build_render_statement, build_template_chunk, Memoizer } from './shared/utils.js'; import { visit_event_attribute } from './shared/events.js'; import { Template } from '../transform-template/template.js'; -import { hoist_template, transform_template } from '../transform-template/index.js'; +import { transform_template } from '../transform-template/index.js'; import { TEMPLATE_FRAGMENT } from '../../../../../constants.js'; /** @@ -398,8 +398,12 @@ export function RegularElement(node, context) { ); // Transform the template to $.from_html(...) and hoist it (deduplicating identical templates) - const template = transform_template(select_state, metadata.namespace, TEMPLATE_FRAGMENT); - const template_name = hoist_template(context.state, `${name}_content`, template); + const template_name = transform_template( + select_state, + `${name}_content`, + metadata.namespace, + TEMPLATE_FRAGMENT + ); // Build the rich content function body // The anchor is the child of the element (a hydration marker during hydration)