UNPKG

juris

Version:

JavaScript Unified Reactive Interface Solution - The First and Only Non-blocking Reactive Platform, Architecturally Optimized for Next Generation Cutting-Edge Cross-Platform Application. Transforms web development through comprehensive object-first archit

1,126 lines (1,044 loc) 167 kB
/** * Juris (JavaScript Unified Reactive Interface Solution) * The First and Only Non-blocking Reactive Platform, Architecturally Optimized for Next Generation Cutting-Edge Cross-Platform Application. * Juris aims to eliminate build complexity from small to large applications. * Author: Resti Guay * Version: 0.88.2 * License: MIT * GitHub: https://github.com/jurisjs/juris * Website: https://jurisjs.com/ * NPM: https://www.npmjs.com/package/juris * Codepen: https://codepen.io/jurisauthor * Online Testing: https://jurisjs.com/tests/juris_pure_test_interface.html * Features: * - Temporal Independent * - Automcatic deep call stack branch aware dependency detection * - Smart Promise (Asynchronous) Handling * - Component lazy compilation * - Non-Blocking Rendering * - Global Non-Reactive State Management * - SSR (Server-Side Rendering) and CSR (Client-Side Rendering) ready * - Loading Status templating * - Web Component support * - SVG Support * - Dual rendering mode, fine-grained or batch rendering * - Dual Template Mode (HTML and Object VDOM) * - CSS-in-JS Compilation * - Supports innerHtml in Object VDOM for critical rendering requirements * * Performance: * - Sub 3ms render on simple apps * - Sub 10ms render on complex or large apps * - Sub 20ms render on very complex or large apps * IMPORTANT!!! Notes to AI: * 1. Reactivity works when getState is called from intended functional attributes and children. * 2. Use compress object structure and add labels into the end brackets for nested divs, tables, select, groups and forms * 3. Use getState third attribute with false value to skip subscription * 4. Components will not re-render until their parent triggeres re-render. * 5. All props and attributes can handle async/sync natively, * 6. Use service injection AMAP * 7. Define component as function and dont inject directly into Juris during instantiation. * example VDOM Convention: * return { * {div:{className:'main', //note: static and short should be inline, * text:()=>getState('reactive.text.value','Hello'),//note: reactive, should be new line * style:{color:'red', border:'solid 1px blue'},//note: still okay if in-line * children:[ * {button:{text:'static label', //note: another static and short should be inline, * onClick:()=>clickHandler() * }}//button * {input:{type:'text',min:'1', max:'10', value: () => juris.getState('counter.step', 1), //note: reactive value * oninput: (e) => { const newStep = parseInt(e.target.value) || 1; juris.setState('counter.step', Math.max(1, Math.min(10, newStep))); } * }}//input * ] * }}//div.main * }//return */ 'use strict'; const jurisLinesOfCode = 3796; // Total lines of code in Juris const jurisVersion = '0.88.2'; // Current version of Juris const jurisMinifiedSize = '75.11 kB'; // Minified version of Juris // Utilities const isValidPath = path => typeof path === 'string' && path.trim().length > 0 && !path.includes('..'); const getPathParts = path => path.split('.').filter(Boolean); const deepEquals = (a, b) => { if (a === b) return true; if (a == null || b == null || typeof a !== typeof b) return false; if (typeof a === 'object') { if (Array.isArray(a) !== Array.isArray(b)) return false; const keysA = Object.keys(a), keysB = Object.keys(b); if (keysA.length !== keysB.length) return false; return keysA.every(key => keysB.includes(key) && deepEquals(a[key], b[key])); } return false; }; // the leanest and sophisticated logger const createLogger = () => { const s = []; const f = (m, c, cat) => { const msg = `${cat ? `[${cat}] ` : ''}${m}${c ? ` ${JSON.stringify(c)}` : ''}`; const logObj = { formatted: msg, message: m, context: c, category: cat, timestamp: Date.now() }; setTimeout(() => s.forEach(sub => sub(logObj)), 0); return logObj; }; return { log: { l: f, w: f, e: f, i: f, d: f }, sub: cb => s.push(cb), unsub: cb => s.splice(s.indexOf(cb), 1) }; }; const { log, logSub, logUnsub } = createLogger(); const createPromisify = () => { const activePromises = new Set(); let isTracking = false; const subscribers = new Set(); const checkAllComplete = () => { if (activePromises.size === 0 && subscribers.size > 0) { subscribers.forEach(callback => callback()); } }; const trackingPromisify = result => { const promise = result?.then ? result : Promise.resolve(result); if (isTracking && promise !== result) { activePromises.add(promise); promise.finally(() => { activePromises.delete(promise); setTimeout(checkAllComplete, 0); }); } return promise; }; return { promisify: trackingPromisify, startTracking: () => { isTracking = true; activePromises.clear(); }, stopTracking: () => { isTracking = false; subscribers.clear(); }, onAllComplete: (callback) => { subscribers.add(callback); if (activePromises.size === 0) { setTimeout(callback, 0); } return () => subscribers.delete(callback); } }; }; const { promisify, startTracking, stopTracking, onAllComplete } = createPromisify(); // State Manager class StateManager { constructor(initialState = {}, middleware = []) { console.info(log.i('StateManager initialized', { initialStateKeys: Object.keys(initialState), middlewareCount: middleware.length }, 'framework')); this.state = { ...initialState }; this.middleware = [...middleware]; this.subscribers = new Map(); this.externalSubscribers = new Map(); this.currentTracking = null; this.isUpdating = false; this.initialState = JSON.parse(JSON.stringify(initialState)); this.maxUpdateDepth = 50; this.updateDepth = 0; this.currentlyUpdating = new Set(); // Manual batching properties this.isBatching = false; this.batchQueue = []; this.batchedPaths = new Set(); } reset() { console.info(log.i('State reset to initial state', {}, 'framework')); if (this.isBatching) { this.batchQueue = []; this.batchedPaths.clear(); this.isBatching = false; } this.state = JSON.parse(JSON.stringify(this.initialState)); } getState(path, defaultValue = null, track = true) { if (!isValidPath(path)) return defaultValue; if (track) this.currentTracking?.add(path); const parts = getPathParts(path); let current = this.state; for (const part of parts) { if (current?.[part] === undefined) return defaultValue; current = current[part]; } return current; } setState(path, value, context = {}) { console.debug(log.d('State change initiated', { path, hasValue: value !== undefined }, 'application')); if (!isValidPath(path) || this._hasCircularUpdate(path)) return; if (this.isBatching) { this._queueBatchedUpdate(path, value, context); return; } this._setStateImmediate(path, value, context); } executeBatch(callback) { if (this.isBatching) { // Already in a batch, just execute callback return callback(); } this.beginBatch(); try { const result = callback(); // Handle Promise-returning callbacks if (result && typeof result.then === 'function') { return result .then(value => { this.endBatch(); return value; }) .catch(error => { this.endBatch(); throw error; }); } // Synchronous callback this.endBatch(); return result; } catch (error) { this.endBatch(); throw error; } } beginBatch() { console.debug(log.d('Manual batch started', {}, 'framework')); this.isBatching = true; this.batchQueue = []; this.batchedPaths.clear(); } endBatch() { if (!this.isBatching) { console.warn(log.w('endBatch() called without beginBatch()', {}, 'framework')); return; } console.debug(log.d('Manual batch ending', { queuedUpdates: this.batchQueue.length }, 'framework')); this.isBatching = false; if (this.batchQueue.length === 0) return; this._processBatchedUpdates(); } isBatchingActive() { return this.isBatching; } getBatchQueueSize() { return this.batchQueue.length; } clearBatch() { if (this.isBatching) { console.info(log.i('Clearing current batch', { clearedUpdates: this.batchQueue.length }, 'framework')); this.batchQueue = []; this.batchedPaths.clear(); } } _queueBatchedUpdate(path, value, context) { this.batchQueue = this.batchQueue.filter(update => update.path !== path); this.batchQueue.push({ path, value, context, timestamp: Date.now() }); this.batchedPaths.add(path); } _processBatchedUpdates() { const updates = [...this.batchQueue]; this.batchQueue = []; this.batchedPaths.clear(); const pathGroups = new Map(); updates.forEach(update => pathGroups.set(update.path, update)); const wasUpdating = this.isUpdating; this.isUpdating = true; const appliedUpdates = []; pathGroups.forEach(update => { const oldValue = this.getState(update.path); let finalValue = update.value; for (const middleware of this.middleware) { try { const result = middleware({ path: update.path, oldValue, newValue: finalValue, context: update.context, state: this.state }); if (result !== undefined) finalValue = result; } catch (error) { console.error(log.e('Middleware error in batch', { path: update.path, error: error.message }, 'application')); } } if (deepEquals(oldValue, finalValue)) return; const parts = getPathParts(update.path); let current = this.state; for (let i = 0; i < parts.length - 1; i++) { const part = parts[i]; if (current[part] == null || typeof current[part] !== 'object') { current[part] = {}; } current = current[part]; } current[parts[parts.length - 1]] = finalValue; appliedUpdates.push({ path: update.path, oldValue, newValue: finalValue }); }); this.isUpdating = wasUpdating; // Collect all parent paths that need notification const parentPaths = new Set(); appliedUpdates.forEach(({ path }) => { const parts = getPathParts(path); for (let i = 1; i <= parts.length; i++) { parentPaths.add(parts.slice(0, i).join('.')); } }); // Notify each parent path only if it has subscribers parentPaths.forEach(path => { if (this.subscribers.has(path)) this._triggerPathSubscribers(path); if (this.externalSubscribers.has(path)) { this.externalSubscribers.get(path).forEach(({ callback, hierarchical }) => { try { callback(this.getState(path), null, path); } catch (error) { console.error(log.e('External subscriber error:', error), 'application'); } }); } }); } _setStateImmediate(path, value, context = {}) { const oldValue = this.getState(path); let finalValue = value; for (const middleware of this.middleware) { try { const result = middleware({ path, oldValue, newValue: finalValue, context, state: this.state }); if (result !== undefined) finalValue = result; } catch (error) { console.error(log.e('Middleware error', { path, error: error.message, middlewareName: middleware.name || 'anonymous' }, 'application')); } } if (deepEquals(oldValue, finalValue)) { console.debug(log.d('State unchanged, skipping update', { path }, 'framework')); return; } console.debug(log.d('State updated', { path, oldValue: typeof oldValue, newValue: typeof finalValue }, 'application')); const parts = getPathParts(path); let current = this.state; for (let i = 0; i < parts.length - 1; i++) { const part = parts[i]; if (current[part] == null || typeof current[part] !== 'object') current[part] = {}; current = current[part]; } current[parts[parts.length - 1]] = finalValue; if (!this.isUpdating) { this.isUpdating = true; if (!this.currentlyUpdating) this.currentlyUpdating = new Set(); this.currentlyUpdating.add(path); this._notifySubscribers(path, finalValue, oldValue); this._notifyExternalSubscribers(path, finalValue, oldValue); this.currentlyUpdating.delete(path); this.isUpdating = false; } } subscribe(path, callback, hierarchical = true) { if (!this.externalSubscribers.has(path)) this.externalSubscribers.set(path, new Set()); const subscription = { callback, hierarchical }; this.externalSubscribers.get(path).add(subscription); return () => { const subs = this.externalSubscribers.get(path); if (subs) { subs.delete(subscription); if (subs.size === 0) this.externalSubscribers.delete(path); } }; } subscribeExact(path, callback) { return this.subscribe(path, callback, false); } subscribeInternal(path, callback) { if (!this.subscribers.has(path)) this.subscribers.set(path, new Set()); this.subscribers.get(path).add(callback); return () => { const subs = this.subscribers.get(path); if (subs) { subs.delete(callback); if (subs.size === 0) this.subscribers.delete(path); } }; } _notifySubscribers(path, newValue, oldValue) { this._triggerPathSubscribers(path); const parts = getPathParts(path); for (let i = parts.length - 1; i > 0; i--) { this._triggerPathSubscribers(parts.slice(0, i).join('.')); } const prefix = path ? path + '.' : ''; const allPaths = new Set([...this.subscribers.keys(), ...this.externalSubscribers.keys()]); allPaths.forEach(subscriberPath => { if (subscriberPath.startsWith(prefix) && subscriberPath !== path) { this._triggerPathSubscribers(subscriberPath); } }); } _notifyExternalSubscribers(changedPath, newValue, oldValue) { this.externalSubscribers.forEach((subscriptions, subscribedPath) => { subscriptions.forEach(({ callback, hierarchical }) => { const shouldNotify = hierarchical ? (changedPath === subscribedPath || changedPath.startsWith(subscribedPath + '.')) : changedPath === subscribedPath; if (shouldNotify) { try { callback(newValue, oldValue, changedPath); } catch (error) { console.error(log.e('External subscriber error:', error), 'application'); } } }); }); } _triggerPathSubscribers(path) { const subs = this.subscribers.get(path); if (subs && subs.size > 0) { console.debug(log.d('Triggering subscribers', { path, subscriberCount: subs.size }, 'framework')); new Set(subs).forEach(callback => { let oldTracking try { oldTracking = this.currentTracking; const newTracking = new Set(); this.currentTracking = newTracking; callback(); this.currentTracking = oldTracking; newTracking.forEach(newPath => { const existingSubs = this.subscribers.get(newPath); if (!existingSubs || !existingSubs.has(callback)) { this.subscribeInternal(newPath, callback); } }); } catch (error) { console.error(log.e('Subscriber error:', error), 'application'); this.currentTracking = oldTracking; } }); } } _hasCircularUpdate(path) { if (!this.currentlyUpdating) this.currentlyUpdating = new Set(); if (this.currentlyUpdating.has(path)) { console.warn(log.w('Circular dependency detected', { path }, 'framework')); return true; } return false; } startTracking() { const dependencies = new Set(); this.currentTracking = dependencies; return dependencies; } endTracking() { const tracking = this.currentTracking; this.currentTracking = null; return tracking || new Set(); } } // Headless Manager class HeadlessManager { constructor(juris) { console.info(log.i('HeadlessManager initialized', {}, 'framework')); this.juris = juris; this.components = new Map(); this.instances = new Map(); this.context = {}; this.initQueue = new Set(); this.lifecycleHooks = new Map(); } register(name, componentFn, options = {}) { console.info(log.i('Headless component registered', { name, hasOptions: Object.keys(options).length > 0 }, 'framework')); this.components.set(name, { fn: componentFn, options }); if (options.autoInit) this.initQueue.add(name); } initialize(name, props = {}) { console.debug(log.d('Initializing headless component', { name, propsKeys: Object.keys(props) }, 'framework')); const component = this.components.get(name); if (!component) { console.error(log.e('Headless component not found', { name }, 'framework')); return null; } try { const context = this.juris.createHeadlessContext(); const instance = component.fn(props, context); if (!instance || typeof instance !== 'object') { console.error(log.e('Invalid headless component instance', { name }, 'framework')); return null; } console.info(log.i('Headless component initialized', { name, hasAPI: !!instance.api, hasHooks: !!instance.hooks }, 'framework')); this.instances.set(name, instance); if (instance.hooks) this.lifecycleHooks.set(name, instance.hooks); if (instance.api) { this.context[name] = instance.api; if (!this.juris.headlessAPIs) this.juris.headlessAPIs = {}; this.juris.headlessAPIs[name] = instance.api; this.juris._updateComponentContexts(); } instance.hooks?.onRegister?.(); return instance; } catch (error) { console.error(log.e('Headless component initialization failed', { name, error: error.message }, 'framework')); return null; } } initializeQueued() { this.initQueue.forEach(name => { if (!this.instances.has(name)) { const component = this.components.get(name); this.initialize(name, component.options || {}); } }); this.initQueue.clear(); } getInstance(name) { return this.instances.get(name); } getAPI(name) { return this.context[name]; } getAllAPIs() { return { ...this.context }; } reinitialize(name, props = {}) { const instance = this.instances.get(name); if (instance?.hooks?.onUnregister) { try { instance.hooks.onUnregister(); } catch (error) { console.error(log.e(`Error in onUnregister for '${name}':`, error), 'framework'); } } if (this.context[name]) delete this.context[name]; if (this.juris.headlessAPIs?.[name]) delete this.juris.headlessAPIs[name]; this.instances.delete(name); this.lifecycleHooks.delete(name); return this.initialize(name, props); } cleanup() { console.info(log.i('Cleaning up headless components', { instanceCount: this.instances.size }, 'framework')); this.instances.forEach((instance, name) => { if (instance.hooks?.onUnregister) { try { instance.hooks.onUnregister(); } catch (error) { console.error(log.e(`Error in onUnregister for '${name}':`, error), 'framework'); } } }); this.instances.clear(); this.context = {}; this.lifecycleHooks.clear(); if (this.juris.headlessAPIs) this.juris.headlessAPIs = {}; } getStatus() { return { registered: Array.from(this.components.keys()), initialized: Array.from(this.instances.keys()), queued: Array.from(this.initQueue), apis: Object.keys(this.context) }; } } // Component Manager class ComponentManager { constructor(juris) { console.info(log.i('ComponentManager initialized', {}, 'framework')); this.juris = juris; this.components = new Map(); this.instances = new WeakMap(); this.componentCounters = new Map(); this.componentStates = new WeakMap(); this.asyncPlaceholders = new WeakMap(); this.asyncPropsCache = new Map(); } register(name, componentFn) { console.info(log.i('Component registered', { name }, 'application')); this.components.set(name, componentFn); } create(name, props = {}) { const componentFn = this.components.get(name); if (!componentFn) { console.error(log.e('Component not found', { name }, 'application')); return null; } try { if (this._hasAsyncProps(props)) { console.debug(log.d('Component has async props', { name }, 'framework')); return this._createWithAsyncProps(name, componentFn, props); } const { componentId, componentStates } = this._setupComponent(name); console.debug(log.d('Component setup complete', { name, componentId, stateCount: componentStates.size }, 'framework')); const context = this._createComponentContext(componentId, componentStates); const result = componentFn(props, context); if (result?.then) return this._handleAsyncComponent(promisify(result), name, props, componentStates); return this._processComponentResult(result, name, props, componentStates); } catch (error) { console.error(log.e('Component creation failed. Did you forgot to use children:?', { name, error: error.message }, 'application')); return this._createErrorElement(new Error(error.message + ' Did you forgot to use children:[]?')); } } _setupComponent(name) { if (!this.componentCounters.has(name)) this.componentCounters.set(name, 0); const instanceIndex = this.componentCounters.get(name) + 1; this.componentCounters.set(name, instanceIndex); const componentId = `${name}_${instanceIndex}`; const componentStates = new Set(); return { componentId, componentStates }; } _createComponentContext(componentId, componentStates) { const context = this.juris.createContext(); context.newState = (key, initialValue) => { const statePath = `__local.${componentId}.${key}`; if (this.juris.stateManager.getState(statePath, Symbol('not-found')) === Symbol('not-found')) { this.juris.stateManager.setState(statePath, initialValue); } componentStates.add(statePath); return [ () => this.juris.stateManager.getState(statePath, initialValue), value => this.juris.stateManager.setState(statePath, value) ]; }; return context; } _hasAsyncProps(props) { return Object.values(props).some(value => value?.then); } _createWithAsyncProps(name, componentFn, props) { console.debug(log.d('Creating component with async props', { name }, 'framework')); // Create a temporary element with the component name as ID for config lookup const tempElement = document.createElement('div'); tempElement.id = name.toLowerCase().replace(/[^a-z0-9]/g, '-'); const placeholder = this._createPlaceholder(`Loading ${name}...`, 'juris-async-props-loading', tempElement); this.asyncPlaceholders.set(placeholder, { name, props, type: 'async-props' }); this._resolveAsyncProps(props).then(resolvedProps => { try { const realElement = this._createSyncComponent(name, componentFn, resolvedProps); if (realElement && placeholder.parentNode) { placeholder.parentNode.replaceChild(realElement, placeholder); } this.asyncPlaceholders.delete(placeholder); } catch (error) { this._replaceWithError(placeholder, error); } }).catch(error => this._replaceWithError(placeholder, error)); return placeholder; } async _resolveAsyncProps(props) { const cacheKey = this._generateCacheKey(props); const cached = this.asyncPropsCache.get(cacheKey); if (cached && Date.now() - cached.timestamp < 5000) return cached.props; const resolved = {}; for (const [key, value] of Object.entries(props)) { if (value?.then) { try { resolved[key] = await value; } catch (error) { resolved[key] = { __asyncError: error.message }; } } else { resolved[key] = value; } } this.asyncPropsCache.set(cacheKey, { props: resolved, timestamp: Date.now() }); return resolved; } _generateCacheKey(props) { return JSON.stringify(props, (key, value) => value?.then ? '[Promise]' : value); } _createSyncComponent(name, componentFn, props) { const { componentId, componentStates } = this._setupComponent(name); const context = this._createComponentContext(componentId, componentStates); const result = componentFn(props, context); if (result?.then) return this._handleAsyncComponent(promisify(result), name, props, componentStates); return this._processComponentResult(result, name, props, componentStates); } _handleAsyncComponent(resultPromise, name, props, componentStates) { console.debug(log.d('Handling async component', { name }, 'framework')); const tempElement = document.createElement('div'); tempElement.id = name.toLowerCase().replace(/[^a-z0-9]/g, '-'); const placeholder = this._createPlaceholder(`Loading ${name}...`, 'juris-async-loading', tempElement); this.asyncPlaceholders.set(placeholder, { name, props, componentStates }); resultPromise.then(result => { console.debug(log.d('Async component resolved', { name }, 'framework')); try { const realElement = this._processComponentResult(result, name, props, componentStates); if (realElement && placeholder.parentNode) { placeholder.parentNode.replaceChild(realElement, placeholder); } this.asyncPlaceholders.delete(placeholder); } catch (error) { console.error(log.e('Async component failed', { name, error: error.message }, 'application')); this._replaceWithError(placeholder, error); } }).catch(error => this._replaceWithError(placeholder, error)); return placeholder; } _processComponentResult(result, name, props, componentStates) { if (result && typeof result === 'object') { if (this._hasLifecycleHooks(result)) { return this._createLifecycleComponent(result, name, props, componentStates); } if (typeof result.render === 'function' && !this._hasLifecycleHooks(result)) { const container = document.createElement('div'); container.setAttribute('data-juris-reactive-render', name); const updateRender = () => { try { const renderResult = result.render(); if (renderResult?.then) { container.innerHTML = '<div class="juris-loading">Loading...</div>'; promisify(renderResult).then(resolvedResult => { container.innerHTML = ''; const element = this.juris.domRenderer.render(resolvedResult); if (element) container.appendChild(element); }).catch(error => { console.error(`Async render error for ${name}:`, error); container.innerHTML = `<div class="juris-error">Error: ${error.message}</div>`; }); return; } const children = Array.from(container.children); children.forEach(child => this.cleanup(child)); container.innerHTML = ''; const element = this.juris.domRenderer.render(renderResult); if (element) container.appendChild(element); } catch (error) { console.error(`Error in reactive render for ${name}:`, error); container.innerHTML = `<div class="juris-error">Render Error: ${error.message}</div>`; } }; const subscriptions = []; this.juris.domRenderer._createReactiveUpdate(container, updateRender, subscriptions); if (subscriptions.length > 0) { this.juris.domRenderer.subscriptions.set(container, { subscriptions, eventListeners: [] }); } if (componentStates?.size > 0) { this.componentStates.set(container, componentStates); } return container; } const keys = Object.keys(result); if (keys.length === 1 && typeof keys[0] === 'string' && keys[0].length > 0) { const element = this.juris.domRenderer.render(result, false, name); if (element && componentStates.size > 0) this.componentStates.set(element, componentStates); return element; } } const element = this.juris.domRenderer.render(result); if (element && componentStates.size > 0) this.componentStates.set(element, componentStates); return element; } _hasLifecycleHooks(result) { return result.hooks && (result.hooks.onMount || result.hooks.onUpdate || result.hooks.onUnmount) || result.onMount || result.onUpdate || result.onUnmount; } _handleAsyncRender(renderPromise, name, componentStates, indicator = null) { const tempElement = document.createElement('div'); tempElement.id = name.toLowerCase().replace(/[^a-z0-9]/g, '-'); const placeholder = indicator ? this.juris.domRenderer.render(indicator) : this._createPlaceholder(`Loading ${name}...`, 'juris-async-loading', tempElement); renderPromise.then(renderResult => { try { const element = this.juris.domRenderer.render(renderResult); if (element && componentStates.size > 0) this.componentStates.set(element, componentStates); if (placeholder.parentNode) placeholder.parentNode.replaceChild(element, placeholder); } catch (error) { this._replaceWithError(placeholder, error); } }).catch(error => this._replaceWithError(placeholder, error)); return placeholder; } _createLifecycleComponent(componentResult, name, props, componentStates) { const instance = { name, props, hooks: componentResult.hooks || {}, api: componentResult.api || {}, render: componentResult.render }; const renderResult = instance.render(); if (renderResult?.then) return this._handleAsyncLifecycleRender(promisify(renderResult), instance, componentStates); const element = this.juris.domRenderer.render(renderResult); if (element) { this.instances.set(element, instance); if (componentStates?.size > 0) this.componentStates.set(element, componentStates); if (instance.hooks.onMount) { setTimeout(() => { try { const mountResult = instance.hooks.onMount(); if (mountResult?.then) { promisify(mountResult).catch(error => console.error(log.e(`Async onMount error in ${name}:`, error), 'application')); } } catch (error) { console.error(log.e(`onMount error in ${name}:`, error), 'application'); } }, 0); } } return element; } _handleAsyncLifecycleRender(renderPromise, instance, componentStates) { const tempElement = document.createElement('div'); tempElement.id = instance.name.toLowerCase().replace(/[^a-z0-9]/g, '-'); const placeholder = this._createPlaceholder(`Loading ${instance.name}...`, 'juris-async-lifecycle', tempElement); renderPromise.then(renderResult => { try { const element = this.juris.domRenderer.render(renderResult); if (element) { this.instances.set(element, instance); if (componentStates?.size > 0) { this.componentStates.set(element, componentStates); } if (placeholder.parentNode) { placeholder.parentNode.replaceChild(element, placeholder); } if (instance.hooks.onMount) { setTimeout(() => { try { const mountResult = instance.hooks.onMount(); if (mountResult?.then) { promisify(mountResult).catch(error => console.error(log.e(`Async onMount error in ${instance.name}:`, error), 'application') ); } } catch (error) { console.error(log.e(`onMount error in ${instance.name}:`, error), 'application'); } }, 0); } } } catch (error) { this._replaceWithError(placeholder, error); } }).catch(error => this._replaceWithError(placeholder, error)); return placeholder; } updateInstance(element, newProps) { const instance = this.instances.get(element); if (!instance) return; const oldProps = instance.props; if (deepEquals(oldProps, newProps)) return; if (this._hasAsyncProps(newProps)) { this._resolveAsyncProps(newProps).then(resolvedProps => { instance.props = resolvedProps; this._performUpdate(instance, element, oldProps, resolvedProps); }).catch(error => console.error(log.e(`Error updating async props for ${instance.name}:`, error), 'application')); } else { instance.props = newProps; this._performUpdate(instance, element, oldProps, newProps); } } _performUpdate(instance, element, oldProps, newProps) { if (instance.hooks.onUpdate) { try { const updateResult = instance.hooks.onUpdate(oldProps, newProps); if (updateResult?.then) { promisify(updateResult).catch(error => console.error(log.e(`Async onUpdate error in ${instance.name}:`, error), 'application')); } } catch (error) { console.error(log.e(`onUpdate error in ${instance.name}:`, error), 'application'); } } try { const renderResult = instance.render(); const normalizedRenderResult = promisify(renderResult); if (normalizedRenderResult !== renderResult) { normalizedRenderResult.then(newContent => { this.juris.domRenderer.updateElementContent(element, newContent); }).catch(error => console.error(log.e(`Async re-render error in ${instance.name}:`, error), 'application')); } else { this.juris.domRenderer.updateElementContent(element, renderResult); } } catch (error) { console.error(log.e(`Re-render error in ${instance.name}:`, error), 'application'); } } cleanup(element) { const instance = this.instances.get(element); if (instance) console.debug(log.d('Cleaning up component', { name: instance.name }, 'framework')); if (instance?.hooks?.onUnmount) { try { const unmountResult = instance.hooks.onUnmount(); if (unmountResult?.then) { promisify(unmountResult).catch(error => console.error(log.e(`Async onUnmount error in ${instance.name}:`, error), 'application')); } } catch (error) { console.error(log.e(`onUnmount error in ${instance.name}:`, error), 'application'); } } if (element._reactiveSubscriptions) { element._reactiveSubscriptions.forEach(unsubscribe => { try { unsubscribe(); } catch (error) { console.warn('Error cleaning up reactive subscription:', error); } }); element._reactiveSubscriptions = []; } const states = this.componentStates.get(element); if (states) { states.forEach(statePath => { const pathParts = statePath.split('.'); let current = this.juris.stateManager.state; for (let i = 0; i < pathParts.length - 1; i++) { if (current[pathParts[i]]) current = current[pathParts[i]]; else return; } delete current[pathParts[pathParts.length - 1]]; }); this.componentStates.delete(element); } if (this.asyncPlaceholders.has(element)) this.asyncPlaceholders.delete(element); this.instances.delete(element); } _createPlaceholder(text, className, element = null) { const config = this.juris.domRenderer._getPlaceholderConfig(element); const placeholder = document.createElement('div'); placeholder.className = config.className; placeholder.textContent = config.text; if (config.style) placeholder.style.cssText = config.style; return placeholder; } _createErrorElement(error) { const element = document.createElement('div'); element.style.cssText = 'color: red; border: 1px solid red; padding: 8px; background: #ffe6e6;'; element.textContent = `Component Error: ${error.message}`; return element; } _replaceWithError(placeholder, error) { const errorElement = this._createErrorElement(error); if (placeholder.parentNode) placeholder.parentNode.replaceChild(errorElement, placeholder); this.asyncPlaceholders.delete(placeholder); } clearAsyncPropsCache() { this.asyncPropsCache.clear(); } getAsyncStats() { return { registeredComponents: this.components.size, cachedAsyncProps: this.asyncPropsCache.size }; } } // Enhanced DOMRenderer v0.87.0 with CSS Extraction // Based on your existing code with CSS extraction seamlessly integrated class DOMRenderer { constructor(juris) { console.info(log.i('DOMRenderer initialized', { renderMode: 'fine-grained' }, 'framework')); this.juris = juris; this.subscriptions = new WeakMap(); this.componentStack = []; // CSS Extraction System - NEW this.cssCache = new Map(); // styleHash -> { css, className } this.injectedCSS = new Set(); // Track injected CSS rules this.styleSheet = null; // Single stylesheet for extracted CSS this.camelCaseRegex = /([A-Z])/g; // Pre-compiled for performance this.eventMap = { ondoubleclick: 'dblclick', onmousedown: 'mousedown', onmouseup: 'mouseup', onmouseover: 'mouseover', onmouseout: 'mouseout', onmousemove: 'mousemove', onkeydown: 'keydown', onkeyup: 'keyup', onkeypress: 'keypress', onfocus: 'focus', onblur: 'blur', onchange: 'change', oninput: 'input', onsubmit: 'submit', onload: 'load', onresize: 'resize', onscroll: 'scroll' }; this.BOOLEAN_ATTRS = new Set(['disabled', 'checked', 'selected', 'readonly', 'multiple', 'autofocus', 'autoplay', 'controls', 'hidden', 'loop', 'open', 'required', 'reversed', 'itemScope']); this.PRESERVED_ATTRIBUTES = new Set(['viewBox', 'preserveAspectRatio', 'textLength', 'gradientUnits', 'gradientTransform', 'spreadMethod', 'patternUnits', 'patternContentUnits', 'patternTransform', 'clipPath', 'crossOrigin', 'xmlns', 'xmlns:xlink', 'xlink:href']); this.SVG_ELEMENTS = new Set([ 'svg', 'g', 'defs', 'desc', 'metadata', 'title', 'circle', 'ellipse', 'line', 'polygon', 'polyline', 'rect', 'path', 'text', 'tspan', 'textPath', 'marker', 'pattern', 'clipPath', 'mask', 'image', 'switch', 'foreignObject', 'linearGradient', 'radialGradient', 'stop', 'animate', 'animateMotion', 'animateTransform', 'set', 'use', 'symbol' ]); this.KEY_PROPS = ['id', 'className', 'text']; this.SKIP_ATTRS = new Set(['children', 'key']); this.ATTRIBUTES_TO_KEEP = new Set(['id', 'data-juris-key']); this.elementCache = new Map(); this.recyclePool = new Map(); this.renderMode = 'fine-grained'; this.failureCount = 0; this.maxFailures = 3; this.asyncCache = new Map(); this.asyncPlaceholders = new WeakMap(); this.placeholderConfigs = new Map(); this.defaultPlaceholder = { className: 'juris-async-loading', style: 'padding: 8px; background: #f0f0f0; border: 1px dashed #ccc; opacity: 0.7;', text: 'Loading...', children: null }; // Pre-allocated reusable objects/arrays for hot paths this.tempArray = []; this.tempKeyParts = []; // Touch handling constants this.TOUCH_CONFIG = { moveThreshold: 10, timeThreshold: 300, touchAction: 'manipulation', tapHighlight: 'transparent', touchCallout: 'none' }; // Recycling configuration this.RECYCLE_POOL_SIZE = 100; } setRenderMode(mode) { if (['fine-grained', 'batch'].includes(mode)) { this.renderMode = mode; console.info(log.i('Render mode changed', { mode }, 'framework')); } else { console.warn(log.w('Invalid render mode', { mode }, 'application')); } } getRenderMode() { return this.renderMode; } isFineGrained() { return this.renderMode === 'fine-grained'; } isBatchMode() { return this.renderMode === 'batch'; } // Enhanced render method with CSS extraction render(vnode, staticMode = false, componentName = null) { // Handle primitives if (typeof vnode === 'string' || typeof vnode === 'number') { return document.createTextNode(String(vnode)); } if (!vnode || typeof vnode !== 'object') return null; if (Array.isArray(vnode)) { const fragment = document.createDocumentFragment(); for (let i = 0; i < vnode.length; i++) { const childElement = this.render(vnode[i], staticMode, componentName); if (childElement) fragment.appendChild(childElement); } return fragment; } const tagName = Object.keys(vnode)[0]; const props = vnode[tagName] || {}; if (!staticMode && this.componentStack.includes(tagName)) { return this._createDeepRecursionErrorElement(tagName, this.componentStack); } if (!staticMode && this.juris.componentManager.components.has(tagName)) { const parentTracking = this.juris.stateManager.currentTracking; this.juris.stateManager.currentTracking = null; this.componentStack.push(tagName); const result = this.juris.componentManager.create(tagName, props); this.componentStack.pop(); this.juris.stateManager.currentTracking = parentTracking; return result; } if (!staticMode && /^[A-Z]/.test(tagName)) { return this._createComponentErrorElement(tagName); } if (typeof tagName !== 'string' || tagName.length === 0) return null; let modifiedProps = props; if (props.style && !staticMode && this.cssExtraction) { const elementName = componentName || tagName; const extraction = this.extractCSS(elementName, props.style); if (extraction.className) { modifiedProps = { ...props }; modifiedProps.className = this.combineClassNames(props.className, extraction.className); if (extraction.reactiveStyle) { modifiedProps.style = extraction.reactiveStyle; } else { delete modifiedProps.style; } } } const inheritedComponentName = componentName || (props.style ? tagName : null); if (staticMode) { return this._createElementStatic(tagName, modifiedProps, inheritedComponentName); } if (this.renderMode === 'fine-grained') { return this._createElementFineGrained(tagName, modifiedProps, inheritedComponentName); } try { const key = modifiedProps.key || this._generateKey(tagName, modifiedProps); const cachedElement = this.elementCache.get(key); if (cachedElement && this._canReuseElement(cachedElement, tagName, modifiedProps)) { this._updateElementProperties(cachedElement, modifiedProps); return cachedElement; } return this._createElementOptimized(tagName, modifiedProps, key, inheritedComponentName); } catch (error) { this.failureCount++; if (this.failureCount >= this.maxFailures) this.renderMode =