out-of-order-rendering
Rich Harris 2 days ago
parent 33d37924cc
commit 3fc998f1cd

@ -339,14 +339,14 @@ function transform_body(program, context) {
// TODO we likely need to account for updates that happen after the declaration,
// e.g. `let obj = $state()` followed by a later `obj = {...}`, otherwise
// a synchronous `{obj.foo}` will fail
}
for (const binding of context.state.scope.declarations.values()) {
// if the binding is updated (TODO or passed to a function, in which case it
// could be mutated), play it safe and block until the end. In future we
// could develop more sophisticated static analysis to optimise further
if (binding.updated) {
binding.blocker = b.member(promises, b.literal(statements.length - 1), true);
for (const binding of context.state.scope.declarations.values()) {
// if the binding is updated (TODO or passed to a function, in which case it
// could be mutated), play it safe and block until the end. In future we
// could develop more sophisticated static analysis to optimise further
if (binding.updated) {
binding.blocker = b.member(promises, b.literal(statements.length - 1), true);
}
}
}

@ -12,5 +12,5 @@ export function HtmlTag(node, context) {
const expression = /** @type {Expression} */ (context.visit(node.expression));
const call = b.call('$.html', expression);
context.state.template.push(create_push(call, node.metadata.expression));
context.state.template.push(create_push(call, node.metadata.expression, true));
}

@ -209,14 +209,14 @@ function transform_body(program, context) {
// TODO we likely need to account for updates that happen after the declaration,
// e.g. `let obj = $state()` followed by a later `obj = {...}`, otherwise
// a synchronous `{obj.foo}` will fail
}
for (const binding of context.state.scope.declarations.values()) {
// if the binding is updated (TODO or passed to a function, in which case it
// could be mutated), play it safe and block until the end. In future we
// could develop more sophisticated static analysis to optimise further
if (binding.updated) {
binding.blocker = b.member(promises, b.literal(statements.length - 1), true);
for (const binding of context.state.scope.declarations.values()) {
// if the binding is updated (TODO or passed to a function, in which case it
// could be mutated), play it safe and block until the end. In future we
// could develop more sophisticated static analysis to optimise further
if (binding.updated) {
binding.blocker = b.member(promises, b.literal(statements.length - 1), true);
}
}
}

@ -77,12 +77,11 @@ export function process_children(nodes, { visit, state }) {
}
for (const node of nodes) {
if (node.type === 'ExpressionTag' && node.metadata.expression.has_await) {
if (node.type === 'ExpressionTag' && node.metadata.expression.is_async()) {
flush();
const visited = /** @type {Expression} */ (visit(node.expression));
state.template.push(
b.stmt(b.call('$$renderer.push', b.thunk(b.call('$.escape', visited), true)))
);
const expression = /** @type {Expression} */ (visit(node.expression));
state.template.push(create_push(b.call('$.escape', expression), node.metadata.expression));
} else if (node.type === 'Text' || node.type === 'Comment' || node.type === 'ExpressionTag') {
sequence.push(node);
} else {
@ -277,26 +276,33 @@ export function create_child_block(body, async) {
* @param {BlockStatement | Expression} body
* @param {ArrayExpression} blockers
* @param {boolean} has_await
* @param {boolean} markers
*/
export function create_async_block(body, blockers = b.array([]), has_await = true) {
export function create_async_block(body, blockers = b.array([]), has_await = true, markers = true) {
return b.stmt(
b.call('$$renderer.async', blockers, b.arrow([b.id('$$renderer')], body, has_await))
b.call(
'$$renderer.async',
blockers,
b.arrow([b.id('$$renderer')], body, has_await),
markers && b.true
)
);
}
/**
* @param {Expression} expression
* @param {ExpressionMetadata} metadata
* @param {boolean} markers
* @returns {Expression | Statement}
*/
export function create_push(expression, metadata) {
export function create_push(expression, metadata, markers = false) {
if (metadata.is_async()) {
let statement = b.stmt(b.call('$$renderer.push', b.thunk(expression, metadata.has_await)));
const blockers = metadata.blockers();
if (blockers.elements.length > 0) {
statement = create_async_block(b.block([statement]), blockers, false);
statement = create_async_block(b.block([statement]), blockers, false, markers);
}
return statement;

@ -102,8 +102,9 @@ export class Renderer {
/**
* @param {Array<Promise<void>>} blockers
* @param {(renderer: Renderer) => void} fn
* @param {boolean} markers
*/
async(blockers, fn) {
async(blockers, fn, markers) {
let callback = fn;
if (blockers.length > 0) {
@ -123,9 +124,9 @@ export class Renderer {
};
}
this.#out.push(BLOCK_OPEN);
if (markers) this.#out.push(BLOCK_OPEN);
this.child(callback);
this.#out.push(BLOCK_CLOSE);
if (markers) this.#out.push(BLOCK_CLOSE);
}
/**

@ -2,24 +2,28 @@ import 'svelte/internal/flags/async';
import * as $ from 'svelte/internal/server';
export default function Async_each_fallback_hoisting($$renderer) {
$$renderer.async([], async ($$renderer) => {
const each_array = $.ensure_array_like((await $.save(Promise.resolve([])))());
$$renderer.async(
[],
async ($$renderer) => {
const each_array = $.ensure_array_like((await $.save(Promise.resolve([])))());
if (each_array.length !== 0) {
$$renderer.push('<!--[-->');
if (each_array.length !== 0) {
$$renderer.push('<!--[-->');
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let item = each_array[$$index];
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let item = each_array[$$index];
$$renderer.push(`<!---->`);
$$renderer.push(async () => $.escape(await Promise.reject('This should never be reached')));
}
} else {
$$renderer.push('<!--[!-->');
$$renderer.push(`<!---->`);
$$renderer.push(async () => $.escape(await Promise.reject('This should never be reached')));
$$renderer.push(async () => $.escape(await Promise.resolve(4)));
}
} else {
$$renderer.push('<!--[!-->');
$$renderer.push(`<!---->`);
$$renderer.push(async () => $.escape(await Promise.resolve(4)));
}
});
},
true
);
$$renderer.push(`<!--]-->`);
}

@ -8,16 +8,20 @@ export default function Async_each_hoisting($$renderer) {
$$renderer.push(`<!--[-->`);
$$renderer.async([], async ($$renderer) => {
const each_array = $.ensure_array_like((await $.save(Promise.resolve([first, second, third])))());
$$renderer.async(
[],
async ($$renderer) => {
const each_array = $.ensure_array_like((await $.save(Promise.resolve([first, second, third])))());
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let item = each_array[$$index];
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let item = each_array[$$index];
$$renderer.push(`<!---->`);
$$renderer.push(async () => $.escape(await item));
}
});
$$renderer.push(`<!---->`);
$$renderer.push(async () => $.escape(await item));
}
},
true
);
$$renderer.push(`<!--]-->`);
}

@ -2,15 +2,19 @@ import 'svelte/internal/flags/async';
import * as $ from 'svelte/internal/server';
export default function Async_if_alternate_hoisting($$renderer) {
$$renderer.async([], async ($$renderer) => {
if ((await $.save(Promise.resolve(false)))()) {
$$renderer.push('<!--[-->');
$$renderer.push(async () => $.escape(await Promise.reject('no no no')));
} else {
$$renderer.push('<!--[!-->');
$$renderer.push(async () => $.escape(await Promise.resolve('yes yes yes')));
}
});
$$renderer.async(
[],
async ($$renderer) => {
if ((await $.save(Promise.resolve(false)))()) {
$$renderer.push('<!--[-->');
$$renderer.push(async () => $.escape(await Promise.reject('no no no')));
} else {
$$renderer.push('<!--[!-->');
$$renderer.push(async () => $.escape(await Promise.resolve('yes yes yes')));
}
},
true
);
$$renderer.push(`<!--]-->`);
}

@ -2,15 +2,19 @@ import 'svelte/internal/flags/async';
import * as $ from 'svelte/internal/server';
export default function Async_if_hoisting($$renderer) {
$$renderer.async([], async ($$renderer) => {
if ((await $.save(Promise.resolve(true)))()) {
$$renderer.push('<!--[-->');
$$renderer.push(async () => $.escape(await Promise.resolve('yes yes yes')));
} else {
$$renderer.push('<!--[!-->');
$$renderer.push(async () => $.escape(await Promise.reject('no no no')));
}
});
$$renderer.async(
[],
async ($$renderer) => {
if ((await $.save(Promise.resolve(true)))()) {
$$renderer.push('<!--[-->');
$$renderer.push(async () => $.escape(await Promise.resolve('yes yes yes')));
} else {
$$renderer.push('<!--[!-->');
$$renderer.push(async () => $.escape(await Promise.reject('no no no')));
}
},
true
);
$$renderer.push(`<!--]-->`);
}

@ -18,24 +18,28 @@ export default function Async_in_derived($$renderer, $$props) {
}
]);
$$renderer.async([], async ($$renderer) => {
if (true) {
$$renderer.push('<!--[-->');
const yes1 = (await $.save(1))();
const yes2 = foo((await $.save(1))());
const no1 = (async () => {
return await 1;
})();
const no2 = (async () => {
return await 1;
})();
} else {
$$renderer.push('<!--[!-->');
}
});
$$renderer.async(
[],
async ($$renderer) => {
if (true) {
$$renderer.push('<!--[-->');
const yes1 = (await $.save(1))();
const yes2 = foo((await $.save(1))());
const no1 = (async () => {
return await 1;
})();
const no2 = (async () => {
return await 1;
})();
} else {
$$renderer.push('<!--[!-->');
}
},
true
);
$$renderer.push(`<!--]-->`);
});

Loading…
Cancel
Save