or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

attributes.mdcontext-system.mdcss-styling.mddata-binding.mddependency-injection.mdhtml-templates.mdindex.mdobservable-system.mdssr-hydration.mdstate-management.mdtemplate-directives.mdtesting-utilities.mdutilities.mdweb-components.md

ssr-hydration.mddocs/

0

# SSR Hydration

1

2

Server-side rendering support with hydration capabilities for fast initial page loads and SEO optimization, enabling isomorphic application development.

3

4

## Capabilities

5

6

### Hydration Markup

7

8

Utilities for generating and processing hydration markers that enable client-side hydration of server-rendered content.

9

10

```typescript { .api }

11

/**

12

* Utilities for hydration markup generation and processing

13

*/

14

const HydrationMarkup: {

15

/** The attribute name used to mark hydratable elements */

16

readonly attributeMarkerName: string;

17

18

/**

19

* Creates a start marker for content binding hydration

20

* @param index - The binding index

21

* @param targetNodeId - The ID of the target node

22

* @returns HTML comment marker string

23

*/

24

contentBindingStartMarker(index: number, targetNodeId: string): string;

25

26

/**

27

* Creates an end marker for content binding hydration

28

* @param index - The binding index

29

* @returns HTML comment marker string

30

*/

31

contentBindingEndMarker(index: number): string;

32

33

/**

34

* Creates a marker for attribute binding hydration

35

* @param index - The binding index

36

* @param targetNodeId - The ID of the target node

37

* @param attributeName - The name of the attribute

38

* @returns HTML comment marker string

39

*/

40

attributeBindingMarker(index: number, targetNodeId: string, attributeName: string): string;

41

42

/**

43

* Parses hydration markers from HTML content

44

* @param html - The HTML content to parse

45

* @returns Parsed hydration information

46

*/

47

parseHydrationMarkers(html: string): HydrationInfo;

48

};

49

50

/**

51

* Hydration information extracted from markers

52

*/

53

interface HydrationInfo {

54

/** Content binding locations */

55

contentBindings: Array<{

56

index: number;

57

targetNodeId: string;

58

startNode: Comment;

59

endNode: Comment;

60

}>;

61

62

/** Attribute binding locations */

63

attributeBindings: Array<{

64

index: number;

65

targetNodeId: string;

66

attributeName: string;

67

marker: Comment;

68

}>;

69

70

/** Hydratable elements */

71

hydratableElements: Array<{

72

element: Element;

73

templateId: string;

74

}>;

75

}

76

```

77

78

**Usage Examples:**

79

80

```typescript

81

import { ViewTemplate, html } from "@microsoft/fast-element";

82

import { HydrationMarkup } from "@microsoft/fast-element/element-hydration.js";

83

84

// Template that supports hydration

85

const userCardTemplate = html<UserCard>`

86

<div class="user-card" ${HydrationMarkup.attributeMarker('data-user-id', x => x.userId)}>

87

${HydrationMarkup.contentBindingStartMarker(0, 'user-name')}

88

<h2 id="user-name">${x => x.name}</h2>

89

${HydrationMarkup.contentBindingEndMarker(0)}

90

91

${HydrationMarkup.contentBindingStartMarker(1, 'user-email')}

92

<p id="user-email">${x => x.email}</p>

93

${HydrationMarkup.contentBindingEndMarker(1)}

94

95

<div class="user-stats">

96

${HydrationMarkup.contentBindingStartMarker(2, 'post-count')}

97

<span id="post-count">Posts: ${x => x.postCount}</span>

98

${HydrationMarkup.contentBindingEndMarker(2)}

99

100

${HydrationMarkup.contentBindingStartMarker(3, 'follower-count')}

101

<span id="follower-count">Followers: ${x => x.followerCount}</span>

102

${HydrationMarkup.contentBindingEndMarker(3)}

103

</div>

104

</div>

105

`;

106

107

// Server-side rendering function

108

function renderUserCardSSR(userData: UserData): string {

109

// Server-side template rendering with hydration markers

110

let html = `

