Skip to content

Commit 8685d49

Browse files
authored
feat: use bracket matching instead of ssr:n comments (#10904)
* use short comments * use bracket matching * fix * update snapshots * update tests * fix
1 parent f1d9afe commit 8685d49

File tree

20 files changed

+98
-135
lines changed

20 files changed

+98
-135
lines changed

packages/svelte/src/compiler/phases/3-transform/server/transform-server.js

Lines changed: 30 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ import { regex_starts_with_newline, regex_whitespaces_strict } from '../../patte
2727
import { DOMBooleanAttributes } from '../../../../constants.js';
2828
import { sanitize_template_string } from '../../../utils/sanitize_template_string.js';
2929

30+
const block_open = t_string('<![>');
31+
const block_close = t_string('<!]>');
32+
3033
/**
3134
* @param {string} value
3235
* @returns {import('./types').TemplateString}
@@ -52,15 +55,6 @@ function t_statement(value) {
5255
return { type: 'statement', value };
5356
}
5457

55-
/**
56-
* @param {import('./types').ServerTransformState} state
57-
* @returns {[import('estree').VariableDeclaration, import('estree').Identifier]}
58-
*/
59-
function serialize_anchor(state) {
60-
const id = state.scope.root.unique('anchor');
61-
return [b.const(id, b.call('$.create_anchor', b.id('$$payload'))), id];
62-
}
63-
6458
/**
6559
* @param {import('./types').Template[]} template
6660
* @param {import('estree').Identifier} out
@@ -1237,12 +1231,10 @@ const template_visitors = {
12371231
},
12381232
HtmlTag(node, context) {
12391233
const state = context.state;
1240-
const [dec, id] = serialize_anchor(state);
1241-
state.init.push(dec);
1242-
state.template.push(t_expression(id));
1234+
state.template.push(block_open);
12431235
const raw = /** @type {import('estree').Expression} */ (context.visit(node.expression));
12441236
context.state.template.push(t_expression(raw));
1245-
state.template.push(t_expression(id));
1237+
state.template.push(block_close);
12461238
},
12471239
ConstTag(node, { state, visit }) {
12481240
const declaration = node.declaration.declarations[0];
@@ -1273,10 +1265,8 @@ const template_visitors = {
12731265
},
12741266
RenderTag(node, context) {
12751267
const state = context.state;
1276-
const [anchor, anchor_id] = serialize_anchor(state);
12771268

1278-
state.init.push(anchor);
1279-
state.template.push(t_expression(anchor_id));
1269+
state.template.push(block_open);
12801270

12811271
const callee = unwrap_optional(node.expression).callee;
12821272
const raw_args = unwrap_optional(node.expression).arguments;
@@ -1302,7 +1292,7 @@ const template_visitors = {
13021292
)
13031293
);
13041294

1305-
state.template.push(t_expression(anchor_id));
1295+
state.template.push(block_close);
13061296
},
13071297
ClassDirective(node) {
13081298
error(node, 'INTERNAL', 'Node should have been handled elsewhere');
@@ -1427,9 +1417,7 @@ const template_visitors = {
14271417
}
14281418
};
14291419

1430-
const [el_anchor, anchor_id] = serialize_anchor(context.state);
1431-
context.state.init.push(el_anchor);
1432-
context.state.template.push(t_expression(anchor_id));
1420+
context.state.template.push(block_open);
14331421

14341422
const main = create_block(node, node.fragment.nodes, {
14351423
...context,
@@ -1465,17 +1453,15 @@ const template_visitors = {
14651453
)
14661454
)
14671455
),
1468-
t_expression(anchor_id)
1456+
block_close
14691457
);
14701458
if (context.state.options.dev) {
14711459
context.state.template.push(t_statement(b.stmt(b.call('$.pop_element'))));
14721460
}
14731461
},
14741462
EachBlock(node, context) {
14751463
const state = context.state;
1476-
const [dec, id] = serialize_anchor(state);
1477-
state.init.push(dec);
1478-
state.template.push(t_expression(id));
1464+
state.template.push(block_open);
14791465

14801466
const each_node_meta = node.metadata;
14811467
const collection = /** @type {import('estree').Expression} */ (context.visit(node.expression));
@@ -1486,14 +1472,6 @@ const template_visitors = {
14861472
: b.id(node.index);
14871473
const children = node.body.nodes;
14881474

1489-
const [each_dec, each_id] = serialize_anchor(state);
1490-
1491-
/** @type {import('./types').Anchor} */
1492-
const anchor = {
1493-
type: 'Anchor',
1494-
id: each_id
1495-
};
1496-
14971475
const array_id = state.scope.root.unique('each_array');
14981476
state.init.push(b.const(array_id, b.call('$.ensure_array_like', collection)));
14991477

@@ -1507,11 +1485,14 @@ const template_visitors = {
15071485
each.push(b.let(node.index, index));
15081486
}
15091487

1488+
each.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(block_open.value))));
1489+
15101490
each.push(
1511-
each_dec,
1512-
.../** @type {import('estree').Statement[]} */ (create_block(node, children, context, anchor))
1491+
.../** @type {import('estree').Statement[]} */ (create_block(node, children, context))
15131492
);
15141493

