chore: update sequencing inside blocks (#10939)

* WIP

* fix timing issue

* compromise

* fix

* add missing before_init

* lint

---------

Co-authored-by: Dominic Gannaway <dg@domgan.com>
pull/10947/head
Rich Harris 12 months ago committed by GitHub
parent 9bbc3328ee
commit 3ce74e47a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -49,6 +49,13 @@ export function client_component(source, analysis, options) {
hoisted: [b.import_all('$', 'svelte/internal')], hoisted: [b.import_all('$', 'svelte/internal')],
node: /** @type {any} */ (null), // populated by the root node node: /** @type {any} */ (null), // populated by the root node
// these should be set by create_block - if they're called outside, it's a bug // these should be set by create_block - if they're called outside, it's a bug
get before_init() {
/** @type {any[]} */
const a = [];
a.push = () =>
error(null, 'INTERNAL', 'before_init.push should not be called outside create_block');
return a;
},
get init() { get init() {
/** @type {any[]} */ /** @type {any[]} */
const a = []; const a = [];

@ -29,6 +29,8 @@ export interface ComponentClientTransformState extends ClientTransformState {
readonly hoisted: Array<Statement | ModuleDeclaration>; readonly hoisted: Array<Statement | ModuleDeclaration>;
readonly events: Set<string>; readonly events: Set<string>;
/** Stuff that happens before the render effect(s) */
readonly before_init: Statement[];
/** Stuff that happens before the render effect(s) */ /** Stuff that happens before the render effect(s) */
readonly init: Statement[]; readonly init: Statement[];
/** Stuff that happens inside the render effect */ /** Stuff that happens inside the render effect */

@ -1005,6 +1005,7 @@ function create_block(parent, name, nodes, context) {
/** @type {import('../types').ComponentClientTransformState} */ /** @type {import('../types').ComponentClientTransformState} */
const state = { const state = {
...context.state, ...context.state,
before_init: [],
init: [], init: [],
update: [], update: [],
after_update: [], after_update: [],
@ -1050,11 +1051,11 @@ function create_block(parent, name, nodes, context) {
args.push(b.false); args.push(b.false);
} }
body.push(b.var(id, b.call('$.open', ...args)), ...state.init); body.push(b.var(id, b.call('$.open', ...args)), ...state.before_init, ...state.init);
close = b.stmt(b.call('$.close', b.id('$$anchor'), id)); close = b.stmt(b.call('$.close', b.id('$$anchor'), id));
} else if (is_single_child_not_needing_template) { } else if (is_single_child_not_needing_template) {
context.visit(trimmed[0], state); context.visit(trimmed[0], state);
body.push(...state.init); body.push(...state.before_init, ...state.init);
} else if (trimmed.length > 0) { } else if (trimmed.length > 0) {
const id = b.id(context.state.scope.generate('fragment')); const id = b.id(context.state.scope.generate('fragment'));
@ -1071,7 +1072,11 @@ function create_block(parent, name, nodes, context) {
state state
}); });
body.push(b.var(id, b.call('$.space_frag', b.id('$$anchor'))), ...state.init); body.push(
b.var(id, b.call('$.space_frag', b.id('$$anchor'))),
...state.before_init,
...state.init
);
close = b.stmt(b.call('$.close', b.id('$$anchor'), id)); close = b.stmt(b.call('$.close', b.id('$$anchor'), id));
} else { } else {
/** @type {(is_text: boolean) => import('estree').Expression} */ /** @type {(is_text: boolean) => import('estree').Expression} */
@ -1107,12 +1112,12 @@ function create_block(parent, name, nodes, context) {
body.push(b.var(id, b.call('$.open_frag', ...args))); body.push(b.var(id, b.call('$.open_frag', ...args)));
} }
body.push(...state.init); body.push(...state.before_init, ...state.init);
close = b.stmt(b.call('$.close_frag', b.id('$$anchor'), id)); close = b.stmt(b.call('$.close_frag', b.id('$$anchor'), id));
} }
} else { } else {
body.push(...state.init); body.push(...state.before_init, ...state.init);
} }
if (state.update.length > 0) { if (state.update.length > 0) {
@ -1256,6 +1261,9 @@ function serialize_event_handler(node, { state, visit }) {
function serialize_event(node, context) { function serialize_event(node, context) {
const state = context.state; const state = context.state;
/** @type {import('estree').Statement} */
let statement;
if (node.expression) { if (node.expression) {
let handler = serialize_event_handler(node, context); let handler = serialize_event_handler(node, context);
const event_name = node.name; const event_name = node.name;
@ -1291,7 +1299,7 @@ function serialize_event(node, context) {
delegated_assignment = handler; delegated_assignment = handler;
} }
state.after_update.push( state.init.push(
b.stmt( b.stmt(
b.assignment( b.assignment(
'=', '=',
@ -1323,14 +1331,19 @@ function serialize_event(node, context) {
} }
// Events need to run in order with bindings/actions // Events need to run in order with bindings/actions
state.after_update.push(b.stmt(b.call('$.event', ...args))); statement = b.stmt(b.call('$.event', ...args));
} else { } else {
state.after_update.push( statement = b.stmt(
b.stmt( b.call('$.event', b.literal(node.name), state.node, serialize_event_handler(node, context))
b.call('$.event', b.literal(node.name), state.node, serialize_event_handler(node, context))
)
); );
} }
const parent = /** @type {import('#compiler').SvelteNode} */ (context.path.at(-1));
if (parent.type === 'SvelteDocument' || parent.type === 'SvelteWindow') {
state.before_init.push(statement);
} else {
state.after_update.push(statement);
}
} }
/** /**
@ -1720,11 +1733,9 @@ export const template_visitors = {
} }
if (is_reactive) { if (is_reactive) {
context.state.after_update.push( context.state.init.push(b.stmt(b.call('$.snippet', b.thunk(snippet_function), ...args)));
b.stmt(b.call('$.snippet', b.thunk(snippet_function), ...args))
);
} else { } else {
context.state.after_update.push( context.state.init.push(
b.stmt( b.stmt(
(node.expression.type === 'CallExpression' ? b.call : b.maybe_call)( (node.expression.type === 'CallExpression' ? b.call : b.maybe_call)(
snippet_function, snippet_function,
@ -2029,6 +2040,7 @@ export const template_visitors = {
state: { state: {
...context.state, ...context.state,
node: element_id, node: element_id,
before_init: [],
init: [], init: [],
update: [], update: [],
after_update: [] after_update: []
@ -2091,7 +2103,7 @@ export const template_visitors = {
} }
}) })
); );
context.state.after_update.push( context.state.init.push(
b.stmt( b.stmt(
b.call( b.call(
'$.element', '$.element',
@ -2372,7 +2384,7 @@ export const template_visitors = {
); );
} }
context.state.after_update.push(b.stmt(b.call(callee, ...args))); context.state.init.push(b.stmt(b.call(callee, ...args)));
}, },
IfBlock(node, context) { IfBlock(node, context) {
context.state.template.push('<!>'); context.state.template.push('<!>');
@ -2423,12 +2435,12 @@ export const template_visitors = {
args.push(b.literal(true)); args.push(b.literal(true));
} }
context.state.after_update.push(b.stmt(b.call('$.if', ...args))); context.state.init.push(b.stmt(b.call('$.if', ...args)));
}, },
AwaitBlock(node, context) { AwaitBlock(node, context) {
context.state.template.push('<!>'); context.state.template.push('<!>');
context.state.after_update.push( context.state.init.push(
b.stmt( b.stmt(
b.call( b.call(
'$.await', '$.await',
@ -2470,7 +2482,7 @@ export const template_visitors = {
context.state.template.push('<!>'); context.state.template.push('<!>');
const key = /** @type {import('estree').Expression} */ (context.visit(node.expression)); const key = /** @type {import('estree').Expression} */ (context.visit(node.expression));
const body = /** @type {import('estree').Expression} */ (context.visit(node.fragment)); const body = /** @type {import('estree').Expression} */ (context.visit(node.fragment));
context.state.after_update.push( context.state.init.push(
b.stmt(b.call('$.key', context.state.node, b.thunk(key), b.arrow([b.id('$$anchor')], body))) b.stmt(b.call('$.key', context.state.node, b.thunk(key), b.arrow([b.id('$$anchor')], body)))
); );
}, },
@ -2791,7 +2803,7 @@ export const template_visitors = {
if (binding !== null && binding.kind !== 'normal') { if (binding !== null && binding.kind !== 'normal') {
// Handle dynamic references to what seems like static inline components // Handle dynamic references to what seems like static inline components
const component = serialize_inline_component(node, '$$component', context); const component = serialize_inline_component(node, '$$component', context);
context.state.after_update.push( context.state.init.push(
b.stmt( b.stmt(
b.call( b.call(
'$.component', '$.component',
@ -2808,12 +2820,12 @@ export const template_visitors = {
return; return;
} }
const component = serialize_inline_component(node, node.name, context); const component = serialize_inline_component(node, node.name, context);
context.state.after_update.push(component); context.state.init.push(component);
}, },
SvelteSelf(node, context) { SvelteSelf(node, context) {
context.state.template.push('<!>'); context.state.template.push('<!>');
const component = serialize_inline_component(node, context.state.analysis.name, context); const component = serialize_inline_component(node, context.state.analysis.name, context);
context.state.after_update.push(component); context.state.init.push(component);
}, },
SvelteComponent(node, context) { SvelteComponent(node, context) {
context.state.template.push('<!>'); context.state.template.push('<!>');
@ -2822,7 +2834,7 @@ export const template_visitors = {
if (context.state.options.dev) { if (context.state.options.dev) {
component = b.stmt(b.call('$.validate_dynamic_component', b.thunk(b.block([component])))); component = b.stmt(b.call('$.validate_dynamic_component', b.thunk(b.block([component]))));
} }
context.state.after_update.push( context.state.init.push(
b.stmt( b.stmt(
b.call( b.call(
'$.component', '$.component',
@ -2974,7 +2986,7 @@ export const template_visitors = {
: b.member(b.member(b.id('$$props'), b.id('$$slots')), name, true, true); : b.member(b.member(b.id('$$props'), b.id('$$slots')), name, true, true);
const slot = b.call('$.slot', context.state.node, expression, props_expression, fallback); const slot = b.call('$.slot', context.state.node, expression, props_expression, fallback);
context.state.after_update.push(b.stmt(slot)); context.state.init.push(b.stmt(slot));
}, },
SvelteHead(node, context) { SvelteHead(node, context) {
// TODO attributes? // TODO attributes?

@ -113,7 +113,7 @@ export function capture_fragment_from_node(node) {
if ( if (
node.nodeType === 8 && node.nodeType === 8 &&
/** @type {Comment} */ (node).data === '[' && /** @type {Comment} */ (node).data === '[' &&
hydrate_nodes[hydrate_nodes.length - 1] !== node hydrate_nodes?.[hydrate_nodes.length - 1] !== node
) { ) {
const nodes = /** @type {Node[]} */ (get_hydrate_nodes(node)); const nodes = /** @type {Node[]} */ (get_hydrate_nodes(node));
const last_child = nodes[nodes.length - 1] || node; const last_child = nodes[nodes.length - 1] || node;

@ -14,6 +14,6 @@ export default test({
}; };
}, },
test({ assert }) { test({ assert }) {
assert.deepEqual(result, ['import_action', 'each_action']); assert.deepEqual(result, ['each_action', 'import_action']); // ideally this would be reversed, but it doesn't matter a whole lot
} }
}); });

@ -28,9 +28,9 @@ export default function State_proxy_literal($$anchor, $$props) {
var button = $.sibling($.sibling(input_1, true)); var button = $.sibling($.sibling(input_1, true));
button.__click = [reset, str, tpl];
$.bind_value(input, () => $.get(str), ($$value) => $.set(str, $$value)); $.bind_value(input, () => $.get(str), ($$value) => $.set(str, $$value));
$.bind_value(input_1, () => $.get(tpl), ($$value) => $.set(tpl, $$value)); $.bind_value(input_1, () => $.get(tpl), ($$value) => $.set(tpl, $$value));
button.__click = [reset, str, tpl];
$.close_frag($$anchor, fragment); $.close_frag($$anchor, fragment);
$.pop(); $.pop();
} }

Loading…
Cancel
Save