userface
Version:
Universal Data-Driven UI Engine with live data, validation, and multi-platform support
227 lines (226 loc) • 9.68 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrowserEngine = void 0;
const react_1 = __importDefault(require("react"));
/**
* Браузерный движок для обработки и рендеринга компонентов
* Работает только в браузере, без серверных зависимостей
*/
class BrowserEngine {
constructor() {
Object.defineProperty(this, "components", {
enumerable: true,
configurable: true,
writable: true,
value: new Map()
});
Object.defineProperty(this, "styleElements", {
enumerable: true,
configurable: true,
writable: true,
value: new Map()
});
}
/**
* Обрабатывает пакет файлов компонента
*/
async processComponentBundle(bundle) {
console.log(`[BrowserEngine] Processing bundle: ${bundle.name}`);
// 1. Найдем главный TSX файл
const mainFile = bundle.files.find(f => f.type === 'tsx' && f.name.includes(bundle.name));
if (!mainFile) {
throw new Error(`Main TSX file not found for component ${bundle.name}`);
}
// 2. Найдем CSS файлы
const cssFiles = bundle.files.filter(f => f.type === 'css');
// 3. Анализируем схему компонента (упрощенная версия)
const schema = this.analyzeComponentSchema(mainFile);
console.log(`[BrowserEngine] Schema extracted:`, schema);
// 4. Компилируем TSX в исполняемую функцию (упрощенная версия)
const factory = this.compileComponent(mainFile, bundle.name);
console.log(`[BrowserEngine] Component compiled:`, factory);
// 5. Обрабатываем стили
const styles = this.processStyles(cssFiles, bundle.name);
console.log(`[BrowserEngine] Styles processed:`, styles.length);
// 6. Генерируем дефолтные пропсы
const props = this.generateDefaultProps(schema);
// 7. Создаем обработанный компонент
const processed = {
name: bundle.name,
factory,
schema,
styles,
props
};
// 8. Регистрируем в движке
this.components.set(bundle.name, processed);
console.log(`[BrowserEngine] Component registered: ${bundle.name}`);
return processed;
}
/**
* Упрощенный анализ схемы компонента через regex
*/
analyzeComponentSchema(file) {
console.log(`[BrowserEngine] Analyzing schema for: ${file.name}`);
const props = [];
let componentName = '';
// Ищем имя компонента
const componentMatch = file.code.match(/(?:export\s+)?(?:const|function|class)\s+(\w+)/);
if (componentMatch) {
componentName = componentMatch[1];
}
// Ищем интерфейс пропсов
const interfaceMatch = file.code.match(/interface\s+(\w+Props)\s*\{([^}]+)\}/);
if (interfaceMatch) {
const interfaceContent = interfaceMatch[2];
const propMatches = interfaceContent.matchAll(/(\w+)\s*:\s*([^;]+);/g);
for (const match of propMatches) {
const propName = match[1];
const typeText = match[2].trim();
let propType = 'text';
if (typeText.includes('boolean'))
propType = 'boolean';
else if (typeText.includes('number'))
propType = 'number';
else if (typeText.includes('function') || typeText.includes('=>'))
propType = 'function';
props.push({
name: propName,
type: propType,
required: !typeText.includes('?'),
description: `${propName}: ${typeText};`
});
}
}
// Если интерфейс не найден, ищем пропсы в параметрах функции
if (props.length === 0) {
const propsMatch = file.code.match(/\([^)]*\{[^}]*\}[^)]*\)/);
if (propsMatch) {
// Простая эвристика - добавляем базовые пропсы
props.push({ name: 'text', type: 'text', required: false, description: 'text: string;' }, { name: 'disabled', type: 'boolean', required: false, description: 'disabled: boolean;' });
}
}
return {
name: componentName || file.name.replace('.tsx', ''),
detectedPlatform: 'react',
props,
events: [],
children: true,
description: `Generated schema for ${componentName}`,
supportsChildren: true
};
}
/**
* Упрощенная компиляция TSX компонента
*/
compileComponent(file, componentName) {
console.log(`[BrowserEngine] Compiling component: ${componentName}`);
// 1. Подготавливаем код - убираем CSS импорты и TypeScript типы
let cleanCode = file.code;
// Удаляем CSS импорты
cleanCode = cleanCode.replace(/import\s+['"][^'"]*\.css['"];?\s*/g, '');
// Удаляем React импорты
cleanCode = cleanCode.replace(/import\s+React[^;]*;?\s*/g, '');
// Удаляем TypeScript типы из параметров
cleanCode = cleanCode.replace(/:\s*[A-Za-z<>\[\]{}|&]+/g, '');
// Удаляем интерфейсы
cleanCode = cleanCode.replace(/interface\s+\w+\s*\{[^}]*\}/g, '');
// Добавляем React глобально
cleanCode = `const React = window.React;\n${cleanCode}`;
// 2. Создаем функцию-компонент
const componentFunction = new Function('React', `${cleanCode}\n\nreturn ${componentName};`);
// 3. Исполняем и получаем компонент
const component = componentFunction(react_1.default);
if (typeof component !== 'function') {
throw new Error(`Component ${componentName} is not a valid React component`);
}
return component;
}
/**
* Обработка CSS стилей
*/
processStyles(cssFiles, componentName) {
const styles = [];
cssFiles.forEach((file, index) => {
const styleId = `${componentName}-style-${index}`;
// Удаляем предыдущий стиль если есть
const existingStyle = this.styleElements.get(styleId);
if (existingStyle) {
existingStyle.remove();
}
// Создаем новый элемент стиля
const styleElement = document.createElement('style');
styleElement.id = styleId;
styleElement.textContent = file.code;
document.head.appendChild(styleElement);
// Сохраняем ссылку
this.styleElements.set(styleId, styleElement);
styles.push(file.code);
console.log(`[BrowserEngine] Style applied: ${file.name}`);
});
return styles;
}
/**
* Генерация дефолтных пропсов
*/
generateDefaultProps(schema) {
const props = {};
schema.props.forEach(prop => {
if (prop.required && ['text', 'boolean', 'number'].includes(prop.type)) {
switch (prop.type) {
case 'text':
props[prop.name] = 'Sample Text';
break;
case 'boolean':
props[prop.name] = false;
break;
case 'number':
props[prop.name] = 0;
break;
}
}
});
return props;
}
/**
* Рендеринг компонента
*/
renderComponent(name, props = {}) {
const processed = this.components.get(name);
if (!processed) {
console.error(`[BrowserEngine] Component not found: ${name}`);
return null;
}
const Component = processed.factory;
const finalProps = { ...processed.props, ...props };
console.log(`[BrowserEngine] Rendering component: ${name}`, finalProps);
return react_1.default.createElement(Component, finalProps);
}
/**
* Получение обработанного компонента
*/
getComponent(name) {
return this.components.get(name);
}
/**
* Получение всех компонентов
*/
getAllComponents() {
return Array.from(this.components.keys());
}
/**
* Очистка движка
*/
clear() {
// Удаляем все стили
this.styleElements.forEach(style => style.remove());
// Очищаем кеши
this.components.clear();
this.styleElements.clear();
console.log(`[BrowserEngine] Engine cleared`);
}
}
exports.BrowserEngine = BrowserEngine;