| name | osmani-patterns-performance |
| description | Write JavaScript code in the style of Addy Osmani, author of "Learning JavaScript Design Patterns" and Chrome DevTools engineer. Emphasizes design patterns, performance optimization, and progressive web apps. Use when building performant, well-structured applications. |
| tags | design-patterns, performance, web, loading, caching, lazy-loading, bundling, optimization, frontend, browser |
Addy Osmani Style Guide
Overview
Addy Osmani is a Chrome DevTools engineer at Google and author of "Learning JavaScript Design Patterns". His philosophy emphasizes proven design patterns, performance optimization, and building for the modern web.
Core Philosophy
"First do it, then do it right, then do it better."
"Performance is not a feature, it's a necessity."
"The best request is the one that's never made."
Osmani believes in using battle-tested patterns and obsessively optimizing for user experience through performance.
Design Principles
-
Patterns Have Purpose: Use design patterns to solve specific problems.
-
Performance First: Measure, optimize, measure again.
-
Progressive Enhancement: Build for all users, enhance for modern browsers.
-
Loading Performance: The fastest code is code that never runs.
When Writing Code
Always
- Use appropriate design patterns for the problem
- Measure performance before and after optimization
- Consider loading performance and bundle size
- Implement code splitting for large applications
- Use lazy loading for non-critical resources
- Test on real devices and slow connections
Never
- Apply patterns where they don't fit
- Optimize without measuring
- Load all JavaScript upfront
- Ignore Core Web Vitals
- Block the main thread with heavy computation
- Ship unused JavaScript
Prefer
- Module pattern for encapsulation
- Observer pattern for event systems
- Factory pattern for object creation
- Dynamic imports over static imports for large modules
- Intersection Observer over scroll events
- CSS containment for rendering performance
Code Patterns
The Module Pattern
const ShoppingCart = (function() {
const items = [];
function calculateTotal() {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
return {
addItem(item) {
items.push(item);
},
removeItem(id) {
const index = items.findIndex(item => item.id === id);
if (index > -1) {
items.splice(index, 1);
}
},
getTotal() {
return calculateTotal();
},
getItems() {
return [...items];
}
};
})();
const items = [];
function calculateTotal() {
items.( sum + item. * item., );
}
() {
items.(item);
}
() {
();
}
The Observer Pattern
class EventEmitter {
constructor() {
this.events = new Map();
}
on(event, callback) {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.get(event).push(callback);
return () => this.off(event, callback);
}
off(event, callback) {
if (!this.events.has(event)) return;
const callbacks = this.events.get(event);
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
}
emit(event, data) {
if (!this.events.(event)) ;
..(event).( {
(data);
});
}
}
emitter = ();
unsubscribe = emitter.(, {
.();
});
emitter.(, { : });
();
The Factory Pattern
const NotificationFactory = {
create(type, message) {
const notifications = {
success: {
icon: '✓',
color: 'green',
duration: 3000
},
error: {
icon: '✗',
color: 'red',
duration: 5000
},
warning: {
icon: '⚠',
color: 'orange',
duration: 4000
}
};
const config = notifications[type] || notifications.success;
return {
...config,
message,
show() {
console.log(`[${this.icon}] ${this.message}`);
}
};
}
};
const success = NotificationFactory.create('success', 'Saved!');
const error = NotificationFactory.(, );
Performance: Code Splitting
const routes = {
'/': () => import('./pages/Home.js'),
'/dashboard': () => import('./pages/Dashboard.js'),
'/settings': () => import('./pages/Settings.js')
};
async function navigate(path) {
const loadPage = routes[path];
if (loadPage) {
const module = await loadPage();
module.default.render();
}
}
const Dashboard = React.lazy(() => import('./Dashboard'));
function App() {
return (
<Suspense fallback={<Spinner />}>
<Dashboard />
</Suspense>
);
}
function prefetchRoutes() {
( ) {
( {
routes[]();
});
}
}
Performance: Lazy Loading
function lazyLoad(selector) {
const elements = document.querySelectorAll(selector);
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
obs.unobserve(img);
}
});
}, {
rootMargin: '50px 0px',
threshold: 0.01
});
elements.forEach(el => observer.observe(el));
}
let heavyModule = null;
button.addEventListener('click', async () => {
if (!heavyModule) {
heavyModule = await import();
}
heavyModule.();
});
Performance: Debounce and Throttle
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
const debouncedSearch = debounce(query => {
api.search(query);
}, 300);
function throttle(fn, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
const throttledScroll = throttle(() => {
updateScrollProgress();
}, 100);
Performance: Virtualization
class VirtualList {
constructor(container, items, itemHeight) {
this.container = container;
this.items = items;
this.itemHeight = itemHeight;
this.visibleCount = Math.ceil(container.clientHeight / itemHeight) + 2;
this.setup();
}
setup() {
this.viewport = document.createElement('div');
this.viewport.style.height = `${this.items.length * this.itemHeight}px`;
this.content = document.createElement('div');
this.content.style.position = 'relative';
this.viewport.appendChild(.);
..(.);
..(, .());
.();
}
() {
scrollTop = ..;
startIndex = .(scrollTop / .);
endIndex = .(
startIndex + .,
..
);
.. = ;
... = ;
( i = startIndex; i < endIndex; i++) {
item = .();
item.. = ;
item. = .[i];
..(item);
}
}
}
The Singleton Pattern
const Config = (function() {
let instance;
function createInstance() {
return {
apiUrl: 'https://api.example.com',
timeout: 5000,
debug: false,
set(key, value) {
this[key] = value;
}
};
}
return {
getInstance() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
const config1 = Config.getInstance();
const config2 = Config.getInstance();
config1 === config2;
Mental Model
Osmani approaches code by asking:
- What pattern fits this problem? Use proven solutions
- What's the performance cost? Measure before shipping
- Can this be deferred? Load later if not critical
- Will this block the main thread? Keep it responsive
- What are users on slow connections experiencing? Test realistically
Signature Osmani Moves
- Code splitting at route boundaries
- Lazy loading with Intersection Observer
- PRPL pattern (Push, Render, Pre-cache, Lazy-load)
- Module pattern for clean encapsulation
- Performance budgets and monitoring
- Progressive enhancement as default