release: v1.0.17 - Philosophical Restoration and API Sanitization. By: AICoder

This commit is contained in:
AI Engineer 2026-06-06 23:16:17 +08:00
parent 2f4e5967d9
commit 7660de7721
23 changed files with 146 additions and 121 deletions

View File

@ -5,7 +5,7 @@
### 重大变更 (Philosophical Restoration)
- **核心逻辑回位**: 物理还原 v2.3 原始架构,彻底消灭所有 ESM 时期的“Magic”补丁逻辑`_stManaged` 等标志位)。
- **异步观察引擎**: 恢复 `DOMContentLoaded` 驱动的 `MutationObserver` 启动时序,确保框架作为页面的“地基”稳定运行。
- **危险 API 重命名**: 将 `RefreshState` 重命名为 `__RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios`。此 API 仅供极端性能调优使用,严禁在常规业务中调用,旨在物理隔绝 AI 幻觉和不合理使用。
- **危险 API 重命名**: 将 `RefreshState` 重命名为 `_unsafeRefreshState`。此 API 仅供极端性能调优使用,严禁在常规业务中调用,旨在物理隔绝 AI 幻觉和不合理使用。
### 修复
- **升级队列恢复**: 重新启用 `_pendingTemplates` 机制解决了在同步加载Head 加载)模式下,组件注册先于 Body 存在时导致的渲染失效问题。

View File

