|
|
|
@ -17,7 +17,7 @@ import { is_boolean_attribute, is_void } from '../../utils.js';
|
|
|
|
|
import { validate_store } from '../shared/validate.js';
|
|
|
|
|
import { current_component, pop, push } from './context.js';
|
|
|
|
|
import { reset_elements } from './dev.js';
|
|
|
|
|
import { close, empty, open, open_else, set_hydratable } from './hydration.js';
|
|
|
|
|
import { close, empty, hydratable, open, open_else, set_hydratable } from './hydration.js';
|
|
|
|
|
|
|
|
|
|
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
|
|
|
|
|
// https://infra.spec.whatwg.org/#noncharacter
|
|
|
|
@ -31,14 +31,16 @@ const RAW_TEXT_ELEMENTS = ['textarea', 'script', 'style', 'title'];
|
|
|
|
|
* @param {Payload} to_copy
|
|
|
|
|
* @returns {Payload}
|
|
|
|
|
*/
|
|
|
|
|
export function copy_payload({ out, css, head }) {
|
|
|
|
|
export function copy_payload({ out, css, head, async, current_async_level }) {
|
|
|
|
|
return {
|
|
|
|
|
out,
|
|
|
|
|
css: new Set(css),
|
|
|
|
|
head: {
|
|
|
|
|
title: head.title,
|
|
|
|
|
out: head.out
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
async: async ? [...async] : [],
|
|
|
|
|
current_async_level
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -92,11 +94,16 @@ export let on_destroy = [];
|
|
|
|
|
* @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]
|
|
|
|
|
* @returns {RenderOutput}
|
|
|
|
|
* @returns {Payload}
|
|
|
|
|
*/
|
|
|
|
|
export function render(component, options = {}) {
|
|
|
|
|
function render_payload(component, options = {}) {
|
|
|
|
|
/** @type {Payload} */
|
|
|
|
|
const payload = { out: '', css: new Set(), head: { title: '', out: '' } };
|
|
|
|
|
const payload = {
|
|
|
|
|
out: '',
|
|
|
|
|
css: new Set(),
|
|
|
|
|
head: { title: '', out: '' },
|
|
|
|
|
current_async_level: ''
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const prev_on_destroy = on_destroy;
|
|
|
|
|
on_destroy = [];
|
|
|
|
@ -128,6 +135,19 @@ export function render(component, options = {}) {
|
|
|
|
|
payload.out += close();
|
|
|
|
|
for (const cleanup of on_destroy) cleanup();
|
|
|
|
|
on_destroy = prev_on_destroy;
|
|
|
|
|
return payload;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Only available on the server and when compiling with the `server` option.
|
|
|
|
|
* 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]
|
|
|
|
|
* @returns {RenderOutput}
|
|
|
|
|
*/
|
|
|
|
|
export function render(component, options = {}) {
|
|
|
|
|
const payload = render_payload(component, options);
|
|
|
|
|
|
|
|
|
|
let head = payload.head.out + payload.head.title;
|
|
|
|
|
|
|
|
|
@ -149,18 +169,33 @@ export function render(component, options = {}) {
|
|
|
|
|
* @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]
|
|
|
|
|
* @returns {RenderOutput}
|
|
|
|
|
* @returns {Promise<RenderOutput>}
|
|
|
|
|
*/
|
|
|
|
|
export function renderStaticHTML(component, options) {
|
|
|
|
|
export async function renderStaticHTML(component, options) {
|
|
|
|
|
set_hydratable(false);
|
|
|
|
|
let payload;
|
|
|
|
|
try {
|
|
|
|
|
payload = render(component, options);
|
|
|
|
|
payload = render_payload(component, options);
|
|
|
|
|
if (payload.async) {
|
|
|
|
|
for (let async_fn of payload.async) {
|
|
|
|
|
await async_fn();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let head = payload.head.out + payload.head.title;
|
|
|
|
|
|
|
|
|
|
for (const { hash, code } of payload.css) {
|
|
|
|
|
head += `<style id="${hash}">${code}</style>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
head,
|
|
|
|
|
html: payload.out,
|
|
|
|
|
body: payload.out
|
|
|
|
|
};
|
|
|
|
|
} finally {
|
|
|
|
|
set_hydratable(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return payload;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -493,16 +528,58 @@ export function bind_props(props_parent, props_now) {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @template V
|
|
|
|
|
* @param {Payload} $$payload
|
|
|
|
|
* @param {Promise<V>} promise
|
|
|
|
|
* @param {null | (() => void)} pending_fn
|
|
|
|
|
* @param {(value: V) => void} then_fn
|
|
|
|
|
* @param {(value: V, $$payload?: Payload) => void} then_fn
|
|
|
|
|
* @param {(error: any, $$payload?: Payload) => void} [catch_fn]
|
|
|
|
|
* @returns {void}
|
|
|
|
|
*/
|
|
|
|
|
function await_block(promise, pending_fn, then_fn) {
|
|
|
|
|
function await_block($$payload, promise, pending_fn, then_fn, catch_fn) {
|
|
|
|
|
if (is_promise(promise)) {
|
|
|
|
|
promise.then(null, noop);
|
|
|
|
|
if (pending_fn !== null) {
|
|
|
|
|
pending_fn();
|
|
|
|
|
if (!hydratable) {
|
|
|
|
|
let level = $$payload.current_async_level + ($$payload.async?.length ?? 0);
|
|
|
|
|
const replace_marker = '\ufff0\ufff0\ufff0' + level;
|
|
|
|
|
$$payload.out += replace_marker;
|
|
|
|
|
($$payload.async ??= []).push(async () => {
|
|
|
|
|
/**
|
|
|
|
|
* @type {Payload}
|
|
|
|
|
*/
|
|
|
|
|
const new_payload = {
|
|
|
|
|
css: new Set(),
|
|
|
|
|
current_async_level: level + '.',
|
|
|
|
|
head: {
|
|
|
|
|
out: '',
|
|
|
|
|
title: ''
|
|
|
|
|
},
|
|
|
|
|
out: '',
|
|
|
|
|
async: []
|
|
|
|
|
};
|
|
|
|
|
try {
|
|
|
|
|
const result = await promise;
|
|
|
|
|
then_fn(result, new_payload);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
if (catch_fn) {
|
|
|
|
|
catch_fn(e, new_payload);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ($$payload.async && new_payload.async) {
|
|
|
|
|
for (let async_replace of new_payload.async) {
|
|
|
|
|
await async_replace();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$$payload.out = $$payload.out.replace(replace_marker, new_payload.out);
|
|
|
|
|
$$payload.head.out = $$payload.head.out.replace(replace_marker, new_payload.head.out);
|
|
|
|
|
$$payload.head.title = $$payload.head.title.replace(replace_marker, new_payload.head.title);
|
|
|
|
|
for (let css_part of new_payload.css) {
|
|
|
|
|
$$payload.css.add(css_part);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
if (pending_fn !== null) {
|
|
|
|
|
pending_fn();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (then_fn !== null) {
|
|
|
|
|
then_fn(promise);
|
|
|
|
|