111

<div class="user-card" data-user-id="${userData.userId}" ${HydrationMarkup.attributeMarkerName}="user-card-template">

112

${HydrationMarkup.contentBindingStartMarker(0, 'user-name')}

113

<h2 id="user-name">${userData.name}</h2>

114

${HydrationMarkup.contentBindingEndMarker(0)}

115

116

${HydrationMarkup.contentBindingStartMarker(1, 'user-email')}

117

<p id="user-email">${userData.email}</p>

118

${HydrationMarkup.contentBindingEndMarker(1)}

119

120

<div class="user-stats">

121

${HydrationMarkup.contentBindingStartMarker(2, 'post-count')}

122

<span id="post-count">Posts: ${userData.postCount}</span>

123

${HydrationMarkup.contentBindingEndMarker(2)}

124

125

${HydrationMarkup.contentBindingStartMarker(3, 'follower-count')}

126

<span id="follower-count">Followers: ${userData.followerCount}</span>

127

${HydrationMarkup.contentBindingEndMarker(3)}

128

</div>

129

</div>

130

`;

131

132

return html;

133

}

134

135

// Client-side hydration process

136

class HydrationManager {

137

static hydrateUserCard(element: Element, userData: UserData): UserCard {

138

// Parse hydration info

139

const hydrationInfo = HydrationMarkup.parseHydrationMarkers(element.outerHTML);

140

141

// Create component instance

142

const userCard = new UserCard();

143

userCard.userId = userData.userId;

144

userCard.name = userData.name;

145

userCard.email = userData.email;

146

userCard.postCount = userData.postCount;

147

userCard.followerCount = userData.followerCount;

148

149

// Hydrate the element

150

this.hydrateElement(userCard, element, hydrationInfo);

151

152

return userCard;

153

}

154

155

private static hydrateElement(

156

component: any,

157

element: Element,

158

hydrationInfo: HydrationInfo

159

): void {

160

// Process content bindings

161

hydrationInfo.contentBindings.forEach(binding => {

162

const targetElement = element.querySelector(`#${binding.targetNodeId}`);

163

if (targetElement) {

164

// Establish reactive binding for content

165

this.establishContentBinding(component, targetElement, binding.index);

166

}

167

});

168

169

// Process attribute bindings

170

hydrationInfo.attributeBindings.forEach(binding => {

171

const targetElement = element.querySelector(`#${binding.targetNodeId}`);

172

if (targetElement) {

173

// Establish reactive binding for attribute

174

this.establishAttributeBinding(

175

component,

176

targetElement,

177

binding.attributeName,

178

binding.index

179

);

180

}

181

});

182

}

183

184

private static establishContentBinding(

185

component: any,

186

element: Element,

187

bindingIndex: number

188

): void {

189

// Set up reactive content binding

190

Observable.getNotifier(component).subscribe({

191

handleChange: () => {

192

// Update content based on component state

193

this.updateElementContent(component, element, bindingIndex);

194

}

195

});

196

}

197

198

private static establishAttributeBinding(

199

component: any,

200

element: Element,

201

attributeName: string,

202

bindingIndex: number

203

): void {

204

// Set up reactive attribute binding

205

Observable.getNotifier(component).subscribe({

206

handleChange: () => {

207

// Update attribute based on component state

208

this.updateElementAttribute(component, element, attributeName, bindingIndex);

209

}

210

});

211

}

212

213

private static updateElementContent(

214

component: any,

215

element: Element,

216

bindingIndex: number

217

): void {

218

// Update logic based on binding index and component state

219

switch (bindingIndex) {

220

case 0: // User name

221

element.textContent = component.name;

222

break;

223

case 1: // User email

224

element.textContent = component.email;

225

break;

226

case 2: // Post count

227

element.textContent = `Posts: ${component.postCount}`;

228

break;

229

case 3: // Follower count

230

element.textContent = `Followers: ${component.followerCount}`;

231

break;

232

}

233

}

