Skip to content

Commit

Permalink
Update browser serializations to use a custom element instead of `<me…
Browse files Browse the repository at this point in the history
…ta>`
  • Loading branch information
lemonmade committed Sep 23, 2024
1 parent 7b51d45 commit 97ba539
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 5 deletions.
6 changes: 6 additions & 0 deletions .changeset/fresh-seahorses-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@quilted/browser': patch
'@quilted/quilt': patch
---

Update browser serializations to use a custom element instead of `<meta>`
42 changes: 39 additions & 3 deletions packages/browser/source/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -185,12 +185,48 @@ export class BrowserElementAttributes<Element extends HTMLElement> {
}
}

// 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<T = unknown> 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<T>(this) as T;
}

set data(value: T) {
this.setAttribute('content', JSON.stringify(encode(value)));
}
}

export class BrowserSerializations {
private readonly serializations = new Map<string, unknown>(
Array.from(
document.querySelectorAll<HTMLMetaElement>(`meta[name^="serialized:"]`),
document.querySelectorAll<HTMLElement>(
DEFAULT_SERIALIZATION_ELEMENT_NAME,
),
).map((node) => [
node.name.replace(/^serialized:/, ''),
node.getAttribute('name') ?? '_default',
getSerializedFromNode(node),
]),
);
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/source/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './types.ts';
export {Browser, BrowserCookies} from './browser.ts';
export * from './browser.ts';
7 changes: 6 additions & 1 deletion packages/quilt/source/server/request-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,12 @@ export async function renderToResponse(
<meta {...(meta as JSX.HTMLAttributes<HTMLMetaElement>)} />
))}
{browserResponse.serializations.value.map(({id, value}) => (
<meta name={`serialized:${id}`} content={JSON.stringify(value)} />
// @ts-expect-error a custom element that I don’t want to define,
// since it’s an optional part of the browser library.
<browser-serialization
name={`serialized:${id}`}
content={JSON.stringify(value)}
/>
))}
{synchronousAssets?.scripts.map((script) => (
<ScriptAsset
Expand Down

0 comments on commit 97ba539

Please sign in to comment.