From 96942400bd350449ff4e4f34edd13a4e370784c4 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 22 Jan 2025 21:45:34 -0500 Subject: [PATCH] basic SSR --- .../98-reference/.generated/shared-errors.md | 6 ++++ .../svelte/messages/shared-errors/errors.md | 6 ++++ .../client/visitors/AwaitExpression.js | 4 +-- .../3-transform/server/transform-server.js | 2 ++ .../server/visitors/AwaitExpression.js | 17 ++++++++++ .../server/visitors/SvelteBoundary.js | 31 ++++++++++++++++--- packages/svelte/src/internal/server/index.js | 2 ++ packages/svelte/src/internal/shared/errors.js | 15 +++++++++ 8 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitExpression.js diff --git a/documentation/docs/98-reference/.generated/shared-errors.md b/documentation/docs/98-reference/.generated/shared-errors.md index 0102aafcbc..df49facef7 100644 --- a/documentation/docs/98-reference/.generated/shared-errors.md +++ b/documentation/docs/98-reference/.generated/shared-errors.md @@ -1,5 +1,11 @@ +### await_outside_boundary + +``` +Cannot await outside a `` with a `pending` snippet +``` + ### invalid_default_snippet ``` diff --git a/packages/svelte/messages/shared-errors/errors.md b/packages/svelte/messages/shared-errors/errors.md index 8b4c61303a..e50c0d922b 100644 --- a/packages/svelte/messages/shared-errors/errors.md +++ b/packages/svelte/messages/shared-errors/errors.md @@ -1,3 +1,9 @@ +## await_outside_boundary + +> Cannot await outside a `` with a `pending` snippet + +TODO + ## invalid_default_snippet > Cannot use `{@render children(...)}` if the parent component uses `let:` directives. Consider using a named snippet instead diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js index a9486fd8c8..48a3bfa584 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js @@ -1,10 +1,10 @@ /** @import { AwaitExpression, Expression } from 'estree' */ -/** @import { ComponentContext } from '../types' */ +/** @import { Context } from '../types' */ import * as b from '../../../../utils/builders.js'; /** * @param {AwaitExpression} node - * @param {ComponentContext} context + * @param {Context} context */ export function AwaitExpression(node, context) { const suspend = context.state.analysis.suspenders.has(node); diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 982b75e12f..9aa2b4061b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -10,6 +10,7 @@ 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'; @@ -44,6 +45,7 @@ import { SvelteBoundary } from './visitors/SvelteBoundary.js'; const global_visitors = { _: set_scope, AssignmentExpression, + AwaitExpression, CallExpression, ClassBody, ExpressionStatement, diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitExpression.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitExpression.js new file mode 100644 index 0000000000..f729c9ca9b --- /dev/null +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitExpression.js @@ -0,0 +1,17 @@ +/** @import { AwaitExpression } from 'estree' */ +/** @import { ComponentContext } from '../types.js' */ +import * as b from '../../../../utils/builders.js'; + +/** + * @param {AwaitExpression} node + * @param {ComponentContext} context + */ +export function AwaitExpression(node, context) { + const suspend = context.state.analysis.suspenders.has(node); + + if (!suspend) { + return context.next(); + } + + return b.call('$.await_outside_boundary'); +} diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js index 0d54feee11..7f90545531 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js @@ -1,17 +1,38 @@ -/** @import { BlockStatement } from 'estree' */ +/** @import { BlockStatement, Expression } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ import { BLOCK_CLOSE, BLOCK_OPEN } from '../../../../../internal/server/hydration.js'; import * as b from '../../../../utils/builders.js'; +import { build_attribute_value } from './shared/utils.js'; /** * @param {AST.SvelteBoundary} node * @param {ComponentContext} context */ export function SvelteBoundary(node, context) { - context.state.template.push( - b.literal(BLOCK_OPEN), - /** @type {BlockStatement} */ (context.visit(node.fragment)), - b.literal(BLOCK_CLOSE) + context.state.template.push(b.literal(BLOCK_OPEN)); + + // if this has a `pending` snippet, render it + const pending_attribute = /** @type {AST.Attribute} */ ( + node.attributes.find((node) => node.type === 'Attribute' && node.name === 'pending') + ); + + const pending_snippet = /** @type {AST.SnippetBlock} */ ( + node.fragment.nodes.find( + (node) => node.type === 'SnippetBlock' && node.expression.name === 'pending' + ) ); + + if (pending_attribute) { + const value = build_attribute_value(pending_attribute.value, context, false, true); + context.state.template.push(b.call(value, b.id('$$payload'))); + } else if (pending_snippet) { + context.state.template.push( + /** @type {BlockStatement} */ (context.visit(pending_snippet.body)) + ); + } else { + context.state.template.push(/** @type {BlockStatement} */ (context.visit(node.fragment))); + } + + context.state.template.push(b.literal(BLOCK_CLOSE)); } diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 89b3c33df8..609b54804b 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -545,3 +545,5 @@ export { } from '../shared/validate.js'; export { escape_html as escape }; + +export { await_outside_boundary } from '../shared/errors.js'; diff --git a/packages/svelte/src/internal/shared/errors.js b/packages/svelte/src/internal/shared/errors.js index 26d6822cdb..c709c431ef 100644 --- a/packages/svelte/src/internal/shared/errors.js +++ b/packages/svelte/src/internal/shared/errors.js @@ -62,4 +62,19 @@ export function svelte_element_invalid_this_value() { } else { throw new Error(`https://svelte.dev/e/svelte_element_invalid_this_value`); } +} + +/** + * Cannot await outside a `` with a `pending` snippet + * @returns {never} + */ +export function await_outside_boundary() { + if (DEV) { + const error = new Error(`await_outside_boundary\nCannot await outside a \`\` with a \`pending\` snippet\nhttps://svelte.dev/e/await_outside_boundary`); + + error.name = 'Svelte error'; + throw error; + } else { + throw new Error(`https://svelte.dev/e/await_outside_boundary`); + } } \ No newline at end of file