234

235

private static updateElementAttribute(

236

component: any,

237

element: Element,

238

attributeName: string,

239

bindingIndex: number

240

): void {

241

// Update attribute based on component state

242

if (attributeName === 'data-user-id') {

243

element.setAttribute(attributeName, component.userId);

244

}

245

}

246

}

247

248

interface UserData {

249

userId: string;

250

name: string;

251

email: string;

252

postCount: number;

253

followerCount: number;

254

}

255

256

class UserCard {

257

userId: string = '';

258

name: string = '';

259

email: string = '';

260

postCount: number = 0;

261

followerCount: number = 0;

262

}

263

```

264

265

### Hydratable Templates

266

267

Template interfaces and implementations that support server-side rendering and client-side hydration.

268

269

```typescript { .api }

270

/**

271

* Checks if an object supports hydration

272

* @param obj - The object to check

273

* @returns True if the object is hydratable

274

*/

275

function isHydratable(obj: any): obj is { [Hydratable]: true };

276

277

/**

278

* Symbol that marks an object as hydratable

279

*/

280

const Hydratable: unique symbol;

281

282

/**

283

* Element view template that supports hydration

284

*/

285

interface HydratableElementViewTemplate<TSource = any, TParent = any>

286

extends ElementViewTemplate<TSource, TParent> {

287

288

/**

289

* Hydrates an element view from server-rendered content

290

* @param firstChild - First child node of the hydration range

291

* @param lastChild - Last child node of the hydration range

292

* @param hostBindingTarget - The element that host behaviors will be bound to

293

*/

294

hydrate(

295

firstChild: Node,

296

lastChild: Node,

297

hostBindingTarget?: Element

298

): ElementView<TSource, TParent>;

299

}

300

301

/**

302

* Synthetic view template that supports hydration

303

*/

304

interface HydratableSyntheticViewTemplate<TSource = any, TParent = any>

305

extends SyntheticViewTemplate<TSource, TParent> {

306

307

/**

308

* Hydrates a synthetic view from server-rendered content

309

* @param firstChild - First child node of the hydration range

310

* @param lastChild - Last child node of the hydration range

311

*/

312

hydrate(firstChild: Node, lastChild: Node): SyntheticView<TSource, TParent>;

313

}

314

315

/**

316

* View interface that supports hydration

317

*/

318

interface HydratableView extends HTMLView {

319

/** Indicates this view supports hydration */

320

readonly isHydratable: true;

321

322

/**

323

* Hydrates the view from existing DOM content

324

* @param firstChild - First child node to hydrate

325

* @param lastChild - Last child node to hydrate

326

*/

327

hydrate(firstChild: Node, lastChild: Node): void;

328

}

329

```

330

331

**Usage Examples:**

332

333

```typescript

334

import {

335

isHydratable,

336

Hydratable,

337

HydratableElementViewTemplate,

338

ViewTemplate,

339

html,

340

FASTElement,

341

customElement

342

} from "@microsoft/fast-element";

343

344

// Create hydratable template

345

function createHydratableTemplate<T>(): HydratableElementViewTemplate<T> {

346

const template = html<T>`

347

<div class="hydratable-content">

348

<h1>${x => (x as any).title}</h1>

349

<p>${x => (x as any).description}</p>

350

</div>

351

`;

352

353

// Mark template as hydratable

354

(template as any)[Hydratable] = true;

355

356

// Add hydration method

357

(template as any).hydrate = function(

358

firstChild: Node,

359

lastChild: Node,

360

hostBindingTarget?: Element

361

) {

362

// Create view from existing DOM

363

const view = this.create(hostBindingTarget);

364

365

// Hydrate from existing nodes

366

view.hydrate(firstChild, lastChild);

367

368

return view;

369

};

370

371

return template as HydratableElementViewTemplate<T>;

372

}

373

374

// Hydratable component

375

@customElement("hydratable-component")

376

