@agentman/chat-widget
Version:
Agentman Chat Widget for easy integration with web applications
257 lines (219 loc) • 6.55 kB
Markdown
# Chat Widget Optimization Recommendations
## Current State Analysis
### Bundle Size Issues
- **Current size**: 282 KB (exceeds recommended 244 KB limit)
- **Major contributors**:
1. **morphdom** (~30KB) - Used only for streaming
2. **uuid** (~25KB) - Used only for generating conversation IDs
3. **SVG assets** (~30KB) - Large inline SVG logo
4. **Abstraction layers** - Multiple renderer interfaces and managers
### Performance Impact
- Users on slow connections will experience delayed widget loading
- The widget is larger than many entire web applications
- Most features aren't used by all users (streaming, file uploads, etc.)
## Recommended Optimizations
### 1. Remove External Dependencies (Saves ~55KB)
#### Replace morphdom with native DOM manipulation
```typescript
// Instead of morphdom, use targeted updates
private updateMessageContent(container: HTMLElement, content: string) {
const contentEl = container.querySelector('.message-content');
if (contentEl) {
// Preserve images
const images = contentEl.querySelectorAll('img');
const imageMap = new Map();
images.forEach(img => {
imageMap.set(img.src, img);
});
// Update content
contentEl.innerHTML = content;
// Restore loaded images
const newImages = contentEl.querySelectorAll('img');
newImages.forEach(img => {
const cached = imageMap.get(img.src);
if (cached && cached.complete) {
img.replaceWith(cached);
}
});
}
}
```
#### Replace uuid with native crypto
```typescript
// Instead of uuid v4
private generateId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
// Or use crypto.randomUUID() where available
private generateId(): string {
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
return crypto.randomUUID();
}
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
```
### 2. Optimize SVG Assets (Saves ~25KB)
#### Option A: External SVG loading
```typescript
// Load SVG on demand
private async loadLogo(): Promise<string> {
const response = await fetch('/assets/logo.svg');
return response.text();
}
```
#### Option B: Simplified SVG
```typescript
// Use a much simpler logo
export const simpleLogo = `
<svg viewBox="0 0 24 24" fill="currentColor">
<circle cx="12" cy="12" r="10"/>
<path d="M8 14s1.5 2 4 2 4-2 4-2" stroke="white" fill="none"/>
</svg>`;
```
### 3. Simplify Streaming Architecture (Saves ~20KB)
#### Merge renderers into a single implementation
```typescript
export class UnifiedMessageRenderer {
private isStreaming = false;
render(message: Message, container: HTMLElement): void {
if (this.isStreaming) {
this.streamingUpdate(message, container);
} else {
container.innerHTML = this.renderMessage(message);
}
}
private streamingUpdate(message: Message, container: HTMLElement): void {
// Simple, efficient update logic
const content = container.querySelector('.content');
if (content) {
content.textContent = message.content;
// Handle markdown in next tick to avoid blocking
requestAnimationFrame(() => {
this.applyMarkdown(content);
});
}
}
}
```
### 4. Code Splitting & Lazy Loading (Saves ~50KB initial)
#### Lazy load marked.js
```typescript
// Already implemented, good!
```
#### Lazy load streaming functionality
```typescript
// Load streaming only when needed
private async enableStreaming() {
if (!this.streamingModule) {
this.streamingModule = await import('./streaming');
}
return this.streamingModule;
}
```
#### Lazy load file upload functionality
```typescript
// Load file handling only when files are selected
private async handleFileSelect(files: FileList) {
const { FileUploadManager } = await import('./FileUploadManager');
this.uploadManager = new FileUploadManager(...);
}
```
### 5. Remove Unnecessary Abstractions
#### Consolidate managers
- Merge `StyleManager`, `ThemeManager` into `ConfigManager`
- Merge `MessageRenderer` interfaces into single implementation
- Remove factory patterns for single implementations
#### Simplify state management
```typescript
// Instead of complex pub/sub, use simple callbacks
class SimpleState {
private state: ChatState;
private onChange: (state: ChatState) => void;
update(updates: Partial<ChatState>) {
this.state = { ...this.state, ...updates };
this.onChange(this.state);
}
}
```
### 6. Production Build Optimizations
#### Webpack configuration improvements
```javascript
module.exports = {
// ... existing config
optimization: {
minimize: true,
sideEffects: false,
usedExports: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
priority: 10,
reuseExistingChunk: true
}
}
}
},
// Tree shaking
module: {
rules: [
{
test: /\.tsx?$/,
use: [{
loader: 'ts-loader',
options: {
transpileOnly: true, // Faster builds
compilerOptions: {
module: 'esnext' // Better tree shaking
}
}
}]
}
]
}
};
```
## Implementation Priority
### Phase 1: Quick Wins (1-2 days)
1. ✅ Replace uuid with native solution
2. ✅ Simplify SVG assets
3. ✅ Remove unused code and types
### Phase 2: Streaming Simplification (2-3 days)
1. ✅ Remove morphdom dependency
2. ✅ Merge renderer implementations
3. ✅ Simplify streaming logic
### Phase 3: Architecture Cleanup (3-4 days)
1. ⏳ Consolidate managers
2. ⏳ Implement code splitting
3. ⏳ Optimize build configuration
## Expected Results
### Bundle Size Reduction
- **Current**: 282 KB
- **After Phase 1**: ~230 KB (-18%)
- **After Phase 2**: ~180 KB (-36%)
- **After Phase 3**: ~150 KB (-47%)
### Performance Improvements
- 50% faster initial load
- 30% less memory usage
- Better performance on low-end devices
## Alternative: Minimal Core + Plugins
Consider a plugin architecture:
```typescript
// Core: 50KB
class ChatWidgetCore {
// Basic messaging only
}
// Plugins loaded on demand
const plugins = {
streaming: () => import('./plugins/streaming'),
fileUpload: () => import('./plugins/fileUpload'),
markdown: () => import('./plugins/markdown'),
persistence: () => import('./plugins/persistence')
};
```
This would provide:
- Ultra-fast initial load
- Pay-for-what-you-use model
- Better long-term maintainability
- Easier testing and updates