mirror of https://github.com/sveltejs/svelte
parent
05e60b1320
commit
caad89b888
@ -1,62 +1,67 @@
|
||||
/** @import { Encode, Transport } from '#shared' */
|
||||
/** @import { HydratableEntry } from '#server' */
|
||||
/** @import { HydratableContext } from '#server' */
|
||||
import { async_mode_flag } from '../flags/index.js';
|
||||
import { get_render_context } from './render-context.js';
|
||||
import * as e from './errors.js';
|
||||
import { DEV } from 'esm-env';
|
||||
|
||||
/** @type {WeakSet<HydratableEntry>} */
|
||||
export const unresolved_hydratables = new WeakSet();
|
||||
import { uneval } from 'devalue';
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {string} key
|
||||
* @param {() => T} fn
|
||||
* @param {Transport<T>} [transport]
|
||||
* @returns {T}
|
||||
*/
|
||||
export function hydratable(key, fn, transport) {
|
||||
export function hydratable(key, fn) {
|
||||
if (!async_mode_flag) {
|
||||
e.experimental_async_required('hydratable');
|
||||
}
|
||||
|
||||
const store = get_render_context();
|
||||
|
||||
const entry = create_entry(fn(), transport?.encode);
|
||||
const existing_entry = store.hydratables.get(key);
|
||||
if (DEV && existing_entry !== undefined) {
|
||||
(existing_entry.dev_competing_entries ??= []).push(entry);
|
||||
return entry.value;
|
||||
const entry = store.hydratable.lookup.get(key);
|
||||
if (entry !== undefined) {
|
||||
return /** @type {T} */ (entry.value);
|
||||
}
|
||||
store.hydratables.set(key, entry);
|
||||
return entry.value;
|
||||
|
||||
const result = fn();
|
||||
store.hydratable.lookup.set(key, {
|
||||
value: result,
|
||||
root_index: encode(result, key, store.hydratable)
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} value
|
||||
* @param {Encode<T> | undefined} encode
|
||||
* @param {unknown} value
|
||||
* @param {string} key
|
||||
* @param {HydratableContext} hydratable_context
|
||||
* @returns {number}
|
||||
*/
|
||||
function create_entry(value, encode) {
|
||||
/** @type {Omit<HydratableEntry, 'value'> & { value: T }} */
|
||||
const entry = {
|
||||
value,
|
||||
encode
|
||||
};
|
||||
function encode(value, key, hydratable_context) {
|
||||
const replacer = create_replacer(key, hydratable_context);
|
||||
return hydratable_context.values.push(uneval(value, replacer)) - 1;
|
||||
}
|
||||
|
||||
if (DEV) {
|
||||
entry.stack = new Error().stack;
|
||||
|
||||
if (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
'then' in value &&
|
||||
typeof value.then === 'function'
|
||||
) {
|
||||
unresolved_hydratables.add(entry);
|
||||
value.then(() => unresolved_hydratables.delete(entry));
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {HydratableContext} hydratable_context
|
||||
* @returns {(value: unknown, uneval: (value: any) => string) => string | undefined}
|
||||
*/
|
||||
function create_replacer(key, hydratable_context) {
|
||||
/**
|
||||
* @param {unknown} value
|
||||
* @param {(value: any) => string} inner_uneval
|
||||
*/
|
||||
const replacer = (value, inner_uneval) => {
|
||||
if (value instanceof Promise) {
|
||||
hydratable_context.unresolved_promises.set(value, key);
|
||||
value.finally(() => hydratable_context.unresolved_promises.delete(value));
|
||||
// use the root-level uneval because we need a separate, top-level entry for each promise
|
||||
const index =
|
||||
hydratable_context.values.push(value.then((v) => `r(${uneval(v, replacer)})`)) - 1;
|
||||
return `d(${index})`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return entry;
|
||||
return replacer;
|
||||
}
|
||||
|
||||
Loading…
Reference in new issue