Skip to content

feat: use bracket matching instead of ssr:n comments #10904

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import { regex_starts_with_newline, regex_whitespaces_strict } from '../../patte
import { DOMBooleanAttributes } from '../../../../constants.js';
import { sanitize_template_string } from '../../../utils/sanitize_template_string.js';

const block_open = t_string('<![>');
const block_close = t_string('<!]>');

/**
* @param {string} value
* @returns {import('./types').TemplateString}
Expand All @@ -52,15 +55,6 @@ function t_statement(value) {
return { type: 'statement', value };
}

/**
* @param {import('./types').ServerTransformState} state
* @returns {[import('estree').VariableDeclaration, import('estree').Identifier]}
*/
function serialize_anchor(state) {
const id = state.scope.root.unique('anchor');
return [b.const(id, b.call('$.create_anchor', b.id('$$payload'))), id];
}

/**
* @param {import('./types').Template[]} template
* @param {import('estree').Identifier} out
Expand Down Expand Up @@ -1237,12 +1231,10 @@ const template_visitors = {
},
HtmlTag(node, context) {
const state = context.state;
const [dec, id] = serialize_anchor(state);
state.init.push(dec);
state.template.push(t_expression(id));
state.template.push(block_open);
const raw = /** @type {import('estree').Expression} */ (context.visit(node.expression));
context.state.template.push(t_expression(raw));
state.template.push(t_expression(id));
state.template.push(block_close);
},
ConstTag(node, { state, visit }) {
const declaration = node.declaration.declarations[0];
Expand Down Expand Up @@ -1273,10 +1265,8 @@ const template_visitors = {
},
RenderTag(node, context) {
const state = context.state;
const [anchor, anchor_id] = serialize_anchor(state);

state.init.push(anchor);
state.template.push(t_expression(anchor_id));
state.template.push(block_open);

const callee = unwrap_optional(node.expression).callee;
const raw_args = unwrap_optional(node.expression).arguments;
Expand All @@ -1302,7 +1292,7 @@ const template_visitors = {
)
);

state.template.push(t_expression(anchor_id));
state.template.push(block_close);
},
ClassDirective(node) {
error(node, 'INTERNAL', 'Node should have been handled elsewhere');
Expand Down Expand Up @@ -1427,9 +1417,7 @@ const template_visitors = {
}
};

const [el_anchor, anchor_id] = serialize_anchor(context.state);
context.state.init.push(el_anchor);
context.state.template.push(t_expression(anchor_id));
context.state.template.push(block_open);

const main = create_block(node, node.fragment.nodes, {
...context,
Expand Down Expand Up @@ -1465,17 +1453,15 @@ const template_visitors = {
)
)
),
t_expression(anchor_id)
block_close
);
if (context.state.options.dev) {
context.state.template.push(t_statement(b.stmt(b.call('$.pop_element'))));
}
},
EachBlock(node, context) {
const state = context.state;
const [dec, id] = serialize_anchor(state);
state.init.push(dec);
state.template.push(t_expression(id));
state.template.push(block_open);

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

const [each_dec, each_id] = serialize_anchor(state);

/** @type {import('./types').Anchor} */
const anchor = {
type: 'Anchor',
id: each_id
};

const array_id = state.scope.root.unique('each_array');
state.init.push(b.const(array_id, b.call('$.ensure_array_like', collection)));

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

each.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(block_open.value))));

each.push(
each_dec,
.../** @type {import('estree').Statement[]} */ (create_block(node, children, context, anchor))
.../** @type {import('estree').Statement[]} */ (create_block(node, children, context))
);

each.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(block_close.value))));

