muspe-cli
Version:
MusPE Advanced Framework v2.1.3 - Mobile User-friendly Simple Progressive Engine with Enhanced CLI Tools, Specialized E-Commerce Templates, Material Design 3, Progressive Enhancement, Mobile Optimizations, Performance Analysis, and Enterprise-Grade Develo
310 lines (250 loc) • 6.91 kB
JavaScript
// MusPE Reactive System - Advanced state management with fine-grained reactivity
class MusPEReactivity {
constructor() {
this.targetStack = [];
this.activeEffect = null;
this.effectStack = [];
this.shouldTrack = true;
}
// Create reactive proxy for objects
reactive(target) {
if (!this.isObject(target)) return target;
if (target.__isMusPEReactive) return target;
const proxy = new Proxy(target, {
get: (target, key, receiver) => {
if (key === '__isMusPEReactive') return true;
const result = Reflect.get(target, key, receiver);
// Track dependency
this.track(target, key);
// If result is object, make it reactive too
if (this.isObject(result)) {
return this.reactive(result);
}
return result;
},
set: (target, key, value, receiver) => {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
// Only trigger if value actually changed
if (oldValue !== value) {
this.trigger(target, key, value, oldValue);
}
return result;
},
deleteProperty: (target, key) => {
const hadKey = key in target;
const result = Reflect.deleteProperty(target, key);
if (hadKey) {
this.trigger(target, key, undefined, target[key]);
}
return result;
}
});
return proxy;
}
// Create reactive reference for primitives
ref(value) {
return {
__isMusPERef: true,
_value: value,
get value() {
this.track(this, 'value');
return this._value;
},
set value(newValue) {
const oldValue = this._value;
this._value = newValue;
if (oldValue !== newValue) {
this.trigger(this, 'value', newValue, oldValue);
}
}
};
}
// Computed properties with caching
computed(getter) {
let value;
let dirty = true;
const computedRef = {
__isMusPEComputed: true,
get value() {
if (dirty) {
value = this.runWithEffect(getter);
dirty = false;
}
return value;
}
};
// Track computed as effect
this.effect(() => {
dirty = true;
this.trigger(computedRef, 'value');
});
return computedRef;
}
// Effect system for reactive side effects
effect(fn, options = {}) {
const effect = {
fn,
options,
deps: new Set(),
active: true,
run: () => {
if (!effect.active) return;
this.cleanup(effect);
const prevEffect = this.activeEffect;
this.activeEffect = effect;
this.effectStack.push(effect);
try {
return fn();
} finally {
this.effectStack.pop();
this.activeEffect = prevEffect;
}
},
stop: () => {
this.cleanup(effect);
effect.active = false;
}
};
if (!options.lazy) {
effect.run();
}
return effect;
}
// Watch for changes with deep watching support
watch(source, callback, options = {}) {
const { immediate = false, deep = false } = options;
let getter;
let oldValue;
if (typeof source === 'function') {
getter = source;
} else if (source.__isMusPERef) {
getter = () => source.value;
} else if (this.isObject(source)) {
getter = () => this.traverse(source);
} else {
getter = () => source;
}
if (immediate) {
oldValue = getter();
callback(oldValue, undefined);
}
return this.effect(() => {
const newValue = getter();
if (newValue !== oldValue || deep) {
callback(newValue, oldValue);
oldValue = newValue;
}
});
}
// Track dependencies
track(target, key) {
if (!this.shouldTrack || !this.activeEffect) return;
let depsMap = this.targetMap.get(target);
if (!depsMap) {
this.targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
if (!dep.has(this.activeEffect)) {
dep.add(this.activeEffect);
this.activeEffect.deps.add(dep);
}
}
// Trigger effects
trigger(target, key, newValue, oldValue) {
const depsMap = this.targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (!dep) return;
const effects = new Set();
dep.forEach(effect => {
if (effect !== this.activeEffect) {
effects.add(effect);
}
});
effects.forEach(effect => {
if (effect.options.scheduler) {
effect.options.scheduler(effect);
} else {
effect.run();
}
});
}
// Cleanup effect dependencies
cleanup(effect) {
effect.deps.forEach(dep => {
dep.delete(effect);
});
effect.deps.clear();
}
// Traverse object deeply for watching
traverse(obj, seen = new Set()) {
if (!this.isObject(obj) || seen.has(obj)) return obj;
seen.add(obj);
for (const key in obj) {
this.traverse(obj[key], seen);
}
return obj;
}
// Utility methods
isObject(val) {
return val !== null && typeof val === 'object';
}
runWithEffect(fn) {
const prevShouldTrack = this.shouldTrack;
this.shouldTrack = false;
try {
return fn();
} finally {
this.shouldTrack = prevShouldTrack;
}
}
// Stop all tracking temporarily
pauseTracking() {
this.shouldTrack = false;
}
enableTracking() {
this.shouldTrack = true;
}
// Reset tracking
resetTracking() {
this.shouldTrack = true;
this.activeEffect = null;
this.effectStack = [];
this.targetMap = new WeakMap();
}
}
// Initialize global reactivity system
MusPEReactivity.prototype.targetMap = new WeakMap();
// Create global instance
const reactivity = new MusPEReactivity();
// Export reactive utilities
const reactive = (target) => reactivity.reactive(target);
const ref = (value) => reactivity.ref(value);
const computed = (getter) => reactivity.computed(getter);
const effect = (fn, options) => reactivity.effect(fn, options);
const watch = (source, callback, options) => reactivity.watch(source, callback, options);
// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
MusPEReactivity,
reactive,
ref,
computed,
effect,
watch,
reactivity
};
}
// Make available globally
if (typeof window !== 'undefined') {
window.MusPEReactivity = MusPEReactivity;
window.reactive = reactive;
window.ref = ref;
window.computed = computed;
window.effect = effect;
window.watch = watch;
}