mirror of https://github.com/sveltejs/svelte
parent
e238e6611e
commit
91882ee9db
@ -0,0 +1,112 @@
|
|||||||
|
/** @import { Decode, Hydratable, Transport } from '#shared' */
|
||||||
|
import { async_mode_flag } from '../flags/index.js';
|
||||||
|
import { hydrating } from './dom/hydration.js';
|
||||||
|
import * as w from './warnings.js';
|
||||||
|
import * as e from './errors.js';
|
||||||
|
import { DEV } from 'esm-env';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {string} key
|
||||||
|
* @param {() => T} fn
|
||||||
|
* @param {{ transport?: Transport<T> }} [options]
|
||||||
|
* @returns {T}
|
||||||
|
*/
|
||||||
|
function isomorphic_hydratable(key, fn, options) {
|
||||||
|
if (!async_mode_flag) {
|
||||||
|
e.experimental_async_required('hydratable');
|
||||||
|
}
|
||||||
|
|
||||||
|
return access_hydratable_store(
|
||||||
|
key,
|
||||||
|
(val, has) => {
|
||||||
|
if (!has) {
|
||||||
|
hydratable_missing_but_expected(key);
|
||||||
|
return fn();
|
||||||
|
}
|
||||||
|
return decode(val, options?.transport?.decode);
|
||||||
|
},
|
||||||
|
fn
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isomorphic_hydratable['get'] = get_hydratable_value;
|
||||||
|
isomorphic_hydratable['has'] = has_hydratable_value;
|
||||||
|
isomorphic_hydratable['set'] = () => e.fn_unavailable_on_client('hydratable.set');
|
||||||
|
|
||||||
|
/** @type {Hydratable} */
|
||||||
|
const hydratable = isomorphic_hydratable;
|
||||||
|
|
||||||
|
export { hydratable };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {string} key
|
||||||
|
* @param {{ decode?: Decode<T> }} [options]
|
||||||
|
* @returns {T | undefined}
|
||||||
|
*/
|
||||||
|
function get_hydratable_value(key, options = {}) {
|
||||||
|
if (!async_mode_flag) {
|
||||||
|
e.experimental_async_required('hydratable.get');
|
||||||
|
}
|
||||||
|
|
||||||
|
return access_hydratable_store(
|
||||||
|
key,
|
||||||
|
(val, has) => {
|
||||||
|
if (!has) {
|
||||||
|
hydratable_missing_but_expected(key);
|
||||||
|
}
|
||||||
|
return decode(val, options.decode);
|
||||||
|
},
|
||||||
|
() => undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function has_hydratable_value(key) {
|
||||||
|
if (!async_mode_flag) {
|
||||||
|
e.experimental_async_required('hydratable.set');
|
||||||
|
}
|
||||||
|
return access_hydratable_store(
|
||||||
|
key,
|
||||||
|
(_, has) => has,
|
||||||
|
() => false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {string} key
|
||||||
|
* @param {(val: unknown, has: boolean) => T} on_hydrating
|
||||||
|
* @param {() => T} on_not_hydrating
|
||||||
|
* @returns {T}
|
||||||
|
*/
|
||||||
|
function access_hydratable_store(key, on_hydrating, on_not_hydrating) {
|
||||||
|
if (!hydrating) {
|
||||||
|
return on_not_hydrating();
|
||||||
|
}
|
||||||
|
var store = window.__svelte?.h;
|
||||||
|
return on_hydrating(store?.get(key), store?.has(key) ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {unknown} val
|
||||||
|
* @param {Decode<T> | undefined} decode
|
||||||
|
* @returns {T}
|
||||||
|
*/
|
||||||
|
function decode(val, decode) {
|
||||||
|
return (decode ?? ((val) => /** @type {T} */ (val)))(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} key */
|
||||||
|
function hydratable_missing_but_expected(key) {
|
||||||
|
if (DEV) {
|
||||||
|
e.hydratable_missing_but_expected_e(key);
|
||||||
|
} else {
|
||||||
|
w.hydratable_missing_but_expected_w(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
/** @import { Encode, Hydratable, Transport } from '#shared' */
|
||||||
|
/** @import { HydratableEntry } 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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {string} key
|
||||||
|
* @param {() => T} fn
|
||||||
|
* @param {{ transport?: Transport<T> }} [options]
|
||||||
|
* @returns {T}
|
||||||
|
*/
|
||||||
|
function isomorphic_hydratable(key, fn, options) {
|
||||||
|
if (!async_mode_flag) {
|
||||||
|
e.experimental_async_required('hydratable');
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = get_render_context();
|
||||||
|
|
||||||
|
if (store.hydratables.has(key)) {
|
||||||
|
e.hydratable_clobbering(key, store.hydratables.get(key)?.stack || 'unknown');
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = create_entry(fn(), options?.transport?.encode);
|
||||||
|
store.hydratables.set(key, entry);
|
||||||
|
return entry.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
isomorphic_hydratable['get'] = () => e.fn_unavailable_on_server('hydratable.get');
|
||||||
|
isomorphic_hydratable['has'] = has_hydratable_value;
|
||||||
|
isomorphic_hydratable['set'] = set_hydratable_value;
|
||||||
|
|
||||||
|
/** @type {Hydratable} */
|
||||||
|
const hydratable = isomorphic_hydratable;
|
||||||
|
|
||||||
|
export { hydratable };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {string} key
|
||||||
|
* @param {T} value
|
||||||
|
* @param {{ encode?: Encode<T> }} [options]
|
||||||
|
*/
|
||||||
|
function set_hydratable_value(key, value, options = {}) {
|
||||||
|
if (!async_mode_flag) {
|
||||||
|
e.experimental_async_required('hydratable.set');
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = get_render_context();
|
||||||
|
|
||||||
|
if (store.hydratables.has(key)) {
|
||||||
|
e.hydratable_clobbering(key, store.hydratables.get(key)?.stack || 'unknown');
|
||||||
|
}
|
||||||
|
|
||||||
|
store.hydratables.set(key, create_entry(value, options?.encode));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function has_hydratable_value(key) {
|
||||||
|
if (!async_mode_flag) {
|
||||||
|
e.experimental_async_required('hydratable.has');
|
||||||
|
}
|
||||||
|
const store = get_render_context();
|
||||||
|
return store.hydratables.has(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {T} value
|
||||||
|
* @param {Encode<T> | undefined} encode
|
||||||
|
*/
|
||||||
|
function create_entry(value, encode) {
|
||||||
|
/** @type {Omit<HydratableEntry, 'value'> & { value: T }} */
|
||||||
|
const entry = {
|
||||||
|
value,
|
||||||
|
encode
|
||||||
|
};
|
||||||
|
|
||||||
|
if (DEV) {
|
||||||
|
entry.stack = new Error().stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
// @ts-ignore -- we don't include node types in the production build
|
||||||
|
/** @import { AsyncLocalStorage } from 'node:async_hooks' */
|
||||||
|
/** @import { RenderContext } from '#server' */
|
||||||
|
|
||||||
|
import { deferred } from '../shared/utils.js';
|
||||||
|
import * as e from './errors.js';
|
||||||
|
|
||||||
|
/** @type {Promise<void> | null} */
|
||||||
|
let current_render = null;
|
||||||
|
|
||||||
|
/** @type {RenderContext | null} */
|
||||||
|
let sync_context = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {Promise<T>} promise
|
||||||
|
* @returns {Promise<() => T>}
|
||||||
|
*/
|
||||||
|
export async function save_render_context(promise) {
|
||||||
|
var previous_context = sync_context;
|
||||||
|
var value = await promise;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
sync_context = previous_context;
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {RenderContext | null} */
|
||||||
|
export function try_get_render_context() {
|
||||||
|
if (sync_context !== null) {
|
||||||
|
return sync_context;
|
||||||
|
}
|
||||||
|
return als?.getStore() ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {RenderContext} */
|
||||||
|
export function get_render_context() {
|
||||||
|
const store = try_get_render_context();
|
||||||
|
|
||||||
|
if (!store) {
|
||||||
|
e.render_context_unavailable(
|
||||||
|
`\`AsyncLocalStorage\` is ${als ? 'available' : 'not available'}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {() => Promise<T>} fn
|
||||||
|
* @returns {Promise<T>}
|
||||||
|
*/
|
||||||
|
export async function with_render_context(fn) {
|
||||||
|
try {
|
||||||
|
sync_context = {
|
||||||
|
hydratables: new Map(),
|
||||||
|
cache: new Map()
|
||||||
|
};
|
||||||
|
if (in_webcontainer()) {
|
||||||
|
const { promise, resolve } = deferred();
|
||||||
|
const previous_render = current_render;
|
||||||
|
current_render = promise;
|
||||||
|
await previous_render;
|
||||||
|
return fn().finally(resolve);
|
||||||
|
}
|
||||||
|
return als ? als.run(sync_context, fn) : fn();
|
||||||
|
} finally {
|
||||||
|
if (!in_webcontainer()) {
|
||||||
|
sync_context = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {AsyncLocalStorage<RenderContext | null> | null} */
|
||||||
|
let als = null;
|
||||||
|
|
||||||
|
export async function init_render_context() {
|
||||||
|
if (als !== null) return;
|
||||||
|
try {
|
||||||
|
// @ts-ignore -- we don't include node types in the production build
|
||||||
|
const { AsyncLocalStorage } = await import('node:async_hooks');
|
||||||
|
als = new AsyncLocalStorage();
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this has to be a function because rollup won't treeshake it if it's a constant
|
||||||
|
function in_webcontainer() {
|
||||||
|
// @ts-ignore -- this will fail when we run typecheck because we exclude node types
|
||||||
|
// eslint-disable-next-line n/prefer-global/process
|
||||||
|
return !!globalThis.process?.versions?.webcontainer;
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { ok, test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
skip_mode: ['server'],
|
||||||
|
|
||||||
|
server_props: { environment: 'server' },
|
||||||
|
ssrHtml: '<p>The current environment is: server</p>',
|
||||||
|
|
||||||
|
props: { environment: 'browser' },
|
||||||
|
html: '<p>The current environment is: server</p>',
|
||||||
|
|
||||||
|
async test({ assert, target }) {
|
||||||
|
await tick();
|
||||||
|
const p = target.querySelector('p');
|
||||||
|
ok(p);
|
||||||
|
assert.htmlEqual(p.outerHTML, '<p>The current environment is: server</p>');
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { hydratable } from "svelte";
|
||||||
|
|
||||||
|
let { environment } = $props();
|
||||||
|
|
||||||
|
const value = hydratable("environment", () => environment)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>The current environment is: {value}</p>
|
||||||
Loading…
Reference in new issue