UNPKG

userface

Version:

Universal Data-Driven UI Engine with live data, validation, and multi-platform support

1,045 lines (886 loc) 31 kB
/** * Robust Browser Engine - Production-ready component processing * No hardcode, no hacks, no temporary solutions */ export class RobustBrowserEngine { constructor(options = {}) { this.options = { debug: false, strictMode: true, isolateComponents: true, ...options }; this.components = new Map(); this.styles = new Map(); this.modules = new Map(); this.dependencies = new Map(); this.log('Engine initialized with options:', this.options); } /** * Process component bundle with proper validation and error handling */ async processComponentBundle(bundle) { this.validateBundle(bundle); const context = this.createProcessingContext(bundle); try { // 1. Parse and validate all files const parsedFiles = await this.parseFiles(bundle.files, context); // 2. Extract component metadata const metadata = this.extractMetadata(parsedFiles, context); // 3. Resolve dependencies const dependencies = this.resolveDependencies(parsedFiles, context); // 4. Compile component const factory = await this.compileComponent(parsedFiles, metadata, dependencies, context); // 5. Process styles const styles = this.processComponentStyles(parsedFiles, context); // 6. Create component descriptor const descriptor = this.createComponentDescriptor({ name: bundle.name, factory, metadata, dependencies, styles, context }); // 7. Register component this.registerComponent(descriptor); return descriptor; } catch (error) { this.handleProcessingError(error, context); throw error; } } /** * Validate bundle structure */ validateBundle(bundle) { if (!bundle || typeof bundle !== 'object') { throw new Error('Bundle must be an object'); } if (!bundle.name || typeof bundle.name !== 'string') { throw new Error('Bundle must have a valid name'); } if (!Array.isArray(bundle.files) || bundle.files.length === 0) { throw new Error('Bundle must contain files array'); } // Validate each file bundle.files.forEach((file, index) => { if (!file.name || !file.code || !file.type) { throw new Error(`File at index ${index} is missing required properties`); } }); } /** * Create isolated processing context */ createProcessingContext(bundle) { return { bundleName: bundle.name, timestamp: Date.now(), scope: `component_${bundle.name}_${Date.now()}`, errors: [], warnings: [], metadata: {} }; } /** * Parse all files in bundle */ async parseFiles(files, context) { const parsed = new Map(); for (const file of files) { try { const parseResult = await this.parseFile(file); parsed.set(file.name, parseResult); } catch (error) { this.log(`Failed to parse file ${file.name}:`, error); throw error; } } return parsed; } /** * Parse a single file and extract metadata */ async parseFile(file) { console.log(`[RobustBrowserEngine] Parsing file: ${file.path} (${file.type})`); const result = { path: file.path, name: file.name, type: file.type, originalCode: file.code, cleanCode: null, metadata: {} }; if (file.type === 'tsx') { // Clean code by removing TypeScript-specific syntax result.cleanCode = await this.cleanTypeScriptCode(file.code, result); return result; } else if (file.type === 'css') { // CSS files are used as-is result.cleanCode = file.code; return result; } return result; } /** * Parse TypeScript/TSX file with proper analysis */ async parseTypeScriptFile(file, context) { const result = { type: 'typescript', originalCode: file.code, cleanCode: '', exports: [], imports: [], interfaces: [], components: [], dependencies: [] }; // Extract imports const importRegex = /import\s+(?:(?:\{[^}]*\}|\w+|\*\s+as\s+\w+)\s+from\s+)?['"]([^'"]+)['"];?/g; let match; while ((match = importRegex.exec(file.code)) !== null) { result.imports.push({ statement: match[0], module: match[1] }); } // Extract interfaces const interfaceRegex = /interface\s+(\w+)\s*\{([^}]+)\}/g; while ((match = interfaceRegex.exec(file.code)) !== null) { result.interfaces.push({ name: match[1], definition: match[2], props: this.parseInterfaceProps(match[2]) }); } // Extract component definition - AST-BASED APPROACH console.log(`[RobustBrowserEngine] Extracting components using AST`); if (typeof window !== 'undefined' && window.Babel) { try { // Parse with Babel to get AST const ast = window.Babel.parse(file.code, { presets: [['react', { runtime: 'classic' }]], plugins: ['@babel/plugin-syntax-typescript'] }); // Extract components from AST this.extractComponentsFromAST(ast, result); } catch (error) { console.warn(`[RobustBrowserEngine] AST parsing failed, falling back to regex:`, error); // Fallback to regex this.extractComponentsWithRegex(file.code, result); } } else { // Fallback to regex this.extractComponentsWithRegex(file.code, result); } console.log(`[RobustBrowserEngine] Extracted components:`, result.components); // Clean code by removing TypeScript-specific syntax result.cleanCode = await this.cleanTypeScriptCode(file.code, result); return result; } /** * Parse interface properties */ parseInterfaceProps(interfaceBody) { const props = []; const propRegex = /(\w+)\s*\??\s*:\s*([^;,]+)/g; let match; while ((match = propRegex.exec(interfaceBody)) !== null) { props.push({ name: match[1], type: match[2].trim(), optional: interfaceBody.includes(`${match[1]}?`) }); } return props; } /** * Clean TypeScript code for execution - PRECISE PATTERNS ONLY */ async cleanTypeScriptCode(code, parseResult) { console.log(`[RobustBrowserEngine] CLEANING CODE - ORIGINAL:`, code); let cleaned = code; // Remove imports (will be handled by dependency resolution) cleaned = cleaned.replace(/import\s+[^;]+;?\s*/g, ''); console.log(`[RobustBrowserEngine] After removing imports:`, cleaned); // Remove ALL interfaces completely - including extends - PRECISE PATTERNS cleaned = cleaned.replace(/export\s+interface\s+\w+\s+extends\s+[^{]+\{[^}]*\}\s*/g, ''); cleaned = cleaned.replace(/interface\s+\w+\s+extends\s+[^{]+\{[^}]*\}\s*/g, ''); cleaned = cleaned.replace(/export\s+interface\s+\w+[^{]*\{[^}]*\}\s*/g, ''); cleaned = cleaned.replace(/interface\s+\w+[^{]*\{[^}]*\}\s*/g, ''); console.log(`[RobustBrowserEngine] After removing interfaces:`, cleaned); // Remove React.FC type annotations - PRECISE PATTERN cleaned = cleaned.replace(/:\s*React\.FC<[^>]+>/g, ''); console.log(`[RobustBrowserEngine] After removing React.FC:`, cleaned); // Remove ONLY TypeScript function parameter type annotations - PRECISE PATTERNS // Pattern 1: ({ param1, param2 }: { param1: Type, param2: Type }) cleaned = cleaned.replace(/\(\s*\{([^}]+)\}\s*:\s*\{[^}]+\}\s*\)/g, '({ $1 })'); // Pattern 2: ({ param1, param2 }: InterfaceName) cleaned = cleaned.replace(/\(\s*\{([^}]+)\}\s*:\s*[A-Z][A-Za-z]+\s*\)/g, '({ $1 })'); // Pattern 3: (param: Type) - including React.Type<Generic> cleaned = cleaned.replace(/\(\s*(\w+)\s*:\s*[A-Za-z][A-Za-z.]*<[^>]+>\s*\)/g, '($1)'); // Pattern 4: (param: SimpleType) cleaned = cleaned.replace(/\(\s*(\w+)\s*:\s*[A-Z][A-Za-z.]+\s*\)/g, '($1)'); console.log(`[RobustBrowserEngine] After removing function parameter types:`, cleaned); // Remove variable type annotations - PRECISE PATTERNS cleaned = cleaned.replace(/const\s+(\w+)\s*:\s*[A-Z][A-Za-z<>]+\s*=/g, 'const $1 ='); cleaned = cleaned.replace(/let\s+(\w+)\s*:\s*[A-Z][A-Za-z<>]+\s*=/g, 'let $1 ='); cleaned = cleaned.replace(/var\s+(\w+)\s*:\s*[A-Z][A-Za-z<>]+\s*=/g, 'var $1 ='); console.log(`[RobustBrowserEngine] After removing variable types:`, cleaned); // Remove export statements cleaned = cleaned.replace(/export\s+(?:default\s+)?/g, ''); console.log(`[RobustBrowserEngine] After removing exports:`, cleaned); // Remove type assertions cleaned = cleaned.replace(/as\s+\w+/g, ''); // Fix reserved words in object properties cleaned = this.fixReservedWords(cleaned); console.log(`[RobustBrowserEngine] After fixing reserved words:`, cleaned); // Transform JSX using BABEL - the ONLY correct way if (cleaned.includes('<') && cleaned.includes('>') && !cleaned.includes('interface')) { console.log(`[RobustBrowserEngine] Detected JSX syntax, using Babel`); try { if (typeof window !== 'undefined' && window.Babel) { const result = window.Babel.transform(cleaned, { presets: [['react', { runtime: 'classic' }]], plugins: [] }); cleaned = result.code; console.log(`[RobustBrowserEngine] Babel JSX transformation completed`); } else { console.warn(`[RobustBrowserEngine] Babel not available, JSX will fail`); } } catch (error) { console.warn(`[RobustBrowserEngine] Babel transformation failed:`, error); } } console.log(`[RobustBrowserEngine] FINAL CLEANED CODE:`, cleaned); return cleaned.trim(); } /** * Fix reserved words in object properties and destructuring */ fixReservedWords(code) { const reservedWords = [ 'class', 'interface', 'package', 'private', 'protected', 'public', 'static', 'extends', 'implements', 'import', 'export', 'default', 'enum', 'namespace', 'module', 'declare', 'abstract', 'readonly' ]; reservedWords.forEach(word => { // Fix in object properties: { class: value } -> { 'class': value } const objectPropRegex = new RegExp(`\\b${word}\\s*:`, 'g'); code = code.replace(objectPropRegex, `'${word}':`); // Fix in destructuring: { class } -> { 'class': class } const destructuringRegex = new RegExp(`\\{([^}]*\\b)${word}(\\b[^}]*)\\}`, 'g'); code = code.replace(destructuringRegex, (match, before, after) => { return `{${before}'${word}': ${word}${after}}`; }); // Fix property access: obj.class -> obj['class'] const propAccessRegex = new RegExp(`\\.${word}\\b`, 'g'); code = code.replace(propAccessRegex, `['${word}']`); }); return code; } /** * Transform JSX to React.createElement calls - USING BABEL */ async transformJSXToCreateElement(code) { console.log(`[RobustBrowserEngine] Starting BABEL JSX transformation`); // Don't transform if there are still TypeScript artifacts if (code.includes('interface') || code.includes('React.FC')) { console.log(`[RobustBrowserEngine] Skipping JSX transformation - TypeScript artifacts present`); return code; } try { // Use Babel for proper JSX transformation if (typeof window !== 'undefined' && window.Babel) { const result = window.Babel.transform(code, { presets: [['react', { runtime: 'classic' }]], plugins: [] }); console.log(`[RobustBrowserEngine] Babel JSX transformation completed`); return result.code; } else { console.warn(`[RobustBrowserEngine] Babel not available, skipping JSX transformation`); return code; } } catch (error) { console.warn(`[RobustBrowserEngine] Babel JSX transformation failed:`, error); return code; // Return original code if transformation fails } } /** * Parse CSS file */ parseCSSFile(file, context) { return { type: 'css', originalCode: file.code, rules: this.extractCSSRules(file.code), variables: this.extractCSSVariables(file.code) }; } /** * Extract CSS rules */ extractCSSRules(css) { const rules = []; const ruleRegex = /([^{]+)\{([^}]+)\}/g; let match; while ((match = ruleRegex.exec(css)) !== null) { rules.push({ selector: match[1].trim(), declarations: match[2].trim() }); } return rules; } /** * Extract CSS variables */ extractCSSVariables(css) { const variables = []; const varRegex = /--([\w-]+):\s*([^;]+);/g; let match; while ((match = varRegex.exec(css)) !== null) { variables.push({ name: match[1], value: match[2].trim() }); } return variables; } /** * Extract component metadata */ extractMetadata(parsedFiles, context) { const metadata = { name: context.bundleName, props: [], events: [], slots: [], platform: 'react' }; // Find main component file const mainFile = this.findMainComponentFile(parsedFiles, context.bundleName); if (!mainFile) { throw new Error(`Main component file not found for ${context.bundleName}`); } // Extract props from interface const propsInterface = mainFile.interfaces.find(iface => iface.name.toLowerCase().includes('props') ); if (propsInterface) { metadata.props = propsInterface.props.map(prop => ({ name: prop.name, type: this.mapTypeScriptType(prop.type), required: !prop.optional, description: `${prop.name}: ${prop.type}` })); } return metadata; } /** * Find main component file */ findMainComponentFile(parsedFiles, componentName) { console.log(`[RobustBrowserEngine] Searching for component: ${componentName}`); console.log(`[RobustBrowserEngine] Parsed files:`, parsedFiles); for (const [fileName, parsed] of parsedFiles) { console.log(`[RobustBrowserEngine] Checking file: ${fileName}`, { type: parsed.type, components: parsed.components, hasComponents: !!parsed.components }); if ((parsed.type === 'typescript' || parsed.type === 'tsx') && parsed.components && parsed.components.some(comp => comp.name === componentName)) { console.log(`[RobustBrowserEngine] Found main component file: ${fileName}`); return parsed; } } console.log(`[RobustBrowserEngine] No main component file found for: ${componentName}`); return null; } /** * Map TypeScript types to our internal types */ mapTypeScriptType(tsType) { const typeMap = { 'string': 'text', 'boolean': 'boolean', 'number': 'number', 'Function': 'function', '() => void': 'function' }; return typeMap[tsType] || 'text'; } /** * Resolve component dependencies */ resolveDependencies(parsedFiles, context) { const dependencies = new Set(); for (const [fileName, parsed] of parsedFiles) { if (parsed.imports) { parsed.imports.forEach(imp => { if (!imp.module.startsWith('.')) { // External dependency dependencies.add(imp.module); } }); } } return Array.from(dependencies); } /** * Compile component with proper isolation */ async compileComponent(parsedFiles, metadata, dependencies, context) { const mainFile = this.findMainComponentFile(parsedFiles, context.bundleName); if (!mainFile) { throw new Error('Main component file not found'); } try { // Create isolated execution context const executionContext = this.createExecutionContext(dependencies, context); // Compile the component const factory = this.executeComponentCode(mainFile.cleanCode, metadata.name, executionContext); // Validate compiled component this.validateCompiledComponent(factory, metadata); return factory; } catch (error) { this.log('Compilation failed:', error); return this.createFallbackComponent(metadata, error); } } /** * Create execution context with proper dependency injection */ createExecutionContext(dependencies, context) { const ctx = { React: this.getReactReference(), console: this.createScopedConsole(context.scope), // Add other required globals }; // Inject dependencies dependencies.forEach(dep => { if (this.isKnownDependency(dep)) { ctx[dep] = this.resolveDependency(dep); } }); return ctx; } /** * Get React reference safely */ getReactReference() { if (typeof window !== 'undefined' && window.React) { return window.React; } throw new Error('React is not available in global scope'); } /** * Create scoped console for debugging */ createScopedConsole(scope) { return { log: (...args) => this.log(`[${scope}]`, ...args), warn: (...args) => this.log(`[${scope}] WARN:`, ...args), error: (...args) => this.log(`[${scope}] ERROR:`, ...args) }; } /** * Execute component code in controlled environment */ executeComponentCode(code, componentName, context) { const contextKeys = Object.keys(context); const contextValues = Object.values(context); console.log(`[RobustBrowserEngine] EXECUTING CODE FOR ${componentName}:`); console.log('Clean code:', code); console.log('Context keys:', contextKeys); // Try without strict mode first let wrappedCode = ` ${code} if (typeof ${componentName} !== 'function') { throw new Error('Component ${componentName} is not a function'); } return ${componentName}; `; console.log(`[RobustBrowserEngine] WRAPPED CODE (non-strict):`, wrappedCode); try { const factory = new Function(...contextKeys, wrappedCode); const result = factory(...contextValues); console.log(`[RobustBrowserEngine] EXECUTION RESULT:`, result); console.log(`[RobustBrowserEngine] IS FUNCTION:`, typeof result === 'function'); return result; } catch (error) { console.warn(`[RobustBrowserEngine] Non-strict execution failed:`, error); // Try with more aggressive cleaning const cleanedCode = this.aggressiveCleanCode(code); const fallbackWrappedCode = ` ${cleanedCode} if (typeof ${componentName} !== 'function') { throw new Error('Component ${componentName} is not a function'); } return ${componentName}; `; console.log(`[RobustBrowserEngine] FALLBACK WRAPPED CODE:`, fallbackWrappedCode); try { const fallbackFactory = new Function(...contextKeys, fallbackWrappedCode); const fallbackResult = fallbackFactory(...contextValues); console.log(`[RobustBrowserEngine] FALLBACK EXECUTION SUCCESS:`, fallbackResult); return fallbackResult; } catch (fallbackError) { console.error(`[RobustBrowserEngine] All execution attempts failed:`, fallbackError); throw new Error(`Compilation failed: ${fallbackError.message}`); } } } /** * Aggressive code cleaning for problematic cases - PRECISE PATTERNS ONLY */ aggressiveCleanCode(code) { console.log(`[RobustBrowserEngine] Starting aggressive cleaning`); let cleaned = code; // Remove all comments cleaned = cleaned.replace(/\/\*[\s\S]*?\*\//g, ''); cleaned = cleaned.replace(/\/\/.*$/gm, ''); // Remove ALL interfaces completely - PRECISE PATTERNS cleaned = cleaned.replace(/export\s+interface\s+\w+\s+extends\s+[^{]+\{[^}]*\}\s*/g, ''); cleaned = cleaned.replace(/interface\s+\w+\s+extends\s+[^{]+\{[^}]*\}\s*/g, ''); cleaned = cleaned.replace(/export\s+interface\s+\w+[^{]*\{[^}]*\}\s*/g, ''); cleaned = cleaned.replace(/interface\s+\w+[^{]*\{[^}]*\}\s*/g, ''); // Remove React.FC type annotations - PRECISE PATTERNS cleaned = cleaned.replace(/:\s*React\.FC<[^>]+>/g, ''); cleaned = cleaned.replace(/const\s+(\w+)\s*:\s*React\.FC<[^>]+>\s*=/g, 'const $1 ='); // Remove type annotations from variable declarations - PRECISE PATTERNS cleaned = cleaned.replace(/const\s+(\w+)\s*:\s*[A-Z][A-Za-z<>]+\s*=/g, 'const $1 ='); cleaned = cleaned.replace(/let\s+(\w+)\s*:\s*[A-Z][A-Za-z<>]+\s*=/g, 'let $1 ='); cleaned = cleaned.replace(/var\s+(\w+)\s*:\s*[A-Z][A-Za-z<>]+\s*=/g, 'var $1 ='); // Fix common strict mode issues cleaned = cleaned.replace(/\bwith\s*\(/g, '// with('); cleaned = cleaned.replace(/\beval\s*\(/g, '// eval('); // Remove export statements cleaned = cleaned.replace(/export\s+(?:default\s+)?/g, ''); // Remove ONLY TypeScript function parameter type annotations - PRECISE PATTERNS cleaned = cleaned.replace(/\(\s*\{([^}]+)\}\s*:\s*\{[^}]+\}\s*\)/g, '({ $1 })'); cleaned = cleaned.replace(/\(\s*\{([^}]+)\}\s*:\s*[A-Z][A-Za-z]+\s*\)/g, '({ $1 })'); cleaned = cleaned.replace(/\(\s*(\w+)\s*:\s*[A-Za-z][A-Za-z.]*<[^>]+>\s*\)/g, '($1)'); cleaned = cleaned.replace(/\(\s*(\w+)\s*:\s*[A-Z][A-Za-z.]+\s*\)/g, '($1)'); // Fix reserved words cleaned = this.fixReservedWords(cleaned); console.log(`[RobustBrowserEngine] Aggressive cleaning completed`); return cleaned; } /** * Validate compiled component */ validateCompiledComponent(factory, metadata) { if (typeof factory !== 'function') { throw new Error(`Component ${metadata.name} is not a function`); } // Additional validation can be added here } /** * Create fallback component for failed compilation */ createFallbackComponent(metadata, error) { return (props) => { const React = this.getReactReference(); return React.createElement('div', { style: { padding: '20px', border: '2px dashed #ff6b6b', borderRadius: '8px', backgroundColor: '#fff5f5', color: '#c53030', fontFamily: 'monospace', textAlign: 'center' } }, [ React.createElement('h3', { key: 'title' }, `⚠️ ${metadata.name}`), React.createElement('p', { key: 'error' }, `Compilation Error: ${error.message}`), React.createElement('details', { key: 'props' }, [ React.createElement('summary', { key: 'summary' }, 'Props'), React.createElement('pre', { key: 'data' }, JSON.stringify(props, null, 2)) ]) ]); }; } /** * Process component styles with proper scoping */ processComponentStyles(parsedFiles, context) { const styles = []; for (const [fileName, parsed] of parsedFiles) { if (parsed.type === 'css') { const scopedCSS = this.scopeCSS(parsed.originalCode, context.scope); styles.push({ fileName, originalCSS: parsed.originalCode, scopedCSS, rules: parsed.rules, variables: parsed.variables }); } } return styles; } /** * Scope CSS to prevent conflicts */ scopeCSS(css, scope) { // Simple scoping - prepend scope to selectors return css.replace(/([^{}]+)\{/g, (match, selector) => { const trimmedSelector = selector.trim(); if (trimmedSelector.startsWith('@') || trimmedSelector.startsWith(':root')) { return match; // Don't scope at-rules or :root } return `.${scope} ${trimmedSelector} {`; }); } /** * Create component descriptor */ createComponentDescriptor(data) { return { id: `${data.name}_${data.context.timestamp}`, name: data.name, factory: data.factory, metadata: data.metadata, dependencies: data.dependencies, styles: data.styles, context: data.context, createdAt: new Date().toISOString(), version: '1.0.0' }; } /** * Register component in engine */ registerComponent(descriptor) { // Unregister previous version if exists if (this.components.has(descriptor.name)) { this.unregisterComponent(descriptor.name); } this.components.set(descriptor.name, descriptor); // Apply styles this.applyComponentStyles(descriptor); this.log(`Component registered: ${descriptor.name}`); } /** * Apply component styles to DOM */ applyComponentStyles(descriptor) { descriptor.styles.forEach((style, index) => { const styleId = `${descriptor.id}_style_${index}`; // Remove existing style if present const existing = document.getElementById(styleId); if (existing) { existing.remove(); } // Create and inject new style const styleElement = document.createElement('style'); styleElement.id = styleId; styleElement.textContent = style.scopedCSS; document.head.appendChild(styleElement); // Store reference for cleanup this.styles.set(styleId, styleElement); }); } /** * Unregister component and cleanup */ unregisterComponent(name) { const descriptor = this.components.get(name); if (!descriptor) return; // Remove styles descriptor.styles.forEach((_, index) => { const styleId = `${descriptor.id}_style_${index}`; const styleElement = this.styles.get(styleId); if (styleElement) { styleElement.remove(); this.styles.delete(styleId); } }); // Remove component this.components.delete(name); this.log(`Component unregistered: ${name}`); } /** * Render component */ renderComponent(name, props = {}) { const descriptor = this.components.get(name); if (!descriptor) { this.log(`Component not found: ${name}`); return null; } try { const React = this.getReactReference(); const finalProps = { ...descriptor.metadata.props.reduce((acc, prop) => { acc[prop.name] = this.getDefaultValue(prop.type); return acc; }, {}), ...props }; // Wrap in scoped container return React.createElement('div', { className: descriptor.context.scope }, React.createElement(descriptor.factory, finalProps)); } catch (error) { this.log(`Render error for ${name}:`, error); return this.createErrorComponent(name, error); } } /** * Get default value for prop type */ getDefaultValue(type) { const defaults = { 'text': 'Sample Text', 'boolean': false, 'number': 0, 'function': () => {} }; return defaults[type] || null; } /** * Create error component */ createErrorComponent(name, error) { const React = this.getReactReference(); return React.createElement('div', { style: { color: 'red', padding: '10px', border: '1px solid red' } }, `Error rendering ${name}: ${error.message}`); } /** * Handle processing errors */ handleProcessingError(error, context) { context.errors.push(error.message); this.log(`Processing error in ${context.bundleName}:`, error); } /** * Check if dependency is known */ isKnownDependency(dep) { const knownDeps = ['react', 'react-dom']; return knownDeps.includes(dep); } /** * Resolve dependency */ resolveDependency(dep) { switch (dep) { case 'react': return this.getReactReference(); default: return null; } } /** * Get component by name */ getComponent(name) { return this.components.get(name); } /** * Get all registered components */ getAllComponents() { return Array.from(this.components.keys()); } /** * Clear all components and cleanup */ clear() { // Cleanup all components for (const name of this.components.keys()) { this.unregisterComponent(name); } // Clear maps this.components.clear(); this.styles.clear(); this.modules.clear(); this.dependencies.clear(); this.log('Engine cleared'); } /** * Logging utility */ log(...args) { if (this.options.debug) { console.log('[RobustBrowserEngine]', ...args); } } /** * Extract components from Babel AST */ extractComponentsFromAST(ast, result) { if (!ast || !ast.program || !ast.program.body) return; ast.program.body.forEach(node => { if (node.type === 'ExportNamedDeclaration' || node.type === 'ExportDefaultDeclaration') { this.extractComponentFromExport(node, result); } else if (node.type === 'VariableDeclaration') { this.extractComponentFromVariable(node, result); } else if (node.type === 'FunctionDeclaration') { this.extractComponentFromFunction(node, result); } }); } /** * Extract component from export declaration */ extractComponentFromExport(node, result) { if (node.declaration) { if (node.declaration.type === 'VariableDeclaration') { this.extractComponentFromVariable(node.declaration, result); } else if (node.declaration.type === 'FunctionDeclaration') { this.extractComponentFromFunction(node.declaration, result); } } } /** * Extract component from variable declaration */ extractComponentFromVariable(node, result) { node.declarations.forEach(declaration => { if (declaration.id && declaration.id.name) { const name = declaration.id.name; // Check if it's a React component (starts with uppercase) if (name[0] === name[0].toUpperCase()) { result.components.push({ name: name, type: 'function' }); } } }); } /** * Extract component from function declaration */ extractComponentFromFunction(node, result) { if (node.id && node.id.name) { const name = node.id.name; // Check if it's a React component (starts with uppercase) if (name[0] === name[0].toUpperCase()) { result.components.push({ name: name, type: 'function' }); } } } /** * Fallback regex extraction */ extractComponentsWithRegex(code, result) { const componentRegex = /(?:export\s+)?(?:const|function|class)\s+(\w+)\s*(?:[=:]|:\s*React\.FC<[^>]+>)/g; let match; while ((match = componentRegex.exec(code)) !== null) { result.components.push({ name: match[1], type: 'function' }); } } }