From 3f7493f0b9cc7a66e49f051f7f0f402ca7cf7113 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 17 Sep 2024 06:49:23 -0400 Subject: [PATCH 1/3] chore: tweak template generation logic --- .../phases/3-transform/client/types.d.ts | 5 +- .../3-transform/client/visitors/AwaitBlock.js | 2 +- .../3-transform/client/visitors/Comment.js | 2 +- .../3-transform/client/visitors/EachBlock.js | 2 +- .../3-transform/client/visitors/Fragment.js | 68 +++++++++---------- .../3-transform/client/visitors/HtmlTag.js | 2 +- .../3-transform/client/visitors/IfBlock.js | 2 +- .../3-transform/client/visitors/KeyBlock.js | 2 +- .../client/visitors/RegularElement.js | 18 ++--- .../3-transform/client/visitors/RenderTag.js | 2 +- .../client/visitors/SlotElement.js | 2 +- .../client/visitors/SvelteElement.js | 2 +- .../client/visitors/shared/component.js | 4 +- .../client/visitors/shared/fragment.js | 4 +- 14 files changed, 56 insertions(+), 61 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts index cf41afc0d5f9..0b54de538419 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts +++ b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts @@ -54,10 +54,7 @@ export interface ComponentClientTransformState extends ClientTransformState { /** Stuff that happens after the render effect (control blocks, dynamic elements, bindings, actions, etc) */ readonly after_update: Statement[]; /** The HTML template string */ - readonly template: { - push_quasi: (q: string) => void; - push_expression: (e: Expression) => void; - }; + readonly template: Array; readonly locations: SourceLocation[]; readonly metadata: { namespace: Namespace; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js index 9342cc756864..62c61151bb73 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js @@ -9,7 +9,7 @@ import { create_derived_block_argument } from '../utils.js'; * @param {ComponentContext} context */ export function AwaitBlock(node, context) { - context.state.template.push_quasi(''); + context.state.template.push(''); // Visit {#await } first to ensure that scopes are in the correct order const expression = b.thunk(/** @type {Expression} */ (context.visit(node.expression))); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Comment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Comment.js index 4c0bee53d376..24011e62aabd 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Comment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Comment.js @@ -7,5 +7,5 @@ */ export function Comment(node, context) { // We'll only get here if comments are not filtered out, which they are unless preserveComments is true - context.state.template.push_quasi(``); + context.state.template.push(``); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js index 28ace3af93e6..495a33e3974d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js @@ -32,7 +32,7 @@ export function EachBlock(node, context) { ); if (!each_node_meta.is_controlled) { - context.state.template.push_quasi(''); + context.state.template.push(''); } if (each_node_meta.array_name !== null) { 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 705db02b7d56..ba144043c18f 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 @@ -5,6 +5,7 @@ import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../../../constants.js'; import { dev } from '../../../../state.js'; import * as b from '../../../../utils/builders.js'; +import { sanitize_template_string } from '../../../../utils/sanitize_template_string.js'; import { clean_nodes, infer_namespace } from '../../utils.js'; import { process_children } from './shared/fragment.js'; import { build_render_statement } from './shared/utils.js'; @@ -57,11 +58,6 @@ export function Fragment(node, context) { /** @type {Statement | undefined} */ let close = undefined; - /** @type {string[]} */ - const quasi = []; - /** @type {Expression[]} */ - const expressions = []; - /** @type {ComponentClientTransformState} */ const state = { ...context.state, @@ -69,22 +65,7 @@ export function Fragment(node, context) { init: [], update: [], after_update: [], - template: { - push_quasi: (/** @type {string} */ quasi_to_add) => { - if (quasi.length === 0) { - quasi.push(quasi_to_add); - return; - } - quasi[quasi.length - 1] = quasi[quasi.length - 1].concat(quasi_to_add); - }, - push_expression: (/** @type {Expression} */ expression_to_add) => { - if (quasi.length === 0) { - quasi.push(''); - } - expressions.push(expression_to_add); - quasi.push(''); - } - }, + template: [], locations: [], transform: { ...context.state.transform }, metadata: { @@ -135,12 +116,7 @@ export function Fragment(node, context) { }); /** @type {Expression[]} */ - const args = [ - b.template( - quasi.map((q) => b.quasi(q, true)), - expressions - ) - ]; + const args = [join_template(state.template)]; if (state.metadata.context.template_needs_import_node) { args.push(b.literal(TEMPLATE_USE_IMPORT_NODE)); @@ -195,17 +171,11 @@ export function Fragment(node, context) { flags |= TEMPLATE_USE_IMPORT_NODE; } - if (quasi.length === 1 && quasi[0] === '') { + if (state.template.length === 1 && state.template[0] === '') { // special case — we can use `$.comment` instead of creating a unique template body.push(b.var(id, b.call('$.comment'))); } else { - add_template(template_name, [ - b.template( - quasi.map((q) => b.quasi(q, true)), - expressions - ), - b.literal(flags) - ]); + add_template(template_name, [join_template(state.template), b.literal(flags)]); body.push(b.var(id, b.call(template_name))); } @@ -235,6 +205,34 @@ export function Fragment(node, context) { return b.block(body); } +/** + * @param {Array} template + */ +function join_template(template) { + let quasi = b.quasi(''); + const quasis = [quasi]; + + /** @type {Expression[]} */ + const expressions = []; + + for (const item of template) { + if (typeof item === 'string') { + quasi.value.cooked += item; + } else { + expressions.push(item); + quasis.push((quasi = b.quasi(''))); + } + } + + for (const quasi of quasis) { + quasi.value.raw = sanitize_template_string(/** @type {string} */ (quasi.value.cooked)); + } + + quasi.tail = true; + + return b.template(quasis, expressions); +} + /** * * @param {Namespace} namespace diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/HtmlTag.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/HtmlTag.js index 1c1353f95963..32439879de38 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/HtmlTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/HtmlTag.js @@ -9,7 +9,7 @@ import * as b from '../../../../utils/builders.js'; * @param {ComponentContext} context */ export function HtmlTag(node, context) { - context.state.template.push_quasi(''); + context.state.template.push(''); // push into init, so that bindings run afterwards, which might trigger another run and override hydration context.state.init.push( diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js index 6f8894c2c83b..0aa6e5d24a53 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js @@ -8,7 +8,7 @@ import * as b from '../../../../utils/builders.js'; * @param {ComponentContext} context */ export function IfBlock(node, context) { - context.state.template.push_quasi(''); + context.state.template.push(''); const consequent = /** @type {BlockStatement} */ (context.visit(node.consequent)); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js index 14b3d15539b1..a013827f60bd 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js @@ -8,7 +8,7 @@ import * as b from '../../../../utils/builders.js'; * @param {ComponentContext} context */ export function KeyBlock(node, context) { - context.state.template.push_quasi(''); + context.state.template.push(''); const key = /** @type {Expression} */ (context.visit(node.expression)); const body = /** @type {Expression} */ (context.visit(node.fragment)); 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 816667542ba9..603abb8ceaf8 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 @@ -54,7 +54,7 @@ export function RegularElement(node, context) { } if (node.name === 'noscript') { - context.state.template.push_quasi(''); + context.state.template.push(''); return; } @@ -68,7 +68,7 @@ export function RegularElement(node, context) { namespace: determine_namespace_for_children(node, context.state.metadata.namespace) }; - context.state.template.push_quasi(`<${node.name}`); + context.state.template.push(`<${node.name}`); /** @type {Array} */ const attributes = []; @@ -242,7 +242,7 @@ export function RegularElement(node, context) { const value = is_text_attribute(attribute) ? attribute.value[0].data : true; if (name !== 'class' || value) { - context.state.template.push_quasi( + context.state.template.push( ` ${attribute.name}${ is_boolean_attribute(name) && value === true ? '' @@ -279,7 +279,7 @@ export function RegularElement(node, context) { context.state.after_update.push(b.stmt(b.call('$.replay_events', node_id))); } - context.state.template.push_quasi('>'); + context.state.template.push('>'); /** @type {SourceLocation[]} */ const child_locations = []; @@ -384,7 +384,7 @@ export function RegularElement(node, context) { } if (!is_void(node.name)) { - context.state.template.push_quasi(``); + context.state.template.push(``); } } @@ -472,7 +472,7 @@ function build_element_spread_attributes( value.type === 'Literal' && context.state.metadata.namespace === 'html' ) { - context.state.template.push_quasi(` is="${escape_html(value.value, true)}"`); + context.state.template.push(` is="${escape_html(value.value, true)}"`); continue; } @@ -630,9 +630,9 @@ function build_element_attribute_update_assignment(element, node_id, attribute, return true; } else { if (inlinable_expression) { - context.state.template.push_quasi(` ${name}="`); - context.state.template.push_expression(value); - context.state.template.push_quasi('"'); + context.state.template.push(` ${name}="`); + context.state.template.push(value); + context.state.template.push('"'); } else { state.init.push(update); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js index b33e16198f83..7da987f6cc4d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js @@ -9,7 +9,7 @@ import * as b from '../../../../utils/builders.js'; * @param {ComponentContext} context */ export function RenderTag(node, context) { - context.state.template.push_quasi(''); + context.state.template.push(''); const callee = unwrap_optional(node.expression).callee; const raw_args = unwrap_optional(node.expression).arguments; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js index 96bf77de460d..9a2240a4d6f5 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js @@ -10,7 +10,7 @@ import { build_attribute_value } from './shared/element.js'; */ export function SlotElement(node, context) { // fallback --> $.slot($$slots.default, { get a() { .. } }, () => ...fallback); - context.state.template.push_quasi(''); + context.state.template.push(''); /** @type {Property[]} */ const props = []; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js index ab79980f7cc8..81314d67dd3f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js @@ -21,7 +21,7 @@ import { build_render_statement, build_update } from './shared/utils.js'; * @param {ComponentContext} context */ export function SvelteElement(node, context) { - context.state.template.push_quasi(``); + context.state.template.push(``); /** @type {Array} */ const attributes = []; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js index 3b411b15b07e..03d873ba78b5 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js @@ -357,7 +357,7 @@ export function build_component(node, component_name, context, anchor = context. } if (Object.keys(custom_css_props).length > 0) { - context.state.template.push_quasi( + context.state.template.push( context.state.metadata.namespace === 'svg' ? '' : '
' @@ -369,7 +369,7 @@ export function build_component(node, component_name, context, anchor = context. b.stmt(b.call('$.reset', anchor)) ); } else { - context.state.template.push_quasi(''); + context.state.template.push(''); statements.push(b.stmt(fn(anchor))); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js index 141e283ea87f..4008dbdfa3cb 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js @@ -62,11 +62,11 @@ export function process_children(nodes, initial, is_element, { visit, state }) { function flush_sequence(sequence) { if (sequence.every((node) => node.type === 'Text')) { skipped += 1; - state.template.push_quasi(sequence.map((node) => node.raw).join('')); + state.template.push(sequence.map((node) => node.raw).join('')); return; } - state.template.push_quasi(' '); + state.template.push(' '); const { has_state, has_call, value } = build_template_literal(sequence, visit, state); From e9ce4de2ed2a020e47cef94eb72b9e873ac5c2b3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 17 Sep 2024 06:55:20 -0400 Subject: [PATCH 2/3] collapse --- .../phases/3-transform/client/visitors/RegularElement.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 603abb8ceaf8..d004da04110b 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 @@ -630,9 +630,7 @@ function build_element_attribute_update_assignment(element, node_id, attribute, return true; } else { if (inlinable_expression) { - context.state.template.push(` ${name}="`); - context.state.template.push(value); - context.state.template.push('"'); + context.state.template.push(` ${name}="`, value, '"'); } else { state.init.push(update); } From f107c768337717992d1bd9ed8886c4c1e6edd44b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 17 Sep 2024 07:01:23 -0400 Subject: [PATCH 3/3] simplify --- .../3-transform/client/visitors/Fragment.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) 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 ba144043c18f..08e06cc714d4 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 @@ -206,31 +206,28 @@ export function Fragment(node, context) { } /** - * @param {Array} template + * @param {Array} items */ -function join_template(template) { +function join_template(items) { let quasi = b.quasi(''); - const quasis = [quasi]; + const template = b.template([quasi], []); - /** @type {Expression[]} */ - const expressions = []; - - for (const item of template) { + for (const item of items) { if (typeof item === 'string') { quasi.value.cooked += item; } else { - expressions.push(item); - quasis.push((quasi = b.quasi(''))); + template.expressions.push(item); + template.quasis.push((quasi = b.quasi(''))); } } - for (const quasi of quasis) { + for (const quasi of template.quasis) { quasi.value.raw = sanitize_template_string(/** @type {string} */ (quasi.value.cooked)); } quasi.tail = true; - return b.template(quasis, expressions); + return template; } /**