chore: tweak template generation logic (#13283)

* chore: tweak template generation logic

* collapse

* simplify
pull/13272/head
Rich Harris 2 months ago committed by GitHub
parent 2a1d1c1282
commit a88e2dc6fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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<string | Expression>;
readonly locations: SourceLocation[];
readonly metadata: {
namespace: Namespace;

@ -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 <expression>} first to ensure that scopes are in the correct order
const expression = b.thunk(/** @type {Expression} */ (context.visit(node.expression)));

@ -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(`<!--${node.data}-->`);
context.state.template.push(`<!--${node.data}-->`);
}

@ -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) {

@ -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,31 @@ export function Fragment(node, context) {
return b.block(body);
}
/**
* @param {Array<string | Expression>} items
*/
function join_template(items) {
let quasi = b.quasi('');
const template = b.template([quasi], []);
for (const item of items) {
if (typeof item === 'string') {
quasi.value.cooked += item;
} else {
template.expressions.push(item);
template.quasis.push((quasi = b.quasi('')));
}
}
for (const quasi of template.quasis) {
quasi.value.raw = sanitize_template_string(/** @type {string} */ (quasi.value.cooked));
}
quasi.tail = true;
return template;
}
/**
*
* @param {Namespace} namespace

@ -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(

@ -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));

@ -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));

@ -54,7 +54,7 @@ export function RegularElement(node, context) {
}
if (node.name === 'noscript') {
context.state.template.push_quasi('<noscript></noscript>');
context.state.template.push('<noscript></noscript>');
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<AST.Attribute | AST.SpreadAttribute>} */
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(`</${node.name}>`);
context.state.template.push(`</${node.name}>`);
}
}
@ -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,7 @@ 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}="`, value, '"');
} else {
state.init.push(update);
}

@ -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;

@ -10,7 +10,7 @@ import { build_attribute_value } from './shared/element.js';
*/
export function SlotElement(node, context) {
// <slot {a}>fallback</slot> --> $.slot($$slots.default, { get a() { .. } }, () => ...fallback);
context.state.template.push_quasi('<!>');
context.state.template.push('<!>');
/** @type {Property[]} */
const props = [];

@ -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<AST.Attribute | AST.SpreadAttribute>} */
const attributes = [];

@ -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'
? '<g><!></g>'
: '<div style="display: contents"><!></div>'
@ -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)));
}

@ -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);

Loading…
Cancel
Save