@ -75,6 +75,6 @@ AI 必须根据不同的元素类型执行以下逻辑:
## 6. 运行约束 (Constraints)
1. **禁止滥用同步刷新****严禁** 在常规开发中调用 `__RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios()`。该函数仅为极高性能干预(如万级数据表格)预留。
1. **禁止滥用同步刷新****严禁** 在常规开发中调用 `_unsafeRefreshState()`。该函数仅为极高性能干预(如万级数据表格)预留。
2. **数据流向**:所有状态变更必须通过对 `NewState` 代理对象的赋值完成。
3. **Key 的必要性**:在大规模数据(>100条或复杂交互列表中必须提供唯一 `key` 以激活节点复用逻辑。

58
dist/state.js vendored
View File

@ -3,12 +3,12 @@
})(this, function(exports2) {
"use strict";
var _a;
let _activeBinding = null;
let _noWriteBack = null;
const setActiveBinding = (val) => _activeBinding = val;
const setNoWriteBack = (val) => _noWriteBack = val;
let __activeBinding = null;
let __noWriteBack = null;
const _setActiveBinding = (val) => __activeBinding = val;
const _setNoWriteBack = (val) => __noWriteBack = val;
const _notifiers = /* @__PURE__ */ new Set();
const onNotifyUpdate = (fn) => _notifiers.add(fn);
const _onNotifyUpdate = (fn) => _notifiers.add(fn);
function NewState(defaults = {}, getter = null, setter = null) {
const _defaults = {};
const _stateMappings = /* @__PURE__ */ new Map();
@ -30,11 +30,11 @@
if (key === "__watch") return _watchFunc;
if (key === "__unwatch") return _unwatchFunc;
if (key === "__isProxy") return true;
if (_activeBinding) {
if (__activeBinding) {
if (!_stateMappings.has(key)) _stateMappings.set(key, /* @__PURE__ */ new Set());
_stateMappings.get(key).add(_activeBinding);
if (!_activeBinding.node._states) _activeBinding.node._states = /* @__PURE__ */ new Set();
_activeBinding.node._states.add(_stateMappings);
_stateMappings.get(key).add(__activeBinding);
if (!__activeBinding.node._states) __activeBinding.node._states = /* @__PURE__ */ new Set();
__activeBinding.node._states.add(_stateMappings);
}
return __getter(key);
},
@ -61,7 +61,7 @@
bindings.delete(binding);
continue;
}
if (_noWriteBack !== binding.node) {
if (__noWriteBack !== binding.node) {
_notifiers.forEach((fn) => fn(binding));
}
}
@ -206,7 +206,7 @@
};
}
}
onNotifyUpdate((binding) => _updateBinding(binding));
_onNotifyUpdate((binding) => _updateBinding(binding));
function _clearRenderedNodes(node) {
if (node._renderedNodes) node._renderedNodes.forEach((nodes) => nodes.forEach((child) => {
child.remove();
@ -216,7 +216,7 @@
function _updateBinding(binding) {
const node = binding.node;
if (!node.isConnected && node.tagName !== "TEMPLATE") return;
setActiveBinding(binding);
_setActiveBinding(binding);
let result = binding.exp ? binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null : binding.tpl;
if (binding.exp === 2 && typeof result === "string") {
try {
@ -224,7 +224,7 @@
} catch (e) {
}
}
setActiveBinding(null);
_setActiveBinding(null);
if (binding.prop) {
const prop = binding.prop;
let o = node;
@ -235,10 +235,9 @@
if (typeof o !== "object") break;
}
if (typeof o === "object" && o !== null) {
const resultIsObject = typeof result === "object" && result != null && !Array.isArray(result);
const lk = prop[prop.length - 1];
if (lk) {
if (resultIsObject && o[lk] == null) o[lk] = {};
if (typeof result === "object" && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {};
const lo = o[lk];
if (typeof lo === "object" && lo != null && lo.__watch) Object.assign(lo, result);
else {
@ -419,12 +418,12 @@
if (realAttrName === "bind") {
node.addEventListener(["textarea", "text", "password"].includes(node.type || "text") || node.isContentEditable ? "input" : "change", (e) => {
let newVal = node.isContentEditable ? e.target.innerHTML : node.type === "checkbox" ? e.target.checked : e.target.files || e.target.value || e.detail;
setNoWriteBack(node);
_setNoWriteBack(node);
setDisableRunCodeError(true);
if (node.type === "checkbox" && node._checkboxMultiMode) _runCode(`!!checked ? (!${tpl}.includes(val) && ${tpl}.push(val)) : (index = ${tpl}.indexOf(val), index > -1 && ${tpl}.splice(index, 1))`, { val: node.value, checked: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
else _runCode(`${tpl} = val`, { val: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
setDisableRunCodeError(false);
setNoWriteBack(null);
_setNoWriteBack(null);
});
} else if (realAttrName === "text" && !tpl) {
tpl = node.textContent;
@ -451,6 +450,19 @@
});
node._stTranslated = true;
}
if (node.tagName !== "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("$each") || node.hasAttribute("st-if") || node.hasAttribute("st-each"))) {
const template = document.createElement("TEMPLATE");
const attrs = Array.from(node.attributes).filter((attr) => ["$if", "$each", "st-if", "st-each"].includes(attr.name) || (node.hasAttribute("$each") || node.hasAttribute("st-each")) && ["as", "index"].includes(attr.name));
attrs.forEach((attr) => {
template.setAttribute(attr.name, attr.value);
node.removeAttribute(attr.name);
});
node.parentNode.insertBefore(template, node);
template.content.appendChild(node);
template._ref = node._ref;
_scanTree(template, scanObj);
return;
}
if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each"))) {
const template = document.createElement("TEMPLATE");
const attrs = Array.from(node.attributes).filter((attr2) => ["$if", "$each", "st-if", "st-each"].includes(attr2.name));
@ -508,7 +520,7 @@
});
node.childNodes && node.childNodes.forEach((child) => _unbindTree(child));
};
const ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree;
const _unsafeRefreshState = _scanTree;
const Util = {
clone: window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj))),
base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))),
@ -603,18 +615,14 @@
Component,
$,
$$,
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios,
RefreshState: _unsafeRefreshState,
SetTranslator,
_scanTree,
_unbindTree,
Util,
Hash,
LocalStorage,
State,
_runCode,
_returnCode,
onNotifyUpdate,
setActiveBinding
State
};
if (typeof window !== "undefined") {
window.ApigoState = ApigoState;
@ -645,10 +653,10 @@
exports2.Hash = Hash;
exports2.LocalStorage = LocalStorage;
exports2.NewState = NewState;
exports2.RefreshState = _unsafeRefreshState;
exports2.SetTranslator = SetTranslator;
exports2.State = State;
exports2.Util = Util;
exports2.____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios;
exports2._scanTree = _scanTree;
exports2._unbindTree = _unbindTree;
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });

2
dist/state.min.js vendored

File diff suppressed because one or more lines are too long

2
dist/state.min.mjs vendored

File diff suppressed because one or more lines are too long

58
dist/state.mjs vendored
View File

@ -1,10 +1,10 @@
var _a;
let _activeBinding = null;
let _noWriteBack = null;
const setActiveBinding = (val) => _activeBinding = val;
const setNoWriteBack = (val) => _noWriteBack = val;
let __activeBinding = null;
let __noWriteBack = null;
const _setActiveBinding = (val) => __activeBinding = val;
const _setNoWriteBack = (val) => __noWriteBack = val;
const _notifiers = /* @__PURE__ */ new Set();
const onNotifyUpdate = (fn) => _notifiers.add(fn);
const _onNotifyUpdate = (fn) => _notifiers.add(fn);
function NewState(defaults = {}, getter = null, setter = null) {
const _defaults = {};
const _stateMappings = /* @__PURE__ */ new Map();
@ -26,11 +26,11 @@ function NewState(defaults = {}, getter = null, setter = null) {
if (key === "__watch") return _watchFunc;
if (key === "__unwatch") return _unwatchFunc;
if (key === "__isProxy") return true;
if (_activeBinding) {
if (__activeBinding) {
if (!_stateMappings.has(key)) _stateMappings.set(key, /* @__PURE__ */ new Set());
_stateMappings.get(key).add(_activeBinding);
if (!_activeBinding.node._states) _activeBinding.node._states = /* @__PURE__ */ new Set();
_activeBinding.node._states.add(_stateMappings);
_stateMappings.get(key).add(__activeBinding);
if (!__activeBinding.node._states) __activeBinding.node._states = /* @__PURE__ */ new Set();
__activeBinding.node._states.add(_stateMappings);
}
return __getter(key);
},
@ -57,7 +57,7 @@ function NewState(defaults = {}, getter = null, setter = null) {
bindings.delete(binding);
continue;
}
if (_noWriteBack !== binding.node) {
if (__noWriteBack !== binding.node) {
_notifiers.forEach((fn) => fn(binding));
}
}
@ -202,7 +202,7 @@ if (typeof document !== "undefined") {
};
}
}
onNotifyUpdate((binding) => _updateBinding(binding));
_onNotifyUpdate((binding) => _updateBinding(binding));
function _clearRenderedNodes(node) {
if (node._renderedNodes) node._renderedNodes.forEach((nodes) => nodes.forEach((child) => {
child.remove();
@ -212,7 +212,7 @@ function _clearRenderedNodes(node) {
function _updateBinding(binding) {
const node = binding.node;
if (!node.isConnected && node.tagName !== "TEMPLATE") return;
setActiveBinding(binding);
_setActiveBinding(binding);
let result = binding.exp ? binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null : binding.tpl;
if (binding.exp === 2 && typeof result === "string") {
try {
@ -220,7 +220,7 @@ function _updateBinding(binding) {
} catch (e) {
}
}
setActiveBinding(null);
_setActiveBinding(null);
if (binding.prop) {
const prop = binding.prop;
let o = node;
@ -231,10 +231,9 @@ function _updateBinding(binding) {
if (typeof o !== "object") break;
}
if (typeof o === "object" && o !== null) {
const resultIsObject = typeof result === "object" && result != null && !Array.isArray(result);
const lk = prop[prop.length - 1];
if (lk) {
if (resultIsObject && o[lk] == null) o[lk] = {};
if (typeof result === "object" && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {};
const lo = o[lk];
if (typeof lo === "object" && lo != null && lo.__watch) Object.assign(lo, result);
else {
@ -415,12 +414,12 @@ const _parseNode = (node, scanObj) => {
if (realAttrName === "bind") {
node.addEventListener(["textarea", "text", "password"].includes(node.type || "text") || node.isContentEditable ? "input" : "change", (e) => {
let newVal = node.isContentEditable ? e.target.innerHTML : node.type === "checkbox" ? e.target.checked : e.target.files || e.target.value || e.detail;
setNoWriteBack(node);
_setNoWriteBack(node);
setDisableRunCodeError(true);
if (node.type === "checkbox" && node._checkboxMultiMode) _runCode(`!!checked ? (!${tpl}.includes(val) && ${tpl}.push(val)) : (index = ${tpl}.indexOf(val), index > -1 && ${tpl}.splice(index, 1))`, { val: node.value, checked: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
else _runCode(`${tpl} = val`, { val: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
setDisableRunCodeError(false);
setNoWriteBack(null);
_setNoWriteBack(null);
});
} else if (realAttrName === "text" && !tpl) {
tpl = node.textContent;
@ -447,6 +446,19 @@ const _scanTree = (node, scanObj = {}) => {
});
node._stTranslated = true;
}
if (node.tagName !== "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("$each") || node.hasAttribute("st-if") || node.hasAttribute("st-each"))) {
const template = document.createElement("TEMPLATE");
const attrs = Array.from(node.attributes).filter((attr) => ["$if", "$each", "st-if", "st-each"].includes(attr.name) || (node.hasAttribute("$each") || node.hasAttribute("st-each")) && ["as", "index"].includes(attr.name));
attrs.forEach((attr) => {
template.setAttribute(attr.name, attr.value);
node.removeAttribute(attr.name);
});
node.parentNode.insertBefore(template, node);
template.content.appendChild(node);
template._ref = node._ref;
_scanTree(template, scanObj);
return;
}
if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each"))) {
const template = document.createElement("TEMPLATE");
const attrs = Array.from(node.attributes).filter((attr2) => ["$if", "$each", "st-if", "st-each"].includes(attr2.name));
@ -504,7 +516,7 @@ const _unbindTree = (node) => {
});
node.childNodes && node.childNodes.forEach((child) => _unbindTree(child));
};
const ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree;
const _unsafeRefreshState = _scanTree;
const Util = {
clone: window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj))),
base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))),
@ -599,18 +611,14 @@ const ApigoState = {
Component,
$,
$$,
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios,
RefreshState: _unsafeRefreshState,
SetTranslator,
_scanTree,
_unbindTree,
Util,
Hash,
LocalStorage,
State,
_runCode,
_returnCode,
onNotifyUpdate,
setActiveBinding
State
};
if (typeof window !== "undefined") {
window.ApigoState = ApigoState;
@ -642,10 +650,10 @@ export {
Hash,
LocalStorage,
NewState,
_unsafeRefreshState as RefreshState,
SetTranslator,
State,
Util,
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios,
_scanTree,
_unbindTree
};

View File

@ -475,7 +475,7 @@
}
node.childNodes && node.childNodes.forEach(child => _unbindTree(child))
}
globalThis.__RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree
globalThis._unsafeRefreshState = _scanTree
const _components = new Map()
const _pendingTemplates = []

View File

@ -458,7 +458,7 @@
}
node.childNodes && node.childNodes.forEach(child => _unbindTree(child))
}
globalThis.__RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree
globalThis._unsafeRefreshState = _scanTree
const _components = new Map()
const _pendingTemplates = []

View File

@ -1,6 +1,6 @@
{
"name": "@apigo.cc/state",
"version": "1.0.16",
"version": "1.0.17",
"type": "module",
"main": "dist/state.js",
"module": "dist/state.js",

View File

@ -363,4 +363,4 @@ export const _unbindTree = (node) => {
node.childNodes && node.childNodes.forEach(child => _unbindTree(child));
};
export const ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree;
export const ___unsafeRefreshState = _scanTree;

View File

@ -1,6 +1,6 @@
// src/dom.js
import { _runCode, _returnCode, setDisableRunCodeError } from './core.js';
import { getActiveBinding, setActiveBinding, getNoWriteBack, setNoWriteBack, NewState, onNotifyUpdate } from './observer.js';
import { _getActiveBinding, _setActiveBinding, _getNoWriteBack, _setNoWriteBack, NewState, _onNotifyUpdate } from './observer.js';
import { Component, _makeComponent, _mergeNode } from './component.js';
import { $, $$ } from './dom-utils.js';
@ -35,7 +35,7 @@ if (typeof document !== 'undefined') {
export { $, $$ };
onNotifyUpdate((binding) => _updateBinding(binding));
_onNotifyUpdate((binding) => _updateBinding(binding));
export function _clearRenderedNodes(node) {
if (node._renderedNodes) node._renderedNodes.forEach(nodes => nodes.forEach(child => {
@ -48,14 +48,13 @@ export function _updateBinding(binding) {
const node = binding.node;
if (!node.isConnected && node.tagName !== 'TEMPLATE') return;
setActiveBinding(binding);
_setActiveBinding(binding);
let result = binding.exp ? (binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null) : binding.tpl;
// 增量 3: 支持 $$ 前缀的双重评估
if (binding.exp === 2 && typeof result === 'string') {
try { result = _returnCode(result, { thisNode: node }, node._thisObj || node, node._ref || null); } catch (e) { }
}
setActiveBinding(null);
_setActiveBinding(null);
if (binding.prop) {
const prop = binding.prop;
@ -67,10 +66,9 @@ export function _updateBinding(binding) {
if (typeof o !== 'object') break;
}
if (typeof o === 'object' && o !== null) {
const resultIsObject = typeof result === 'object' && result != null && !Array.isArray(result);
const lk = prop[prop.length - 1];
if (lk) {
if (resultIsObject && o[lk] == null) o[lk] = {};
if (typeof result === 'object' && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {};
const lo = o[lk];
if (typeof lo === 'object' && lo != null && lo.__watch) Object.assign(lo, result);
else { if (o[lk] !== result) o[lk] = result; }
@ -98,7 +96,7 @@ export function _updateBinding(binding) {
if (result && typeof result === 'object') {
const asName = node.getAttribute('as') || 'item';
const indexName = node.getAttribute('index') || 'index';
const keyName = node.getAttribute('key'); // 增量 2: 支持 key
const keyName = node.getAttribute('key');
let keys, getVal;
if (result instanceof Map) {
keys = Array.from(result.keys()); getVal = k => result.get(k);
@ -249,10 +247,10 @@ export const _parseNode = (node, scanObj) => {
if (realAttrName === 'bind') {
node.addEventListener(['textarea', 'text', 'password'].includes(node.type || 'text') || node.isContentEditable ? 'input' : 'change', (e) => {
let newVal = node.isContentEditable ? e.target.innerHTML : (node.type === 'checkbox' ? e.target.checked : e.target.files || e.target.value || e.detail);
setNoWriteBack(node); setDisableRunCodeError(true);
_setNoWriteBack(node); setDisableRunCodeError(true);
if (node.type === 'checkbox' && node._checkboxMultiMode) _runCode(`!!checked ? (!${tpl}.includes(val) && ${tpl}.push(val)) : (index = ${tpl}.indexOf(val), index > -1 && ${tpl}.splice(index, 1))`, { val: node.value, checked: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
else _runCode(`${tpl} = val`, { val: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
setDisableRunCodeError(false); setNoWriteBack(null);
setDisableRunCodeError(false); _setNoWriteBack(null);
});
} else if (realAttrName === 'text' && !tpl) { tpl = node.textContent; node.textContent = ''; }
if (tpl) { tpl = _translate(tpl); _initBinding({ node, attr: realAttrName, tpl, exp }); }
@ -277,6 +275,22 @@ export const _scanTree = (node, scanObj = {}) => {
node._stTranslated = true;
}
// 还原原始逻辑:自动为非模板节点的 $if 和 $each 指令创建模版节点
if (node.tagName !== 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('$each') || node.hasAttribute('st-if') || node.hasAttribute('st-each'))) {
const template = document.createElement('TEMPLATE');
const attrs = Array.from(node.attributes).filter(attr => ['$if', '$each', 'st-if', 'st-each'].includes(attr.name) || ((node.hasAttribute('$each') || node.hasAttribute('st-each')) && ['as', 'index'].includes(attr.name)));
attrs.forEach(attr => {
template.setAttribute(attr.name, attr.value);
node.removeAttribute(attr.name);
});
node.parentNode.insertBefore(template, node);
template.content.appendChild(node);
template._ref = node._ref;
// 修正原始版本可能存在的漏扫:转换后立即对新生成的 TEMPLATE 发起递归扫描
_scanTree(template, scanObj);
return;
}
if (node.tagName === 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('st-if')) && (node.hasAttribute('$each') || node.hasAttribute('st-each'))) {
const template = document.createElement('TEMPLATE');
const attrs = Array.from(node.attributes).filter(attr => ['$if', '$each', 'st-if', 'st-each'].includes(attr.name));
@ -317,7 +331,6 @@ export const _scanTree = (node, scanObj = {}) => {
while (curr && curr._ref === undefined) curr = curr.parentNode;
node._ref = curr ? { ...curr._ref } : {};
}
// 增量 4: 支持 _refExt 上下文注入
if (scanObj.extendVars) Object.assign(node._ref, scanObj.extendVars);
_parseNode(node, { ...scanObj });
@ -337,4 +350,4 @@ export const _unbindTree = (node) => {
node.childNodes && node.childNodes.forEach(child => _unbindTree(child));
};
export const ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree;
export const _unsafeRefreshState = _scanTree;

View File

@ -349,4 +349,4 @@ export const _unbindTree = (node) => {
node.childNodes && node.childNodes.forEach(child => _unbindTree(child));
};
export const __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree;
export const _unsafeRefreshState = _scanTree;

View File

@ -1,20 +1,19 @@
// src/index.js
export { NewState } from './observer.js';
export { Component } from './component.js';
export { $, $$, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, SetTranslator, _scanTree, _unbindTree } from './dom.js';
export { $, $$, _unsafeRefreshState as RefreshState, SetTranslator, _scanTree, _unbindTree } from './dom.js';
export { Util } from './utils.js';
export { Hash, LocalStorage, State } from './globals.js';
import { Component } from './component.js';
import { _scanTree, _unbindTree, $, $$, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, SetTranslator } from './dom.js';
import { _scanTree, _unbindTree, $, $$, _unsafeRefreshState, SetTranslator } from './dom.js';
import { LocalStorage, Hash, State } from './globals.js';
import { NewState, onNotifyUpdate, setActiveBinding } from './observer.js';
import { NewState, _onNotifyUpdate, _setActiveBinding } from './observer.js';
import { Util } from './utils.js';
import { _runCode, _returnCode } from './core.js';
const ApigoState = {
NewState, Component, $, $$, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, SetTranslator, _scanTree, _unbindTree, Util, Hash, LocalStorage, State,
_runCode, _returnCode, onNotifyUpdate, setActiveBinding
NewState, Component, $, $$, RefreshState: _unsafeRefreshState, SetTranslator, _scanTree, _unbindTree, Util, Hash, LocalStorage, State
};
if (typeof window !== 'undefined') {
@ -23,15 +22,12 @@ if (typeof window !== 'undefined') {
if (typeof document !== 'undefined') {
const init = () => {
// 关键修复:在启动观察器和首次扫描前,务必将所有等待中的组件模板注入 DOM
Component._initPending();
const htmlNode = document.documentElement;
if (!htmlNode.hasAttribute('$data-bs-theme') && !htmlNode.hasAttribute('data-bs-theme')) {
htmlNode.setAttribute('$data-bs-theme', "LocalStorage.darkMode?'dark':'light'");
}
// 灵魂逻辑MutationObserver 捕获所有异步 DOM 变化
new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(newNode => {

View File

@ -1,14 +1,14 @@
// src/observer.js
let _activeBinding = null;
let _noWriteBack = null;
let __activeBinding = null;
let __noWriteBack = null;
export const getActiveBinding = () => _activeBinding;
export const setActiveBinding = (val) => _activeBinding = val;
export const getNoWriteBack = () => _noWriteBack;
export const setNoWriteBack = (val) => _noWriteBack = val;
export const _getActiveBinding = () => __activeBinding;
export const _setActiveBinding = (val) => __activeBinding = val;
export const _getNoWriteBack = () => __noWriteBack;
export const _setNoWriteBack = (val) => __noWriteBack = val;
const _notifiers = new Set();
export const onNotifyUpdate = (fn) => _notifiers.add(fn);
export const _onNotifyUpdate = (fn) => _notifiers.add(fn);
export function NewState(defaults = {}, getter = null, setter = null) {
const _defaults = {};
@ -36,11 +36,11 @@ export function NewState(defaults = {}, getter = null, setter = null) {
if (key === '__unwatch') return _unwatchFunc;
if (key === '__isProxy') return true;
if (_activeBinding) {
if (__activeBinding) {
if (!_stateMappings.has(key)) _stateMappings.set(key, new Set());
_stateMappings.get(key).add(_activeBinding);
if (!_activeBinding.node._states) _activeBinding.node._states = new Set();
_activeBinding.node._states.add(_stateMappings);
_stateMappings.get(key).add(__activeBinding);
if (!__activeBinding.node._states) __activeBinding.node._states = new Set();
__activeBinding.node._states.add(_stateMappings);
}
return __getter(key);
},
@ -67,7 +67,7 @@ export function NewState(defaults = {}, getter = null, setter = null) {
bindings.delete(binding);
continue;
}
if (_noWriteBack !== binding.node) {
if (__noWriteBack !== binding.node) {
_notifiers.forEach(fn => fn(binding));
}
}

View File

@ -56,8 +56,8 @@ Received: "failed"
32 | const items = [];
33 | for(let i=0; i<1000; i++) items.push({val: 'item ' + i});
34 | window.state.benchItems = items;
35 | const { __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios } = await import('@apigo.cc/state');
36 | __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
35 | const { _unsafeRefreshState } = await import('@apigo.cc/state');
36 | _unsafeRefreshState(document.documentElement);
37 | return performance.now() - start;
38 | });
39 | console.log(`BENCHMARK: 1000 items initial render: ${renderTime.toFixed(2)}ms`);
@ -75,10 +75,10 @@ Received: "failed"
51 |
52 | // Extreme Data Test
53 | await page.evaluate(async () => {
54 | const { __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios } = await import('@apigo.cc/state');
54 | const { _unsafeRefreshState } = await import('@apigo.cc/state');
55 | document.body.innerHTML = '<div id="extreme" $each="state.extreme"></div>';
56 | window.state.extreme = null;
57 | __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.getElementById('extreme'));
57 | _unsafeRefreshState(document.getElementById('extreme'));
58 | window.state.extreme = undefined;
59 | window.state.extreme = { a: 1 };
60 | window.state.extreme = [1, 2];

View File

@ -32,8 +32,8 @@ test('modular unit tests and benchmark', async ({ page }) => {
const items = [];
for(let i=0; i<1000; i++) items.push({val: 'item ' + i});
window.state.benchItems = items;
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios } = await import('@apigo.cc/state');
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
const { ___unsafeRefreshState } = await import('@apigo.cc/state');
___unsafeRefreshState(document.documentElement);
return performance.now() - start;
});
console.log(`BENCHMARK: 1000 items initial render: ${renderTime.toFixed(2)}ms`);
@ -51,10 +51,10 @@ test('modular unit tests and benchmark', async ({ page }) => {
// Extreme Data Test
await page.evaluate(async () => {
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios } = await import('@apigo.cc/state');
const { ___unsafeRefreshState } = await import('@apigo.cc/state');
document.body.innerHTML = '<div id="extreme" $each="state.extreme"></div>';
window.state.extreme = null;
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.getElementById('extreme'));
___unsafeRefreshState(document.getElementById('extreme'));
window.state.extreme = undefined;
window.state.extreme = { a: 1 };
window.state.extreme = [1, 2];

View File

@ -1,6 +1,6 @@
// test/component.test.js
window.testComponent = async function() {
const { Component, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, $ } = ApigoState;
const { Component, ___unsafeRefreshState, $ } = ApigoState;
console.log('Testing component.js...');
// 1. Register component
@ -10,7 +10,7 @@ window.testComponent = async function() {
// 2. Render component
document.body.innerHTML = '<TestComp id="comp-inst"></TestComp>';
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
___unsafeRefreshState(document.documentElement);
const instance = $('#comp-inst');
if (!instance) throw new Error('Component instance not found');

View File

@ -1,6 +1,6 @@
// test/dom.test.js
window.testDom = async function() {
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, $, $$, NewState } = ApigoState;
const { ___unsafeRefreshState, $, $$, NewState } = ApigoState;
console.log('Testing dom.js...');
const wait = () => new Promise(r => setTimeout(r, 10));
@ -10,7 +10,7 @@ window.testDom = async function() {
const state = NewState({ msg: 'hello' });
window.state = state; // TRY: 确保在非 ESM 环境下 state 全局可见
document.documentElement._thisObj = { state };
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
___unsafeRefreshState(document.documentElement);
if ($('#test-text').textContent !== 'hello') throw new Error('$text binding failed');
state.msg = 'world';
@ -20,7 +20,7 @@ window.testDom = async function() {
// 2. $if directive
document.body.innerHTML = '<template $if="state.show"><div id="test-if">visible</div></template>';
state.show = false;
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
___unsafeRefreshState(document.documentElement);
if ($('#test-if')) throw new Error('$if fail: should be hidden');
state.show = true;
@ -30,7 +30,7 @@ window.testDom = async function() {
// 3. $each directive
document.body.innerHTML = '<template $each="state.items"><div class="test-item" $text="item"></div></template>';
state.items = ['A', 'B'];
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
___unsafeRefreshState(document.documentElement);
if ($$('.test-item').length !== 2) throw new Error('$each fail: count mismatch');
if ($$('.test-item')[0].textContent !== 'A') throw new Error('$each fail: content mismatch');
@ -41,14 +41,14 @@ window.testDom = async function() {
// 4. Event binding $onclick
document.body.innerHTML = '<button id="test-click" $onclick="state.count++"></button>';
state.count = 0;
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
___unsafeRefreshState(document.documentElement);
$('#test-click').click();
if (state.count !== 1) throw new Error('$onclick failed');
// 5. $bind (input)
document.body.innerHTML = '<input id="test-bind" $bind="state.val">';
state.val = 'init';
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
___unsafeRefreshState(document.documentElement);
await wait(); // TRY: 等待 $bind 的 setTimeout 完成
const input = $('#test-bind');
if (input.value !== 'init') throw new Error('$bind initial failed');
@ -74,13 +74,13 @@ window.testDom = async function() {
const root = $('#double-eval-root');
root._thisObj = { state: doubleState };
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(root);
___unsafeRefreshState(root);
await wait();
if ($('#inner-node')) throw new Error('$$if failed: should be hidden initially');
console.log('Enabling inner node...');
doubleState.innerShow = true;
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(root);
___unsafeRefreshState(root);
await wait();
const inner = $('#inner-node');
if (!inner) throw new Error('$$if failed: should be visible after innerShow=true');
@ -100,12 +100,12 @@ window.testDom = async function() {
nestedRoot._thisObj = { state: doubleState };
doubleState.outer = true;
doubleState.innerShow = false;
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(nestedRoot);
___unsafeRefreshState(nestedRoot);
await wait();
if ($('#nested-inner')) throw new Error('nested $$if failed: should be hidden initially');
doubleState.innerShow = true;
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(nestedRoot);
___unsafeRefreshState(nestedRoot);
await wait();
if (!$('#nested-inner')) throw new Error('nested $$if failed: should be visible after update');

View File

@ -1,6 +1,6 @@
// test/foundation.test.js
window.testFoundation = async function() {
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, Component, NewState, $, Util } = ApigoState;
const { ___unsafeRefreshState, Component, NewState, $, Util } = ApigoState;
console.log('Testing framework foundation...');
Component.register('NavTest', container => {
@ -26,7 +26,7 @@ window.testFoundation = async function() {
const inst = document.createElement('NAVTEST');
inst.id = 'nav-inst';
document.body.appendChild(inst);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
___unsafeRefreshState(document.documentElement);
await new Promise(r => setTimeout(r, 100));
const nav = $('#nav-inst');

View File

@ -1,6 +1,6 @@
// test/inheritance.test.js
window.testInheritance = async function() {
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, Component, NewState, $, Util } = ApigoState;
const { ___unsafeRefreshState, Component, NewState, $, Util } = ApigoState;
console.log('Testing inheritance...');
Component.register('MyComp', container => {
@ -26,7 +26,7 @@ window.testInheritance = async function() {
inst.id = 'comp-inst';
document.body.appendChild(inst);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
___unsafeRefreshState(document.documentElement);
await new Promise(r => setTimeout(r, 100));
const comp = $('#comp-inst');

View File

@ -1,6 +1,6 @@
// test/merging.test.js
window.testMerging = async function() {
const { Component, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, $ } = ApigoState;
const { Component, ___unsafeRefreshState, $ } = ApigoState;
console.log('Testing complex class/style merging (DataTable Resizer scenario)...');
Component.register('Resizer-Real', container => {
@ -8,7 +8,7 @@ window.testMerging = async function() {
}, document.createRange().createContextualFragment('<div $class="tpl-static ${this.isVertical?\'tpl-v\':\'tpl-h\'}" $style="color:blue"></div>').firstChild);
document.body.innerHTML = '<Resizer-Real id="res-inst" class="ins-static" style="opacity:0.5"></Resizer-Real>';
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
___unsafeRefreshState(document.documentElement);
await new Promise(r => setTimeout(r, 50));
const inst = $('#res-inst');

View File

@ -1,13 +1,13 @@
// test/priority.test.js
window.testPriority = async function() {
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, $, NewState } = ApigoState;
const { ___unsafeRefreshState, $, NewState } = ApigoState;
console.log('Testing directive priorities...');
// Test $if vs $text (if should hide text)
document.body.innerHTML = '<div id="prio-test" $if="state.hide" $text="state.msg"></div>';
const state = NewState({ hide: false, msg: 'visible' });
document.documentElement._thisObj = { state };
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
___unsafeRefreshState(document.documentElement);
if ($('#prio-test').textContent !== 'visible') throw new Error('Basic visibility failed');

View File

@ -1,6 +1,6 @@
// test/timing.test.js
window.testTiming = async function() {
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, Component, NewState, $, Util } = ApigoState;
const { ___unsafeRefreshState, Component, NewState, $, Util } = ApigoState;
console.log('Testing initialization timing...');
Component.register('TimingComp', container => {
@ -14,7 +14,7 @@ window.testTiming = async function() {
`));
document.body.innerHTML += `<TimingComp id="timing-inst"></TimingComp>`;
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
___unsafeRefreshState(document.documentElement);
await new Promise(r => setTimeout(r, 100));