push wrong hydratable

hydratable-flag
paoloricciuti 10 months ago
parent 396ea2ef37
commit 3e083307f5

@ -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<Expression | Statement>} */ ([
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);
}

@ -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<Expression | Statement>} */ ([
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<Expression | Statement>} */ ([for_loop]);
if (hydratable) {
templates.unshift(block_open);
templates.push(block_close);
}
state.template.push(...templates);
}
}

@ -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);
}

@ -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);
}

@ -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<Expression | Statement>} */ ([
/** @type {BlockStatement} */ (context.visit(node.fragment))
]);
if (is_hydratable()) {
templates.unshift(empty_comment);
templates.push(empty_comment);
}
context.state.template.push(...templates);
}

@ -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);
}
}

@ -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<Expression | Statement>} */ ([b.stmt(slot)]);
if (is_hydratable()) {
templates.unshift(empty_comment);
templates.push(empty_comment);
}
context.state.template.push(...templates);
}

@ -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);
}
}

@ -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 ? `<!--${hash(html)}-->` : '<!---->';
return open + html + '<!---->';
var open = hydratable ? (DEV ? `<!--${hash(html)}-->` : EMPTY_COMMENT) : '';
return open + html + (hydratable ? EMPTY_COMMENT : '');
}

@ -4,3 +4,17 @@ export const BLOCK_OPEN = `<!--${HYDRATION_START}-->`;
export const BLOCK_OPEN_ELSE = `<!--${HYDRATION_START_ELSE}-->`;
export const BLOCK_CLOSE = `<!--${HYDRATION_END}-->`;
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;
}

@ -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 += `</${tag}>`;
}
}
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<string, any>} Props
* @param {import('svelte').Component<Props> | ComponentType<SvelteComponent<Props>>} component
* @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }} [options]
* @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any>, 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 += `<!----></svelte-css-wrapper>`;
payload.out += comment + `</svelte-css-wrapper>`;
} else {
payload.out += `<!----></g>`;
payload.out += comment + `</g>`;
}
}

@ -0,0 +1,5 @@
import { test } from '../../test';
export default test({
hydratable: false
});

@ -0,0 +1,28 @@
<script lang="ts">
import Component from "./Component.svelte";
</script>
{#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()}
<Component />

@ -15,6 +15,7 @@ import type { CompileOptions } from '#compiler';
interface SSRTest extends BaseTest {
compileOptions?: Partial<CompileOptions>;
props?: Record<string, any>;
hydratable?: boolean;
withoutNormalizeHtml?: boolean;
errors?: string[];
}
@ -33,7 +34,8 @@ const { test, run } = suite<SSRTest>(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);

Loading…
Cancel
Save