Passing data from server to client
When you output Web Components from the server, with or without the declarative shadow DOM pre-render, it’s often desirable to initialize them with server-generated, complex data.
Instead of reinventing the wheel, Gracile is just providing you the guidelines on how to achieve that, well… gracefully 😌.
Note that this isn’t even a JS framework thing. The patterns described below are transposable for any HTML-outputting backends.
Typically you’ll find two philosophies:
- Put the hydrating data in a
<script type="application/json">{...}</script>, and from there the client-side framework will pick that up to hydrate the component tree by reconciling it with data.
Examples: Nuxt, Next, Gatsby… - Put the hydrating data in a child element attribute, typically called an
“Island”.
Examples: Astro, Fresh,…
With Lit SSR you can use both methods, though. The first one is
described here.
In
this mini-guide, we’ll focus on the “Island-ey” pattern, for now.
Custom Element examples
📄 /src/features/my-partial.ts
import { htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
} from 'lit';
Will handle server rendering just fine as well!
import './my-lit-element.js';
import type { InitialDatatype InitialData = any } from './my-lit-element.js';
export type InitialDatatype InitialData = {
foo?: string;
baz?: null | number;
}
= { foofoo?: string | undefined ?: string; bazbaz?: number | null | undefined ?: null | number };
const dataconst data: InitialData = { foofoo: string : 'bar', bazbaz: number : 2 } satisfies InitialDatatype InitialData = any ;
export const myServerRenderedPartialconst myServerRenderedPartial: TemplateResult<1> = htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`
<!-- ... -->
<section>
<my-lit-element initialData=${JSONvar JSON: JSONAn intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
.stringifyJSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
(dataconst data: InitialData )}></my-lit-element>
</section>
<section>
<my-bare-element data-initial=${JSONvar JSON: JSONAn intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
.stringifyJSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
(dataconst data: InitialData )}></my-bare-element>
</section>
<!-- ... -->
`;
📄 /src/features/my-partial.client.ts
import './my-lit-element.ts';
import './my-bare-element.ts';
📄 /src/features/my-lit-element.ts
import { LitElementclass LitElementBase element class that manages element properties and attributes, and
renders a lit-html template.
To define a component, subclass LitElement and implement a
render method to provide the component's template. Define properties
using the
{@linkcode
LitElement.properties
properties
}
property or the
{@linkcode
property
}
decorator.
, htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
} from 'lit';
import { customElementconst customElement: (tagName: string) => CustomElementDecoratorClass decorator factory that defines the decorated class as a custom element.
, propertyfunction property(options?: PropertyDeclaration): PropertyDecoratorA class field or accessor decorator which creates a reactive property that
reflects a corresponding attribute value. When a decorated property is set
the element will update and render. A
{@linkcode
PropertyDeclaration
}
may
optionally be supplied to configure property features.
This decorator should only be used for public fields. As public fields,
properties should be considered as primarily settable by element users,
either via attribute or the property itself.
Generally, properties that are changed by the element should be private or
protected fields and should use the
{@linkcode
state
}
decorator.
However, sometimes element code does need to set a public property. This
should typically only be done in response to user interaction, and an event
should be fired informing the user; for example, a checkbox sets its
checked property when clicked and fires a changed event. Mutating public
properties should typically not be done for non-primitive (object or array)
properties. In other cases when an element needs to manage state, a private
property decorated via the
{@linkcode
state
}
decorator should be used. When
needed, state properties can be initialized via public properties to
facilitate complex interactions.
class MyElement {
} from 'lit/decorators.js';
import type { InitialDatatype InitialData = {
foo?: string;
baz?: null | number;
}
} from './my-partial.ts';
@customElementfunction customElement(tagName: string): CustomElementDecoratorClass decorator factory that defines the decorated class as a custom element.
('my-lit-element')
export class MyLitElementclass MyLitElement extends LitElementclass LitElementBase element class that manages element properties and attributes, and
renders a lit-html template.
To define a component, subclass LitElement and implement a
render method to provide the component's template. Define properties
using the
{@linkcode
LitElement.properties
properties
}
property or the
{@linkcode
property
}
decorator.
{
@propertyfunction property(options?: PropertyDeclaration): PropertyDecoratorA class field or accessor decorator which creates a reactive property that
reflects a corresponding attribute value. When a decorated property is set
the element will update and render. A
{@linkcode
PropertyDeclaration
}
may
optionally be supplied to configure property features.
This decorator should only be used for public fields. As public fields,
properties should be considered as primarily settable by element users,
either via attribute or the property itself.
Generally, properties that are changed by the element should be private or
protected fields and should use the
{@linkcode
state
}
decorator.
However, sometimes element code does need to set a public property. This
should typically only be done in response to user interaction, and an event
should be fired informing the user; for example, a checkbox sets its
checked property when clicked and fires a changed event. Mutating public
properties should typically not be done for non-primitive (object or array)
properties. In other cases when an element needs to manage state, a private
property decorated via the
{@linkcode
state
}
decorator should be used. When
needed, state properties can be initialized via public properties to
facilitate complex interactions.
class MyElement {
({ typePropertyDeclaration<unknown, unknown>.type?: unknownIndicates the type of the property. This is used only as a hint for the
converter to determine how to convert the attribute
to/from a property.
: Objectvar Object: ObjectConstructorProvides functionality common to all JavaScript objects.
})
initialDataMyLitElement.initialData: string | InitialData : InitialDatatype InitialData = {
foo?: string;
baz?: null | number;
}
| string = {};
renderMyLitElement.render(): TemplateResult<1>Invoked on each update to perform rendering tasks. This method may return
any value renderable by lit-html's ChildPart - typically a
TemplateResult. Setting properties inside this method will not trigger
the element to update.
() {
if (typeof this.initialDataMyLitElement.initialData: string | InitialData !== 'object') return htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`Invalid data`;
return htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`
<!-- -->
<div>${this.initialDataMyLitElement.initialData: InitialData .foofoo?: string | undefined }</div>
<div>${this.initialDataMyLitElement.initialData: InitialData .bazbaz?: number | null | undefined || 'Oh no'}</div>
`;
}
}
📄 /src/features/my-bare-element.js
import type { InitialDatatype InitialData = {
foo?: string;
baz?: null | number;
}
} from './my-partial.ts';
class MyBareElementclass MyBareElement extends HTMLElementvar HTMLElement: {
new (): HTMLElement;
prototype: HTMLElement;
}
The HTMLElement interface represents any HTML element. Some elements directly implement this interface, while others implement it via an interface that inherits it.
{
#initialData?: InitialDatatype InitialData = {
foo?: string;
baz?: null | number;
}
;
connectedCallbackMyBareElement.connectedCallback(): void () {
this.attachShadowElement.attachShadow(init: ShadowRootInit): ShadowRootThe Element.attachShadow() method attaches a shadow DOM tree to the specified element and returns a reference to its ShadowRoot.
({ modeShadowRootInit.mode: ShadowRootMode : 'open' });
const attrDataconst attrData: string | undefined = this.datasetHTMLOrSVGElement.dataset: DOMStringMap .initialDOMStringMap[string]: string | undefined ;
if (attrDataconst attrData: string | undefined ) this.#initialData = JSONvar JSON: JSONAn intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
.parseJSON.parse(text: string, reviver?: (this: any, key: string, value: any) => any): anyConverts a JavaScript Object Notation (JSON) string into an object.
(attrDataconst attrData: string );
this.shadowRootElement.shadowRoot: ShadowRoot | nullThe Element.shadowRoot read-only property represents the shadow root hosted by the element.
!.innerHTMLShadowRoot.innerHTML: stringThe innerHTML property of the ShadowRoot interface gets or sets the HTML markup to the DOM tree inside the ShadowRoot.
= `
<div>${this.#initialData?.foofoo?: string | undefined || 'Not defined!'}</div>
<div>${this.#initialData?.bazbaz?: number | null | undefined || 'Not defined!'}</div>
`;
}
}
customElementsvar customElements: CustomElementRegistryThe customElements read-only property of the Window interface returns a reference to the CustomElementRegistry object, which can be used to register new custom elements and get information about previously registered custom elements.
.defineCustomElementRegistry.define(name: string, constructor: CustomElementConstructor, options?: ElementDefinitionOptions): voidThe define() method of the CustomElementRegistry interface adds a definition for a custom element to the custom element registry, mapping its name to the constructor which will be used to create it.
('my-bare-element', MyBareElementclass MyBareElement );
Another big win by leveraging Web Component intrinsic versatility!
No need to bring superfluous JS kilobytes to do this hard work, it’s already
embedded in your browser.
Please note that these examples could be improved, notably for:
initialData: InitialData | string = {};
string is just for the
ts-lit-plugin to be appeased
inside the html template.
Also, initialData and initialdata are equivalent, but hyphenated properties
are rejected by the linter.
Maybe a bit more elegant solutions could be achieved, but those a more convention/typing issues, not runtime ones.
Advanced serialization / deserialization
If you want to pass more complex data like object with self referencing children, dates, and more, you can use a dedicated library for that, there are plenty.
For example the excellent Seroval library
can be used in conjunction with a Lit
@property custom converter
to properly “revive” the serialized data.
This is a fully working example:
📄 /src/routes/serialization.ts
import { defineRouteimport defineRoute } from '@gracile/gracile/route';
import { htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
} from 'lit';
import { serializefunction serialize<T>(source: T, options?: SyncParserContextOptions): string } from 'seroval';
import { documentconst document: (props: {
url: URL;
}) => any
} from '../document.js';
import { complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
} from './_serialization-demo-data.js';
export default defineRouteimport defineRoute ({
documentdocument: (context: any) => any : (contextcontext: any ) => documentfunction document(props: {
url: URL;
}): any
(contextcontext: any ),
templatetemplate: () => TemplateResult<1> : () => htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`
<serialization-example
myComplexData=${serializeserialize<{
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}>(source: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}, options?: SyncParserContextOptions): string
(complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
)}
></serialization-example>
`,
});
📄 /src/routes/_serialization-demo-data.ts
export const complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
= {
numbernumber: readonly [number, 0, number, number, number] : [Mathvar Math: MathAn intrinsic object that provides basic mathematics functionality and constants.
.randomMath.random(): numberReturns a pseudorandom number between 0 and 1.
(), -0, NaNvar NaN: number , Infinityvar Infinity: number , -Infinityvar Infinity: number ],
stringstring: readonly ["hello world", "<script>Hello World</script>"] : ['hello world', '<script>Hello World</script>'],
booleanboolean: readonly [true, false] : [true, false],
nullnull: null : null,
undefinedundefined: undefined : undefinedvar undefined ,
bigintbigint: 9007199254740991n : 9007199254740991n,
arrayarray: readonly [undefined, undefined, undefined] : [, , ,], // holes
regexpregexp: RegExp : /[a-z0-9]+/i,
datedate: Date : new Datevar Date: DateConstructor
new () => Date (+4 overloads)
(),
mapmap: Map<string, string> : new Mapvar Map: MapConstructor
new <string, string>(iterable?: Iterable<readonly [string, string]> | null | undefined) => Map<string, string> (+3 overloads)
([['hello', 'world']]),
setset: Set<string> : new Setvar Set: SetConstructor
new <string>(iterable?: Iterable<string> | null | undefined) => Set<string> (+1 overload)
(['hello', 'world']),
} as consttype const = {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
;
export type MyDatatype MyData = {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
= typeof complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
;
// self cyclic references
// recursive objects
complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
.self = complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
;
// recursive arrays
complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
.arrayarray: readonly [undefined, undefined, undefined] .push(complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
.arrayarray: readonly [undefined, undefined, undefined] );
// recursive maps
complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
.mapmap: Map<string, string> .setMap<string, string>.set(key: string, value: string): Map<string, string>Adds a new element with a specified key and value to the Map. If an element with the same key already exists, the element will be updated.
('self', complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
.mapmap: Map<string, string> );
// recursive sets
complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
.setset: Set<string> .addSet<string>.add(value: string): Set<string>Appends a new element with a specified value to the end of the Set.
(complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
.setset: Set<string> );
// mutual cyclic references
complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
.arrayarray: readonly [undefined, undefined, undefined] .push(complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
.mapmap: Map<string, string> );
complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
.mapmap: Map<string, string> .setMap<string, string>.set(key: string, value: string): Map<string, string>Adds a new element with a specified key and value to the Map. If an element with the same key already exists, the element will be updated.
('mutual', complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
.setset: Set<string> );
complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
.setset: Set<string> .addSet<string>.add(value: string): Set<string>Appends a new element with a specified value to the end of the Set.
(complexDataconst complexData: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
.arrayarray: readonly [undefined, undefined, undefined] );
📄 /src/routes/serialization.client.ts
import { LitElementclass LitElementBase element class that manages element properties and attributes, and
renders a lit-html template.
To define a component, subclass LitElement and implement a
render method to provide the component's template. Define properties
using the
{@linkcode
LitElement.properties
properties
}
property or the
{@linkcode
property
}
decorator.
, htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
} from 'lit';
import { customElementconst customElement: (tagName: string) => CustomElementDecoratorClass decorator factory that defines the decorated class as a custom element.
, propertyfunction property(options?: PropertyDeclaration): PropertyDecoratorA class field or accessor decorator which creates a reactive property that
reflects a corresponding attribute value. When a decorated property is set
the element will update and render. A
{@linkcode
PropertyDeclaration
}
may
optionally be supplied to configure property features.
This decorator should only be used for public fields. As public fields,
properties should be considered as primarily settable by element users,
either via attribute or the property itself.
Generally, properties that are changed by the element should be private or
protected fields and should use the
{@linkcode
state
}
decorator.
However, sometimes element code does need to set a public property. This
should typically only be done in response to user interaction, and an event
should be fired informing the user; for example, a checkbox sets its
checked property when clicked and fires a changed event. Mutating public
properties should typically not be done for non-primitive (object or array)
properties. In other cases when an element needs to manage state, a private
property decorated via the
{@linkcode
state
}
decorator should be used. When
needed, state properties can be initialized via public properties to
facilitate complex interactions.
class MyElement {
} from 'lit/decorators.js';
import { serializefunction serialize<T>(source: T, options?: SyncParserContextOptions): string , deserializefunction deserialize<T>(source: string): T } from 'seroval';
import type { MyDatatype MyData = {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
} from './_serialization-demo-data.js';
@customElementfunction customElement(tagName: string): CustomElementDecoratorClass decorator factory that defines the decorated class as a custom element.
('serialization-example')
export class SerializationExampleclass SerializationExample extends LitElementclass LitElementBase element class that manages element properties and attributes, and
renders a lit-html template.
To define a component, subclass LitElement and implement a
render method to provide the component's template. Define properties
using the
{@linkcode
LitElement.properties
properties
}
property or the
{@linkcode
property
}
decorator.
{
@propertyfunction property(options?: PropertyDeclaration): PropertyDecoratorA class field or accessor decorator which creates a reactive property that
reflects a corresponding attribute value. When a decorated property is set
the element will update and render. A
{@linkcode
PropertyDeclaration
}
may
optionally be supplied to configure property features.
This decorator should only be used for public fields. As public fields,
properties should be considered as primarily settable by element users,
either via attribute or the property itself.
Generally, properties that are changed by the element should be private or
protected fields and should use the
{@linkcode
state
}
decorator.
However, sometimes element code does need to set a public property. This
should typically only be done in response to user interaction, and an event
should be fired informing the user; for example, a checkbox sets its
checked property when clicked and fires a changed event. Mutating public
properties should typically not be done for non-primitive (object or array)
properties. In other cases when an element needs to manage state, a private
property decorated via the
{@linkcode
state
}
decorator should be used. When
needed, state properties can be initialized via public properties to
facilitate complex interactions.
class MyElement {
({
converterPropertyDeclaration<unknown, unknown>.converter?: AttributeConverter<unknown, unknown> | undefinedIndicates how to convert the attribute to/from a property. If this value
is a function, it is used to convert the attribute value a the property
value. If it's an object, it can have keys for fromAttribute and
toAttribute. If no toAttribute function is provided and
reflect is set to true, the property value is set directly to the
attribute. A default converter is used if none is provided; it supports
Boolean, String, Number, Object, and Array. Note,
when a property changes and the converter is used to update the attribute,
the property is never updated again as a result of the attribute changing,
and vice versa.
: {
fromAttributeComplexAttributeConverter<unknown, unknown>.fromAttribute?(value: string | null, type?: unknown): unknownCalled to convert an attribute value to a property
value.
: (valuevalue: string | null ) =>
valuevalue: string | null ? deserializedeserialize<{
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}>(source: string): {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
<MyDatatype MyData = {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
>(valuevalue: string ) : undefinedvar undefined ,
},
})
Reminder that `string` is used to prevent a type error in the template.
myComplexDataSerializationExample.myComplexData?: string | {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
} | undefined
?: MyDatatype MyData = {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
| string;
renderSerializationExample.render(): TemplateResult<1>Invoked on each update to perform rendering tasks. This method may return
any value renderable by lit-html's ChildPart - typically a
TemplateResult. Setting properties inside this method will not trigger
the element to update.
() {
if (typeof this.myComplexDataSerializationExample.myComplexData?: string | {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
} | undefined
!== 'object') return htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`Invalid data`;
return htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`
<span>Dump</span>
<pre>${serializeserialize<{
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}>(source: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}, options?: SyncParserContextOptions): string
(this.myComplexDataSerializationExample.myComplexData?: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
)}</pre>
<!-- -->
${this.myComplexDataSerializationExample.myComplexData?: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
.mapmap: Map<string, string> .sizeMap<string, string>.size: number }
<!-- -->
${this.myComplexDataSerializationExample.myComplexData?: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
.datedate: Date .getFullYearDate.getFullYear(): numberGets the year, using local time.
()}
<!-- -->
${this.myComplexDataSerializationExample.myComplexData?: {
readonly number: readonly [number, 0, number, number, number];
readonly string: readonly ["hello world", "<script>Hello World</script>"];
readonly boolean: readonly [true, false];
readonly null: null;
readonly undefined: undefined;
readonly bigint: 9007199254740991n;
readonly array: readonly [undefined, undefined, undefined];
readonly regexp: RegExp;
readonly date: Date;
readonly map: Map<string, string>;
readonly set: Set<string>;
}
.regexpregexp: RegExp .testRegExp.test(string: string): booleanReturns a Boolean value that indicates whether or not a pattern exists in a searched string.
('Regexp test string')}
`;
}
}
Refreshing data without a full page reload
For that, just set up a
JSON handler in
your route. Then, connect this pipe to your Web Components properties and
interactions.
Could be bidirectional, too (GET/POST → Response).