Skip to content

Commit 0283e50

Browse files
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 <[email protected]> Co-authored-by: Simon Holthausen <[email protected]>
1 parent d83bd7f commit 0283e50

File tree

4 files changed

+89
-39
lines changed

4 files changed

+89
-39
lines changed

.changeset/few-mugs-fail.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
chore: reuse common templates

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -974,9 +974,6 @@ function create_block(parent, name, nodes, context) {
974974
/** @type {import('estree').Statement | undefined} */
975975
let close = undefined;
976976

977-
/** @type {import('estree').Identifier | undefined} */
978-
let id = undefined;
979-
980977
/** @type {import('../types').ComponentClientTransformState} */
981978
const state = {
982979
...context.state,
@@ -999,7 +996,7 @@ function create_block(parent, name, nodes, context) {
999996
if (is_single_element) {
1000997
const element = /** @type {import('#compiler').RegularElement} */ (trimmed[0]);
1001998

1002-
id = b.id(context.state.scope.generate(element.name));
999+
const id = b.id(context.state.scope.generate(element.name));
10031000

10041001
context.visit(element, {
10051002
...state,
@@ -1014,7 +1011,7 @@ function create_block(parent, name, nodes, context) {
10141011

10151012
body.push(
10161013
b.var(
1017-
id.name,
1014+
id,
10181015
b.call(
10191016
'$.open',
10201017
b.id('$$anchor'),
@@ -1028,15 +1025,30 @@ function create_block(parent, name, nodes, context) {
10281025
} else if (is_single_child_not_needing_template) {
10291026
context.visit(trimmed[0], state);
10301027
body.push(...state.init);
1031-
} else {
1032-
id = b.id(context.state.scope.generate('fragment'));
1028+
} else if (trimmed.length > 0) {
1029+
const id = b.id(context.state.scope.generate('fragment'));
1030+
const node_id = b.id(context.state.scope.generate('node'));
10331031

1034-
process_children(trimmed, b.call('$.child_frag', id), {
1032+
process_children(trimmed, node_id, {
10351033
...context,
10361034
state
10371035
});
10381036

1039-
if (state.template.length > 0) {
1037+
const template = state.template[0];
1038+
1039+
if (state.template.length === 1 && (template === ' ' || template === '<!>')) {
1040+
if (template === ' ') {
1041+
body.push(b.var(node_id, b.call('$.space', b.id('$$anchor'))), ...state.init);
1042+
close = b.stmt(b.call('$.close', b.id('$$anchor'), node_id));
1043+
} else {
1044+
body.push(
1045+
b.var(id, b.call('$.comment', b.id('$$anchor'))),
1046+
b.var(node_id, b.call('$.child_frag', id)),
1047+
...state.init
1048+
);
1049+
close = b.stmt(b.call('$.close_frag', b.id('$$anchor'), id));
1050+
}
1051+
} else {
10401052
const callee = namespace === 'svg' ? '$.svg_template' : '$.template';
10411053

10421054
state.hoisted.push(
@@ -1048,20 +1060,22 @@ function create_block(parent, name, nodes, context) {
10481060

10491061
body.push(
10501062
b.var(
1051-
id.name,
1063+
id,
10521064
b.call(
10531065
'$.open_frag',
10541066
b.id('$$anchor'),
10551067
b.literal(!state.metadata.template_needs_import_node),
10561068
template_name
10571069
)
10581070
),
1071+
b.var(node_id, b.call('$.child_frag', id)),
10591072
...state.init
10601073
);
1074+
10611075
close = b.stmt(b.call('$.close_frag', b.id('$$anchor'), id));
1062-
} else {
1063-
body.push(...state.init);
10641076
}
1077+
} else {
1078+
body.push(...state.init);
10651079
}
10661080

10671081
if (state.update.length > 0 || state.update_effects.length > 0) {
@@ -1359,13 +1373,11 @@ function process_children(nodes, parent, { visit, state }) {
13591373

13601374
state.template.push(' ');
13611375

1362-
const name = state.scope.generate('text');
1363-
state.init.push(b.var(name, expression));
1364-
1376+
const text_id = get_node_id(expression, state, 'text');
13651377
const singular = b.stmt(
13661378
b.call(
13671379
'$.text_effect',
1368-
b.id(name),
1380+
text_id,
13691381
b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression)))
13701382
)
13711383
);
@@ -1378,7 +1390,7 @@ function process_children(nodes, parent, { visit, state }) {
13781390
grouped: b.stmt(
13791391
b.call(
13801392
'$.text',
1381-
b.id(name),
1393+
text_id,
13821394
/** @type {import('estree').Expression} */ (visit(node.expression))
13831395
)
13841396
)
@@ -1388,7 +1400,7 @@ function process_children(nodes, parent, { visit, state }) {
13881400
b.stmt(
13891401
b.assignment(
13901402
'=',
1391-
b.id(`${name}.nodeValue`),
1403+
b.member(text_id, b.id('nodeValue')),
13921404
b.call(
13931405
'$.stringify',
13941406
/** @type {import('estree').Expression} */ (visit(node.expression))
@@ -1403,17 +1415,16 @@ function process_children(nodes, parent, { visit, state }) {
14031415

14041416
state.template.push(' ');
14051417

1406-
const name = state.scope.generate('text');
1418+
const text_id = get_node_id(expression, state, 'text');
14071419
const contains_call_expression = sequence.some(
14081420
(n) => n.type === 'ExpressionTag' && n.metadata.contains_call_expression
14091421
);
1410-
state.init.push(b.var(name, expression));
14111422
const assignment = serialize_template_literal(sequence, visit, state)[1];
1412-
const init = b.stmt(b.assignment('=', b.id(`${name}.nodeValue`), assignment));
1423+
const init = b.stmt(b.assignment('=', b.member(text_id, b.id('nodeValue')), assignment));
14131424
const singular = b.stmt(
14141425
b.call(
14151426
'$.text_effect',
1416-
b.id(name),
1427+
text_id,
14171428
b.thunk(serialize_template_literal(sequence, visit, state)[1])
14181429
)
14191430
);
@@ -1426,13 +1437,13 @@ function process_children(nodes, parent, { visit, state }) {
14261437
) {
14271438
state.update.push({
14281439
singular,
1429-
grouped: b.stmt(b.call('$.text', b.id(name), assignment))
1440+
grouped: b.stmt(b.call('$.text', text_id, assignment))
14301441
});
14311442
} else {
14321443
state.init.push(init);
14331444
}
14341445

1435-
expression = b.call('$.sibling', b.id(name));
1446+
expression = b.call('$.sibling', text_id);
14361447
}
14371448

14381449
for (let i = 0; i < nodes.length; i += 1) {
@@ -1456,9 +1467,6 @@ function process_children(nodes, parent, { visit, state }) {
14561467
// get hoisted inside clean_nodes?
14571468
visit(node, state);
14581469
} else {
1459-
const name = state.scope.generate(node.type === 'RegularElement' ? node.name : 'node');
1460-
const id = b.id(name);
1461-
14621470
// Optimization path for each blocks. If the parent isn't a fragment and it only has
14631471
// a single child, then we can classify the block as being "controlled".
14641472
if (
@@ -1471,7 +1479,12 @@ function process_children(nodes, parent, { visit, state }) {
14711479
node.metadata.is_controlled = true;
14721480
visit(node, state);
14731481
} else {
1474-
state.init.push(b.var(name, expression));
1482+
const id = get_node_id(
1483+
expression,
1484+
state,
1485+
node.type === 'RegularElement' ? node.name : 'node'
1486+
);
1487+
14751488
expression = b.call('$.sibling', id);
14761489

14771490
visit(node, {
@@ -1488,6 +1501,22 @@ function process_children(nodes, parent, { visit, state }) {
14881501
}
14891502
}
14901503

1504+
/**
1505+
* @param {import('estree').Expression} expression
1506+
* @param {import('../types.js').ComponentClientTransformState} state
1507+
* @param {string} name
1508+
*/
1509+
function get_node_id(expression, state, name) {
1510+
let id = expression;
1511+
1512+
if (id.type !== 'Identifier') {
1513+
id = b.id(state.scope.generate(name));
1514+
1515+
state.init.push(b.var(id, expression));
1516+
}
1517+
return id;
1518+
}
1519+
14911520
/**
14921521
* @param {true | Array<import('#compiler').Text | import('#compiler').ExpressionTag>} attribute_value
14931522
* @param {import('../types').ComponentContext} context

packages/svelte/src/internal/client/render.js

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { DEV } from 'esm-env';
22
import {
33
append_child,
44
child,
5+
child_frag,
56
clone_node,
67
create_element,
78
init_operations,
@@ -133,7 +134,7 @@ export function svg_replace(node) {
133134
* @param {boolean} is_fragment
134135
* @param {boolean} use_clone_node
135136
* @param {null | Text | Comment | Element} anchor
136-
* @param {() => Element} [template_element_fn]
137+
* @param {() => Node} [template_element_fn]
137138
* @returns {Element | DocumentFragment | Node[]}
138139
*/
139140
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)
156157
/**
157158
* @param {null | Text | Comment | Element} anchor
158159
* @param {boolean} use_clone_node
159-
* @param {() => Element} [template_element_fn]
160+
* @param {() => Node} [template_element_fn]
160161
* @returns {Element | DocumentFragment | Node[]}
161162
*/
162163
/*#__NO_SIDE_EFFECTS__*/
@@ -167,14 +168,33 @@ export function open(anchor, use_clone_node, template_element_fn) {
167168
/**
168169
* @param {null | Text | Comment | Element} anchor
169170
* @param {boolean} use_clone_node
170-
* @param {() => Element} [template_element_fn]
171+
* @param {() => Node} [template_element_fn]
171172
* @returns {Element | DocumentFragment | Node[]}
172173
*/
173174
/*#__NO_SIDE_EFFECTS__*/
174175
export function open_frag(anchor, use_clone_node, template_element_fn) {
175176
return open_template(true, use_clone_node, anchor, template_element_fn);
176177
}
177178

179+
const space_template = template(' ', false);
180+
const comment_template = template('<!>', true);
181+
182+
/**
183+
* @param {null | Text | Comment | Element} anchor
184+
*/
185+
/*#__NO_SIDE_EFFECTS__*/
186+
export function space(anchor) {
187+
return open(anchor, true, space_template);
188+
}
189+
190+
/**
191+
* @param {null | Text | Comment | Element} anchor
192+
*/
193+
/*#__NO_SIDE_EFFECTS__*/
194+
export function comment(anchor) {
195+
return open_frag(anchor, true, comment_template);
196+
}
197+
178198
/**
179199
* @param {Element | Text} dom
180200
* @param {boolean} is_fragment

packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
import "svelte/internal/disclose-version";
44
import * as $ from "svelte/internal";
55

6-
var Button_default = $.template(` `, true);
7-
var frag = $.template(`<!>`, true);
8-
96
export default function Function_prop_no_getter($$anchor, $$props) {
107
$.push($$props, true);
118

@@ -16,20 +13,19 @@ export default function Function_prop_no_getter($$anchor, $$props) {
1613
}
1714

1815
/* Init */
19-
var fragment = $.open_frag($$anchor, true, frag);
16+
var fragment = $.comment($$anchor);
2017
var node = $.child_frag(fragment);
2118

2219
Button(node, {
2320
onmousedown: () => $.set(count, $.get(count) + 1),
2421
onmouseup,
2522
children: ($$anchor, $$slotProps) => {
2623
/* Init */
27-
var fragment_1 = $.open_frag($$anchor, true, Button_default);
28-
var text = $.child_frag(fragment_1);
24+
var node_1 = $.space($$anchor);
2925

3026
/* Update */
31-
$.text_effect(text, () => `clicks: ${$.stringify($.get(count))}`);
32-
$.close_frag($$anchor, fragment_1);
27+
$.text_effect(node_1, () => `clicks: ${$.stringify($.get(count))}`);
28+
$.close($$anchor, node_1);
3329
}
3430
});
3531

0 commit comments

Comments
 (0)