Skip to main content

Performance Optimization

@rhtml provides several techniques and best practices for optimizing the performance of your applications. This guide covers various optimization strategies from component-level improvements to application-wide optimizations.

🚀 Component Optimization​

Lazy Loading Components​

Load components only when needed:

import { Component } from '@rhtml/component';
import { html, LitElement } from '@rxdi/lit-html';

@Component({
selector: 'lazy-component',
template: () => html`
<div>
<button @click=${() => this.loadHeavyComponent()}>
Load Heavy Component
</button>
<heavy-component></heavy-component>
</div>
`,
})
export class LazyComponent extends LitElement {
async loadHeavyComponent() {
// Dynamically import the heavy component
await import('./heavy-component');
}
}

Virtual Scrolling​

Handle large datasets efficiently:

import { Component, html, LitElement, property } from '@rxdi/lit-html';
import { HostListener } from '@rhtml/decorators';

@Component({
selector: 'virtual-list',
template: () => html`
<div class="virtual-container" style="height: 400px; overflow-y: auto;">
<div style="height: ${this.totalHeight}px; position: relative;">
${this.visibleItems.map(
(item) => html`
<div
class="list-item"
style="position: absolute; top: ${item.offset}px; height: 50px;"
>
${item.data.name}
</div>
`
)}
</div>
</div>
`,
})
export class VirtualListComponent extends LitElement {
@property({ type: Array })
items = [];

@property({ type: Number })
itemHeight = 50;

@property({ type: Number })
containerHeight = 400;

private scrollTop = 0;

get totalHeight() {
return this.items.length * this.itemHeight;
}

get visibleItems() {
const startIndex = Math.floor(this.scrollTop / this.itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(this.containerHeight / this.itemHeight),
this.items.length
);

return this.items.slice(startIndex, endIndex).map((item, index) => ({
data: item,
offset: (startIndex + index) * this.itemHeight,
}));
}

@HostListener('scroll')
handleScroll(e: Event) {
this.scrollTop = (e.target as HTMLElement).scrollTop;
this.requestUpdate();
}
}

🔄 Reactive Optimization​

Efficient Observable Usage​

Optimize RxJS observable chains:

import { Component } from '@rhtml/component';
import { html, LitElement, property } from '@rxdi/lit-html';
import { BehaviorSubject, combineLatest } from 'rxjs';
import { map, distinctUntilChanged, debounceTime } from 'rxjs/operators';

@Component({
selector: 'optimized-reactive',
template: () => html`
<div class="optimized-reactive">
<input
type="text"
placeholder="Search..."
@input=${(e) => this.searchTerm$.next(e.target.value)}
/>

<r-for .of=${this.filteredItems$}>
<r-let
.item=${(item) => html` <div class="item">${item.name}</div> `}
></r-let>
</r-for>
</div>
`,
})
export class OptimizedReactiveComponent extends LitElement {
@property({ type: Array })
items = [];

private searchTerm$ = new BehaviorSubject('');
private items$ = new BehaviorSubject([]);

private filteredItems$ = combineLatest([
this.searchTerm$.pipe(
debounceTime(300), // Debounce search input
distinctUntilChanged() // Only emit when value changes
),
this.items$,
]).pipe(
map(([searchTerm, items]) =>
items.filter((item) =>
item.name.toLowerCase().includes(searchTerm.toLowerCase())
)
)
);

updated(changedProperties: Map<string, any>) {
if (changedProperties.has('items')) {
this.items$.next(this.items);
}
}
}

Computed Values (Not implemented yet)​

Use computed values for derived state:

import { computed } from '@rhtml/operators';

@Component({
selector: 'computed-values',
template: () => html`
<div class="computed-values">
<r-let
.data=${this.stats$}
.item=${(stats) => html`
<div class="stats">
<div class="stat">
<span>Total: ${stats.total}</span>
</div>
<div class="stat">
<span>Active: ${stats.active}</span>
</div>
<div class="stat">
<span>Average: ${stats.average.toFixed(2)}</span>
</div>
</div>
`}
></r-let>
</div>
`,
})
export class ComputedValuesComponent extends LitElement {
@property({ type: Array })
items = [];

private stats$ = computed(this.items, (items) => {
const total = items.length;
const active = items.filter((item) => item.active).length;
const average =
total > 0 ? items.reduce((sum, item) => sum + item.value, 0) / total : 0;

return { total, active, average };
});
}

📦 Bundle Optimization​

Tree Shaking​

Ensure unused code is eliminated:

// Good: Import only what you need
import { Component } from '@rhtml/component';
import { html } from '@rxdi/lit-html';

// Bad: Import entire library
import * as rhtml from '@rhtml/component';

// Good: Import specific operators
import { map, filter } from 'rxjs/operators';

// Bad: Import entire RxJS
import { Observable } from 'rxjs';

Code Splitting​

Split your application into smaller chunks:

// main.ts
import { Component } from '@rhtml/component';

// Lazy load feature modules
const loadUserModule = () => import('./features/user/user.module.js');
const loadAdminModule = () => import('./features/admin/admin.module.js');

