Skip to content

Commit dffeef1

Browse files
authored
perf: inline module variables into template (#13075)
1 parent a6df4eb commit dffeef1

File tree

19 files changed

+158
-26
lines changed

19 files changed

+158
-26
lines changed

.changeset/eighty-dragons-search.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+
perf: inline module variables into template

packages/svelte/src/compiler/phases/3-transform/client/types.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ export interface ComponentClientTransformState extends ClientTransformState {
5454
/** Stuff that happens after the render effect (control blocks, dynamic elements, bindings, actions, etc) */
5555
readonly after_update: Statement[];
5656
/** The HTML template string */
57-
readonly template: string[];
57+
readonly template: {
58+
push_quasi: (q: string) => void;
59+
push_expression: (e: Expression) => void;
60+
};
5861
readonly locations: SourceLocation[];
5962
readonly metadata: {
6063
namespace: Namespace;

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,3 +311,18 @@ export function create_derived_block_argument(node, context) {
311311
export function create_derived(state, arg) {
312312
return b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', arg);
313313
}
314+
315+
/**
316+
* Whether a variable can be referenced directly from template string.
317+
* @param {import('#compiler').Binding | undefined} binding
318+
* @returns {boolean}
319+
*/
320+
export function can_inline_variable(binding) {
321+
return (
322+
!!binding &&
323+
// in a `<script module>` block
324+
!binding.scope.parent &&
325+
// to prevent the need for escaping
326+
binding.initial?.type === 'Literal'
327+
);
328+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { create_derived_block_argument } from '../utils.js';
99
* @param {ComponentContext} context
1010
*/
1111
export function AwaitBlock(node, context) {
12-
context.state.template.push('<!>');
12+
context.state.template.push_quasi('<!>');
1313

1414
// Visit {#await <expression>} first to ensure that scopes are in the correct order
1515
const expression = b.thunk(/** @type {Expression} */ (context.visit(node.expression)));

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@
77
*/
88
export function Comment(node, context) {
99
// We'll only get here if comments are not filtered out, which they are unless preserveComments is true
10-
context.state.template.push(`<!--${node.data}-->`);
10+
context.state.template.push_quasi(`<!--${node.data}-->`);
1111
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export function EachBlock(node, context) {
3232
);
3333

3434
if (!each_node_meta.is_controlled) {
35-
context.state.template.push('<!>');
35+
context.state.template.push_quasi('<!>');
3636
}
3737

3838
if (each_node_meta.array_name !== null) {

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

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,34 @@ export function Fragment(node, context) {
5757
/** @type {Statement | undefined} */
5858
let close = undefined;
5959

60+
/** @type {string[]} */
61+
const quasi = [];
62+
/** @type {Expression[]} */
63+
const expressions = [];
64+
6065
/** @type {ComponentClientTransformState} */
6166
const state = {
6267
...context.state,
6368
before_init: [],
6469
init: [],
6570
update: [],
6671
after_update: [],
67-
template: [],
72+
template: {
73+
push_quasi: (/** @type {string} */ quasi_to_add) => {
74+
if (quasi.length === 0) {
75+
quasi.push(quasi_to_add);
76+
return;
77+
}
78+
quasi[quasi.length - 1] = quasi[quasi.length - 1].concat(quasi_to_add);
79+
},
80+
push_expression: (/** @type {Expression} */ expression_to_add) => {
81+
if (quasi.length === 0) {
82+
quasi.push('');
83+
}
84+
expressions.push(expression_to_add);
85+
quasi.push('');
86+
}
87+
},
6888
locations: [],
6989
transform: { ...context.state.transform },
7090
metadata: {
@@ -115,7 +135,12 @@ export function Fragment(node, context) {
115135
});
116136

117137
/** @type {Expression[]} */
118-
const args = [b.template([b.quasi(state.template.join(''), true)], [])];
138+
const args = [
139+
b.template(
140+
quasi.map((q) => b.quasi(q, true)),
141+
expressions
142+
)
143+
];
119144

120145
if (state.metadata.context.template_needs_import_node) {
121146
args.push(b.literal(TEMPLATE_USE_IMPORT_NODE));
@@ -170,12 +195,15 @@ export function Fragment(node, context) {
170195
flags |= TEMPLATE_USE_IMPORT_NODE;
171196
}
172197

173-
if (state.template.length === 1 && state.template[0] === '<!>') {
198+
if (quasi.length === 1 && quasi[0] === '<!>') {
174199
// special case — we can use `$.comment` instead of creating a unique template
175200
body.push(b.var(id, b.call('$.comment')));
176201
} else {
177202
add_template(template_name, [
178-
b.template([b.quasi(state.template.join(''), true)], []),
203+
b.template(
204+
quasi.map((q) => b.quasi(q, true)),
205+
expressions
206+
),
179207
b.literal(flags)
180208
]);
181209

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as b from '../../../../utils/builders.js';
99
* @param {ComponentContext} context
1010
*/
1111
export function HtmlTag(node, context) {
12-
context.state.template.push('<!>');
12+
context.state.template.push_quasi('<!>');
1313

1414
// push into init, so that bindings run afterwards, which might trigger another run and override hydration
1515
context.state.init.push(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as b from '../../../../utils/builders.js';
88
* @param {ComponentContext} context
99
*/
1010
export function IfBlock(node, context) {
11-
context.state.template.push('<!>');
11+
context.state.template.push_quasi('<!>');
1212

1313
const consequent = /** @type {BlockStatement} */ (context.visit(node.consequent));
1414

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as b from '../../../../utils/builders.js';
88
* @param {ComponentContext} context
99
*/
1010
export function KeyBlock(node, context) {
11-
context.state.template.push('<!>');
11+
context.state.template.push_quasi('<!>');
1212

1313
const key = /** @type {Expression} */ (context.visit(node.expression));
1414
const body = /** @type {Expression} */ (context.visit(node.fragment));

0 commit comments

Comments
 (0)