export class HydratableComponent extends FASTElement {

377

title: string = "Default Title";

378

description: string = "Default Description";

379

380

// Use hydratable template

381

static template = createHydratableTemplate<HydratableComponent>();

382

383

// Support SSR hydration

384

static supportsHydration = true;

385

386

connectedCallback() {

387

super.connectedCallback();

388

389

// Check if this component needs hydration

390

if (this.needsHydration()) {

391

this.performHydration();

392

}

393

}

394

395

private needsHydration(): boolean {

396

// Check for hydration markers or server-rendered content

397

return this.hasAttribute('data-ssr') ||

398

this.querySelector('[data-hydration-marker]') !== null;

399

}

400

401

private performHydration(): void {

402

if (isHydratable(HydratableComponent.template)) {

403

// Get existing content range

404

const firstChild = this.shadowRoot?.firstChild;

405

const lastChild = this.shadowRoot?.lastChild;

406

407

if (firstChild && lastChild) {

408

// Perform hydration

409

const view = HydratableComponent.template.hydrate(

410

firstChild,

411

lastChild,

412

this

413

);

414

415

// Bind to current component data

416

view.bind(this);

417

}

418

}

419

}

420

}

421

422

// SSR rendering utility

423

class SSRRenderer {

424

static renderComponentToString<T>(

425

template: HydratableElementViewTemplate<T>,

426

data: T

427

): string {

428

if (!isHydratable(template)) {

429

throw new Error('Template must be hydratable for SSR');

430

}

431

432

// Server-side rendering logic

433

return this.renderTemplateWithData(template, data);

434

}

435

436

private static renderTemplateWithData<T>(

437

template: HydratableElementViewTemplate<T>,

438

data: T

439

): string {

440

// Simplified SSR rendering

441

// In real implementation, this would involve proper template processing

442

443

const title = (data as any).title || 'Default Title';

444

const description = (data as any).description || 'Default Description';

445

446

return `

447

<div class="hydratable-content" data-ssr="true">

448

<!-- ${HydrationMarkup.contentBindingStartMarker(0, 'title')} -->

449

<h1>${title}</h1>

450

<!-- ${HydrationMarkup.contentBindingEndMarker(0)} -->

451

452

<!-- ${HydrationMarkup.contentBindingStartMarker(1, 'description')} -->

453

<p>${description}</p>

454

<!-- ${HydrationMarkup.contentBindingEndMarker(1)} -->

455

</div>

456

`;

457

}

458

}

459

460

// Client-side hydration orchestrator

461

class HydrationOrchestrator {

462

private hydrationQueue: Array<{

463

element: Element;

464

component: any;

465

template: HydratableElementViewTemplate<any>;

466

}> = [];

467

468

queueForHydration<T>(

469

element: Element,

470

component: T,

471

template: HydratableElementViewTemplate<T>

472

): void {

473

if (!isHydratable(template)) {

474

console.warn('Template is not hydratable, skipping hydration queue');

475

return;

476

}

477

478

this.hydrationQueue.push({ element, component, template });

479

}

480

481

async processHydrationQueue(): Promise<void> {

482

for (const item of this.hydrationQueue) {

483

try {

484

await this.hydrateComponent(item);

485

} catch (error) {

486

console.error('Hydration failed for component:', error);

487

}

488

}

489

490

this.hydrationQueue = [];

491

}

492

493

private async hydrateComponent(item: {

494

element: Element;

495

component: any;

496

template: HydratableElementViewTemplate<any>;

497

}): Promise<void> {

498

const { element, component, template } = item;

499

500

// Find hydration range

501

const shadowRoot = element.shadowRoot;

502

if (!shadowRoot) {

503

throw new Error('Element must have shadow root for hydration');

504

}

505

506

const firstChild = shadowRoot.firstChild;

507

const lastChild = shadowRoot.lastChild;

508

509

if (firstChild && lastChild) {

510

// Perform hydration

511

const view = template.hydrate(firstChild, lastChild, element);

512

513

// Bind to component data

514

view.bind(component);

515

516

// Mark as hydrated

517

element.setAttribute('data-hydrated', 'true');

518

}

519

}

520

}