const for_loop = b.for(
b.let(index, b.literal(0)),
b.binary('<', index, b.member(array_id, b.id('length'))),
Expand All @@ -1535,13 +1516,11 @@ const template_visitors = {
} else {
state.template.push(t_statement(for_loop));
}
state.template.push(t_expression(id));
state.template.push(block_close);
},
IfBlock(node, context) {
const state = context.state;
const [dec, id] = serialize_anchor(state);
state.init.push(dec);
state.template.push(t_expression(id));
state.template.push(block_open);

// Insert ssr:if:true/false anchors in addition to the other anchors so that
// the if block can catch hydration mismatches (false on the server, true on the client and vice versa)
Expand All @@ -1568,13 +1547,11 @@ const template_visitors = {
)
)
);
state.template.push(t_expression(id));
state.template.push(block_close);
},
AwaitBlock(node, context) {
const state = context.state;
const [dec, id] = serialize_anchor(state);
state.init.push(dec);
state.template.push(t_expression(id));
state.template.push(block_open);

state.template.push(
t_statement(
Expand Down Expand Up @@ -1608,16 +1585,14 @@ const template_visitors = {
)
);

state.template.push(t_expression(id));
state.template.push(block_close);
},
KeyBlock(node, context) {
const state = context.state;
const [dec, id] = serialize_anchor(state);
state.init.push(dec);
state.template.push(t_expression(id));
state.template.push(block_open);
const body = create_block(node, node.fragment.nodes, context);
state.template.push(t_statement(b.block(body)));
state.template.push(t_expression(id));
state.template.push(block_close);
},
SnippetBlock(node, context) {
// TODO hoist where possible
Expand All @@ -1635,34 +1610,28 @@ const template_visitors = {
},
Component(node, context) {
const state = context.state;
const [dec, id] = serialize_anchor(state);
state.init.push(dec);
state.template.push(t_expression(id));
state.template.push(block_open);
const call = serialize_inline_component(node, node.name, context);
state.template.push(t_statement(call));
state.template.push(t_expression(id));
state.template.push(block_close);
},
SvelteSelf(node, context) {
const state = context.state;
const [dec, id] = serialize_anchor(state);
state.init.push(dec);
state.template.push(t_expression(id));
state.template.push(block_open);
const call = serialize_inline_component(node, context.state.analysis.name, context);
state.template.push(t_statement(call));
state.template.push(t_expression(id));
state.template.push(block_close);
},
SvelteComponent(node, context) {
const state = context.state;
const [dec, id] = serialize_anchor(state);
state.init.push(dec);
state.template.push(t_expression(id));
state.template.push(block_open);
const call = serialize_inline_component(
node,
/** @type {import('estree').Expression} */ (context.visit(node.expression)),
context
);
state.template.push(t_statement(call));
state.template.push(t_expression(id));
state.template.push(block_close);
},
LetDirective(node, { state }) {
if (node.expression && node.expression.type !== 'Identifier') {
Expand Down Expand Up @@ -1745,9 +1714,7 @@ const template_visitors = {
},
SlotElement(node, context) {
const state = context.state;
const [dec, id] = serialize_anchor(state);
state.init.push(dec);
state.template.push(t_expression(id));
state.template.push(block_open);

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

state.template.push(t_statement(b.stmt(slot)));
state.template.push(t_expression(id));
state.template.push(block_close);
},
SvelteHead(node, context) {
const state = context.state;
Expand Down
33 changes: 21 additions & 12 deletions packages/svelte/src/internal/client/dom/hydration.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function update_hydrate_nodes(first, insert_text) {
}

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

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

/** @type {null | string} */
var target_depth = null;
var depth = 0;

var will_start = false;
var started = false;

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

if (data.startsWith('ssr:')) {
var depth = data.slice(4);
if (data === '[') {
depth += 1;
will_start = true;
} else if (data === ']') {
if (!started) {
// TODO get rid of this — it exists because each blocks are doubly wrapped
return null;
}

if (target_depth === null) {
target_depth = depth;
} else if (depth === target_depth) {
if (--depth === 0) {
if (insert_text && nodes.length === 0) {
var text = empty();
nodes.push(text);
current_node.before(text);
}

return nodes;
} else {
nodes.push(current_node);
}
}
} else if (target_depth !== null) {
}

if (started) {
nodes.push(current_node);
}

current_node = /** @type {null | import('#client').TemplateNode} */ (current_node.nextSibling);

started = will_start;
}

return null;
Expand All @@ -103,7 +112,7 @@ export function hydrate_block_anchor(node) {
export function capture_fragment_from_node(node) {
if (
node.nodeType === 8 &&
/** @type {Comment} */ (node).data.startsWith('ssr:') &&
/** @type {Comment} */ (node).data === '[' &&
hydrate_nodes[hydrate_nodes.length - 1] !== node
) {
const nodes = /** @type {Node[]} */ (get_hydrate_nodes(node));
Expand Down
14 changes: 10 additions & 4 deletions packages/svelte/src/internal/client/render.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { DEV } from 'esm-env';
import { append_child, create_element, empty, init_operations } from './dom/operations.js';
import {
append_child,
clear_text_content,
create_element,
empty,
init_operations
} from './dom/operations.js';
import { PassiveDelegatedEvents } from '../../constants.js';
import { remove } from './dom/reconciler.js';
import { flush_sync, push, pop, current_component_context } from './runtime.js';
Expand Down Expand Up @@ -170,9 +176,9 @@ export function hydrate(component, options) {
: ''),
error
);
remove(nodes);
first_child.remove();
nodes[nodes.length - 1]?.nextSibling?.remove();

clear_text_content(container);

set_hydrating(false);
return mount(component, options);
} else {
Expand Down
Loading