@sveltejs/kit
Version:
SvelteKit is the fastest way to build Svelte apps
164 lines (140 loc) • 5.57 kB
JavaScript
/** @import { RemoteResource, RemotePrerenderFunction } from '@sveltejs/kit' */
/** @import { RemotePrerenderInputsGenerator, RemoteInfo, MaybePromise } from 'types' */
/** @import { StandardSchemaV1 } from '@standard-schema/spec' */
import { error, json } from '@sveltejs/kit';
import { DEV } from 'esm-env';
import { getRequestEvent } from '../event.js';
import { create_remote_cache_key, stringify, stringify_remote_arg } from '../../../shared.js';
import { app_dir, base } from '__sveltekit/paths';
import {
check_experimental,
create_validator,
get_response,
parse_remote_response,
run_remote_function
} from './shared.js';
import { get_event_state } from '../../../server/event-state.js';
/**
* Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a `fetch` call.
*
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#prerender) for full documentation.
*
* @template Output
* @overload
* @param {() => MaybePromise<Output>} fn
* @param {{ inputs?: RemotePrerenderInputsGenerator<void>, dynamic?: boolean }} [options]
* @returns {RemotePrerenderFunction<void, Output>}
* @since 2.27
*/
/**
* Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a `fetch` call.
*
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#prerender) for full documentation.
*
* @template Input
* @template Output
* @overload
* @param {'unchecked'} validate
* @param {(arg: Input) => MaybePromise<Output>} fn
* @param {{ inputs?: RemotePrerenderInputsGenerator<Input>, dynamic?: boolean }} [options]
* @returns {RemotePrerenderFunction<Input, Output>}
* @since 2.27
*/
/**
* Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a `fetch` call.
*
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#prerender) for full documentation.
*
* @template {StandardSchemaV1} Schema
* @template Output
* @overload
* @param {Schema} schema
* @param {(arg: StandardSchemaV1.InferOutput<Schema>) => MaybePromise<Output>} fn
* @param {{ inputs?: RemotePrerenderInputsGenerator<StandardSchemaV1.InferOutput<Schema>>, dynamic?: boolean }} [options]
* @returns {RemotePrerenderFunction<StandardSchemaV1.InferOutput<Schema>, Output>}
* @since 2.27
*/
/**
* @template Input
* @template Output
* @param {any} validate_or_fn
* @param {any} [fn_or_options]
* @param {{ inputs?: RemotePrerenderInputsGenerator<Input>, dynamic?: boolean }} [maybe_options]
* @returns {RemotePrerenderFunction<Input, Output>}
* @since 2.27
*/
/*@__NO_SIDE_EFFECTS__*/
export function prerender(validate_or_fn, fn_or_options, maybe_options) {
check_experimental('prerender');
const maybe_fn = typeof fn_or_options === 'function' ? fn_or_options : undefined;
/** @type {typeof maybe_options} */
const options = maybe_options ?? (maybe_fn ? undefined : fn_or_options);
/** @type {(arg?: Input) => MaybePromise<Output>} */
const fn = maybe_fn ?? validate_or_fn;
/** @type {(arg?: any) => MaybePromise<Input>} */
const validate = create_validator(validate_or_fn, maybe_fn);
/** @type {RemoteInfo} */
const __ = {
type: 'prerender',
id: '',
name: '',
has_arg: !!maybe_fn,
inputs: options?.inputs,
dynamic: options?.dynamic
};
/** @type {RemotePrerenderFunction<Input, Output> & { __: RemoteInfo }} */
const wrapper = (arg) => {
/** @type {Promise<Output> & Partial<RemoteResource<Output>>} */
const promise = (async () => {
const event = getRequestEvent();
const state = get_event_state(event);
const payload = stringify_remote_arg(arg, state.transport);
const id = __.id;
const url = `${base}/${app_dir}/remote/${id}${payload ? `/${payload}` : ''}`;
if (!state.prerendering && !DEV && !event.isRemoteRequest) {
try {
return await get_response(id, arg, event, async () => {
// TODO adapters can provide prerendered data more efficiently than
// fetching from the public internet
const response = await fetch(new URL(url, event.url.origin).href);
if (!response.ok) {
throw new Error('Prerendered response not found');
}
const prerendered = await response.json();
if (prerendered.type === 'error') {
error(prerendered.status, prerendered.error);
}
// TODO can we redirect here?
(state.remote_data ??= {})[create_remote_cache_key(id, payload)] = prerendered.result;
return parse_remote_response(prerendered.result, state.transport);
});
} catch {
// not available prerendered, fallback to normal function
}
}
if (state.prerendering?.remote_responses.has(url)) {
return /** @type {Promise<any>} */ (state.prerendering.remote_responses.get(url));
}
const promise = get_response(id, arg, event, () =>
run_remote_function(event, false, arg, validate, fn)
);
if (state.prerendering) {
state.prerendering.remote_responses.set(url, promise);
}
const result = await promise;
if (state.prerendering) {
const body = { type: 'result', result: stringify(result, state.transport) };
state.prerendering.dependencies.set(url, {
body: JSON.stringify(body),
response: json(body)
});
}
// TODO this is missing error/loading/current/status
return result;
})();
promise.catch(() => {});
return /** @type {RemoteResource<Output>} */ (promise);
};
Object.defineProperty(wrapper, '__', { value: __ });
return wrapper;
}