chore: reuse common templates (#9601)

#9589 - add comment and space as reusable templates to save a few bytes. We can definitely take this idea further, but this is a base to iterate from.

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
pull/9615/head
Rich Harris 7 months ago committed by GitHub
parent d83bd7f7c1
commit 0283e50070
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: reuse common templates

@ -974,9 +974,6 @@ function create_block(parent, name, nodes, context) {
/** @type {import('estree').Statement | undefined} */
let close = undefined;
/** @type {import('estree').Identifier | undefined} */
let id = undefined;
/** @type {import('../types').ComponentClientTransformState} */
const state = {
...context.state,
@ -999,7 +996,7 @@ function create_block(parent, name, nodes, context) {
if (is_single_element) {
const element = /** @type {import('#compiler').RegularElement} */ (trimmed[0]);
id = b.id(context.state.scope.generate(element.name));
const id = b.id(context.state.scope.generate(element.name));
context.visit(element, {
...state,
@ -1014,7 +1011,7 @@ function create_block(parent, name, nodes, context) {
body.push(
b.var(
id.name,
id,
b.call(
'$.open',
b.id('$$anchor'),
@ -1028,15 +1025,30 @@ function create_block(parent, name, nodes, context) {
} else if (is_single_child_not_needing_template) {
context.visit(trimmed[0], state);
body.push(...state.init);
} else {
id = b.id(context.state.scope.generate('fragment'));
} else if (trimmed.length > 0) {
const id = b.id(context.state.scope.generate('fragment'));
const node_id = b.id(context.state.scope.generate('node'));
process_children(trimmed, b.call('$.child_frag', id), {
process_children(trimmed, node_id, {
...context,
state
});
if (state.template.length > 0) {
const template = state.template[0];
if (state.template.length === 1 && (template === ' ' || template === '<!>')) {
if (template === ' ') {
body.push(b.var(node_id, b.call('$.space', b.id('$$anchor'))), ...state.init);
close = b.stmt(b.call('$.close', b.id('$$anchor'), node_id));
} else {
body.push(
b.var(id, b.call('$.comment', b.id('$$anchor'))),
b.var(node_id, b.call('$.child_frag', id)),
...state.init
);
close = b.stmt(b.call('$.close_frag', b.id('$$anchor'), id));
}
} else {
const callee = namespace === 'svg' ? '$.svg_template' : '$.template';
state.hoisted.push(
@ -1048,7 +1060,7 @@ function create_block(parent, name, nodes, context) {
body.push(
b.var(
id.name,
id,
b.call(
'$.open_frag',
b.id('$$anchor'),
@ -1056,12 +1068,14 @@ function create_block(parent, name, nodes, context) {
template_name
)
),
b.var(node_id, b.call('$.child_frag', id)),
...state.init
);
close = b.stmt(b.call('$.close_frag', b.id('$$anchor'), id));
} else {
body.push(...state.init);
}
} else {
body.push(...state.init);
}
if (state.update.length > 0 || state.update_effects.length > 0) {
@ -1359,13 +1373,11 @@ function process_children(nodes, parent, { visit, state }) {
state.template.push(' ');
const name = state.scope.generate('text');
state.init.push(b.var(name, expression));
const text_id = get_node_id(expression, state, 'text');
const singular = b.stmt(
b.call(
'$.text_effect',
b.id(name),
text_id,
b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression)))
)
);
@ -1378,7 +1390,7 @@ function process_children(nodes, parent, { visit, state }) {
grouped: b.stmt(
b.call(
'$.text',
b.id(name),
text_id,
/** @type {import('estree').Expression} */ (visit(node.expression))
)
)
@ -1388,7 +1400,7 @@ function process_children(nodes, parent, { visit, state }) {
b.stmt(
b.assignment(
'=',
b.id(`${name}.nodeValue`),
b.member(text_id, b.id('nodeValue')),
b.call(
'$.stringify',
/** @type {import('estree').Expression} */ (visit(node.expression))
@ -1403,17 +1415,16 @@ function process_children(nodes, parent, { visit, state }) {
state.template.push(' ');
const name = state.scope.generate('text');
const text_id = get_node_id(expression, state, 'text');
const contains_call_expression = sequence.some(
(n) => n.type === 'ExpressionTag' && n.metadata.contains_call_expression
);
state.init.push(b.var(name, expression));
const assignment = serialize_template_literal(sequence, visit, state)[1];
const init = b.stmt(b.assignment('=', b.id(`${name}.nodeValue`), assignment));
const init = b.stmt(b.assignment('=', b.member(text_id, b.id('nodeValue')), assignment));
const singular = b.stmt(
b.call(
'$.text_effect',
b.id(name),
text_id,
b.thunk(serialize_template_literal(sequence, visit, state)[1])
)
);
@ -1426,13 +1437,13 @@ function process_children(nodes, parent, { visit, state }) {
) {
state.update.push({
singular,
grouped: b.stmt(b.call('$.text', b.id(name), assignment))
grouped: b.stmt(b.call('$.text', text_id, assignment))
});
} else {
state.init.push(init);
}
expression = b.call('$.sibling', b.id(name));
expression = b.call('$.sibling', text_id);
}
for (let i = 0; i < nodes.length; i += 1) {
@ -1456,9 +1467,6 @@ function process_children(nodes, parent, { visit, state }) {
// get hoisted inside clean_nodes?
visit(node, state);
} else {
const name = state.scope.generate(node.type === 'RegularElement' ? node.name : 'node');
const id = b.id(name);
// Optimization path for each blocks. If the parent isn't a fragment and it only has
// a single child, then we can classify the block as being "controlled".
if (
@ -1471,7 +1479,12 @@ function process_children(nodes, parent, { visit, state }) {
node.metadata.is_controlled = true;
visit(node, state);
} else {
state.init.push(b.var(name, expression));
const id = get_node_id(
expression,
state,
node.type === 'RegularElement' ? node.name : 'node'
);
expression = b.call('$.sibling', id);
visit(node, {
@ -1488,6 +1501,22 @@ function process_children(nodes, parent, { visit, state }) {
}
}
/**
* @param {import('estree').Expression} expression
* @param {import('../types.js').ComponentClientTransformState} state
* @param {string} name
*/
function get_node_id(expression, state, name) {
let id = expression;
if (id.type !== 'Identifier') {
id = b.id(state.scope.generate(name));
state.init.push(b.var(id, expression));
}
return id;
}
/**
* @param {true | Array<import('#compiler').Text | import('#compiler').ExpressionTag>} attribute_value
* @param {import('../types').ComponentContext} context

@ -2,6 +2,7 @@ import { DEV } from 'esm-env';
import {
append_child,
child,
child_frag,
clone_node,
create_element,
init_operations,
@ -133,7 +134,7 @@ export function svg_replace(node) {
* @param {boolean} is_fragment
* @param {boolean} use_clone_node
* @param {null | Text | Comment | Element} anchor
* @param {() => Element} [template_element_fn]
* @param {() => Node} [template_element_fn]
* @returns {Element | DocumentFragment | Node[]}
*/
function open_template(is_fragment, use_clone_node, anchor, template_element_fn) {
@ -156,7 +157,7 @@ function open_template(is_fragment, use_clone_node, anchor, template_element_fn)
/**
* @param {null | Text | Comment | Element} anchor
* @param {boolean} use_clone_node
* @param {() => Element} [template_element_fn]
* @param {() => Node} [template_element_fn]
* @returns {Element | DocumentFragment | Node[]}
*/
/*#__NO_SIDE_EFFECTS__*/
@ -167,7 +168,7 @@ export function open(anchor, use_clone_node, template_element_fn) {
/**
* @param {null | Text | Comment | Element} anchor
* @param {boolean} use_clone_node
* @param {() => Element} [template_element_fn]
* @param {() => Node} [template_element_fn]
* @returns {Element | DocumentFragment | Node[]}
*/
/*#__NO_SIDE_EFFECTS__*/
@ -175,6 +176,25 @@ export function open_frag(anchor, use_clone_node, template_element_fn) {
return open_template(true, use_clone_node, anchor, template_element_fn);
}
const space_template = template(' ', false);
const comment_template = template('<!>', true);
/**
* @param {null | Text | Comment | Element} anchor
*/
/*#__NO_SIDE_EFFECTS__*/
export function space(anchor) {
return open(anchor, true, space_template);
}
/**
* @param {null | Text | Comment | Element} anchor
*/
/*#__NO_SIDE_EFFECTS__*/
export function comment(anchor) {
return open_frag(anchor, true, comment_template);
}
/**
* @param {Element | Text} dom
* @param {boolean} is_fragment

@ -3,9 +3,6 @@
import "svelte/internal/disclose-version";
import * as $ from "svelte/internal";
var Button_default = $.template(` `, true);
var frag = $.template(`<!>`, true);
export default function Function_prop_no_getter($$anchor, $$props) {
$.push($$props, true);
@ -16,7 +13,7 @@ export default function Function_prop_no_getter($$anchor, $$props) {
}
/* Init */
var fragment = $.open_frag($$anchor, true, frag);
var fragment = $.comment($$anchor);
var node = $.child_frag(fragment);
Button(node, {
@ -24,12 +21,11 @@ export default function Function_prop_no_getter($$anchor, $$props) {
onmouseup,
children: ($$anchor, $$slotProps) => {
/* Init */
var fragment_1 = $.open_frag($$anchor, true, Button_default);
var text = $.child_frag(fragment_1);
var node_1 = $.space($$anchor);
/* Update */
$.text_effect(text, () => `clicks: ${$.stringify($.get(count))}`);
$.close_frag($$anchor, fragment_1);
$.text_effect(node_1, () => `clicks: ${$.stringify($.get(count))}`);
$.close($$anchor, node_1);
}
});

Loading…
Cancel
Save