521

522

// Application setup with SSR hydration

523

class SSRApp {

524

private hydrationOrchestrator = new HydrationOrchestrator();

525

526

async initializeWithHydration(): Promise<void> {

527

// Find all server-rendered components

528

const ssrElements = document.querySelectorAll('[data-ssr]');

529

530

for (const element of ssrElements) {

531

await this.setupComponentHydration(element);

532

}

533

534

// Process all queued hydrations

535

await this.hydrationOrchestrator.processHydrationQueue();

536

}

537

538

private async setupComponentHydration(element: Element): Promise<void> {

539

const tagName = element.tagName.toLowerCase();

540

541

// Map element to component class and template

542

const componentInfo = this.getComponentInfo(tagName);

543

if (!componentInfo) {

544

return;

545

}

546

547

const { componentClass, template } = componentInfo;

548

549

// Create component instance

550

const component = new componentClass();

551

552

// Extract data from SSR attributes or content

553

this.populateComponentFromSSR(component, element);

554

555

// Queue for hydration

556

this.hydrationOrchestrator.queueForHydration(

557

element,

558

component,

559

template

560

);

561

}

562

563

private getComponentInfo(tagName: string): {

564

componentClass: any;

565

template: HydratableElementViewTemplate<any>;

566

} | null {

567

// Component registry lookup

568

const registry: Record<string, any> = {

569

'hydratable-component': {

570

componentClass: HydratableComponent,

571

template: HydratableComponent.template

572

}

573

};

574

575

return registry[tagName] || null;

576

}

577

578

private populateComponentFromSSR(component: any, element: Element): void {

579

// Extract component data from SSR attributes or content

580

const title = element.querySelector('h1')?.textContent;

581

const description = element.querySelector('p')?.textContent;

582

583

if (title) component.title = title;

584

if (description) component.description = description;

585

}

586

}

587

588

// Initialize SSR hydration

589

const ssrApp = new SSRApp();

590

document.addEventListener('DOMContentLoaded', () => {

591

ssrApp.initializeWithHydration().then(() => {

592

console.log('SSR hydration completed');

593

}).catch(error => {

594

console.error('SSR hydration failed:', error);

595

});

596

});

597

```

598

599

### Hydration Error Handling

600

601

Error handling and debugging utilities for hydration processes, helping identify and resolve hydration mismatches.

602

603

```typescript { .api }

604

/**

605

* Error thrown when hydration fails due to content mismatch

606

*/

607

class HydrationBindingError extends Error {

608

/**

609

* Creates a hydration binding error

610

* @param message - Error message

611

* @param propertyBag - Additional error information

612

*/

613

constructor(

614

message: string | undefined,

615

public readonly propertyBag: {

616

index: number;

617

hydrationStage: string;

618

itemsLength?: number;

619

viewsState: string[];

620

viewTemplateString?: string;

621

rootNodeContent: string;

622

}

623

);

624

}

625

626

/**

627

* Hydration validation utilities

628

*/

629

const HydrationValidator: {

630

/**

631

* Validates that server and client content match

632

* @param serverContent - Content from server-side rendering

633

* @param clientTemplate - Client-side template

634

* @param data - Data used for rendering

635

* @returns Validation result

636

*/

637

validateContentMatch(

638

serverContent: string,

639

clientTemplate: ViewTemplate,

640

data: any

641

): HydrationValidationResult;

642

643

/**

644

* Checks for common hydration issues

645

* @param element - Element being hydrated

646

* @returns Array of potential issues

647

*/

648

checkForHydrationIssues(element: Element): HydrationIssue[];

649

650

/**

651

* Enables debug mode for hydration

652

* @param enabled - Whether to enable debug mode

653

*/

654

setDebugMode(enabled: boolean): void;

655

};

656

657

/**

658

* Result of hydration validation

659

*/

