Skip to content
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

feat: oneling optimisation #11274

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -70,6 +70,7 @@ export interface ComponentClientTransformState extends ClientTransformState {
};
readonly preserve_whitespace: boolean;

readonly optimisation_oneling: boolean;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could use a comment. I 'm not sure I'd know what optimisation_oneling meant from the name alone

/** The anchor node for the current context */
readonly node: Identifier;
}
Expand Down
Expand Up @@ -1039,7 +1039,8 @@ function create_block(parent, name, nodes, context) {
},
namespace,
bound_contenteditable: context.state.metadata.bound_contenteditable
}
},
optimisation_oneling: false
};

for (const node of hoisted) {
Expand Down Expand Up @@ -1507,7 +1508,7 @@ function process_children(nodes, expression, is_element, { visit, state }) {
// get hoisted inside clean_nodes?
visit(node, state);
} else {
if (node.type === 'EachBlock' && nodes.length === 1 && is_element) {
if (node.type === 'EachBlock' && state.optimisation_oneling) {
node.metadata.is_controlled = true;
visit(node, state);
} else {
Expand Down Expand Up @@ -1659,7 +1660,7 @@ export const template_visitors = {
context.state.template.push(`<!--${node.data}-->`);
},
HtmlTag(node, context) {
context.state.template.push('<!>');
if (!context.state.optimisation_oneling) context.state.template.push('<!>');

// push into init, so that bindings run afterwards, which might trigger another run and override hydration
context.state.init.push(
Expand Down Expand Up @@ -1764,7 +1765,7 @@ export const template_visitors = {
);
},
RenderTag(node, context) {
context.state.template.push('<!>');
if (!context.state.optimisation_oneling) context.state.template.push('<!>');
const callee = unwrap_optional(node.expression).callee;
const raw_args = unwrap_optional(node.expression).arguments;
const is_reactive =
Expand Down Expand Up @@ -2071,6 +2072,21 @@ export const template_visitors = {
state.options.preserveComments
);

const optimisation_oneling =
trimmed.length === 1 &&
(trimmed[0].type === 'Component' ||
trimmed[0].type === 'HtmlTag' ||
trimmed[0].type === 'RenderTag' ||
trimmed[0].type === 'IfBlock' ||
trimmed[0].type === 'AwaitBlock' ||
trimmed[0].type === 'KeyBlock' ||
trimmed[0].type === 'EachBlock' ||
trimmed[0].type === 'SvelteSelf' ||
trimmed[0].type === 'SvelteElement' ||
trimmed[0].type === 'SvelteComponent' ||
trimmed[0].type === 'SlotElement');
state.optimisation_oneling = optimisation_oneling;

for (const node of hoisted) {
context.visit(node, state);
}
Expand All @@ -2079,7 +2095,7 @@ export const template_visitors = {
trimmed,
() =>
b.call(
'$.child',
optimisation_oneling ? '$.anchor' : '$.child',
node.name === 'template'
? b.member(context.state.node, b.id('content'))
: context.state.node
Expand All @@ -2098,7 +2114,7 @@ export const template_visitors = {
}
},
SvelteElement(node, context) {
context.state.template.push(`<!>`);
if (!context.state.optimisation_oneling) context.state.template.push(`<!>`);

/** @type {Array<import('#compiler').Attribute | import('#compiler').SpreadAttribute>} */
const attributes = [];
Expand Down Expand Up @@ -2456,7 +2472,7 @@ export const template_visitors = {
context.state.init.push(b.stmt(b.call('$.each', ...args)));
},
IfBlock(node, context) {
context.state.template.push('<!>');
if (!context.state.optimisation_oneling) context.state.template.push('<!>');

const consequent = /** @type {import('estree').BlockStatement} */ (
context.visit(node.consequent)
Expand Down Expand Up @@ -2507,7 +2523,7 @@ export const template_visitors = {
context.state.init.push(b.stmt(b.call('$.if', ...args)));
},
AwaitBlock(node, context) {
context.state.template.push('<!>');
if (!context.state.optimisation_oneling) context.state.template.push('<!>');

context.state.init.push(
b.stmt(
Expand Down Expand Up @@ -2548,7 +2564,7 @@ export const template_visitors = {
);
},
KeyBlock(node, context) {
context.state.template.push('<!>');
if (!context.state.optimisation_oneling) context.state.template.push('<!>');
const key = /** @type {import('estree').Expression} */ (context.visit(node.expression));
const body = /** @type {import('estree').Expression} */ (context.visit(node.fragment));
context.state.init.push(
Expand Down Expand Up @@ -2869,7 +2885,7 @@ export const template_visitors = {
}
},
Component(node, context) {
context.state.template.push('<!>');
if (!context.state.optimisation_oneling) context.state.template.push('<!>');

const binding = context.state.scope.get(
node.name.includes('.') ? node.name.slice(0, node.name.indexOf('.')) : node.name
Expand Down Expand Up @@ -2897,12 +2913,12 @@ export const template_visitors = {
context.state.init.push(component);
},
SvelteSelf(node, context) {
context.state.template.push('<!>');
if (!context.state.optimisation_oneling) context.state.template.push('<!>');
const component = serialize_inline_component(node, context.state.analysis.name, context);
context.state.init.push(component);
},
SvelteComponent(node, context) {
context.state.template.push('<!>');
if (!context.state.optimisation_oneling) context.state.template.push('<!>');

let component = serialize_inline_component(node, '$$component', context);
if (context.state.options.dev) {
Expand Down Expand Up @@ -3002,7 +3018,7 @@ export const template_visitors = {
},
SlotElement(node, context) {
// <slot {a}>fallback</slot> --> $.slot($$slots.default, { get a() { .. } }, () => ...fallback);
context.state.template.push('<!>');
if (!context.state.optimisation_oneling) context.state.template.push('<!>');

/** @type {import('estree').Property[]} */
const props = [];
Expand Down
Expand Up @@ -1249,10 +1249,10 @@ const template_visitors = {
},
HtmlTag(node, context) {
const state = context.state;
state.template.push(block_open);
if (!state.optimisation_oneling) 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(block_close);
if (!state.optimisation_oneling) state.template.push(block_close);
},
ConstTag(node, { state, visit }) {
const declaration = node.declaration.declarations[0];
Expand Down Expand Up @@ -1284,7 +1284,7 @@ const template_visitors = {
RenderTag(node, context) {
const state = context.state;

state.template.push(block_open);
if (!state.optimisation_oneling) state.template.push(block_open);

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

state.template.push(block_close);
if (!state.optimisation_oneling) state.template.push(block_close);
},
ClassDirective(node) {
throw new Error('Node should have been handled elsewhere');
Expand Down Expand Up @@ -1359,6 +1359,21 @@ const template_visitors = {
state.options.preserveComments
);

const optimisation_oneling =
trimmed.length === 1 &&
(trimmed[0].type === 'Component' ||
trimmed[0].type === 'HtmlTag' ||
trimmed[0].type === 'RenderTag' ||
trimmed[0].type === 'IfBlock' ||
trimmed[0].type === 'AwaitBlock' ||
trimmed[0].type === 'KeyBlock' ||
trimmed[0].type === 'EachBlock' ||
trimmed[0].type === 'SvelteSelf' ||
trimmed[0].type === 'SvelteElement' ||
trimmed[0].type === 'SvelteComponent' ||
trimmed[0].type === 'SlotElement');
state.optimisation_oneling = optimisation_oneling;

for (const node of hoisted) {
inner_context.visit(node, state);
}
Expand Down Expand Up @@ -1435,7 +1450,7 @@ const template_visitors = {
}
};

context.state.template.push(block_open);
if (!context.state.optimisation_oneling) context.state.template.push(block_open);

const main = create_block(node, node.fragment.nodes, {
...context,
Expand Down Expand Up @@ -1470,16 +1485,16 @@ const template_visitors = {
)
)
)
),
block_close
)
);
if (!context.state.optimisation_oneling) context.state.template.push(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;
state.template.push(block_open);
if (!context.state.optimisation_oneling) state.template.push(block_open);

const each_node_meta = node.metadata;
const collection = /** @type {import('estree').Expression} */ (context.visit(node.expression));
Expand Down Expand Up @@ -1529,23 +1544,25 @@ const template_visitors = {
t_statement(
b.if(
b.binary('!==', b.member(array_id, b.id('length')), b.literal(0)),
b.block([for_loop, close]),
b.block(!context.state.optimisation_oneling ? [for_loop, close] : [for_loop]),
b.block(fallback)
)
)
);
} else {
state.template.push(t_statement(for_loop), t_statement(close));
state.template.push(t_statement(for_loop));
if (!context.state.optimisation_oneling) state.template.push(t_statement(close));
}
},
IfBlock(node, context) {
const state = context.state;
state.template.push(block_open);
if (!context.state.optimisation_oneling) state.template.push(block_open);

const consequent = create_block(node, node.consequent.nodes, context);
const alternate = node.alternate ? create_block(node, node.alternate.nodes, context) : [];

consequent.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE))));
if (!context.state.optimisation_oneling)
consequent.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE))));
alternate.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE_ELSE))));

state.template.push(
Expand All @@ -1560,7 +1577,7 @@ const template_visitors = {
},
AwaitBlock(node, context) {
const state = context.state;
state.template.push(block_open);
if (!context.state.optimisation_oneling) state.template.push(block_open);

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

state.template.push(block_close);
if (!context.state.optimisation_oneling) state.template.push(block_close);
},
KeyBlock(node, context) {
const state = context.state;
state.template.push(block_open);
if (!state.optimisation_oneling) 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(block_close);
if (!state.optimisation_oneling) state.template.push(block_close);
},
SnippetBlock(node, context) {
const fn = b.function_declaration(
Expand All @@ -1620,28 +1637,28 @@ const template_visitors = {
},
Component(node, context) {
const state = context.state;
state.template.push(block_open);
if (!context.state.optimisation_oneling) state.template.push(block_open);
const call = serialize_inline_component(node, node.name, context);
state.template.push(t_statement(call));
state.template.push(block_close);
if (!context.state.optimisation_oneling) state.template.push(block_close);
},
SvelteSelf(node, context) {
const state = context.state;
state.template.push(block_open);
if (!context.state.optimisation_oneling) 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(block_close);
if (!context.state.optimisation_oneling) state.template.push(block_close);
},
SvelteComponent(node, context) {
const state = context.state;
state.template.push(block_open);
if (!state.optimisation_oneling) 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(block_close);
if (!state.optimisation_oneling) state.template.push(block_close);
},
LetDirective(node, { state }) {
if (node.expression && node.expression.type !== 'Identifier') {
Expand Down Expand Up @@ -1724,7 +1741,7 @@ const template_visitors = {
},
SlotElement(node, context) {
const state = context.state;
state.template.push(block_open);
if (!state.optimisation_oneling) state.template.push(block_open);

/** @type {import('estree').Property[]} */
const props = [];
Expand Down Expand Up @@ -1771,7 +1788,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(block_close);
if (!state.optimisation_oneling) state.template.push(block_close);
},
SvelteHead(node, context) {
const state = context.state;
Expand Down
Expand Up @@ -53,6 +53,7 @@ export interface ComponentServerTransformState extends ServerTransformState {
namespace: Namespace;
};
readonly preserve_whitespace: boolean;
readonly optimisation_oneling: boolean;
}

export type Context = import('zimmerframe').Context<SvelteNode, ServerTransformState>;
Expand Down
2 changes: 2 additions & 0 deletions packages/svelte/src/compiler/types/template.d.ts
Expand Up @@ -293,6 +293,8 @@ export interface RegularElement extends BaseElement {
/** `true` if contains a SpreadAttribute */
has_spread: boolean;
scoped: boolean;
/** `true` if only has one child */
optimisation_oneling: boolean;
};
}

Expand Down
8 changes: 2 additions & 6 deletions packages/svelte/src/internal/client/dom/blocks/each.js
Expand Up @@ -9,7 +9,7 @@ import {
HYDRATION_START
} from '../../../../constants.js';
import { hydrate_anchor, hydrate_nodes, hydrating, set_hydrating } from '../hydration.js';
import { clear_text_content, empty } from '../operations.js';
import { clear_text_content, empty, anchor as get_anchor } from '../operations.js';
import { remove } from '../reconciler.js';
import { untrack } from '../../runtime.js';
import {
Expand Down Expand Up @@ -99,11 +99,7 @@ export function each(anchor, flags, get_collection, get_key, render_fn, fallback
if (is_controlled) {
var parent_node = /** @type {Element} */ (anchor);

anchor = hydrating
? /** @type {Comment | Text} */ (
hydrate_anchor(/** @type {Comment | Text} */ (parent_node.firstChild))
)
: parent_node.appendChild(empty());
anchor = get_anchor(parent_node);
}

/** @type {import('#client').Effect | null} */
Expand Down