diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index 7d4b6b325f..b67d16c905 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -103,6 +103,154 @@ export function serialize_get_binding(node, state) { return node; } +/** + * @param {import('estree').Expression} expression + * @returns {boolean} + */ +function is_async(expression) { + switch (expression.type) { + case 'ArrayExpression': { + for (const element of expression.elements) { + if (!element) { + continue; + } else if (element.type === 'SpreadElement') { + if (is_async(element.argument)) { + return true; + } + } else { + if (is_async(element)) { + return true; + } + } + } + + return false; + } + case 'ArrowFunctionExpression': + case 'FunctionExpression': { + return expression.async ?? false; + } + case 'AwaitExpression': { + return true; + } + case 'BinaryExpression': { + return is_async(expression.left) || is_async(expression.right); + } + case 'CallExpression': + case 'NewExpression': { + const callee_is_async = expression.callee.type === 'Super' ? false : is_async(expression.callee); + if (callee_is_async) { + return true; + } + + for (const element of expression.arguments) { + if (element.type === 'SpreadElement') { + if (is_async(element.argument)) { + return true; + } + } else { + if (is_async(element)) { + return true; + } + } + } + + return false; + } + case 'ChainExpression': { + return is_async(expression.expression); + } + case 'ClassExpression': { + return false; + } + case 'ConditionalExpression': { + return is_async(expression.test) || is_async(expression.alternate) || is_async(expression.consequent); + } + case 'Identifier': { + return false; + } + case 'Literal': { + return false; + } + case 'ImportExpression': { + return is_async(expression.source); + } + case 'LogicalExpression': { + return is_async(expression.left) || is_async(expression.right); + } + case 'MemberExpression': { + const object_is_async = expression.object.type === 'Super' ? false : is_async(expression.object); + if (object_is_async) { + return true; + } + + const property_is_async = expression.property.type === 'PrivateIdentifier' ? false : is_async(expression.property); + if (property_is_async) { + return true; + } + + return false; + } + case 'MetaProperty': { + return false; + } + case 'ObjectExpression': { + for (const property of expression.properties) { + if (property.type === 'SpreadElement') { + if (is_async(property.argument)) { + return true; + } + } else { + const key_is_async = property.key.type === 'PrivateIdentifier' ? false : is_async(property.key); + if (key_is_async) { + return true; + } + + const value_is_async = is_async(/** @type {import('estree').Expression} */ (property.value)); + if (value_is_async) { + return true; + } + } + } + + return false + } + case 'SequenceExpression': { + for (const subexpression of expression.expressions) { + if (is_async(subexpression)) { + return true; + } + } + return false; + } + case 'TaggedTemplateExpression': { + return is_async(expression.tag) || is_async(expression.quasi); + } + case 'TemplateLiteral': { + for (const subexpression of expression.expressions) { + if (is_async(subexpression)) { + return true; + } + } + return false; + } + case 'ThisExpression': { + return false; + } + case 'UnaryExpression': { + return is_async(expression.argument); + } + case 'UpdateExpression': { + return is_async(expression.argument); + } + case 'YieldExpression': { + return expression.argument ? is_async(expression.argument) : false; + } + default: + return false; + } +} + /** * @template {import('./types').ClientTransformState} State * @param {import('estree').AssignmentExpression} node @@ -141,18 +289,29 @@ export function serialize_set_binding(node, context, fallback) { return fallback(); } - return b.call( - b.arrow( - [b.id(tmp_id)], - b.block([ - b.stmt(b.sequence(assignments)), - // return because it could be used in a nested expression where the value is needed. - // example: { foo: ({ bar } = { bar: 1 })} - b.return(b.id(tmp_id)) - ]) - ), - /** @type {import('estree').Expression} */ (visit(node.right)) + const rhs_expression = /** @type {import('estree').Expression} */ (visit(node.right)); + const iife_is_async = is_async(rhs_expression) || assignments.some((assignment) => is_async(assignment)); + + let iife = b.arrow( + [], + b.block([ + b.const(tmp_id, rhs_expression), + b.stmt(b.sequence(assignments)), + // return because it could be used in a nested expression where the value is needed. + // example: { foo: ({ bar } = { bar: 1 })} + b.return(b.id(tmp_id)) + ]) ); + if (iife_is_async) { + iife = b.async(iife); + } + + let iife_call = /** @type {import('estree').Expression} */ (b.call(iife)); + if (iife_is_async) { + iife_call = b.await(iife_call); + } + + return iife_call; } if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') { diff --git a/packages/svelte/src/compiler/utils/builders.js b/packages/svelte/src/compiler/utils/builders.js index d98b840a19..3a44380f67 100644 --- a/packages/svelte/src/compiler/utils/builders.js +++ b/packages/svelte/src/compiler/utils/builders.js @@ -43,6 +43,23 @@ export function assignment(operator, left, right) { return { type: 'AssignmentExpression', operator, left, right }; } +/** + * @template T + * @param {T & import('estree').BaseFunction} func + * @returns {T & import('estree').BaseFunction} + */ +export function async(func) { + return { ...func, async: true }; +} + +/** + * @param {import('estree').Expression} argument + * @returns {import('estree').AwaitExpression} + */ +export function await_builder(argument) { + return { type: 'AwaitExpression', argument }; +} + /** * @param {import('estree').BinaryOperator} operator * @param {import('estree').Expression} left @@ -572,6 +589,7 @@ export function throw_error(str) { } export { + await_builder as await, new_builder as new, let_builder as let, const_builder as const,