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'; 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 // something to be synchronously hydratable and then have it not be
return fn(); return fn();
} }
return parse(val, options.transport?.parse); return parse(val, options.transport?.decode);
} }
/** /**
* @template T * @template T
* @param {string} key * @param {string} key
* @param {{ parse?: Parse<T> }} [options] * @param {{ parse?: Decode<T> }} [options]
* @returns {T | undefined} * @returns {T | undefined}
*/ */
export function get_hydratable_value(key, options = {}) { export function get_hydratable_value(key, options = {}) {
@ -57,10 +57,10 @@ export function has_hydratable_value(key) {
/** /**
* @template T * @template T
* @param {string} val * @param {unknown} val
* @param {Parse<T> | undefined} parse * @param {Decode<T> | undefined} parse
* @returns {T} * @returns {T}
*/ */
function parse(val, parse) { 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'; import { get_render_context } from './render-context.js';
@ -26,14 +26,14 @@ export function hydratable(key, fn, options = {}) {
} }
const result = fn(); 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; return result;
} }
/** /**
* @template T * @template T
* @param {string} key * @param {string} key
* @param {T} value * @param {T} value
* @param {{ stringify?: Stringify<T> }} [options] * @param {{ encode?: Encode<T> }} [options]
*/ */
export function set_hydratable_value(key, value, options = {}) { export function set_hydratable_value(key, value, options = {}) {
const store = get_render_context(); const store = get_render_context();
@ -45,6 +45,6 @@ export function set_hydratable_value(key, value, options = {}) {
store.hydratables.set(key, { store.hydratables.set(key, {
value, value,
stringify: options.stringify encode: options.encode
}); });
} }

@ -578,15 +578,15 @@ export class Renderer {
/** @type {(value: unknown) => string} */ /** @type {(value: unknown) => string} */
let default_stringify; let default_stringify;
/** @type {[string, string][]} */ /** @type {[string, unknown][]} */
let entries = []; let entries = [];
for (const [k, v] of map) { 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 // 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; 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) { static #hydratable_block(serialized) {
let entries = '';
for (const [k, v] of serialized) {
entries += `["${k}",${v}],`;
}
// TODO csp? // TODO csp?
// TODO how can we communicate this error better? Is there a way to not just send it to the console? // 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) // (it is probably very rare so... not too worried)
return ` return `
<script> <script>
var store = (window.__svelte ??= {}).h ??= new Map(); var store = (window.__svelte ??= {}).h ??= new Map();
for (const [k,v] of ${serialized}) { for (const [k,v] of ${entries}) {
if (!store.has(k)) {
store.set(k, v); 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>`; </script>`;
} }

@ -1,4 +1,4 @@
import type { CacheEntry, Stringify } from '#shared'; import type { CacheEntry, Encode } from '#shared';
import type { Element } from './dev'; import type { Element } from './dev';
import type { Renderer } from './renderer'; import type { Renderer } from './renderer';
@ -20,7 +20,7 @@ export interface RenderContext {
string, string,
{ {
value: unknown; value: unknown;
stringify: Stringify<any> | undefined; encode: Encode<any> | undefined;
} }
>; >;
cache: Map<string, CacheEntry>; 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 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> = export type Transport<T> =
| { | {
stringify: Stringify<T>; encode: Encode<T>;
parse?: undefined; decode?: undefined;
} }
| { | {
stringify?: undefined; encode?: undefined;
parse: Parse<T>; decode: Decode<T>;
}; };
export type Resource<T> = { export type Resource<T> = {

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

Loading…
Cancel
Save