1494+
each.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(block_close.value))));
1495+
15151496
const for_loop = b.for(
15161497
b.let(index, b.literal(0)),
15171498
b.binary('<', index, b.member(array_id, b.id('length'))),
@@ -1535,13 +1516,11 @@ const template_visitors = {
15351516
} else {
15361517
state.template.push(t_statement(for_loop));
15371518
}
1538-
state.template.push(t_expression(id));
1519+
state.template.push(block_close);
15391520
},
15401521
IfBlock(node, context) {
15411522
const state = context.state;
1542-
const [dec, id] = serialize_anchor(state);
1543-
state.init.push(dec);
1544-
state.template.push(t_expression(id));
1523+
state.template.push(block_open);
15451524

15461525
// Insert ssr:if:true/false anchors in addition to the other anchors so that
15471526
// the if block can catch hydration mismatches (false on the server, true on the client and vice versa)
@@ -1568,13 +1547,11 @@ const template_visitors = {
15681547
)
15691548
)
15701549
);
1571-
state.template.push(t_expression(id));
1550+
state.template.push(block_close);
15721551
},
15731552
AwaitBlock(node, context) {
15741553
const state = context.state;
1575-
const [dec, id] = serialize_anchor(state);
1576-
state.init.push(dec);
1577-
state.template.push(t_expression(id));
1554+
state.template.push(block_open);
15781555

15791556
state.template.push(
15801557
t_statement(
@@ -1608,16 +1585,14 @@ const template_visitors = {
16081585
)
16091586
);
16101587

1611-
state.template.push(t_expression(id));
1588+
state.template.push(block_close);
16121589
},
16131590
KeyBlock(node, context) {
16141591
const state = context.state;
1615-
const [dec, id] = serialize_anchor(state);
1616-
state.init.push(dec);
1617-
state.template.push(t_expression(id));
1592+
state.template.push(block_open);
16181593
const body = create_block(node, node.fragment.nodes, context);
16191594
state.template.push(t_statement(b.block(body)));
1620-
state.template.push(t_expression(id));
1595+
state.template.push(block_close);
16211596
},
16221597
SnippetBlock(node, context) {
16231598
// TODO hoist where possible
@@ -1635,34 +1610,28 @@ const template_visitors = {
16351610
},
16361611
Component(node, context) {
16371612
const state = context.state;
1638-
const [dec, id] = serialize_anchor(state);
1639-
state.init.push(dec);
1640-
state.template.push(t_expression(id));
1613+
state.template.push(block_open);
16411614
const call = serialize_inline_component(node, node.name, context);
16421615
state.template.push(t_statement(call));
1643-
state.template.push(t_expression(id));
1616+
state.template.push(block_close);
16441617
},
16451618
SvelteSelf(node, context) {
16461619
const state = context.state;
1647-
const [dec, id] = serialize_anchor(state);
1648-
state.init.push(dec);
1649-
state.template.push(t_expression(id));
1620+
state.template.push(block_open);
16501621
const call = serialize_inline_component(node, context.state.analysis.name, context);
16511622
state.template.push(t_statement(call));
1652-
state.template.push(t_expression(id));
1623+
state.template.push(block_close);
16531624
},
16541625
SvelteComponent(node, context) {
16551626
const state = context.state;
1656-
const [dec, id] = serialize_anchor(state);
1657-
state.init.push(dec);
1658-
state.template.push(t_expression(id));
1627+
state.template.push(block_open);
16591628
const call = serialize_inline_component(
16601629
node,
16611630
/** @type {import('estree').Expression} */ (context.visit(node.expression)),
16621631
context
16631632
);
16641633
state.template.push(t_statement(call));
1665-
state.template.push(t_expression(id));
1634+
state.template.push(block_close);
16661635
},
16671636
LetDirective(node, { state }) {
16681637
if (node.expression && node.expression.type !== 'Identifier') {
@@ -1745,9 +1714,7 @@ const template_visitors = {
17451714
},
17461715
SlotElement(node, context) {
17471716
const state = context.state;
1748-
const [dec, id] = serialize_anchor(state);
1749-
state.init.push(dec);
1750-
state.template.push(t_expression(id));
1717+
state.template.push(block_open);
17511718

17521719
/** @type {import('estree').Property[]} */
17531720
const props = [];
@@ -1794,7 +1761,7 @@ const template_visitors = {
17941761
const slot = b.call('$.slot', b.id('$$payload'), expression, props_expression, fallback);
17951762

17961763
state.template.push(t_statement(b.stmt(slot)));
1797-
state.template.push(t_expression(id));
1764+
state.template.push(block_close);
17981765
},
17991766
SvelteHead(node, context) {
18001767
const state = context.state;

packages/svelte/src/internal/client/dom/hydration.js

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export function update_hydrate_nodes(first, insert_text) {
3939
}
4040

4141
/**
42-
* Returns all nodes between the first `<!--ssr:...-->` comment tag pair encountered.
42+
* Returns all nodes between the first `<![>...<!]>` comment tag pair encountered.
4343
* @param {Node | null} node
4444
* @param {boolean} [insert_text] Whether to insert an empty text node if `nodes` is empty
4545
* @returns {import('#client').TemplateNode[] | null}
@@ -50,34 +50,43 @@ function get_hydrate_nodes(node, insert_text = false) {
5050

5151
var current_node = /** @type {null | import('#client').TemplateNode} */ (node);
5252

53-
/** @type {null | string} */
54-
var target_depth = null;
53+
var depth = 0;
54+
55+
var will_start = false;
56+
var started = false;
5557

5658
while (current_node !== null) {
5759
if (current_node.nodeType === 8) {
5860
var data = /** @type {Comment} */ (current_node).data;
5961

60-
if (data.startsWith('ssr:')) {
61-
var depth = data.slice(4);
62+
if (data === '[') {
63+
depth += 1;
64+
will_start = true;
65+
} else if (data === ']') {
66+
if (!started) {
67+
// TODO get rid of this — it exists because each blocks are doubly wrapped
68+
return null;
69+
}
6270

63-
if (target_depth === null) {
64-
target_depth = depth;
65-
} else if (depth === target_depth) {
71+
if (--depth === 0) {
6672
if (insert_text && nodes.length === 0) {
6773
var text = empty();
6874
nodes.push(text);
6975
current_node.before(text);
7076
}
77+
7178
return nodes;
72-
} else {
73-
nodes.push(current_node);
7479
}
7580
}
76-
} else if (target_depth !== null) {
81+
}
82+
83+
if (started) {
7784
nodes.push(current_node);
7885
}
7986

8087
current_node = /** @type {null | import('#client').TemplateNode} */ (current_node.nextSibling);
88+
89+
started = will_start;
8190
}
8291

8392
return null;
@@ -103,7 +112,7 @@ export function hydrate_block_anchor(node) {
103112
export function capture_fragment_from_node(node) {
104113
if (
105114
node.nodeType === 8 &&
106-
/** @type {Comment} */ (node).data.startsWith('ssr:') &&
115+
/** @type {Comment} */ (node).data === '[' &&
107116
hydrate_nodes[hydrate_nodes.length - 1] !== node
108117
) {
109118
const nodes = /** @type {Node[]} */ (get_hydrate_nodes(node));

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { DEV } from 'esm-env';
2-
import { append_child, create_element, empty, init_operations } from './dom/operations.js';
2+
import {
3+
append_child,
4+
clear_text_content,
5+
create_element,
6+
empty,
7+
init_operations
8+
} from './dom/operations.js';
39
import { PassiveDelegatedEvents } from '../../constants.js';
410
import { remove } from './dom/reconciler.js';
511
import { flush_sync, push, pop, current_component_context } from './runtime.js';
@@ -170,9 +176,9 @@ export function hydrate(component, options) {
170176
: ''),
171177
error
172178
);
173-
remove(nodes);
174-
first_child.remove();
175-
nodes[nodes.length - 1]?.nextSibling?.remove();
179+
180+
clear_text_content(container);
181+
176182
set_hydrating(false);
177183
return mount(component, options);
178184
} else {

0 commit comments

Comments
 (0)