Skip to content

Commit 056475f

Browse files
committed
better inlining
1 parent 16ec011 commit 056475f

File tree

3 files changed

+49
-16
lines changed

3 files changed

+49
-16
lines changed

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

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/** @import { Expression } from 'estree' */
22
/** @import { AST, SvelteNode } from '#compiler' */
3+
/** @import { Scope } from '../../../../scope.js' */
34
/** @import { ComponentContext } from '../../types' */
45
import { escape_html } from '../../../../../../escaping.js';
56
import { is_event_attribute } from '../../../../../utils/ast.js';
@@ -67,7 +68,7 @@ export function process_children(nodes, initial, element, { visit, state }) {
6768
if (can_inline) {
6869
skipped += 1;
6970
const raw = element?.name === 'script' || element?.name === 'style';
70-
state.template.push(raw ? value : escape_inline_expression(value));
71+
state.template.push(raw ? value : escape_inline_expression(value, state.scope));
7172
return;
7273
}
7374

@@ -165,24 +166,39 @@ function is_static_element(node) {
165166

166167
/**
167168
* @param {Expression} node
168-
* @param {boolean} [is_attr]
169+
* @param {Scope} scope
169170
* @returns {Expression}
170171
*/
171-
function escape_inline_expression(node, is_attr) {
172+
function escape_inline_expression(node, scope) {
172173
if (node.type === 'Literal') {
173174
if (typeof node.value === 'string') {
174-
return b.literal(escape_html(node.value, is_attr));
175+
return b.literal(escape_html(node.value));
175176
}
176177

177178
return node;
178179
}
179180

180181
if (node.type === 'TemplateLiteral') {
181182
return b.template(
182-
node.quasis.map((q) => b.quasi(escape_html(q.value.cooked, is_attr))),
183-
node.expressions.map((expression) => escape_inline_expression(expression, is_attr))
183+
node.quasis.map((q) => b.quasi(escape_html(q.value.cooked))),
184+
node.expressions.map((expression) => escape_inline_expression(expression, scope))
184185
);
185186
}
186187

187-
return b.call('$.escape', node, is_attr && b.true);
188+
/**
189+
* If we can't determine the range of possible values statically, wrap in
190+
* `$.escape(...)`. TODO expand this to cover more cases
191+
*/
192+
let needs_escape = true;
193+
194+
if (node.type === 'Identifier') {
195+
const binding = scope.get(node.name);
196+
197+
// TODO handle more cases
198+
if (binding?.initial?.type === 'Literal' && !binding.reassigned) {
199+
needs_escape = escape_html(binding.initial.value) !== String(binding.initial.value);
200+
}
201+
}
202+
203+
return needs_escape ? b.call('$.escape', node) : node;
188204
}

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

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,26 +51,43 @@ export function build_template_chunk(values, visit, state) {
5151
if (node.type === 'Text') {
5252
quasi.value.cooked += node.data;
5353
} else {
54-
if (node.expression.type === 'Literal') {
55-
if (node.expression.value != null) {
56-
quasi.value.cooked += node.expression.value + '';
54+
const expression = /** @type {Expression} */ (visit(node.expression, state));
55+
56+
if (expression.type === 'Literal') {
57+
if (expression.value != null) {
58+
quasi.value.cooked += expression.value + '';
5759
}
5860
} else {
61+
let value = expression;
62+
63+
// if we don't know the value, we need to add `?? ''` to replace
64+
// `null` and `undefined` with the empty string
65+
let needs_fallback = true;
66+
67+
if (value.type === 'Identifier') {
68+
const binding = state.scope.get(value.name);
69+
70+
if (binding && binding.initial?.type === 'Literal' && !binding.reassigned) {
71+
needs_fallback = binding.initial.value === null;
72+
}
73+
}
74+
75+
if (needs_fallback) {
76+
value = b.logical('??', expression, b.literal(''));
77+
}
78+
5979
if (contains_multiple_call_expression) {
6080
const id = b.id(state.scope.generate('stringified_text'));
61-
const expression = /** @type {Expression} */ (visit(node.expression, state));
6281

63-
state.init.push(
64-
b.const(id, create_derived(state, b.thunk(b.logical('??', expression, b.literal('')))))
65-
);
82+
state.init.push(b.const(id, create_derived(state, b.thunk(value))));
6683

6784
expressions.push(b.call('$.get', id));
6885
} else if (values.length === 1) {
6986
// If we have a single expression, then pass that in directly to possibly avoid doing
7087
// extra work in the template_effect (instead we do the work in set_text).
7188
return { value: visit(node.expression, state), has_state, has_call, can_inline };
7289
} else {
73-
expressions.push(b.logical('??', visit(node.expression, state), b.literal('')));
90+
expressions.push(value);
7491
}
7592

7693
quasi = b.quasi('', i + 1 === values.length);

packages/svelte/tests/snapshot/samples/inline-module-vars/_expected/client/index.svelte.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const __DECLARED_ASSET_2__ = "__VITE_ASSET__2AM7_y_e__ 1440w, __VITE_ASSET__2AM7
77
const __DECLARED_ASSET_3__ = "__VITE_ASSET__2AM7_y_g__";
88
const a = 1;
99
const b = 2;
10-
var root = $.template(`<picture><source srcset="${__DECLARED_ASSET_0__}" type="image/avif"> <source srcset="${__DECLARED_ASSET_1__}" type="image/webp"> <source srcset="${__DECLARED_ASSET_2__}" type="image/png"> <img src="${__DECLARED_ASSET_3__}" alt="production test" width="1440" height="1440"></picture> <p>${$.escape(a ?? "")} + ${$.escape(b ?? "")} = ${$.escape(a + b ?? "")}</p>`, 1);
10+
var root = $.template(`<picture><source srcset="${__DECLARED_ASSET_0__}" type="image/avif"> <source srcset="${__DECLARED_ASSET_1__}" type="image/webp"> <source srcset="${__DECLARED_ASSET_2__}" type="image/png"> <img src="${__DECLARED_ASSET_3__}" alt="production test" width="1440" height="1440"></picture> <p>${a} + ${b} = ${$.escape(a + b ?? "")}</p>`, 1);
1111

1212
export default function Inline_module_vars($$anchor) {
1313
var fragment = root();

0 commit comments

Comments
 (0)