660

interface HydrationValidationResult {

661

/** Whether validation passed */

662

isValid: boolean;

663

664

/** List of validation errors */

665

errors: HydrationValidationError[];

666

667

/** List of validation warnings */

668

warnings: HydrationValidationWarning[];

669

}

670

671

/**

672

* Hydration validation error

673

*/

674

interface HydrationValidationError {

675

/** Error type */

676

type: 'content-mismatch' | 'structure-mismatch' | 'missing-marker';

677

678

/** Error message */

679

message: string;

680

681

/** Expected content */

682

expected: string;

683

684

/** Actual content */

685

actual: string;

686

687

/** Location of error */

688

location: {

689

bindingIndex?: number;

690

elementPath?: string;

691

};

692

}

693

694

/**

695

* Hydration validation warning

696

*/

697

interface HydrationValidationWarning {

698

/** Warning type */

699

type: 'performance' | 'accessibility' | 'seo';

700

701

/** Warning message */

702

message: string;

703

704

/** Suggested fix */

705

suggestion?: string;

706

}

707

708

/**

709

* Potential hydration issue

710

*/

711

interface HydrationIssue {

712

/** Issue severity */

713

severity: 'error' | 'warning' | 'info';

714

715

/** Issue category */

716

category: 'content' | 'structure' | 'performance' | 'accessibility';

717

718

/** Issue description */

719

description: string;

720

721

/** Element causing the issue */

722

element: Element;

723

724

/** Suggested resolution */

725

resolution?: string;

726

}

727

```

728

729

**Usage Examples:**

730

731

```typescript

732

import {

733

HydrationBindingError,

734

HydrationValidator,

735

HydrationValidationResult

736

} from "@microsoft/fast-element";

737

738

// Hydration error handler

739

class HydrationErrorHandler {

740

static handleHydrationError(error: HydrationBindingError): void {

741

console.group('Hydration Error Details');

742

console.error('Message:', error.message);

743

console.log('Binding Index:', error.propertyBag.index);

744

console.log('Hydration Stage:', error.propertyBag.hydrationStage);

745

console.log('Views State:', error.propertyBag.viewsState);

746

console.log('Root Node Content:', error.propertyBag.rootNodeContent);

747

748

if (error.propertyBag.viewTemplateString) {

749

console.log('Template:', error.propertyBag.viewTemplateString);

750

}

751

752

console.groupEnd();

753

754

// Attempt recovery

755

this.attemptHydrationRecovery(error);

756

}

757

758

private static attemptHydrationRecovery(error: HydrationBindingError): void {

759

// Recovery strategies based on error stage

760

switch (error.propertyBag.hydrationStage) {

761

case 'content-binding':

762

this.recoverContentBinding(error);

763

break;

764

case 'attribute-binding':

765

this.recoverAttributeBinding(error);

766

break;

767

case 'event-binding':

768

this.recoverEventBinding(error);

769

break;

770

default:

771

console.warn('No recovery strategy available for stage:', error.propertyBag.hydrationStage);

772

}

773

}

774

775

private static recoverContentBinding(error: HydrationBindingError): void {

776

console.log('Attempting content binding recovery...');

777

// Implement content binding recovery logic

778

}

779

780

private static recoverAttributeBinding(error: HydrationBindingError): void {

781

console.log('Attempting attribute binding recovery...');

782

// Implement attribute binding recovery logic

783

}

784

785

private static recoverEventBinding(error: HydrationBindingError): void {

786

console.log('Attempting event binding recovery...');

787

// Implement event binding recovery logic

788

}

789

}

790

791

// Hydration debugging utility

792

