From 3e083307f52abadaff0604d9903cea289b02f995 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Sun, 17 Nov 2024 21:38:53 +0100 Subject: [PATCH] push wrong hydratable --- .../3-transform/server/visitors/AwaitBlock.js | 19 ++++++--- .../3-transform/server/visitors/EachBlock.js | 42 ++++++++++++------- .../3-transform/server/visitors/Fragment.js | 4 +- .../3-transform/server/visitors/IfBlock.js | 13 +++--- .../3-transform/server/visitors/KeyBlock.js | 18 +++++--- .../3-transform/server/visitors/RenderTag.js | 3 +- .../server/visitors/SlotElement.js | 12 +++++- .../server/visitors/shared/component.js | 6 ++- .../svelte/src/internal/server/blocks/html.js | 6 ++- .../svelte/src/internal/server/hydration.js | 14 +++++++ packages/svelte/src/internal/server/index.js | 34 ++++++++++----- .../samples/hydratable-false/Component.svelte | 1 + .../samples/hydratable-false/_config.js | 5 +++ .../samples/hydratable-false/main.svelte | 28 +++++++++++++ .../tests/server-side-rendering/test.ts | 4 +- 15 files changed, 157 insertions(+), 52 deletions(-) create mode 100644 packages/svelte/tests/server-side-rendering/samples/hydratable-false/Component.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/hydratable-false/_config.js create mode 100644 packages/svelte/tests/server-side-rendering/samples/hydratable-false/main.svelte diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitBlock.js index 8fc82b8905..e8c5d94c3b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitBlock.js @@ -1,6 +1,7 @@ -/** @import { BlockStatement, Expression, Pattern } from 'estree' */ +/** @import { BlockStatement, Expression, Pattern, Statement } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types.js' */ +import { is_hydratable } from '../../../../../internal/server/hydration.js'; import * as b from '../../../../utils/builders.js'; import { empty_comment } from './shared/utils.js'; @@ -9,8 +10,8 @@ import { empty_comment } from './shared/utils.js'; * @param {ComponentContext} context */ export function AwaitBlock(node, context) { - context.state.template.push( - empty_comment, + const hydratable = is_hydratable(); + const templates = /** @type {Array} */ ([ b.stmt( b.call( '$.await', @@ -27,7 +28,13 @@ export function AwaitBlock(node, context) { node.catch ? /** @type {BlockStatement} */ (context.visit(node.catch)) : b.block([]) ) ) - ), - empty_comment - ); + ) + ]); + + if (hydratable) { + templates.unshift(empty_comment); + templates.push(empty_comment); + } + + context.state.template.push(...templates); } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js index 478bb355a7..4510b173e8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js @@ -1,7 +1,7 @@ /** @import { BlockStatement, Expression, Pattern, Statement } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types.js' */ -import { BLOCK_OPEN_ELSE } from '../../../../../internal/server/hydration.js'; +import { BLOCK_OPEN_ELSE, is_hydratable } from '../../../../../internal/server/hydration.js'; import * as b from '../../../../utils/builders.js'; import { block_close, block_open } from './shared/utils.js'; @@ -10,6 +10,7 @@ import { block_close, block_open } from './shared/utils.js'; * @param {ComponentContext} context */ export function EachBlock(node, context) { + const hydratable = is_hydratable(); const state = context.state; const each_node_meta = node.metadata; @@ -40,23 +41,32 @@ export function EachBlock(node, context) { ); if (node.fallback) { - const open = b.stmt(b.assignment('+=', b.id('$$payload.out'), block_open)); - const fallback = /** @type {BlockStatement} */ (context.visit(node.fallback)); - fallback.body.unshift( - b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_OPEN_ELSE))) - ); - - state.template.push( - b.if( - b.binary('!==', b.member(array_id, 'length'), b.literal(0)), - b.block([open, for_loop]), - fallback - ), - block_close - ); + const block = /** @type {Statement[]} */ ([for_loop]); + + if (hydratable) { + block.unshift(b.stmt(b.assignment('+=', b.id('$$payload.out'), block_open))); + + fallback.body.unshift( + b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_OPEN_ELSE))) + ); + } + + const templates = /** @type {Array} */ ([ + b.if(b.binary('!==', b.member(array_id, 'length'), b.literal(0)), b.block(block), fallback) + ]); + if (hydratable) { + templates.push(block_close); + } + + state.template.push(...templates); } else { - state.template.push(block_open, for_loop, block_close); + const templates = /** @type {Array} */ ([for_loop]); + if (hydratable) { + templates.unshift(block_open); + templates.push(block_close); + } + state.template.push(...templates); } } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/Fragment.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/Fragment.js index a293b98e7e..20baec514d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/Fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/Fragment.js @@ -3,6 +3,7 @@ import { clean_nodes, infer_namespace } from '../../utils.js'; import * as b from '../../../../utils/builders.js'; import { empty_comment, process_children, build_template } from './shared/utils.js'; +import { is_hydratable } from '../../../../../internal/server/hydration.js'; /** * @param {AST.Fragment} node @@ -35,7 +36,8 @@ export function Fragment(node, context) { context.visit(node, state); } - if (is_text_first) { + if (is_text_first && is_hydratable()) { + console.log({ hid: is_hydratable() }); // insert `` to prevent this from being glued to the previous fragment state.template.push(empty_comment); } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js index 4df09aa8b9..138420a3af 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js @@ -1,7 +1,7 @@ /** @import { BlockStatement, Expression } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types.js' */ -import { BLOCK_OPEN_ELSE } from '../../../../../internal/server/hydration.js'; +import { BLOCK_OPEN_ELSE, is_hydratable } from '../../../../../internal/server/hydration.js'; import * as b from '../../../../utils/builders.js'; import { block_close, block_open } from './shared/utils.js'; @@ -10,6 +10,7 @@ import { block_close, block_open } from './shared/utils.js'; * @param {ComponentContext} context */ export function IfBlock(node, context) { + const hydratable = is_hydratable(); const test = /** @type {Expression} */ (context.visit(node.test)); const consequent = /** @type {BlockStatement} */ (context.visit(node.consequent)); @@ -18,11 +19,13 @@ export function IfBlock(node, context) { ? /** @type {BlockStatement} */ (context.visit(node.alternate)) : b.block([]); - consequent.body.unshift(b.stmt(b.assignment('+=', b.id('$$payload.out'), block_open))); + if (hydratable) { + consequent.body.unshift(b.stmt(b.assignment('+=', b.id('$$payload.out'), block_open))); - alternate.body.unshift( - b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_OPEN_ELSE))) - ); + alternate.body.unshift( + b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_OPEN_ELSE))) + ); + } context.state.template.push(b.if(test, consequent, alternate), block_close); } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/KeyBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/KeyBlock.js index f1dc9fa1b4..9729fe607b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/KeyBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/KeyBlock.js @@ -1,6 +1,7 @@ -/** @import { BlockStatement } from 'estree' */ +/** @import { BlockStatement, Expression, Statement } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types.js' */ +import { is_hydratable } from '../../../../../internal/server/hydration.js'; import { empty_comment } from './shared/utils.js'; /** @@ -8,9 +9,14 @@ import { empty_comment } from './shared/utils.js'; * @param {ComponentContext} context */ export function KeyBlock(node, context) { - context.state.template.push( - empty_comment, - /** @type {BlockStatement} */ (context.visit(node.fragment)), - empty_comment - ); + const templates = /** @type {Array} */ ([ + /** @type {BlockStatement} */ (context.visit(node.fragment)) + ]); + + if (is_hydratable()) { + templates.unshift(empty_comment); + templates.push(empty_comment); + } + + context.state.template.push(...templates); } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/RenderTag.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/RenderTag.js index ebf8c3be1c..00eb9630c4 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/RenderTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/RenderTag.js @@ -1,6 +1,7 @@ /** @import { Expression } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types.js' */ +import { is_hydratable } from '../../../../../internal/server/hydration.js'; import { unwrap_optional } from '../../../../utils/ast.js'; import * as b from '../../../../utils/builders.js'; import { empty_comment } from './shared/utils.js'; @@ -29,7 +30,7 @@ export function RenderTag(node, context) { ) ); - if (!context.state.skip_hydration_boundaries) { + if (!context.state.skip_hydration_boundaries && is_hydratable()) { context.state.template.push(empty_comment); } } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SlotElement.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SlotElement.js index 7ece04ae3d..2e9f5a2b94 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SlotElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SlotElement.js @@ -1,6 +1,7 @@ -/** @import { BlockStatement, Expression, Literal, Property } from 'estree' */ +/** @import { BlockStatement, Expression, Literal, Property, Statement } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types.js' */ +import { is_hydratable } from '../../../../../internal/server/hydration.js'; import * as b from '../../../../utils/builders.js'; import { empty_comment, build_attribute_value } from './shared/utils.js'; @@ -50,5 +51,12 @@ export function SlotElement(node, context) { fallback ); - context.state.template.push(empty_comment, b.stmt(slot), empty_comment); + const templates = /** @type {Array} */ ([b.stmt(slot)]); + + if (is_hydratable()) { + templates.unshift(empty_comment); + templates.push(empty_comment); + } + + context.state.template.push(...templates); } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js index 79df3cdd04..7580b4ea84 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js @@ -4,6 +4,7 @@ import { empty_comment, build_attribute_value } from './utils.js'; import * as b from '../../../../../utils/builders.js'; import { is_element_node } from '../../../../nodes.js'; +import { is_hydratable } from '../../../../../../internal/server/hydration.js'; /** * @param {AST.Component | AST.SvelteComponent | AST.SvelteSelf} node @@ -268,13 +269,14 @@ export function build_inline_component(node, expression, context) { ) ); } else { - if (dynamic) { + const hydratable = is_hydratable(); + if (dynamic && hydratable) { context.state.template.push(empty_comment); } context.state.template.push(statement); - if (!context.state.skip_hydration_boundaries) { + if (!context.state.skip_hydration_boundaries && hydratable) { context.state.template.push(empty_comment); } } diff --git a/packages/svelte/src/internal/server/blocks/html.js b/packages/svelte/src/internal/server/blocks/html.js index c8ef5d81b4..300707094c 100644 --- a/packages/svelte/src/internal/server/blocks/html.js +++ b/packages/svelte/src/internal/server/blocks/html.js @@ -1,11 +1,13 @@ import { DEV } from 'esm-env'; import { hash } from '../../../utils.js'; +import { EMPTY_COMMENT, is_hydratable } from '../hydration.js'; /** * @param {string} value */ export function html(value) { + const hydratable = is_hydratable(); var html = String(value ?? ''); - var open = DEV ? `` : ''; - return open + html + ''; + var open = hydratable ? (DEV ? `` : EMPTY_COMMENT) : ''; + return open + html + (hydratable ? EMPTY_COMMENT : ''); } diff --git a/packages/svelte/src/internal/server/hydration.js b/packages/svelte/src/internal/server/hydration.js index 94fc61b89c..d8d87d0104 100644 --- a/packages/svelte/src/internal/server/hydration.js +++ b/packages/svelte/src/internal/server/hydration.js @@ -4,3 +4,17 @@ export const BLOCK_OPEN = ``; export const BLOCK_OPEN_ELSE = ``; export const BLOCK_CLOSE = ``; export const EMPTY_COMMENT = ``; + +let hydratable = true; + +export function is_hydratable() { + return hydratable; +} + +/** + * + * @param {boolean} new_hydratable + */ +export function set_hydratable(new_hydratable) { + hydratable = new_hydratable; +} diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 615a49fbd4..e318bf6156 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -14,7 +14,13 @@ import { import { escape_html } from '../../escaping.js'; import { DEV } from 'esm-env'; import { current_component, pop, push } from './context.js'; -import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js'; +import { + EMPTY_COMMENT, + BLOCK_CLOSE, + BLOCK_OPEN, + set_hydratable, + is_hydratable +} from './hydration.js'; import { validate_store } from '../shared/validate.js'; import { is_boolean_attribute, is_void } from '../../utils.js'; import { reset_elements } from './dev.js'; @@ -61,7 +67,9 @@ export function assign_payload(p1, p2) { * @returns {void} */ export function element(payload, tag, attributes_fn = noop, children_fn = noop) { - payload.out += ''; + let hydratable = is_hydratable(); + + if (hydratable) payload.out += EMPTY_COMMENT; if (tag) { payload.out += `<${tag} `; @@ -70,14 +78,14 @@ export function element(payload, tag, attributes_fn = noop, children_fn = noop) if (!is_void(tag)) { children_fn(); - if (!RAW_TEXT_ELEMENTS.includes(tag)) { + if (!RAW_TEXT_ELEMENTS.includes(tag) && hydratable) { payload.out += EMPTY_COMMENT; } payload.out += ``; } } - payload.out += ''; + if (hydratable) payload.out += EMPTY_COMMENT; } /** @@ -91,16 +99,20 @@ export let on_destroy = []; * Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app. * @template {Record} Props * @param {import('svelte').Component | ComponentType>} component - * @param {{ props?: Omit; context?: Map }} [options] + * @param {{ props?: Omit; context?: Map, hydratable?: boolean }} [options] * @returns {RenderOutput} */ export function render(component, options = {}) { + let hydratable = options?.hydratable ?? true; + console.log({ hydratable }); + set_hydratable(hydratable); + /** @type {Payload} */ const payload = { out: '', css: new Set(), head: { title: '', out: '' } }; const prev_on_destroy = on_destroy; on_destroy = []; - payload.out += BLOCK_OPEN; + if (hydratable) payload.out += BLOCK_OPEN; let reset_reset_element; @@ -125,7 +137,7 @@ export function render(component, options = {}) { reset_reset_element(); } - payload.out += BLOCK_CLOSE; + if (hydratable) payload.out += BLOCK_CLOSE; for (const cleanup of on_destroy) cleanup(); on_destroy = prev_on_destroy; @@ -163,6 +175,8 @@ export function head(payload, fn) { * @returns {void} */ export function css_props(payload, is_html, props, component, dynamic = false) { + let comment = is_hydratable() ? EMPTY_COMMENT : ''; + const styles = style_object_to_string(props); if (is_html) { @@ -172,15 +186,15 @@ export function css_props(payload, is_html, props, component, dynamic = false) { } if (dynamic) { - payload.out += ''; + payload.out += comment; } component(); if (is_html) { - payload.out += ``; + payload.out += comment + ``; } else { - payload.out += ``; + payload.out += comment + ``; } } diff --git a/packages/svelte/tests/server-side-rendering/samples/hydratable-false/Component.svelte b/packages/svelte/tests/server-side-rendering/samples/hydratable-false/Component.svelte new file mode 100644 index 0000000000..0385342cef --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/hydratable-false/Component.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/hydratable-false/_config.js b/packages/svelte/tests/server-side-rendering/samples/hydratable-false/_config.js new file mode 100644 index 0000000000..1f0f0dc459 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/hydratable-false/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + hydratable: false +}); diff --git a/packages/svelte/tests/server-side-rendering/samples/hydratable-false/main.svelte b/packages/svelte/tests/server-side-rendering/samples/hydratable-false/main.svelte new file mode 100644 index 0000000000..d71e423044 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/hydratable-false/main.svelte @@ -0,0 +1,28 @@ + + + +{#if true} + if +{/if} + +{#each [] as i} + {i} +{/each} + +{#await Promise.resolve() then x} + {x} +{/await} + +{#key true} + cool +{/key} + +{#snippet to_render()} + cool +{/snippet} + +{@render to_render()} + + \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/test.ts b/packages/svelte/tests/server-side-rendering/test.ts index f76c5b539f..d54392f37d 100644 --- a/packages/svelte/tests/server-side-rendering/test.ts +++ b/packages/svelte/tests/server-side-rendering/test.ts @@ -15,6 +15,7 @@ import type { CompileOptions } from '#compiler'; interface SSRTest extends BaseTest { compileOptions?: Partial; props?: Record; + hydratable?: boolean; withoutNormalizeHtml?: boolean; errors?: string[]; } @@ -33,7 +34,8 @@ const { test, run } = suite(async (config, test_dir) => { const Component = (await import(`${test_dir}/_output/server/main.svelte.js`)).default; const expected_html = try_read_file(`${test_dir}/_expected.html`); - const rendered = render(Component, { props: config.props || {} }); + console.log({ config }); + const rendered = render(Component, { props: config.props || {}, hydratable: config.hydratable }); const { body, head } = rendered; fs.writeFileSync(`${test_dir}/_output/rendered.html`, body);