Skip to content

chore: tidy up some SSR compiler code #11976

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 3 commits into from
Jun 9, 2024
Merged
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 @@ -1344,64 +1344,48 @@ const template_visitors = {

state.template.push(block_close);
},
ClassDirective(node) {
ClassDirective() {
throw new Error('Node should have been handled elsewhere');
},
StyleDirective(node) {
StyleDirective() {
throw new Error('Node should have been handled elsewhere');
},
RegularElement(node, context) {
const metadata = {
...context.state.metadata,
namespace: determine_namespace_for_children(node, context.state.metadata.namespace)
};

context.state.template.push(t_string(`<${node.name}`));
const body_expression = serialize_element_attributes(node, context);
const body = serialize_element_attributes(node, context);
context.state.template.push(t_string('>'));

const namespace = determine_namespace_for_children(node, context.state.metadata.namespace);

/** @type {import('./types').ComponentServerTransformState} */
const state = {
...context.state,
metadata,
metadata: { ...context.state.metadata, namespace },
preserve_whitespace:
context.state.preserve_whitespace ||
((node.name === 'pre' || node.name === 'textarea') && metadata.namespace !== 'foreign')
((node.name === 'pre' || node.name === 'textarea') && namespace !== 'foreign')
};

/** @type {import('./types').ComponentContext} */
const inner_context =
body_expression !== null
? {
...context,
state: {
...state,
template: [],
init: []
}
}
: { ...context, state };

const { hoisted, trimmed } = clean_nodes(
node,
node.fragment.nodes,
inner_context.path,
metadata.namespace,
context.path,
namespace,
{
...context.state,
scope: /** @type {import('../../scope').Scope} */ (context.state.scopes.get(node.fragment))
...state,
scope: /** @type {import('../../scope').Scope} */ (state.scopes.get(node.fragment))
},
state.preserve_whitespace,
state.options.preserveComments
);

for (const node of hoisted) {
inner_context.visit(node, state);
context.visit(node, state);
}

if (context.state.options.dev) {
if (state.options.dev) {
const location = /** @type {import('locate-character').Location} */ (locator(node.start));
context.state.template.push(
state.template.push(
t_statement(
b.stmt(
b.call(
Expand All @@ -1416,40 +1400,39 @@ const template_visitors = {
);
}

process_children(trimmed, node, inner_context);
if (body === null) {
process_children(trimmed, node, { ...context, state });
} else {
let id = body;

if (body_expression !== null) {
let body_id;
const expression = body_expression.escape
? b.call('$.escape', body_expression.expression)
: body_expression.expression;
if (expression.type === 'Identifier') {
body_id = expression;
} else {
body_id = b.id(context.state.scope.generate('$$body'));
context.state.template.push(t_statement(b.const(body_id, expression)));
if (body.type !== 'Identifier') {
id = b.id(state.scope.generate('$$body'));
state.template.push(t_statement(b.const(id, body)));
}

// if this is a `<textarea>` value or a contenteditable binding, we only add
// the body if the attribute/binding is falsy
const inner_state = { ...state, template: [], init: [] };
process_children(trimmed, node, { ...context, state: inner_state });

// Use the body expression as the body if it's truthy, otherwise use the inner template
context.state.template.push(
state.template.push(
t_statement(
b.if(
body_id,
b.block(serialize_template([t_expression(body_id)])),
b.block([
...inner_context.state.init,
...serialize_template(inner_context.state.template)
])
id,
b.block(serialize_template([t_expression(id)])),
b.block([...inner_state.init, ...serialize_template(inner_state.template)])
)
)
);
}

if (!VoidElements.includes(node.name) && metadata.namespace !== 'foreign') {
context.state.template.push(t_string(`</${node.name}>`));
if (!VoidElements.includes(node.name) && namespace !== 'foreign') {
state.template.push(t_string(`</${node.name}>`));
}
if (context.state.options.dev) {
context.state.template.push(t_statement(b.stmt(b.call('$.pop_element'))));

if (state.options.dev) {
state.template.push(t_statement(b.stmt(b.call('$.pop_element'))));
}
},
SvelteElement(node, context) {
Expand All @@ -1467,59 +1450,35 @@ const template_visitors = {
context.state.init.push(b.stmt(b.call('$.validate_dynamic_element_tag', b.thunk(tag))));
}

const metadata = {
...context.state.metadata,
namespace: determine_namespace_for_children(node, context.state.metadata.namespace)
};
/** @type {import('./types').ComponentContext} */
const inner_context = {
...context,
state: {
...context.state,
metadata,
template: [],
init: []
}
const state = {
...context.state,
metadata: {
...context.state.metadata,
namespace: determine_namespace_for_children(node, context.state.metadata.namespace)
},
template: [],
init: []
};

const main = /** @type {import('estree').BlockStatement} */ (
context.visit(node.fragment, {
...context.state,
metadata
})
);

serialize_element_attributes(node, inner_context);
serialize_element_attributes(node, { ...context, state });

if (context.state.options.dev) {
context.state.template.push(
t_statement(b.stmt(b.call('$.push_element', tag, b.id('$$payload'))))
);
}

context.state.template.push(
t_statement(
b.if(
tag,
const attributes = b.block([...state.init, ...serialize_template(state.template)]);
const children = /** @type {import('estree').BlockStatement} */ (
context.visit(node.fragment, state)
);

b.stmt(
b.call(
'$.element',
b.id('$$payload'),
tag,
b.thunk(
b.block([
...inner_context.state.init,
...serialize_template(inner_context.state.template)
])
),
b.thunk(main)
)
)
)
),
block_anchor
const body = b.stmt(
b.call('$.element', b.id('$$payload'), tag, b.thunk(attributes), b.thunk(children))
);

context.state.template.push(t_statement(b.if(tag, body)), block_anchor);

if (context.state.options.dev) {
context.state.template.push(t_statement(b.stmt(b.call('$.pop_element'))));
}
Expand Down Expand Up @@ -1834,7 +1793,7 @@ function serialize_element_attributes(node, context) {
/** @type {import('estree').ExpressionStatement[]} */
const lets = [];

/** @type {{ escape: boolean; expression: import('estree').Expression } | null} */
/** @type {import('estree').Expression | null} */
let content = null;

let has_spread = false;
Expand All @@ -1857,10 +1816,7 @@ function serialize_element_attributes(node, context) {
// also see related code in analysis phase
attribute.value[0].data = '\n' + attribute.value[0].data;
}
content = {
escape: true,
expression: serialize_attribute_value(attribute.value, context)
};
content = b.call('$.escape', serialize_attribute_value(attribute.value, context));
} else if (node.name !== 'select') {
// omit value attribute for select elements, it's irrelevant for the initially selected value and has no
// effect on the selected value after the user interacts with the select element (the value _property_ does, but not the attribute)
Expand Down Expand Up @@ -1903,19 +1859,12 @@ function serialize_element_attributes(node, context) {
if (binding?.omit_in_ssr) continue;

if (ContentEditableBindings.includes(attribute.name)) {
content = {
escape: false,
expression: /** @type {import('estree').Expression} */ (
context.visit(attribute.expression)
)
};
content = /** @type {import('estree').Expression} */ (context.visit(attribute.expression));
} else if (attribute.name === 'value' && node.name === 'textarea') {
content = {
escape: true,
expression: /** @type {import('estree').Expression} */ (
context.visit(attribute.expression)
)
};
content = b.call(
'$.escape',
/** @type {import('estree').Expression} */ (context.visit(attribute.expression))
);
} else if (attribute.name === 'group') {
const value_attribute = /** @type {import('#compiler').Attribute | undefined} */ (
node.attributes.find((attr) => attr.type === 'Attribute' && attr.name === 'value')
Expand Down
Loading