class HydrationDebugger {

793

private static debugMode = false;

794

795

static enableDebugMode(): void {

796

this.debugMode = true;

797

HydrationValidator.setDebugMode(true);

798

console.log('Hydration debug mode enabled');

799

}

800

801

static disableDebugMode(): void {

802

this.debugMode = false;

803

HydrationValidator.setDebugMode(false);

804

console.log('Hydration debug mode disabled');

805

}

806

807

static validateAndLog(

808

serverContent: string,

809

clientTemplate: ViewTemplate,

810

data: any,

811

elementName: string

812

): boolean {

813

if (!this.debugMode) return true;

814

815

console.group(`Hydration Validation: ${elementName}`);

816

817

try {

818

const result = HydrationValidator.validateContentMatch(

819

serverContent,

820

clientTemplate,

821

data

822

);

823

824

this.logValidationResult(result);

825

826

return result.isValid;

827

} catch (error) {

828

console.error('Validation failed:', error);

829

return false;

830

} finally {

831

console.groupEnd();

832

}

833

}

834

835

private static logValidationResult(result: HydrationValidationResult): void {

836

if (result.isValid) {

837

console.log('✅ Hydration validation passed');

838

} else {

839

console.log('❌ Hydration validation failed');

840

}

841

842

if (result.errors.length > 0) {

843

console.group('Errors:');

844

result.errors.forEach((error, index) => {

845

console.error(`${index + 1}. ${error.type}: ${error.message}`);

846

console.log(' Expected:', error.expected);

847

console.log(' Actual:', error.actual);

848

849

if (error.location.bindingIndex !== undefined) {

850

console.log(' Binding Index:', error.location.bindingIndex);

851

}

852

853

if (error.location.elementPath) {

854

console.log(' Element Path:', error.location.elementPath);

855

}

856

});

857

console.groupEnd();

858

}

859

860

if (result.warnings.length > 0) {

861

console.group('Warnings:');

862

result.warnings.forEach((warning, index) => {

863

console.warn(`${index + 1}. ${warning.type}: ${warning.message}`);

864

865

if (warning.suggestion) {

866

console.log(' Suggestion:', warning.suggestion);

867

}

868

});

869

console.groupEnd();

870

}

871

}

872

873

static checkElementForIssues(element: Element): void {

874

if (!this.debugMode) return;

875

876

const issues = HydrationValidator.checkForHydrationIssues(element);

877

878

if (issues.length === 0) {

879

console.log('✅ No hydration issues found for element:', element.tagName);

880

return;

881

}

882

883

console.group(`Hydration Issues for ${element.tagName}:`);

884

885

issues.forEach((issue, index) => {

886

const icon = issue.severity === 'error' ? '❌' :

887

issue.severity === 'warning' ? '⚠️' : 'ℹ️';

888

889

console.log(`${icon} ${index + 1}. [${issue.category}] ${issue.description}`);

890

891

if (issue.resolution) {

892

console.log(` 💡 Resolution: ${issue.resolution}`);

893

}

894

});

895

896

console.groupEnd();

897

}

898

}

899

900

// Production hydration with error handling

901

class ProductionHydrationManager {

902

private errorReporter?: (error: any) => void;

903

904

constructor(errorReporter?: (error: any) => void) {

905

this.errorReporter = errorReporter;

906

}

907

908

async safeHydration<T>(

909

element: Element,

910

template: HydratableElementViewTemplate<T>,

911

data: T

912

): Promise<boolean> {

913

try {

914

// Validate before hydration in development

915

if (process.env.NODE_ENV === 'development') {

916

HydrationDebugger.enableDebugMode();

917

HydrationDebugger.checkElementForIssues(element);

918

}

919

920

// Perform hydration

921

const view = template.hydrate(

922

element.firstChild!,

923

element.lastChild!,

924

element

925

);

926

927

view.bind(data);

928

929

return true;

930

931

} catch (error) {

932

// Handle hydration errors

933

if (error instanceof HydrationBindingError) {

934

HydrationErrorHandler.handleHydrationError(error);

935

} else {

936

console.error('Unexpected hydration error:', error);

937

}

938

939

// Report error in production

940

this.errorReporter?.(error);

941

942

// Fallback to client-side rendering

943

return this.fallbackToCSR(element, template, data);

944

}

945

}

946

947

private async fallbackToCSR<T>(

948

element: Element,

949

template: HydratableElementViewTemplate<T>,

950

data: T

951

): Promise<boolean> {

952

try {

953

console.log('Falling back to client-side rendering');

954

955

// Clear server-rendered content

956

element.innerHTML = '';

957

958

// Create fresh client-side view

959

const view = template.create(element);

960

view.bind(data);

961

view.appendTo(element);

962

963

return true;

964

965

} catch (error) {

966

console.error('Client-side rendering fallback failed:', error);

967

this.errorReporter?.(error);

968

969

return false;

970

}

971

}

972

}

