Skip to content
Merged
Show file tree
Hide file tree
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
3 changes: 1 addition & 2 deletions packages/svelte/src/compiler/phases/1-parse/utils/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ export function create_fragment(transparent = false) {
transparent,
dynamic: false,
has_await: false,
// name is added later, after we've done scope analysis
hoisted_promises: { id: b.id('$$promises'), promises: [] }
is_async: false
}
};
}
3 changes: 1 addition & 2 deletions packages/svelte/src/compiler/phases/2-analyze/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -542,8 +542,7 @@ export function analyze_component(root, source, options) {
source,
snippet_renderers: new Map(),
snippets: new Set(),
async_deriveds: new Set(),
hoisted_promises: new Map()
async_deriveds: new Set()
};

if (!runes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,6 @@ export function AwaitExpression(node, context) {
context.state.fragment.metadata.has_await = true;
}

if (context.state.fragment) {
// const len = context.state.fragment.metadata.hoisted_promises.promises.push(node.argument);
// context.state.analysis.hoisted_promises.set(
// node.argument,
// b.member(context.state.fragment.metadata.hoisted_promises.id, b.literal(len - 1), true)
// );
}

suspend = true;
}

Expand Down
12 changes: 0 additions & 12 deletions packages/svelte/src/compiler/phases/2-analyze/visitors/Fragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,4 @@
*/
export function Fragment(node, context) {
context.next({ ...context.state, fragment: node });

// TODO this indicates whether the fragment contains an `await` expression (not inside
// a child fragment), which is necessary for ensuring that a `SnippetBlock` creates an
// async function in SSR. It feels like this is probably duplicative, but it's late
// and it works, so for now I'm doing it like this
node.metadata.is_async ||= node.metadata.hoisted_promises.promises.length > 0;

if (node.metadata.hoisted_promises.promises.length === 1) {
// if there's only one promise in this fragment, we don't need to de-waterfall it
context.state.analysis.hoisted_promises.delete(node.metadata.hoisted_promises.promises[0]);
node.metadata.hoisted_promises.promises.length = 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { dev, filename } from '../../../state.js';
import { render_stylesheet } from '../css/index.js';
import { AssignmentExpression } from './visitors/AssignmentExpression.js';
import { AwaitBlock } from './visitors/AwaitBlock.js';
import { AwaitExpression } from './visitors/AwaitExpression.js';
import { CallExpression } from './visitors/CallExpression.js';
import { ClassBody } from './visitors/ClassBody.js';
import { Component } from './visitors/Component.js';
Expand Down Expand Up @@ -59,7 +58,6 @@ const global_visitors = {

/** @type {ComponentVisitors} */
const template_visitors = {
AwaitExpression,
AwaitBlock,
Component,
ConstTag,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,6 @@ export function Fragment(node, context) {
/** @type {Statement[]} */
const statements = [];

if (node.metadata.hoisted_promises.promises.length > 0) {
statements.push(
b.const(node.metadata.hoisted_promises.id, b.array(node.metadata.hoisted_promises.promises))
);
}

statements.push(...state.init);
statements.push(...build_template(state.template));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types.js' */
import * as b from '#compiler/builders';
import { empty_comment, build_attribute_value } from './shared/utils.js';
import {
empty_comment,
build_attribute_value,
PromiseOptimiser,
call_child_payload
} from './shared/utils.js';

/**
* @param {AST.SlotElement} node
Expand All @@ -15,13 +20,22 @@ export function SlotElement(node, context) {
/** @type {Expression[]} */
const spreads = [];

const optimiser = new PromiseOptimiser();

let name = b.literal('default');

for (const attribute of node.attributes) {
if (attribute.type === 'SpreadAttribute') {
spreads.push(/** @type {Expression} */ (context.visit(attribute)));
let expression = /** @type {Expression} */ (context.visit(attribute));
spreads.push(optimiser.transform(expression, attribute.metadata.expression));
} else if (attribute.type === 'Attribute') {
const value = build_attribute_value(attribute.value, context, false, true);
const value = build_attribute_value(
attribute.value,
context,
false,
true,
optimiser.transform
);

if (attribute.name === 'name') {
name = /** @type {Literal} */ (value);
Expand Down Expand Up @@ -50,5 +64,10 @@ export function SlotElement(node, context) {
fallback
);

context.state.template.push(empty_comment, b.stmt(slot), empty_comment);
const statement =
optimiser.expressions.length > 0
? call_child_payload(b.block([optimiser.apply(), b.stmt(slot)]), true)
: b.stmt(slot);

context.state.template.push(empty_comment, statement, empty_comment);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,6 @@ import { process_children, build_template, call_child_payload } from './shared/u
* @param {ComponentContext} context
*/
export function TitleElement(node, context) {
if (node.fragment.metadata.hoisted_promises.promises.length > 0) {
context.state.init.push(
b.const(
node.fragment.metadata.hoisted_promises.id,
b.array(node.fragment.metadata.hoisted_promises.promises)
)
);
}

// title is guaranteed to contain only text/expression tag children
const template = [b.literal('<title>')];
process_children(node.fragment.nodes, { ...context, state: { ...context.state, template } });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
/** @import { BlockStatement, Expression, Pattern, Property, SequenceExpression, Statement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../../types.js' */
import { empty_comment, build_attribute_value, call_child_payload } from './utils.js';
import {
empty_comment,
build_attribute_value,
call_child_payload,
PromiseOptimiser
} from './utils.js';
import * as b from '#compiler/builders';
import { is_element_node } from '../../../../nodes.js';
import { dev } from '../../../../../state.js';
import { get_attribute_chunks } from '../../../../../utils/ast.js';

/**
* @param {AST.Component | AST.SvelteComponent | AST.SvelteSelf} node
Expand Down Expand Up @@ -72,16 +78,26 @@ export function build_inline_component(node, expression, context) {
}
}

const optimiser = new PromiseOptimiser();

for (const attribute of node.attributes) {
if (attribute.type === 'LetDirective') {
if (!slot_scope_applies_to_itself) {
lets.default.push(attribute);
}
} else if (attribute.type === 'SpreadAttribute') {
props_and_spreads.push(/** @type {Expression} */ (context.visit(attribute)));
let expression = /** @type {Expression} */ (context.visit(attribute));
props_and_spreads.push(optimiser.transform(expression, attribute.metadata.expression));
} else if (attribute.type === 'Attribute') {
const value = build_attribute_value(
attribute.value,
context,
false,
true,
optimiser.transform
);

if (attribute.name.startsWith('--')) {
const value = build_attribute_value(attribute.value, context, false, true);
custom_css_props.push(b.init(attribute.name, value));
continue;
}
Expand All @@ -90,7 +106,6 @@ export function build_inline_component(node, expression, context) {
has_children_prop = true;
}

const value = build_attribute_value(attribute.value, context, false, true);
push_prop(b.prop('init', b.key(attribute.name), value));
} else if (attribute.type === 'BindDirective' && attribute.name !== 'this') {
if (attribute.expression.type === 'SequenceExpression') {
Expand Down Expand Up @@ -298,27 +313,29 @@ export function build_inline_component(node, expression, context) {
node.type === 'SvelteComponent' || (node.type === 'Component' && node.metadata.dynamic);

if (custom_css_props.length > 0) {
context.state.template.push(
b.stmt(
b.call(
'$.css_props',
b.id('$$payload'),
b.literal(context.state.namespace === 'svg' ? false : true),
b.object(custom_css_props),
b.thunk(b.block([statement])),
dynamic && b.true
)
statement = b.stmt(
b.call(
'$.css_props',
b.id('$$payload'),
b.literal(context.state.namespace === 'svg' ? false : true),
b.object(custom_css_props),
b.thunk(b.block([statement])),
dynamic && b.true
)
);
} else {
if (dynamic) {
context.state.template.push(empty_comment);
}
}

context.state.template.push(statement);
if (optimiser.expressions.length > 0) {
statement = call_child_payload(b.block([optimiser.apply(), statement]), true);
}

if (!context.state.skip_hydration_boundaries) {
context.state.template.push(empty_comment);
}
if (dynamic && custom_css_props.length === 0) {
context.state.template.push(empty_comment);
}

context.state.template.push(statement);

if (!context.state.skip_hydration_boundaries && custom_css_props.length === 0) {
context.state.template.push(empty_comment);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** @import { AssignmentOperator, Expression, Identifier, Node, Statement, BlockStatement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { AST, ExpressionMetadata } from '#compiler' */
/** @import { ComponentContext, ServerTransformState } from '../../types.js' */

import { escape_html } from '../../../../../../escaping.js';
Expand All @@ -11,6 +11,7 @@ import {
import * as b from '#compiler/builders';
import { sanitize_template_string } from '../../../../../utils/sanitize_template_string.js';
import { regex_whitespaces_strict } from '../../../../patterns.js';
import { has_await } from '../../../../../utils/ast.js';

/** Opens an if/each block, so that we can remove nodes in the case of a mismatch */
export const block_open = b.literal(BLOCK_OPEN);
Expand Down Expand Up @@ -183,13 +184,15 @@ export function build_template(template, out = b.id('$$payload'), operator = 'pu
* @param {ComponentContext} context
* @param {boolean} trim_whitespace
* @param {boolean} is_component
* @param {(expression: Expression, metadata: ExpressionMetadata) => Expression} transform
* @returns {Expression}
*/
export function build_attribute_value(
value,
context,
trim_whitespace = false,
is_component = false
is_component = false,
transform = (expression) => expression
) {
if (value === true) {
return b.true;
Expand All @@ -206,7 +209,10 @@ export function build_attribute_value(
return b.literal(is_component ? data : escape_html(data, true));
}

return /** @type {Expression} */ (context.visit(chunk.expression));
return transform(
/** @type {Expression} */ (context.visit(chunk.expression)),
chunk.metadata.expression
);
}

let quasi = b.quasi('', false);
Expand All @@ -224,7 +230,13 @@ export function build_attribute_value(
: node.data;
} else {
expressions.push(
b.call('$.stringify', /** @type {Expression} */ (context.visit(node.expression)))
b.call(
'$.stringify',
transform(
/** @type {Expression} */ (context.visit(node.expression)),
node.metadata.expression
)
)
);

quasi = b.quasi('', i + 1 === value.length);
Expand Down Expand Up @@ -269,3 +281,41 @@ export function build_getter(node, state) {
export function call_child_payload(body, async) {
return b.stmt(b.call('$$payload.child', b.arrow([b.id('$$payload')], body, async)));
}

export class PromiseOptimiser {
/** @type {Expression[]} */
expressions = [];

/**
*
* @param {Expression} expression
* @param {ExpressionMetadata} metadata
*/
transform = (expression, metadata) => {
if (metadata.has_await) {
const length = this.expressions.push(expression);
return b.id(`$$${length - 1}`);
}

return expression;
};

apply() {
if (this.expressions.length === 1) {
return b.const('$$0', this.expressions[0]);
}

const promises = b.array(
this.expressions.map((expression) => {
return expression.type === 'AwaitExpression' && !has_await(expression.argument)
? expression.argument
: b.call(b.thunk(expression, true));
})
);

return b.const(
b.array_pattern(this.expressions.map((_, i) => b.id(`$$${i}`))),
b.await(b.call('Promise.all', promises))
);
}
}
1 change: 0 additions & 1 deletion packages/svelte/src/compiler/phases/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ export interface ComponentAnalysis extends Analysis {
* Every snippet that is declared locally
*/
snippets: Set<AST.SnippetBlock>;
hoisted_promises: Map<Expression, MemberExpression>;
}

declare module 'estree' {
Expand Down
1 change: 0 additions & 1 deletion packages/svelte/src/compiler/types/template.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ export namespace AST {
has_await: boolean;
/** TODO document */
is_async: boolean;
hoisted_promises: { id: Identifier; promises: Expression[] };
};
}

Expand Down
Loading
Loading