diff --git a/.changeset/fresh-seahorses-cry.md b/.changeset/fresh-seahorses-cry.md
new file mode 100644
index 000000000..d44bbaaca
--- /dev/null
+++ b/.changeset/fresh-seahorses-cry.md
@@ -0,0 +1,8 @@
+---
+'@quilted/browser': patch
+'@quilted/preact-browser': patch
+'@quilted/react-query': patch
+'@quilted/quilt': patch
+---
+
+Update browser serializations to use a custom element instead of ``
diff --git a/integrations/react-query/source/ReactQueryContext.tsx b/integrations/react-query/source/ReactQueryContext.tsx
index 0e979cf50..b4ea0b9d8 100644
--- a/integrations/react-query/source/ReactQueryContext.tsx
+++ b/integrations/react-query/source/ReactQueryContext.tsx
@@ -35,8 +35,8 @@ export function ReactQueryContext({
{typeof document === 'undefined' && (
+ name={SERIALIZATION_ID}
+ content={() =>
dehydrate(client, {
shouldDehydrateQuery: () => true,
})
diff --git a/packages/browser/source/browser.ts b/packages/browser/source/browser.ts
index 181a841a8..ee8940686 100644
--- a/packages/browser/source/browser.ts
+++ b/packages/browser/source/browser.ts
@@ -9,7 +9,7 @@ import {
} from '@quilted/signals';
import type {BrowserDetails, CookieOptions, Cookies} from './types.ts';
-import {decode} from './encoding.ts';
+import {encode, decode} from './encoding.ts';
export class Browser implements BrowserDetails {
readonly title = new BrowserTitle();
@@ -185,12 +185,48 @@ export class BrowserElementAttributes {
}
}
+// Make this module execute in non-DOM environments
+const HTMLElement: typeof globalThis.HTMLElement =
+ typeof globalThis.HTMLElement === 'function'
+ ? globalThis.HTMLElement
+ : (class HTMLElement {} as any);
+
+const DEFAULT_SERIALIZATION_ELEMENT_NAME = 'browser-serialization';
+
+export class BrowserSerializationElement extends HTMLElement {
+ static define(name: string = DEFAULT_SERIALIZATION_ELEMENT_NAME) {
+ customElements.define(name, this);
+ }
+
+ get name(): string | undefined {
+ return this.getAttribute('name') ?? undefined;
+ }
+
+ set name(value: string | undefined) {
+ if (value) {
+ this.setAttribute('name', value);
+ } else {
+ this.removeAttribute('name');
+ }
+ }
+
+ get data() {
+ return getSerializedFromNode(this) as T;
+ }
+
+ set data(value: T) {
+ this.setAttribute('content', JSON.stringify(encode(value)));
+ }
+}
+
export class BrowserSerializations {
private readonly serializations = new Map(
Array.from(
- document.querySelectorAll(`meta[name^="serialized:"]`),
+ document.querySelectorAll(
+ DEFAULT_SERIALIZATION_ELEMENT_NAME,
+ ),
).map((node) => [
- node.name.replace(/^serialized:/, ''),
+ node.getAttribute('name') ?? '_default',
getSerializedFromNode(node),
]),
);
@@ -213,7 +249,7 @@ export class BrowserSerializations {
}
function getSerializedFromNode(node: Element): T | undefined {
- const value = (node as HTMLMetaElement).content;
+ const value = node.getAttribute('content');
try {
return value ? (decode(JSON.parse(value)) as T) : undefined;
diff --git a/packages/browser/source/index.ts b/packages/browser/source/index.ts
index 08dde23c5..2c952ec8c 100644
--- a/packages/browser/source/index.ts
+++ b/packages/browser/source/index.ts
@@ -1,2 +1,2 @@
export * from './types.ts';
-export {Browser, BrowserCookies} from './browser.ts';
+export * from './browser.ts';
diff --git a/packages/browser/source/server.ts b/packages/browser/source/server.ts
index 930610219..ae18e4688 100644
--- a/packages/browser/source/server.ts
+++ b/packages/browser/source/server.ts
@@ -219,9 +219,9 @@ export class BrowserResponseSerializations {
readonly #serializations = new Map();
get value() {
- return [...this.#serializations].map(([id, value]) => ({
- id,
- value: encode(typeof value === 'function' ? value() : value),
+ return [...this.#serializations].map(([name, content]) => ({
+ name,
+ content: encode(typeof content === 'function' ? content() : content),
}));
}
@@ -245,11 +245,11 @@ export class BrowserResponseSerializations {
return this.#serializations.get(id) as any;
}
- set(id: string, data: unknown) {
- if (data === undefined) {
- this.#serializations.delete(id);
+ set(name: string, content: unknown) {
+ if (content === undefined) {
+ this.#serializations.delete(name);
} else {
- this.#serializations.set(id, data);
+ this.#serializations.set(name, content);
}
}
diff --git a/packages/preact-browser/source/server/components/Serialize.tsx b/packages/preact-browser/source/server/components/Serialize.tsx
index 51abe25a9..5535e5062 100644
--- a/packages/preact-browser/source/server/components/Serialize.tsx
+++ b/packages/preact-browser/source/server/components/Serialize.tsx
@@ -1,13 +1,13 @@
import {useResponseSerialization} from '../hooks/serialized.ts';
export function Serialize({
- id,
- value,
+ name,
+ content,
}: {
- id: string;
- value: T | (() => T);
+ name: string;
+ content: T | (() => T);
}) {
if (typeof document === 'object') return null;
- useResponseSerialization(id, value);
+ useResponseSerialization(name, content);
return null;
}
diff --git a/packages/preact-browser/source/server/hooks/serialized.ts b/packages/preact-browser/source/server/hooks/serialized.ts
index c9a25ab47..c05a4369f 100644
--- a/packages/preact-browser/source/server/hooks/serialized.ts
+++ b/packages/preact-browser/source/server/hooks/serialized.ts
@@ -4,10 +4,10 @@ import {useBrowserResponseAction} from './browser-response-action.ts';
* Sets a serialization for the HTML response. This value can then be read using
* the `useSerialization` hook.
*/
-export function useResponseSerialization(key: string, value: unknown) {
+export function useResponseSerialization(name: string, content: unknown) {
if (typeof document === 'object') return;
useBrowserResponseAction((response) => {
- response.serializations.set(key, value);
+ response.serializations.set(name, content);
});
}
diff --git a/packages/quilt/source/server/request-router.tsx b/packages/quilt/source/server/request-router.tsx
index 019fbd90c..71cd2fd73 100644
--- a/packages/quilt/source/server/request-router.tsx
+++ b/packages/quilt/source/server/request-router.tsx
@@ -224,8 +224,13 @@ export async function renderToResponse(
{browserResponse.metas.value.map((meta) => (
)} />
))}
- {browserResponse.serializations.value.map(({id, value}) => (
-
+ {browserResponse.serializations.value.map(({name, content}) => (
+ // @ts-expect-error a custom element that I don’t want to define,
+ // since it’s an optional part of the browser library.
+
))}
{synchronousAssets?.scripts.map((script) => (
{
return (
<>
Hello, {builder}!
-
+
>
);
}