DOM shim for Lit Server Side Rendering (SSR) providing minimal implementations of DOM APIs for Node.js environments.
npx @tessl/cli install tessl/npm-lit-labs--ssr-dom-shim@1.4.00
# @lit-labs/ssr-dom-shim
1
2
@lit-labs/ssr-dom-shim provides minimal implementations of core DOM APIs (`Element`, `HTMLElement`, `EventTarget`, `Event`, `CustomEvent`, `CustomElementRegistry`, and `customElements`) specifically designed for Server Side Rendering (SSR) web components from Node.js environments. This enables Lit components and other web components to run in server environments without requiring a full DOM implementation.
3
4
## Package Information
5
6
- **Package Name**: @lit-labs/ssr-dom-shim
7
- **Package Type**: npm
8
- **Language**: TypeScript
9
- **Installation**: `npm install @lit-labs/ssr-dom-shim`
10
11
## Core Imports
12
13
```typescript
14
import {
15
Element,
16
HTMLElement,
17
EventTarget,
18
Event,
19
CustomEvent,
20
CustomElementRegistry,
21
customElements,
22
ElementInternals,
23
ariaMixinAttributes,
24
HYDRATE_INTERNALS_ATTR_PREFIX,
25
type HTMLElementWithEventMeta
26
} from "@lit-labs/ssr-dom-shim";
27
```
28
29
For CommonJS:
30
31
```javascript
32
const {
33
Element,
34
HTMLElement,
35
EventTarget,
36
Event,
37
CustomEvent,
38
CustomElementRegistry,
39
customElements,
40
ElementInternals,
41
ariaMixinAttributes,
42
HYDRATE_INTERNALS_ATTR_PREFIX
43
} = require("@lit-labs/ssr-dom-shim");
44
```
45
46
## Basic Usage
47
48
```typescript
49
import { HTMLElement, customElements, Event } from "@lit-labs/ssr-dom-shim";
50
51
// Define a custom element
52
class MyComponent extends HTMLElement {
53
connectedCallback() {
54
this.setAttribute('initialized', 'true');
55
this.dispatchEvent(new Event('component-ready'));
56
}
57
}
58
59
// Register the custom element
60
customElements.define('my-component', MyComponent);
61
62
// Create and use the element
63
const element = new MyComponent();
64
element.setAttribute('title', 'Hello SSR');
65
console.log(element.getAttribute('title')); // "Hello SSR"
66
67
// Event handling
68
element.addEventListener('component-ready', () => {
69
console.log('Component is ready!');
70
});
71
```
72
73
## Architecture
74
75
The SSR DOM shim is built around several key components:
76
77
- **Element Hierarchy**: `EventTarget` → `Element` → `HTMLElement` providing the standard DOM inheritance chain
78
- **Event System**: Complete event lifecycle with capturing, bubbling, and composition across shadow boundaries
79
- **Custom Elements**: Registry system supporting web component definition, lookup, and lifecycle management
80
- **Element Internals**: Form association and ARIA support for accessibility in SSR contexts
81
- **Attribute Management**: WeakMap-based attribute storage maintaining browser-like behavior
82
- **Shadow DOM**: Lightweight shadow root support with proper encapsulation boundaries
83
84
## Capabilities
85
86
### Event System
87
88
Core event handling functionality compatible with browser EventTarget API, supporting all standard event lifecycle phases.
89
90
```typescript { .api }
91
class EventTarget {
92
/**
93
* Add an event listener with optional capture and once behaviors
94
* @param type - Event type to listen for
95
* @param callback - Event handler function or object with handleEvent method
96
* @param options - Configuration options or boolean for capture mode
97
*/
98
addEventListener(
99
type: string,
100
callback: EventListenerOrEventListenerObject | null,
101
options?: AddEventListenerOptions | boolean
102
): void;
103
104
/**
105
* Remove an event listener
106
* @param type - Event type
107
* @param callback - Event handler to remove
108
* @param options - Configuration options or boolean for capture mode
109
*/
110
removeEventListener(
111
type: string,
112
callback: EventListenerOrEventListenerObject | null,
113
options?: EventListenerOptions | boolean
114
): void;
115
116
/**
117
* Dispatch an event through the event system
118
* @param event - Event instance to dispatch
119
* @returns true if event was not cancelled
120
*/
121
dispatchEvent(event: Event): boolean;
122
}
123
124
/**
125
* Basic Event implementation with standard DOM Event API
126
*/
127
class Event {
128
/**
129
* Create a new Event
130
* @param type - Event type name
131
* @param options - Event configuration
132
*/
133
constructor(type: string, options?: EventInit);
134
135
readonly type: string;
136
readonly bubbles: boolean;
137
readonly cancelable: boolean;
138
readonly composed: boolean;
139
readonly defaultPrevented: boolean;
140
readonly target: EventTarget | null;
141
readonly currentTarget: EventTarget | null;
142
readonly eventPhase: number;
143
readonly timeStamp: number;
144
readonly isTrusted: boolean;
145
146
/** Prevent default behavior */
147
preventDefault(): void;
148
/** Stop event propagation to parent elements */
149
stopPropagation(): void;
150
/** Stop all further event handler execution */
151
stopImmediatePropagation(): void;
152
/** Get the event path for this event */
153
composedPath(): EventTarget[];
154
155
// Event phase constants
156
static readonly NONE = 0;
157
static readonly CAPTURING_PHASE = 1;
158
static readonly AT_TARGET = 2;
159
static readonly BUBBLING_PHASE = 3;
160
}
161
162
/**
163
* CustomEvent with detail data payload
164
*/
165
class CustomEvent<T = any> extends Event {
166
/**
167
* Create a new CustomEvent with detail data
168
* @param type - Event type name
169
* @param options - Event configuration including detail property
170
*/
171
constructor(type: string, options?: CustomEventInit<T>);
172
173
/** Custom data payload */
174
readonly detail: T;
175
}
176
177
interface EventInit {
178
bubbles?: boolean;
179
cancelable?: boolean;
180
composed?: boolean;
181
}
182
183
interface CustomEventInit<T = any> extends EventInit {
184
detail?: T;
185
}
186
187
interface AddEventListenerOptions {
188
capture?: boolean;
189
once?: boolean;
190
passive?: boolean;
191
signal?: AbortSignal;
192
}
193
194
interface EventListenerOptions {
195
capture?: boolean;
196
}
197
```
198
199
### DOM Elements
200
201
Element implementations providing attribute management, shadow DOM support, and element internals for SSR environments.
202
203
```typescript { .api }
204
/**
205
* Base Element class with attribute management and shadow DOM support
206
*/
207
class Element extends EventTarget {
208
/** Array of element attributes as name/value pairs */
209
readonly attributes: Array<{ name: string; value: string }>;
210
/** Element's local name (tag name in lowercase) */
211
readonly localName: string | undefined;
212
/** Element's tag name (uppercase local name) */
213
readonly tagName: string | undefined;
214
/** Shadow root attached to this element (null for closed shadows) */
215
readonly shadowRoot: ShadowRoot | null;
216
217
/**
218
* Set an attribute value (silently converts any value to string)
219
* @param name - Attribute name
220
* @param value - Attribute value (converted to string)
221
*/
222
setAttribute(name: string, value: unknown): void;
223
224
/**
225
* Get an attribute value
226
* @param name - Attribute name
227
* @returns Attribute value or null if not present
228
*/
229
getAttribute(name: string): string | null;
230
231
/**
232
* Remove an attribute
233
* @param name - Attribute name to remove
234
*/
235
removeAttribute(name: string): void;
236
237
/**
238
* Check if element has an attribute
239
* @param name - Attribute name to check
240
* @returns true if attribute exists
241
*/
242
hasAttribute(name: string): boolean;
243
244
/**
245
* Toggle an attribute's existence
246
* @param name - Attribute name
247
* @param force - Optional force flag to explicitly add/remove
248
* @returns true if attribute exists after toggle
249
*/
250
toggleAttribute(name: string, force?: boolean): boolean;
251
252
/**
253
* Attach a shadow root to this element
254
* @param init - Shadow root configuration
255
* @returns Shadow root instance
256
*/
257
attachShadow(init: ShadowRootInit): ShadowRoot;
258
259
/**
260
* Attach ElementInternals for form association and ARIA
261
* @returns ElementInternals instance
262
* @throws Error if internals already attached
263
*/
264
attachInternals(): ElementInternals;
265
}
266
267
/**
268
* HTMLElement class extending Element for HTML-specific functionality
269
*/
270
class HTMLElement extends Element {}
271
272
interface ShadowRootInit {
273
mode: ShadowRootMode;
274
}
275
276
type ShadowRootMode = 'open' | 'closed';
277
278
interface ShadowRoot {
279
readonly host: Element;
280
}
281
```
282
283
### Custom Elements Registry
284
285
Custom element registration and management system supporting web component definition, lookup, and lifecycle management for SSR contexts.
286
287
```typescript { .api }
288
/**
289
* Registry for custom element definitions
290
*/
291
class CustomElementRegistry {
292
/**
293
* Define a custom element
294
* @param name - Element tag name (must contain hyphen)
295
* @param ctor - Element constructor class
296
* @throws Error if name already registered or constructor already used
297
*/
298
define(name: string, ctor: CustomHTMLElementConstructor): void;
299
300
/**
301
* Get constructor for a custom element name
302
* @param name - Element tag name
303
* @returns Constructor class or undefined if not registered
304
*/
305
get(name: string): CustomHTMLElementConstructor | undefined;
306
307
/**
308
* Get element name for a constructor
309
* @param ctor - Element constructor
310
* @returns Element name or null if not registered
311
*/
312
getName(ctor: CustomHTMLElementConstructor): string | null;
313
314
/**
315
* Promise that resolves when element is defined
316
* @param name - Element tag name to wait for
317
* @returns Promise resolving with constructor when defined
318
*/
319
whenDefined(name: string): Promise<CustomElementConstructor>;
320
321
/**
322
* Upgrade an element (not supported in SSR)
323
* @param element - Element to upgrade
324
* @throws Error indicating SSR limitation
325
*/
326
upgrade(element: HTMLElement): void;
327
}
328
329
/** Global custom elements registry instance */
330
const customElements: CustomElementRegistry;
331
332
interface CustomHTMLElementConstructor {
333
new (): HTMLElement;
334
observedAttributes?: string[];
335
}
336
337
type CustomElementConstructor = CustomHTMLElementConstructor;
338
```
339
340
### Element Internals and ARIA
341
342
ElementInternals implementation providing form association capabilities and comprehensive ARIA attribute support for accessibility in SSR environments.
343
344
```typescript { .api }
345
/**
346
* ElementInternals class for form association and ARIA support
347
*/
348
class ElementInternals {
349
/** Host element that this internals instance is attached to */
350
readonly __host: HTMLElement;
351
/** Shadow root of the host element */
352
readonly shadowRoot: ShadowRoot;
353
354
// ARIA Properties (all string type, settable)
355
ariaAtomic: string;
356
ariaAutoComplete: string;
357
ariaBrailleLabel: string;
358
ariaBrailleRoleDescription: string;
359
ariaBusy: string;
360
ariaChecked: string;
361
ariaColCount: string;
362
ariaColIndex: string;
363
ariaColSpan: string;
364
ariaCurrent: string;
365
ariaDescription: string;
366
ariaDisabled: string;
367
ariaExpanded: string;
368
ariaHasPopup: string;
369
ariaHidden: string;
370
ariaInvalid: string;
371
ariaKeyShortcuts: string;
372
ariaLabel: string;
373
ariaLevel: string;
374
ariaLive: string;
375
ariaModal: string;
376
ariaMultiLine: string;
377
ariaMultiSelectable: string;
378
ariaOrientation: string;
379
ariaPlaceholder: string;
380
ariaPosInSet: string;
381
ariaPressed: string;
382
ariaReadOnly: string;
383
ariaRequired: string;
384
ariaRoleDescription: string;
385
ariaRowCount: string;
386
ariaRowIndex: string;
387
ariaRowSpan: string;
388
ariaSelected: string;
389
ariaSetSize: string;
390
ariaSort: string;
391
ariaValueMax: string;
392
ariaValueMin: string;
393
ariaValueNow: string;
394
ariaValueText: string;
395
role: string;
396
397
// Form-related Properties
398
/** Associated form element */
399
readonly form: HTMLFormElement | null;
400
/** Associated label elements */
401
readonly labels: NodeListOf<HTMLLabelElement>;
402
/** Custom element states */
403
readonly states: Set<string>;
404
/** Current validation message */
405
readonly validationMessage: string;
406
/** Current validity state */
407
readonly validity: ValidityState;
408
/** Whether element will validate */
409
readonly willValidate: boolean;
410
411
/**
412
* Check element validity (always returns true in SSR)
413
* @returns true (always valid in SSR environment)
414
*/
415
checkValidity(): boolean;
416
417
/**
418
* Report element validity (always returns true in SSR)
419
* @returns true (always valid in SSR environment)
420
*/
421
reportValidity(): boolean;
422
423
/**
424
* Set form value (no-op in SSR)
425
*/
426
setFormValue(): void;
427
428
/**
429
* Set element validity (no-op in SSR)
430
*/
431
setValidity(): void;
432
}
433
434
/**
435
* Mapping of ARIA property names to their corresponding HTML attribute names
436
*/
437
const ariaMixinAttributes: {
438
readonly [K in keyof ARIAMixin]: string;
439
};
440
441
/**
442
* Prefix for hydration-related internal attributes
443
*/
444
const HYDRATE_INTERNALS_ATTR_PREFIX: string;
445
446
/**
447
* Internal type combining HTMLElement with event polyfill metadata
448
* Used internally for event system functionality
449
*/
450
type HTMLElementWithEventMeta = HTMLElement & EventTargetShimMeta;
451
452
interface EventTargetShimMeta {
453
/**
454
* The event target parent represents the previous event target for an event
455
* in capture phase and the next event target for a bubbling event.
456
* Note that this is not the element parent
457
*/
458
__eventTargetParent: EventTarget | undefined;
459
/**
460
* The host event target/element of this event target, if this event target
461
* is inside a Shadow DOM.
462
*/
463
__host: EventTarget | undefined;
464
}
465
466
interface ARIAMixin {
467
ariaAtomic: string | null;
468
ariaAutoComplete: string | null;
469
ariaBrailleLabel: string | null;
470
ariaBrailleRoleDescription: string | null;
471
ariaBusy: string | null;
472
ariaChecked: string | null;
473
ariaColCount: string | null;
474
ariaColIndex: string | null;
475
ariaColSpan: string | null;
476
ariaCurrent: string | null;
477
ariaDescription: string | null;
478
ariaDisabled: string | null;
479
ariaExpanded: string | null;
480
ariaHasPopup: string | null;
481
ariaHidden: string | null;
482
ariaInvalid: string | null;
483
ariaKeyShortcuts: string | null;
484
ariaLabel: string | null;
485
ariaLevel: string | null;
486
ariaLive: string | null;
487
ariaModal: string | null;
488
ariaMultiLine: string | null;
489
ariaMultiSelectable: string | null;
490
ariaOrientation: string | null;
491
ariaPlaceholder: string | null;
492
ariaPosInSet: string | null;
493
ariaPressed: string | null;
494
ariaReadOnly: string | null;
495
ariaRequired: string | null;
496
ariaRoleDescription: string | null;
497
ariaRowCount: string | null;
498
ariaRowIndex: string | null;
499
ariaRowSpan: string | null;
500
ariaSelected: string | null;
501
ariaSetSize: string | null;
502
ariaSort: string | null;
503
ariaValueMax: string | null;
504
ariaValueMin: string | null;
505
ariaValueNow: string | null;
506
ariaValueText: string | null;
507
role: string | null;
508
}
509
510
interface ValidityState {
511
readonly badInput: boolean;
512
readonly customError: boolean;
513
readonly patternMismatch: boolean;
514
readonly rangeOverflow: boolean;
515
readonly rangeUnderflow: boolean;
516
readonly stepMismatch: boolean;
517
readonly tooLong: boolean;
518
readonly tooShort: boolean;
519
readonly typeMismatch: boolean;
520
readonly valid: boolean;
521
readonly valueMissing: boolean;
522
}
523
```
524
525
## Usage Examples
526
527
### Custom Element with Shadow DOM
528
529
```typescript
530
import { HTMLElement, customElements } from "@lit-labs/ssr-dom-shim";
531
532
class MyCard extends HTMLElement {
533
constructor() {
534
super();
535
this.attachShadow({ mode: 'open' });
536
}
537
538
connectedCallback() {
539
this.setAttribute('role', 'article');
540
this.setAttribute('aria-label', 'Card component');
541
}
542
}
543
544
customElements.define('my-card', MyCard);
545
546
const card = new MyCard();
547
console.log(card.shadowRoot); // ShadowRoot object
548
console.log(card.getAttribute('role')); // "article"
549
```
550
551
### Event Handling with Custom Events
552
553
```typescript
554
import { HTMLElement, CustomEvent } from "@lit-labs/ssr-dom-shim";
555
556
class EventComponent extends HTMLElement {
557
emitCustomEvent() {
558
const event = new CustomEvent('data-updated', {
559
detail: { timestamp: Date.now(), value: 'new data' },
560
bubbles: true,
561
composed: true
562
});
563
564
this.dispatchEvent(event);
565
}
566
567
connectedCallback() {
568
this.addEventListener('data-updated', (event) => {
569
console.log('Data updated:', event.detail);
570
});
571
}
572
}
573
```
574
575
### Form Integration with ElementInternals
576
577
```typescript
578
import { HTMLElement, customElements } from "@lit-labs/ssr-dom-shim";
579
580
class FormInput extends HTMLElement {
581
private internals: ElementInternals;
582
583
constructor() {
584
super();
585
this.internals = this.attachInternals();
586
}
587
588
connectedCallback() {
589
// Set ARIA properties
590
this.internals.ariaLabel = 'Custom input field';
591
this.internals.ariaRequired = 'true';
592
this.internals.role = 'textbox';
593
}
594
}
595
596
customElements.define('form-input', FormInput);
597
```
598
599
### Registry Management
600
601
```typescript
602
import { customElements, HTMLElement } from "@lit-labs/ssr-dom-shim";
603
604
// Define multiple components
605
customElements.define('button-component', class extends HTMLElement {});
606
customElements.define('input-component', class extends HTMLElement {});
607
608
// Check if component is defined
609
if (customElements.get('button-component')) {
610
console.log('Button component is available');
611
}
612
613
// Wait for component to be defined
614
customElements.whenDefined('future-component').then(() => {
615
console.log('Future component has been defined');
616
});
617
618
// Get component name from constructor
619
const ButtonComponent = customElements.get('button-component');
620
if (ButtonComponent) {
621
console.log(customElements.getName(ButtonComponent)); // "button-component"
622
}
623
```
624
625
## Global Environment Integration
626
627
The package automatically sets up global bindings when imported:
628
629
```typescript
630
// These globals are conditionally assigned (only if undefined)
631
globalThis.Event; // Event implementation
632
globalThis.CustomEvent; // CustomEvent implementation
633
globalThis.litServerRoot; // Global HTMLElement for event handling
634
```
635
636
This ensures compatibility with Lit and other libraries that expect these globals to be available in the SSR environment.
637
638
## Error Handling
639
640
The shim implementations include appropriate error handling for SSR-specific limitations:
641
642
- `customElements.upgrade()` throws an error as upgrading is not supported in SSR contexts
643
- `ElementInternals.attachInternals()` throws if internals are already attached
644
- Form validation methods (`checkValidity`, `reportValidity`) log warnings and return sensible defaults for SSR
645
646
These behaviors maintain API compatibility while providing clear feedback about SSR environment constraints.