Design Principles
Technical architecture and design philosophy of PACE.js.
Philosophy
PACE.js embodies a simple philosophy:
"AI-native storefronts should be as easy to build as static websites, but as powerful as modern web applications."
Core Beliefs
- Configuration Over Code - Developers configure, not implement
- Convention Over Configuration - Sensible defaults for everything
- Progressive Enhancement - Start simple, add complexity as needed
- Zero Dependencies - Pure vanilla JavaScript, works everywhere
- Framework Agnostic - Integrates with anything
- Developer Experience First - 5 minutes to "Hello World"
Design Principles
1. Simplicity First
Problem: Modern frameworks have steep learning curves.
Solution: PACE.js is just JavaScript - if you know HTML/CSS/JS, you know PACE.
// This is all you need
const pace = new PACE({
container: '#app',
products: './products.json'
})
pace.mount()No build tools. No compilation. No configuration files.
2. Lightweight by Design
Target: < 15KB minified + gzipped
How:
- Zero dependencies
- Minimal abstractions
- Tree-shakeable ES modules
- No polyfills (modern browsers only)
Comparison:
- React + React DOM: ~42KB
- Vue 3: ~34KB
- Alpine.js: ~15KB
- PACE.js: ~15KB ✅
Why it matters:
- Faster page loads
- Better mobile performance
- Lower bandwidth costs
- Improved SEO
3. Pattern-Driven Development
PACE.js encodes the PACE Pattern into reusable code.
The Pattern:
Product → Discovery & Browse
About → Context & Trust
Chat → Guided Assistance
Executive → Insights & IntelligenceThe Framework:
{
components: {
product: ProductCatalog,
about: AboutPage,
chat: ChatWidget,
executiveSummary: ExecutiveSummary
}
}Benefit: Developers implement the pattern automatically by using the framework.
4. Configuration Over Code
Traditional approach:
// 200+ lines of component code
class ProductList extends Component {
constructor() { ... }
render() { ... }
handleClick() { ... }
// ... more boilerplate
}PACE approach:
// 5 lines of configuration
const pace = new PACE({
container: '#app',
products: './products.json'
})95% less code. Same functionality.
5. Progressive Enhancement
Start simple, add features as needed:
Level 1: Minimal
new PACE({
container: '#app',
products: './products.json'
}).mount()Level 2: Add AI
new PACE({
container: '#app',
products: './products.json',
aiAdapter: new ClaudeAdapter({ apiKey: '...' })
}).mount()Level 3: Add Customization
new PACE({
container: '#app',
products: './products.json',
aiAdapter: new ClaudeAdapter({ apiKey: '...' }),
theme: { primary: '#3b82f6' },
greeting: 'Custom greeting',
plugins: [analyticsPlugin, i18nPlugin]
}).mount()You never pay for features you don't use.
Architecture
MVC-Inspired Structure
┌─────────────────────────────────────┐
│ PACE Orchestrator │
│ (Main Controller) │
└─────────────────────────────────────┘
↓
┌─────────┼─────────┐
↓ ↓ ↓
┌────────┐ ┌──────┐ ┌────────────┐
│ State │ │Router│ │ Components │
│Manager │ │ │ │ │
└────────┘ └──────┘ └────────────┘
↓ ↓ ↓
┌────────────────────────────────┐
│ View Layer │
│ (HTML/CSS) │
└────────────────────────────────┘Separation of concerns:
- PACE — Orchestrates everything
- State — Manages data
- Router — Handles navigation
- Components — Render UI
Component Lifecycle
Each component:
- Constructs with config
- Renders HTML
- Attaches event listeners
- Responds to user actions
- Updates state
- Re-renders if needed
- Cleans up on destroy
Observer Pattern
State management uses the Observer pattern:
// State notifies observers when changed
state.set('activeView', 'chat')
// Observers automatically update
state.subscribe('activeView', (newView) => {
updateUI(newView)
})Benefits:
- Reactive updates
- Decoupled components
- Easy to reason about
- Simple to test
Dependency Injection
Components receive dependencies via constructor:
class ChatWidget {
constructor(config, state, aiAdapter) {
this.config = config // Configuration
this.state = state // Shared state
this.aiAdapter = aiAdapter // AI provider
}
}Benefits:
- Testable (inject mocks)
- Flexible (swap implementations)
- Clear dependencies
- No global state
Extensibility
Plugin System
const analyticsPlugin = {
name: 'analytics',
install(pace) {
pace.on('product:select', ({ product }) => {
gtag('event', 'product_view', {
product_id: product.id
})
})
}
}
pace.use(analyticsPlugin)Plugin capabilities:
- Listen to all PACE events
- Modify configuration
- Add custom components
- Extend state
- Integrate external services
Adapter Pattern
AI providers are swappable via adapters:
// Claude
const claude = new ClaudeAdapter({ apiKey: '...' })
// OpenAI
const openai = new OpenAIAdapter({ apiKey: '...' })
// Custom
const custom = new MyCustomAdapter()
// Use any adapter
new PACE({ aiAdapter: claude })Adapter interface:
interface AIAdapter {
sendMessage(message: string, context?: object): Promise<{
response: string
metadata?: object
}>
}Theme System
Themes use CSS custom properties:
// JavaScript configuration
new PACE({
theme: {
primary: '#3b82f6',
accent: '#8b5cf6'
}
})
// Applies CSS variables
:root {
--pace-primary: #3b82f6;
--pace-accent: #8b5cf6;
}Custom themes:
- Override CSS variables
- Provide custom stylesheets
- Extend default styles
- Full control over appearance
Integration Patterns
Standalone
<div id="app"></div>
<script type="module">
import { PACE } from 'pace.js'
new PACE({ container: '#app' }).mount()
</script>React
function App() {
return <PACEWrapper config={{ ... }} />
}Vue
<template>
<PACEComponent :config="config" />
</template>Svelte
<PACE {config} />All frameworks supported via wrapper components.
Framework Comparison
vs React
| Aspect | React | PACE.js |
|---|---|---|
| Size | 42KB | 15KB |
| Dependencies | Many | Zero |
| Learning curve | Steep | Gentle |
| Build required | Yes | No |
| Use case | General | Specific (PACE) |
When to use:
- React — Complex apps, team familiarity
- PACE.js — Conversational storefronts
vs Alpine.js
| Aspect | Alpine | PACE.js |
|---|---|---|
| Size | 15KB | 15KB |
| Approach | Reactive attributes | Configuration |
| Components | None | 4 built-in |
| AI integration | DIY | Built-in |
| Pattern | None | PACE |
When to use:
- Alpine — General reactivity
- PACE.js — PACE Pattern specifically
Performance
Bundle Size Breakdown
Total: 15KB minified + gzipped
Core: 5KB
State: 2KB
Router: 1KB
Components: 6KB
Utils: 1KBBenchmarks
Initial load:
- Parse time: < 10ms
- Time to interactive: < 100ms
- First render: < 50ms
Runtime:
- State update: < 1ms
- Component re-render: < 5ms
- Route transition: < 10ms
Tested on M1 MacBook Pro, 4x CPU throttle
Best Practices
1. Keep Components Pure
// ✅ Good - pure function
render() {
return `<div>${this.products.map(p => p.name)}</div>`
}
// ❌ Bad - side effects
render() {
fetch('/api/products') // Side effect!
return `<div>...</div>`
}2. Use State for Sharing
// ✅ Good
state.set('selectedProduct', product)
// ❌ Bad
window.selectedProduct = product3. Subscribe Responsibly
// ✅ Good - unsubscribe
const unsub = state.subscribe('view', callback)
return () => unsub()
// ❌ Bad - memory leak
state.subscribe('view', callback)4. Validate Configuration
// ✅ Good
if (!config.container) {
throw new Error('container is required')
}
// ❌ Bad - silent failure
this.container = config.container || '#app'Security Considerations
XSS Prevention
// ✅ Good - sanitize user input
const safe = DOMPurify.sanitize(userInput)
// ❌ Bad - raw HTML
innerHTML = userMessageAPI Key Protection
// ✅ Good - environment variables
apiKey: process.env.CLAUDE_API_KEY
// ❌ Bad - hardcoded
apiKey: 'sk-ant-1234567890'Content Security Policy
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'unsafe-inline'">Testing Strategy
Unit Tests
describe('State', () => {
it('notifies subscribers', () => {
const state = new State()
const spy = jest.fn()
state.subscribe('key', spy)
state.set('key', 'value')
expect(spy).toHaveBeenCalledWith('value', undefined)
})
})Integration Tests
describe('PACE', () => {
it('renders product catalog', async () => {
const pace = new PACE({
container: document.body,
products: mockProducts
})
pace.mount()
expect(document.querySelector('.pace-product-catalog')).toBeTruthy()
})
})Future Enhancements
Planned Features
- Server-side rendering — SSR support for Next.js/Nuxt
- Streaming responses — Real-time AI responses
- Voice interface — Voice input/output
- Multi-language — i18n/l10n support
- A/B testing — Built-in experimentation
- Analytics — First-party analytics
Community Requests
- Vue 3 official adapter
- Svelte official adapter
- WordPress plugin
- Shopify integration
- Django template tags
Design principles that scale from prototype to production. 🏗️