973

974

// Application-level hydration setup

975

class AppHydration {

976

private productionManager = new ProductionHydrationManager(

977

(error) => {

978

// Report to error tracking service

979

console.error('Hydration error reported:', error);

980

}

981

);

982

983

async hydrateApplication(): Promise<void> {

984

const hydratableElements = document.querySelectorAll('[data-hydratable]');

985

986

const hydrationPromises = Array.from(hydratableElements).map(element =>

987

this.hydrateElement(element)

988

);

989

990

const results = await Promise.allSettled(hydrationPromises);

991

992

// Log overall hydration results

993

const successful = results.filter(r => r.status === 'fulfilled').length;

994

const failed = results.filter(r => r.status === 'rejected').length;

995

996

console.log(`Hydration complete: ${successful} successful, ${failed} failed`);

997

998

if (failed > 0) {

999

console.warn('Some components failed to hydrate and fell back to CSR');

1000

}

1001

}

1002

1003

private async hydrateElement(element: Element): Promise<void> {

1004

const componentType = element.getAttribute('data-component-type');

1005

const componentData = this.extractComponentData(element);

1006

1007

// Get component template (would be from registry in real app)

1008

const template = this.getComponentTemplate(componentType!);

1009

1010

if (template && isHydratable(template)) {

1011

const success = await this.productionManager.safeHydration(

1012

element,

1013

template,

1014

componentData

1015

);

1016

1017

if (!success) {

1018

throw new Error(`Failed to hydrate ${componentType}`);

1019

}

1020

}

1021

}

1022

1023

private extractComponentData(element: Element): any {

1024

// Extract component data from element attributes or content

1025

const dataScript = element.querySelector('script[type="application/json"]');

1026

1027

if (dataScript) {

1028

return JSON.parse(dataScript.textContent || '{}');

1029

}

1030

1031

return {};

1032

}

1033

1034

private getComponentTemplate(componentType: string): HydratableElementViewTemplate<any> | null {

1035

// Component template registry

1036

const templates: Record<string, any> = {

1037

'user-card': createHydratableTemplate(),

1038

'product-listing': createHydratableTemplate(),

1039

// Add more component templates

1040

};

1041

1042

return templates[componentType] || null;

1043

}

1044

}

1045

```

1046

1047

## Types

1048

1049

```typescript { .api }

1050

/**

1051

* Hydration stage type

1052

*/

1053

type HydrationStage =

1054

| "initial"

1055

| "content-binding"

1056

| "attribute-binding"

1057

| "event-binding"

1058

| "complete";

1059

1060

/**

1061

* Hydration marker type

1062

*/

1063

interface HydrationMarker {

1064

/** Marker type */

1065

type: 'content' | 'attribute' | 'element';

1066

1067

/** Binding index */

1068

index: number;

1069

1070

/** Target node ID */

1071

targetNodeId?: string;

1072

1073

/** Attribute name for attribute markers */

1074

attributeName?: string;

1075

}

1076

1077

/**

1078

* Hydration context interface

1079

*/

1080

interface HydrationContext {

1081

/** Current hydration stage */

1082

stage: HydrationStage;

1083

1084

/** Available hydration markers */

1085

markers: HydrationMarker[];

1086

1087

/** Root element being hydrated */

1088

rootElement: Element;

1089

1090

/** Component data */

1091

data: any;

1092

}

1093

```