@Component({
selector: 'app-root',
template: () => html`
<nav>
<button @click=${() => this.loadUserModule()}>Users</button>
<button @click=${() => this.loadAdminModule()}>Admin</button>
</nav>
<main>
<slot></slot>
</main>
`,
})
export class AppRoot extends LitElement {
async loadUserModule() {
const { UserModule } = await loadUserModule();
// Initialize user module
}

async loadAdminModule() {
const { AdminModule } = await loadAdminModule();
// Initialize admin module
}
}

Dynamic Imports​

Load components and services dynamically:

@Component({
selector: 'dynamic-loader',
template: () => html`
<div class="dynamic-loader">
<button @click=${() => this.loadComponent()}>Load Component</button>
<heavy-component></heavy-component>
</div>
`,
})
export class DynamicLoaderComponent extends LitElement {
private componentTemplate = null;

async loadComponent() {
await import('./heavy-component');
}
}

🎯 Rendering Optimization​

Conditional Rendering​

Use efficient conditional rendering:

@Component({
selector: 'conditional-rendering',
template: () => html`
<div class="conditional-rendering">
<!-- Use efficient conditionals -->
${this.isLoading ? this.loadingTemplate : ''} ${this.hasError
? this.errorTemplate
: ''} ${this.hasData ? this.dataTemplate : ''}
</div>
`,
})
export class ConditionalRenderingComponent extends LitElement {
@property({ type: Boolean })
isLoading = false;

@property({ type: Boolean })
hasError = false;

@property({ type: Boolean })
hasData = false;

// Cache templates
private loadingTemplate = html`<div class="loading">Loading...</div>`;
private errorTemplate = html`<div class="error">An error occurred</div>`;
private dataTemplate = html`<div class="data">Data loaded successfully</div>`;
}

📊 Performance Monitoring​

Performance Metrics​

Monitor component performance:

@Component({
selector: 'performance-monitor',
template: () => html`
<div class="performance-monitor">
<h3>Performance Metrics</h3>
<p>Render Time: ${this.renderTime}ms</p>
<p>Update Count: ${this.updateCount}</p>
<p>Memory Usage: ${this.memoryUsage}MB</p>
</div>
`,
})
export class PerformanceMonitorComponent extends LitElement {
@property({ type: Number })
renderTime = 0;

@property({ type: Number })
updateCount = 0;

@property({ type: Number })
memoryUsage = 0;

private renderStartTime = 0;

willUpdate(changedProperties: Map<string, any>) {
this.renderStartTime = performance.now();
this.updateCount++;
}

updated(changedProperties: Map<string, any>) {
this.renderTime = performance.now() - this.renderStartTime;
this.memoryUsage = this.getMemoryUsage();
}

private getMemoryUsage(): number {
if ('memory' in performance) {
return (performance as any).memory.usedJSHeapSize / 1024 / 1024;
}
return 0;
}
}

Performance Profiling​

Create performance profiling utilities:

// utils/performance.ts
export class PerformanceProfiler {
private measurements = new Map<string, number[]>();

start(label: string) {
performance.mark(`${label}-start`);
}

end(label: string) {
performance.mark(`${label}-end`);
performance.measure(label, `${label}-start`, `${label}-end`);

const measure = performance.getEntriesByName(label)[0];
if (!this.measurements.has(label)) {
this.measurements.set(label, []);
}
this.measurements.get(label)!.push(measure.duration);
}

getAverage(label: string): number {
const measurements = this.measurements.get(label) || [];
return (
measurements.reduce((sum, time) => sum + time, 0) / measurements.length
);
}

getStats(label: string) {
const measurements = this.measurements.get(label) || [];
const sorted = measurements.sort((a, b) => a - b);

return {
count: measurements.length,
average: this.getAverage(label),
min: sorted[0] || 0,
max: sorted[sorted.length - 1] || 0,
median: sorted[Math.floor(sorted.length / 2)] || 0,
};
}
}

// Usage in component
@Component({
selector: 'profiled-component',
template: () => html`
<div class="profiled-component">
<button @click=${() => this.performExpensiveOperation()}>
Expensive Operation
</button>
<p>Average time: ${this.averageTime}ms</p>
</div>
`,
})
export class ProfiledComponent extends LitElement {
private profiler = new PerformanceProfiler();
@property({ type: Number })
averageTime = 0;

performExpensiveOperation() {
this.profiler.start('expensive-operation');

// Perform expensive operation
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += Math.sqrt(i);
}

this.profiler.end('expensive-operation');
this.averageTime = this.profiler.getAverage('expensive-operation');
}
}

🎯 Best Practices​

1. Component Design​

  • Keep components small and focused
  • Use composition over inheritance
  • Implement proper lifecycle management
  • Avoid deep component hierarchies

2. Data Management​

  • Use efficient data structures
  • Implement proper caching strategies
  • Minimize data transformations
  • Use pagination for large datasets

3. Rendering Optimization​

  • Minimize DOM manipulations
  • Use efficient template rendering
  • Implement virtual scrolling for large lists
  • Cache expensive computations

4. Memory Management​

  • Implement proper cleanup in lifecycle hooks
  • Use weak references for caching
  • Avoid circular references
  • Monitor memory usage

5. Bundle Optimization​

  • Use tree shaking effectively
  • Implement code splitting
  • Minimize bundle size
  • Use dynamic imports for lazy loading

🚀 Next Steps​