/** * Universal Framework Detector * * Detects the current JavaScript framework/library being used. * Supports a wide range of frameworks and is extensible via plugins. * * Supported frameworks: * - React, Preact, Next.js, Remix * - Vue 2, Vue 3, Nuxt * - Svelte, SvelteKit * - Angular, AngularJS * - Solid.js * - Qwik * - Lit, Lit Element * - Alpine.js * - Vanilla JS/TS */ export type FrameworkType = | 'react' | 'preact' | 'next' | 'remix' | 'vue2' | 'vue3' | 'nuxt' | 'svelte' | 'sveltekit' | 'angular' | 'angularjs' | 'solid' | 'qwik' | 'lit' | 'alpine' | 'vanilla' | string; // Allow custom frameworks export interface FrameworkDetectionResult { framework: FrameworkType; version?: string; isSSR: boolean; confidence: number; // 0-1, how confident we are in the detection meta?: Record; } export interface FrameworkDetector { name: FrameworkType; detect: () => boolean; getVersion?: () => string | undefined; isSSR?: () => boolean; priority?: number; // Higher priority runs first } class FrameworkDetectionEngine { private detectors: FrameworkDetector[] = []; private cache: FrameworkDetectionResult | null = null; private customDetectors: Map = new Map(); constructor() { this.registerBuiltInDetectors(); } /** * Register a custom framework detector */ registerDetector(detector: FrameworkDetector): void { this.customDetectors.set(detector.name, detector); this.detectors.push(detector); this.sortDetectors(); this.cache = null; // Invalidate cache } /** * Unregister a framework detector */ unregisterDetector(name: string): void { this.customDetectors.delete(name); this.detectors = this.detectors.filter(d => d.name !== name); this.cache = null; } /** * Detect the current framework */ detect(useCache = true): FrameworkDetectionResult { if (useCache && this.cache) { return this.cache; } for (const detector of this.detectors) { try { if (detector.detect()) { const version = detector.getVersion?.(); const result: FrameworkDetectionResult = { framework: detector.name, ...(version !== undefined && { version }), isSSR: detector.isSSR?.() ?? this.detectSSR(), confidence: 1.0, meta: {} }; this.cache = result; return result; } } catch (error) { console.warn(`Framework detector "${detector.name}" failed:`, error); } } // Fallback to vanilla const fallback: FrameworkDetectionResult = { framework: 'vanilla', isSSR: this.detectSSR(), confidence: 1.0 }; this.cache = fallback; return fallback; } /** * Clear detection cache */ clearCache(): void { this.cache = null; } /** * Get all registered detectors */ getDetectors(): FrameworkDetector[] { return [...this.detectors]; } private sortDetectors(): void { this.detectors.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)); } private detectSSR(): boolean { return typeof window === 'undefined' || typeof document === 'undefined'; } private registerBuiltInDetectors(): void { // React Detection (highest priority for React-based frameworks) this.detectors.push({ name: 'next', priority: 100, detect: () => { if (typeof window === 'undefined') return false; return !!( (window as any).next || (window as any).__NEXT_DATA__ || (window as any).__NEXT_LOADED_PAGES__ ); }, getVersion: () => (window as any).next?.version, isSSR: () => typeof window !== 'undefined' && !!(window as any).__NEXT_DATA__ }); this.detectors.push({ name: 'remix', priority: 95, detect: () => { if (typeof window === 'undefined') return false; return !!( (window as any).__remixContext || (window as any).__remixManifest ); } }); this.detectors.push({ name: 'preact', priority: 90, detect: () => { try { // Check for Preact-specific features const hasPreact = typeof window !== 'undefined' && ( !!(window as any).preact || (typeof (window as any).h === 'function' && !(window as any).React) ); // Try to detect via module resolution if (!hasPreact && typeof require !== 'undefined') { try { const preact = require('preact'); return !!preact.h; } catch {} } return hasPreact; } catch { return false; } }, getVersion: () => (window as any).preact?.version }); this.detectors.push({ name: 'react', priority: 85, detect: () => { try { // Check for React global if (typeof window !== 'undefined' && (window as any).React) { return true; } // Check for React 18/19 root markers on the common "root" element if (typeof window !== 'undefined') { const root = document.getElementById('root') || document.querySelector('[id*="root"]'); if (root && Object.keys(root).some(k => k.startsWith('__reactContainer') || k.startsWith('__reactFiber'))) { return true; } } // Check for React in module system if (typeof require !== 'undefined') { try { const React = require('react'); return !!React.createElement; } catch {} } return false; } catch { return false; } }, getVersion: () => { try { if (typeof window !== 'undefined' && (window as any).React) { return (window as any).React.version; } if (typeof require !== 'undefined') { return require('react').version; } } catch {} return undefined; } }); // Vue Detection this.detectors.push({ name: 'nuxt', priority: 100, detect: () => { if (typeof window === 'undefined') return false; return !!( (window as any).__NUXT__ || (window as any).$nuxt || document.getElementById('__nuxt') ); }, getVersion: () => (window as any).__NUXT__?.nuxt?.version }); this.detectors.push({ name: 'vue3', priority: 90, detect: () => { try { if (typeof window !== 'undefined' && (window as any).Vue) { const version = (window as any).Vue.version; return version && version.startsWith('3'); } if (typeof require !== 'undefined') { try { const Vue = require('vue'); return Vue.version && Vue.version.startsWith('3'); } catch {} } // Check for Vue 3 app instance if (typeof window !== 'undefined') { const app = document.querySelector('[data-v-app]'); if (app && (app as any).__vue_app__) { return true; } } return false; } catch { return false; } }, getVersion: () => { try { if (typeof window !== 'undefined' && (window as any).Vue) { return (window as any).Vue.version; } if (typeof require !== 'undefined') { return require('vue').version; } } catch {} return undefined; } }); this.detectors.push({ name: 'vue2', priority: 85, detect: () => { try { if (typeof window !== 'undefined' && (window as any).Vue) { const version = (window as any).Vue.version; return version && version.startsWith('2'); } if (typeof require !== 'undefined') { try { const Vue = require('vue'); return Vue.version && Vue.version.startsWith('2'); } catch {} } return false; } catch { return false; } }, getVersion: () => { try { if (typeof window !== 'undefined' && (window as any).Vue) { return (window as any).Vue.version; } if (typeof require !== 'undefined') { return require('vue').version; } } catch {} return undefined; } }); // Svelte Detection this.detectors.push({ name: 'sveltekit', priority: 95, detect: () => { if (typeof window === 'undefined') return false; return !!( (window as any).__sveltekit || document.querySelector('[data-sveltekit-hydrate]') ); } }); this.detectors.push({ name: 'svelte', priority: 90, detect: () => { try { // Check for Svelte compiler output markers if (typeof document !== 'undefined') { const scripts = document.querySelectorAll('script'); for (const script of Array.from(scripts)) { if (script.textContent?.includes('SvelteComponent')) { return true; } } } // Check for Svelte in module system if (typeof require !== 'undefined') { try { require('svelte'); return true; } catch {} } return false; } catch { return false; } } }); // Angular Detection this.detectors.push({ name: 'angular', priority: 90, detect: () => { try { if (typeof window === 'undefined') return false; return !!( (window as any).ng || (window as any).getAllAngularRootElements || document.querySelector('[ng-version]') ); } catch { return false; } }, getVersion: () => { const versionEl = document.querySelector('[ng-version]'); return versionEl?.getAttribute('ng-version') || undefined; } }); this.detectors.push({ name: 'angularjs', priority: 85, detect: () => { if (typeof window === 'undefined') return false; return !!( (window as any).angular && typeof (window as any).angular.version === 'object' ); }, getVersion: () => { const angular = (window as any).angular; return angular?.version?.full; } }); // Solid.js Detection this.detectors.push({ name: 'solid', priority: 90, detect: () => { try { if (typeof window !== 'undefined' && (window as any).Solid) { return true; } if (typeof require !== 'undefined') { try { require('solid-js'); return true; } catch {} } return false; } catch { return false; } } }); // Qwik Detection this.detectors.push({ name: 'qwik', priority: 90, detect: () => { if (typeof window === 'undefined') return false; return !!( (window as any).qwik || document.querySelector('[q\\:container]') || document.querySelector('[q\\:base]') ); } }); // Lit Detection this.detectors.push({ name: 'lit', priority: 85, detect: () => { try { if (typeof window !== 'undefined' && (window as any).litHtml) { return true; } if (typeof require !== 'undefined') { try { require('lit'); return true; } catch { try { require('lit-element'); return true; } catch {} } } return false; } catch { return false; } } }); // Alpine.js Detection this.detectors.push({ name: 'alpine', priority: 85, detect: () => { if (typeof window === 'undefined') return false; return !!( (window as any).Alpine || document.querySelector('[x-data]') ); }, getVersion: () => (window as any).Alpine?.version }); // Sort by priority this.sortDetectors(); } } // Singleton instance const detectionEngine = new FrameworkDetectionEngine(); /** * Detect the current framework */ export function detectFramework(useCache = true): FrameworkDetectionResult { return detectionEngine.detect(useCache); } /** * Register a custom framework detector */ export function registerFrameworkDetector(detector: FrameworkDetector): void { detectionEngine.registerDetector(detector); } /** * Unregister a framework detector */ export function unregisterFrameworkDetector(name: string): void { detectionEngine.unregisterDetector(name); } /** * Clear detection cache */ export function clearDetectionCache(): void { detectionEngine.clearCache(); } /** * Get all registered detectors */ export function getRegisteredDetectors(): FrameworkDetector[] { return detectionEngine.getDetectors(); } /** * Check if a specific framework is detected */ export function isFramework(framework: FrameworkType): boolean { const result = detectFramework(); return result.framework === framework; } /** * Get framework family (e.g., 'react' for next, remix, preact) */ export function getFrameworkFamily(framework: FrameworkType): string { const families: Record = { 'react': 'react', 'preact': 'react', 'next': 'react', 'remix': 'react', 'vue2': 'vue', 'vue3': 'vue', 'nuxt': 'vue', 'svelte': 'svelte', 'sveltekit': 'svelte', 'angular': 'angular', 'angularjs': 'angular', 'solid': 'solid', 'qwik': 'qwik', 'lit': 'webcomponents', 'alpine': 'alpine', 'vanilla': 'vanilla' }; return families[framework] || framework; }