Optimize bundle size and loading performance with the UMD Design System
The UMD Design System provides multiple strategies for optimizing bundle size and loading performance. By leveraging ES modules, tree-shaking, and strategic code splitting, you can significantly reduce initial load times and improve the user experience of your applications.
This guide covers the design system's export strategy, bundling configuration with Vite, and production-ready patterns for dynamic component loading. Whether you're building a simple static site or a complex web application, these patterns will help you achieve optimal performance while maintaining code maintainability.
The design system is built with performance in mind, providing granular control over what gets loaded and when. Components are organized into logical groups that align with common usage patterns, allowing you to load only what you need, when you need it. Note: Grouped exports (structural, content, interactive, feed) were introduced in Release 1.14.0.
@universityofmaryland/web-components-library/bundleThe component library provides two complementary export strategies to optimize bundle size and loading performance. Choose the approach that best fits your application's needs.
The design system provides pre-configured component groups based on common usage patterns. This approach balances convenience with performance.
// Structural Components - Page layout and navigation
// Typically loaded first for above-the-fold content
import LoadStructuralComponents from '@universityofmaryland/web-components-library/structural';
LoadStructuralComponents(); // Loads: actions, hero, navigation
// Content Components - Display components for information
// Load after structural components for main content
import LoadContentComponents from '@universityofmaryland/web-components-library/content';
LoadContentComponents(); // Loads: card, text, media, stats, etc.
// Interactive Components - User interaction components
// Can be deferred until user interaction
import LoadInteractiveComponents from '@universityofmaryland/web-components-library/interactive';
LoadInteractiveComponents(); // Loads: accordion, carousel, footer, social, tab
// Feed Components - Dynamic content feeds
// Load on-demand for pages with feeds
import LoadFeedComponents from '@universityofmaryland/web-components-library/feed';
LoadFeedComponents(); // Loads: events, news, people feeds
Import specific component types individually using dynamic imports for maximum tree-shaking efficiency and granular control over what gets loaded.
// Dynamically import individual component types on demand
const loadSpecificComponents = async () => {
// Load only card components and their variants
const cardComponents = await import(
'@universityofmaryland/web-components-library/components/card'
);
cardComponents.standard(); // Register standard card
cardComponents.overlay(); // Register overlay card
cardComponents.event(); // Register event card
// Load only hero components and their variants
const heroComponents = await import(
'@universityofmaryland/web-components-library/components/hero'
);
heroComponents.base(); // Register base hero
heroComponents.expand(); // Register expandable hero
// Load only navigation components
const navComponents = await import(
'@universityofmaryland/web-components-library/components/navigation'
);
navComponents.primary(); // Register primary navigation
navComponents.drawer(); // Register navigation drawer
};
// Conditionally load based on DOM presence
if (document.querySelector('[class*="umd-element-card"]')) {
import('@universityofmaryland/web-components-library/components/card')
.then(module => module.standard());
}
if (document.querySelector('[class*="umd-element-hero"]')) {
import('@universityofmaryland/web-components-library/components/hero')
.then(module => module.base());
}
Even when using the bundle import, you can still benefit from tree-shaking by importing specific components:
// Import specific components from the bundle for tree-shaking
import { Components, Styles } from '@universityofmaryland/web-components-library/bundle';
// Register only the components you need
Components.card.standard();
Components.hero.base();
Components.navigation.primary();
// The bundler will tree-shake unused components from Elements, Feeds, etc.
// This gives you the convenience of a single import path with optimized output
| Approach | Bundle Size | Tree-shaking | Development Speed | Best For |
|---|---|---|---|---|
| Grouped Imports | Moderate (group-level) | Good (per group) | Faster setup | Standard sites with typical patterns |
| Individual Imports | Smallest possible | Maximum efficiency | More verbose | Production apps with specific needs |
| Bundle + Tree-shaking | Small (with proper imports) | Good (when destructured) | Simple imports | Apps wanting single import path |
| Full Bundle Init | Largest | None | Quickest | Prototypes and development only |
Combine both strategies for optimal results. Use grouped imports for common component sets and individual imports for specific components used sparingly:
// Load structural components as a group
import LoadStructuralComponents from '.../structural';
LoadStructuralComponents();
// Import specific components individually
import { Components } from '@universityofmaryland/web-components-library';
Components.specialFeature.custom(); // Only this specific component
The design system includes an optimized Vite configuration that implements intelligent chunk splitting for optimal caching and loading performance.
// vite.config.js
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig(({ mode }) => {
const isProduction = mode === 'production';
return {
build: {
// Enable/disable source maps based on environment
sourcemap: !isProduction,
// Use terser for production minification
minify: isProduction ? 'terser' : false,
rollupOptions: {
input: {
main: path.resolve(__dirname, 'src/main.ts'),
},
output: {
// Named entry chunks without hash for stable names
entryFileNames: '[name].js',
// Smart chunk naming strategy
chunkFileNames: (chunkInfo) => {
// Group vendor chunks by library for better caching
if (chunkInfo.name.includes('styles-library'))
return 'vendor-styles.js';
if (chunkInfo.name.includes('elements-library'))
return 'vendor-elements.js';
// Default naming with hash for cache busting
return '[name]-[hash].js';
},
// CSS extraction with predictable names
assetFileNames: (assetInfo) => {
if (assetInfo.name?.endsWith('.css')) {
if (assetInfo.name.includes('main')) return 'main.css';
if (assetInfo.name.includes('template')) return 'template.css';
return '[name].css';
}
return '[name].[ext]';
},
},
},
// Terser options for aggressive production optimization
terserOptions: isProduction
? {
compress: {
drop_console: true, // Remove console.log in production
drop_debugger: true, // Remove debugger statements
},
}
: undefined,
// Warn about large chunks (500kb)
chunkSizeWarningLimit: 500,
// Enable module preload polyfill for older browsers
modulePreload: {
polyfill: true,
},
},
resolve: {
// Path aliases for cleaner imports and grouped components
alias: {
'@': path.resolve(__dirname, './source'),
// Grouped component exports
'@universityofmaryland/web-components-library/structural':
path.resolve(__dirname, '../packages/components/dist/structural.js'),
'@universityofmaryland/web-components-library/content':
path.resolve(__dirname, '../packages/components/dist/content.js'),
'@universityofmaryland/web-components-library/interactive':
path.resolve(__dirname, '../packages/components/dist/interactive.js'),
'@universityofmaryland/web-components-library/feed':
path.resolve(__dirname, '../packages/components/dist/feed.js'),
// Main library exports
'@universityofmaryland/web-components-library':
path.resolve(__dirname, '../packages/components/dist/index.js'),
'@universityofmaryland/web-elements-library':
path.resolve(__dirname, '../packages/elements'),
'@universityofmaryland/web-styles-library':
path.resolve(__dirname, '../packages/styles'),
},
},
optimizeDeps: {
// Pre-bundle critical dependencies for faster dev server startup
include: [
'@universityofmaryland/web-styles-library',
'@universityofmaryland/web-elements-library',
],
// Scan entry points for dependency discovery
entries: [
'src/main.ts',
'src/styles.ts',
],
},
};
});
chunkFileNames - Creates predictable vendor chunks for better long-term caching
modulePreload - Ensures critical resources are preloaded for faster initial render
optimizeDeps.include - Pre-bundles frequently used dependencies to avoid waterfall loading
alias - Maps grouped exports to their distribution files for cleaner imports
terserOptions - Removes development code in production builds
The design system implements sophisticated loading strategies to optimize initial page load and progressively enhance the user experience.
// main.ts - Production-ready loading strategy
import './styles.ts';
// Component loaders with dynamic imports for code splitting
const loadStructuralComponents = async () => {
const LoadStructuralComponents = (
await import('@universityofmaryland/web-components-library/structural')
).default;
return LoadStructuralComponents();
};
const loadContentComponents = async () => {
const LoadContentComponents = (
await import('@universityofmaryland/web-components-library/content')
).default;
return LoadContentComponents();
};
const loadInteractiveComponents = async () => {
const LoadInteractiveComponents = (
await import('@universityofmaryland/web-components-library/interactive')
).default;
return LoadInteractiveComponents();
};
const loadFeedComponents = async () => {
const LoadFeedComponents = (
await import('@universityofmaryland/web-components-library/feed')
).default;
return LoadFeedComponents();
};
const loadAnimations = async () => {
const { Utilties } = await import(
'@universityofmaryland/web-components-library'
);
return Utilties.Animations.loadIntersectionObserver();
};
const initializeApp = async () => {
// Critical: Load structural components immediately
await loadStructuralComponents();
// Visible content: Load when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', async () => {
await loadContentComponents();
});
} else {
await loadContentComponents();
}
// Below-the-fold: Defer interactive components
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
let componentsLoaded = false;
const loadDeferredComponents = () => {
if (!componentsLoaded) {
componentsLoaded = true;
loadInteractiveComponents();
loadFeedComponents();
loadAnimations();
}
};
// Load on any user interaction
const interactionEvents = [
'mousedown',
'touchstart',
'keydown',
'scroll',
];
interactionEvents.forEach((event) => {
document.addEventListener(event, loadDeferredComponents, {
once: true,
passive: true,
});
});
// Fallback: Ensure loading after 2 seconds
setTimeout(loadDeferredComponents, 2000);
});
} else {
// Fallback for browsers without requestIdleCallback
setTimeout(() => {
loadInteractiveComponents();
loadFeedComponents();
loadAnimations();
}, 100);
}
};
initializeApp();
Load component groups only when they're about to enter the viewport. This leverages the pre-built chunks for optimal performance:
// Lazy load component chunks based on viewport visibility
const observeAndLoadComponents = () => {
// Map components to their optimized chunk groups
const componentGroups = {
interactive: ['umd-element-carousel', 'umd-element-accordion', 'umd-element-tab'],
feed: ['umd-element-feed-events', 'umd-element-feed-news', 'umd-element-feed-people'],
content: ['umd-element-stats', 'umd-element-card', 'umd-element-text-image'],
};
// Track which groups have been loaded
const loadedGroups = new Set();
const loadComponentGroup = async (groupName) => {
if (loadedGroups.has(groupName)) return;
loadedGroups.add(groupName);
// Load the optimized chunk for this component group
const module = await import(
`@universityofmaryland/web-components-library/${groupName}`
);
if (module.default) module.default();
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(async (entry) => {
if (entry.isIntersecting) {
const tagName = entry.target.tagName.toLowerCase();
// Find which group this component belongs to
for (const [groupName, components] of Object.entries(componentGroups)) {
if (components.includes(tagName)) {
await loadComponentGroup(groupName);
observer.unobserve(entry.target);
break;
}
}
}
});
}, {
// Start loading when component is 200px away from viewport
rootMargin: '200px',
});
// Observe all uninitialized components
Object.values(componentGroups).flat().forEach(tagName => {
document.querySelectorAll(tagName).forEach(element => {
observer.observe(element);
});
});
};
The design system implements a two-phase CSS loading strategy to eliminate render-blocking styles and prevent Flash of Unstyled Content (FOUC).
// styles.ts - Critical CSS loading implementation
import * as Styles from '@universityofmaryland/web-styles-library';
const inlineCriticalStyles = async () => {
try {
// Load critical styles in parallel
const [preRenderCss, fonts] = await Promise.all([
Styles.preRenderCss,
Promise.resolve(Styles.typography.fontFace.base64fonts),
]);
// Create inline style element for critical CSS
const criticalStyle = document.createElement('style');
criticalStyle.id = 'critical-css';
criticalStyle.innerHTML = `
${fonts}
${preRenderCss}
`;
// Insert critical styles before any other styles
const firstStyle = document.head.querySelector('style');
if (firstStyle) {
document.head.insertBefore(criticalStyle, firstStyle);
} else {
document.head.appendChild(criticalStyle);
}
// Load non-critical styles when browser is idle
requestIdleCallback(() => loadNonCriticalStyles());
} catch (error) {
console.error('Failed to load critical styles:', error);
// Ensure content is visible even if styles fail
document.body.style.opacity = '1';
}
};
const loadNonCriticalStyles = async () => {
try {
// Load decorative and below-the-fold styles
const postRenderCss = await Styles.postRenderCss;
const style = document.createElement('style');
style.id = 'non-critical-css';
style.innerHTML = postRenderCss;
document.head.appendChild(style);
} catch (error) {
console.error('Failed to load non-critical styles:', error);
document.body.style.opacity = '1';
}
};
// Start loading immediately or when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', inlineCriticalStyles, {
once: true,
});
} else {
inlineCriticalStyles();
}
export { inlineCriticalStyles, loadNonCriticalStyles };
Expected performance improvements when implementing these optimization strategies:
Reduction with grouped exports vs full import
Faster with progressive loading strategy
Improvement with critical CSS inlining
Better with vendor chunk splitting
// Add performance monitoring to track improvements
const measureComponentLoad = (componentName) => {
const startMark = `${componentName}-start`;
const endMark = `${componentName}-end`;
const measureName = `${componentName}-load`;
performance.mark(startMark);
return {
complete: () => {
performance.mark(endMark);
performance.measure(measureName, startMark, endMark);
const measure = performance.getEntriesByName(measureName)[0];
console.log(`${componentName} loaded in ${measure.duration.toFixed(2)}ms`);
// Send to analytics
if (window.gtag) {
window.gtag('event', 'timing_complete', {
name: componentName,
value: Math.round(measure.duration),
event_category: 'Component Loading',
});
}
}
};
};
// Usage example
const heroTimer = measureComponentLoad('hero-components');
await loadStructuralComponents();
heroTimer.complete();
Follow these recommendations to achieve optimal performance with the UMD Design System:
requestIdleCallback for non-critical resourcesrequestIdleCallbackANALYZE=true npm run build) to identify optimization opportunitiesCopy this minimal setup for optimal performance out of the box:
// main.js - Minimal performance-optimized setup
import './styles/critical.css';
// Load structural components immediately
import('@universityofmaryland/web-components-library/structural')
.then(m => m.default());
// Load content when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
import('@universityofmaryland/web-components-library/content')
.then(m => m.default());
});
}
// Defer interactive components
requestIdleCallback(() => {
import('@universityofmaryland/web-components-library/interactive')
.then(m => m.default());
}, { timeout: 2000 });