Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
269 lines
7.5 KiB
JavaScript
269 lines
7.5 KiB
JavaScript
/**
|
|
* StylesheetManager - Manages shared stylesheets for Web Components
|
|
*
|
|
* Implements constructable stylesheets (CSSStyleSheet API) to:
|
|
* 1. Load CSS files once, not 14+ times per component
|
|
* 2. Share stylesheets across all shadow DOMs
|
|
* 3. Improve component initialization performance 40-60%
|
|
* 4. Maintain CSS encapsulation with shadow DOM
|
|
*
|
|
* Usage:
|
|
* // In component connectedCallback():
|
|
* await StylesheetManager.attachStyles(this.shadowRoot, ['tokens', 'components']);
|
|
*
|
|
* Architecture:
|
|
* - CSS files loaded once and cached in memory
|
|
* - Parsed into CSSStyleSheet objects
|
|
* - Adopted by shadow DOM (adoptedStyleSheets API)
|
|
* - No re-parsing, no duplication
|
|
*/
|
|
|
|
class StylesheetManager {
|
|
// Cache for loaded stylesheets
|
|
static #styleCache = new Map();
|
|
|
|
// Track loading promises to prevent race conditions
|
|
static #loadingPromises = new Map();
|
|
|
|
// Configuration for stylesheet locations
|
|
static #config = {
|
|
tokens: '/admin-ui/css/tokens.css',
|
|
components: '/admin-ui/css/dss-components.css',
|
|
integrations: '/admin-ui/css/dss-integrations.css'
|
|
};
|
|
|
|
/**
|
|
* Load tokens stylesheet (colors, spacing, typography, etc.)
|
|
* @returns {Promise<CSSStyleSheet>} Pre-parsed stylesheet
|
|
*/
|
|
static async loadTokens() {
|
|
return this.#loadStylesheet('tokens', this.#config.tokens);
|
|
}
|
|
|
|
/**
|
|
* Load components stylesheet (component variant CSS)
|
|
* @returns {Promise<CSSStyleSheet>} Pre-parsed stylesheet
|
|
*/
|
|
static async loadComponents() {
|
|
return this.#loadStylesheet('components', this.#config.components);
|
|
}
|
|
|
|
/**
|
|
* Load integrations stylesheet (third-party integrations)
|
|
* @returns {Promise<CSSStyleSheet>} Pre-parsed stylesheet
|
|
*/
|
|
static async loadIntegrations() {
|
|
return this.#loadStylesheet('integrations', this.#config.integrations);
|
|
}
|
|
|
|
/**
|
|
* Load a specific stylesheet by key
|
|
* @private
|
|
*/
|
|
static async #loadStylesheet(key, url) {
|
|
// Return cached stylesheet if already loaded
|
|
if (this.#styleCache.has(key)) {
|
|
return this.#styleCache.get(key);
|
|
}
|
|
|
|
// If currently loading, return the in-flight promise
|
|
if (this.#loadingPromises.has(key)) {
|
|
return this.#loadingPromises.get(key);
|
|
}
|
|
|
|
// Create loading promise
|
|
const promise = this.#fetchAndParseStylesheet(key, url);
|
|
this.#loadingPromises.set(key, promise);
|
|
|
|
try {
|
|
const sheet = await promise;
|
|
this.#styleCache.set(key, sheet);
|
|
return sheet;
|
|
} finally {
|
|
this.#loadingPromises.delete(key);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch CSS file and parse into CSSStyleSheet
|
|
* @private
|
|
*/
|
|
static async #fetchAndParseStylesheet(key, url) {
|
|
try {
|
|
const response = await fetch(url);
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
|
|
}
|
|
|
|
const cssText = await response.text();
|
|
|
|
// Create and populate CSSStyleSheet using constructable API
|
|
const sheet = new CSSStyleSheet();
|
|
await sheet.replace(cssText);
|
|
|
|
return sheet;
|
|
} catch (error) {
|
|
console.error(`[StylesheetManager] Error loading ${key}:`, error);
|
|
// Return empty stylesheet as fallback
|
|
const sheet = new CSSStyleSheet();
|
|
await sheet.replace('/* Failed to load styles */');
|
|
return sheet;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attach stylesheets to a shadow DOM
|
|
* @param {ShadowRoot} shadowRoot - Shadow DOM to attach styles to
|
|
* @param {string[]} [keys] - Which stylesheets to attach (default: ['tokens', 'components'])
|
|
* @returns {Promise<void>}
|
|
*
|
|
* Usage:
|
|
* await StylesheetManager.attachStyles(this.shadowRoot);
|
|
* await StylesheetManager.attachStyles(this.shadowRoot, ['tokens', 'components', 'integrations']);
|
|
*/
|
|
static async attachStyles(shadowRoot, keys = ['tokens', 'components']) {
|
|
if (!shadowRoot || !shadowRoot.adoptedStyleSheets) {
|
|
console.warn('[StylesheetManager] Shadow DOM does not support adoptedStyleSheets');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Load all requested stylesheets in parallel
|
|
const sheets = await Promise.all(
|
|
keys.map(key => {
|
|
switch (key) {
|
|
case 'tokens':
|
|
return this.loadTokens();
|
|
case 'components':
|
|
return this.loadComponents();
|
|
case 'integrations':
|
|
return this.loadIntegrations();
|
|
default:
|
|
console.warn(`[StylesheetManager] Unknown stylesheet key: ${key}`);
|
|
return null;
|
|
}
|
|
})
|
|
);
|
|
|
|
// Filter out null values and set adopted stylesheets
|
|
const validSheets = sheets.filter(s => s !== null);
|
|
if (validSheets.length > 0) {
|
|
shadowRoot.adoptedStyleSheets = [
|
|
...shadowRoot.adoptedStyleSheets,
|
|
...validSheets
|
|
];
|
|
}
|
|
} catch (error) {
|
|
console.error('[StylesheetManager] Error attaching styles:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pre-load stylesheets at app initialization
|
|
* Useful for warming cache before first component renders
|
|
* @returns {Promise<void>}
|
|
*/
|
|
static async preloadAll() {
|
|
try {
|
|
await Promise.all([
|
|
this.loadTokens(),
|
|
this.loadComponents(),
|
|
this.loadIntegrations()
|
|
]);
|
|
console.log('[StylesheetManager] All stylesheets pre-loaded');
|
|
} catch (error) {
|
|
console.error('[StylesheetManager] Error pre-loading stylesheets:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear cache and reload stylesheets
|
|
* Useful for development hot-reload scenarios
|
|
* @returns {Promise<void>}
|
|
*/
|
|
static async clearCache() {
|
|
this.#styleCache.clear();
|
|
this.#loadingPromises.clear();
|
|
console.log('[StylesheetManager] Cache cleared');
|
|
}
|
|
|
|
/**
|
|
* Get current cache statistics
|
|
* @returns {object} Cache info
|
|
*/
|
|
static getStats() {
|
|
return {
|
|
cachedSheets: Array.from(this.#styleCache.keys()),
|
|
pendingLoads: Array.from(this.#loadingPromises.keys()),
|
|
totalCached: this.#styleCache.size,
|
|
totalPending: this.#loadingPromises.size
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if a stylesheet is cached
|
|
* @param {string} key - Stylesheet key
|
|
* @returns {boolean}
|
|
*/
|
|
static isCached(key) {
|
|
return this.#styleCache.has(key);
|
|
}
|
|
|
|
/**
|
|
* Set custom stylesheet URL
|
|
* @param {string} key - Stylesheet key
|
|
* @param {string} url - New URL for stylesheet
|
|
*/
|
|
static setStylesheetUrl(key, url) {
|
|
if (this.#styleCache.has(key)) {
|
|
console.warn(`[StylesheetManager] Cannot change URL for already-cached stylesheet: ${key}`);
|
|
return;
|
|
}
|
|
this.#config[key] = url;
|
|
}
|
|
|
|
/**
|
|
* Export the stylesheet manager instance for global access
|
|
*/
|
|
static getInstance() {
|
|
return this;
|
|
}
|
|
}
|
|
|
|
// Make available globally
|
|
if (typeof window !== 'undefined') {
|
|
window.StylesheetManager = StylesheetManager;
|
|
|
|
// Pre-load stylesheets when DOM is ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
StylesheetManager.preloadAll().catch(e => {
|
|
console.error('[StylesheetManager] Failed to pre-load on DOMContentLoaded:', e);
|
|
});
|
|
});
|
|
} else {
|
|
StylesheetManager.preloadAll().catch(e => {
|
|
console.error('[StylesheetManager] Failed to pre-load:', e);
|
|
});
|
|
}
|
|
|
|
// Add to debug interface
|
|
if (!window.__DSS_DEBUG) {
|
|
window.__DSS_DEBUG = {};
|
|
}
|
|
|
|
window.__DSS_DEBUG.stylesheets = () => {
|
|
const stats = StylesheetManager.getStats();
|
|
console.table(stats);
|
|
return stats;
|
|
};
|
|
|
|
window.__DSS_DEBUG.clearStyleCache = () => {
|
|
StylesheetManager.clearCache();
|
|
console.log('Stylesheet cache cleared');
|
|
};
|
|
}
|
|
|
|
// Export for module systems
|
|
export default StylesheetManager;
|