or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

tessl/npm-lit-labs--ssr-dom-shim

DOM shim for Lit Server Side Rendering (SSR) providing minimal implementations of DOM APIs for Node.js environments.

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/@lit-labs/ssr-dom-shim@1.4.x

To install, run

npx @tessl/cli install tessl/npm-lit-labs--ssr-dom-shim@1.4.0

0

# @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.