better serialization

pull/17124/head
Elliott Johnson 2 weeks ago
parent b36ba6d3cf
commit d6f240a949

@ -1,4 +1,4 @@
/** @import { Parse, Transport } from '#shared' */
/** @import { Decode, Transport } from '#shared' */
import { hydrating } from './dom/hydration.js';
/**
@ -19,13 +19,13 @@ export function hydratable(key, fn, options = {}) {
// something to be synchronously hydratable and then have it not be
return fn();
}
return parse(val, options.transport?.parse);
return parse(val, options.transport?.decode);
}
/**
* @template T
* @param {string} key
* @param {{ parse?: Parse<T> }} [options]
* @param {{ parse?: Decode<T> }} [options]
* @returns {T | undefined}
*/
export function get_hydratable_value(key, options = {}) {
@ -57,10 +57,10 @@ export function has_hydratable_value(key) {
/**
* @template T
* @param {string} val
* @param {Parse<T> | undefined} parse
* @param {unknown} val
* @param {Decode<T> | undefined} parse
* @returns {T}
*/
function parse(val, parse) {
return (parse ?? ((val) => new Function(`return (${val})`)()))(val);
return (parse ?? ((val) => /** @type {T} */ (val)))(val);
}

@ -1,4 +1,4 @@
/** @import { Stringify, Transport } from '#shared' */
/** @import { Encode, Transport } from '#shared' */
import { get_render_context } from './render-context.js';
@ -26,14 +26,14 @@ export function hydratable(key, fn, options = {}) {
}
const result = fn();
store.hydratables.set(key, { value: result, stringify: options.transport?.stringify });
store.hydratables.set(key, { value: result, encode: options.transport?.encode });
return result;
}
/**
* @template T
* @param {string} key
* @param {T} value
* @param {{ stringify?: Stringify<T> }} [options]
* @param {{ encode?: Encode<T> }} [options]
*/
export function set_hydratable_value(key, value, options = {}) {
const store = get_render_context();
@ -45,6 +45,6 @@ export function set_hydratable_value(key, value, options = {}) {
store.hydratables.set(key, {
value,
stringify: options.stringify
encode: options.encode
});
}

@ -578,15 +578,15 @@ export class Renderer {
/** @type {(value: unknown) => string} */
let default_stringify;
/** @type {[string, string][]} */
/** @type {[string, unknown][]} */
let entries = [];
for (const [k, v] of map) {
const serialize = v.stringify ?? (default_stringify ??= uneval);
const encode = v.encode ?? (default_stringify ??= new MemoizedUneval().uneval);
// sequential await is okay here -- all the work is already kicked off
entries.push([k, serialize(await v.value)]);
entries.push([k, encode(await v.value)]);
}
if (entries.length === 0) return null;
return Renderer.#hydratable_block(JSON.stringify(entries));
return Renderer.#hydratable_block(entries);
}
/**
@ -643,23 +643,20 @@ export class Renderer {
};
}
/** @param {string} serialized */
/** @param {[string, unknown][]} serialized */
static #hydratable_block(serialized) {
let entries = '';
for (const [k, v] of serialized) {
entries += `["${k}",${v}],`;
}
// TODO csp?
// TODO how can we communicate this error better? Is there a way to not just send it to the console?
// (it is probably very rare so... not too worried)
return `
<script>
var store = (window.__svelte ??= {}).h ??= new Map();
for (const [k,v] of ${serialized}) {
if (!store.has(k)) {
for (const [k,v] of ${entries}) {
store.set(k, v);
continue;
}
var stored_val = store.get(k);
if (stored_val.value !== v) {
throw new Error('TODO tried to populate the same hydratable key twice with different values');
}
}
</script>`;
}

@ -1,4 +1,4 @@
import type { CacheEntry, Stringify } from '#shared';
import type { CacheEntry, Encode } from '#shared';
import type { Element } from './dev';
import type { Renderer } from './renderer';
@ -20,7 +20,7 @@ export interface RenderContext {
string,
{
value: unknown;
stringify: Stringify<any> | undefined;
encode: Encode<any> | undefined;
}
>;
cache: Map<string, CacheEntry>;

@ -11,18 +11,18 @@ export type Snapshot<T> = ReturnType<typeof $state.snapshot<T>>;
export type MaybePromise<T> = T | Promise<T>;
export type Parse<T> = (value: string) => T;
export type Decode<T> = (value: unknown) => T;
export type Stringify<T> = (value: T) => string;
export type Encode<T> = (value: T) => unknown;
export type Transport<T> =
| {
stringify: Stringify<T>;
parse?: undefined;
encode: Encode<T>;
decode?: undefined;
}
| {
stringify?: undefined;
parse: Parse<T>;
encode?: undefined;
decode: Decode<T>;
};
export type Resource<T> = {

@ -598,18 +598,18 @@ declare module 'svelte' {
[K in keyof T]: () => T[K];
};
type Parse<T> = (value: string) => T;
type Decode<T> = (value: unknown) => T;
type Stringify<T> = (value: T) => string;
type Encode<T> = (value: T) => unknown;
type Transport<T> =
| {
stringify: Stringify<T>;
parse?: undefined;
encode: Encode<T>;
decode?: undefined;
}
| {
stringify?: undefined;
parse: Parse<T>;
encode?: undefined;
decode: Decode<T>;
};
export {};
@ -2613,20 +2613,20 @@ declare module 'svelte/server' {
type RenderOutput = SyncRenderOutput & PromiseLike<SyncRenderOutput>;
export function setHydratableValue<T>(key: string, value: T, options?: {
stringify?: Stringify<T>;
encode?: Encode<T>;
} | undefined): void;
type Stringify<T> = (value: T) => string;
type Encode<T> = (value: T) => unknown;
export {};
}
declare module 'svelte/client' {
export function getHydratableValue<T>(key: string, options?: {
parse?: Parse<T>;
parse?: Decode<T>;
} | undefined): T | undefined;
export function hasHydratableValue(key: string): boolean;
type Parse<T> = (value: string) => T;
type Decode<T> = (value: unknown) => T;
export {};
}

Loading…
Cancel
Save