Standardized dangerous API naming workspace-wide. By: AICoder

This commit is contained in:
AI Engineer 2026-06-06 22:30:12 +08:00
parent d77cdd92fd
commit 2f4e5967d9
22 changed files with 70 additions and 50 deletions

View File

@ -1,5 +1,20 @@
# CHANGELOG
## v1.0.17 (2026-06-05)
### 重大变更 (Philosophical Restoration)
- **核心逻辑回位**: 物理还原 v2.3 原始架构,彻底消灭所有 ESM 时期的“Magic”补丁逻辑`_stManaged` 等标志位)。
- **异步观察引擎**: 恢复 `DOMContentLoaded` 驱动的 `MutationObserver` 启动时序,确保框架作为页面的“地基”稳定运行。
- **危险 API 重命名**: 将 `RefreshState` 重命名为 `__RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios`。此 API 仅供极端性能调优使用,严禁在常规业务中调用,旨在物理隔绝 AI 幻觉和不合理使用。
### 修复
- **升级队列恢复**: 重新启用 `_pendingTemplates` 机制解决了在同步加载Head 加载)模式下,组件注册先于 Body 存在时导致的渲染失效问题。
- **Hash setter 同步**: 修复了 `Hash` 对象在赋值后内部状态更新延迟的问题,确保响应式即时可见。
### 增强 (批准合入)
- **样式智能合并**: 优化 `_mergeNode` 逻辑,支持组件内外 `style` 属性合并。
- **Keyed Each**: 仅在克隆环节增加 `key` 支持,不破坏原有递归扫描哲学。
## v1.0.16 (2026-06-05)
### 修复

View File

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

9
dist/state.js vendored
View File

@ -138,7 +138,7 @@
if (template) {
const tplnode = template.content.cloneNode(true);
if (tplnode.childNodes.length) {
const rootNode = Array.from(tplnode.childNodes).find((n) => n.nodeType === Node.ELEMENT_NODE);
const rootNode = tplnode.children[0];
if (rootNode) _mergeNode(rootNode, node, scanObj, exists);
$$(node, "[slot-id]").forEach((placeholder) => {
const slotName = placeholder.getAttribute("slot-id");
@ -508,7 +508,7 @@
});
node.childNodes && node.childNodes.forEach((child) => _unbindTree(child));
};
const RefreshState = _scanTree;
const ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree;
const Util = {
clone: window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj))),
base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))),
@ -603,7 +603,7 @@
Component,
$,
$$,
RefreshState,
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios,
SetTranslator,
_scanTree,
_unbindTree,
@ -621,6 +621,7 @@
}
if (typeof document !== "undefined") {
const init = () => {
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'");
@ -644,10 +645,10 @@
exports2.Hash = Hash;
exports2.LocalStorage = LocalStorage;
exports2.NewState = NewState;
exports2.RefreshState = RefreshState;
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

9
dist/state.mjs vendored
View File

@ -134,7 +134,7 @@ function _makeComponent(name, node, scanObj, exists = {}) {
if (template) {
const tplnode = template.content.cloneNode(true);
if (tplnode.childNodes.length) {
const rootNode = Array.from(tplnode.childNodes).find((n) => n.nodeType === Node.ELEMENT_NODE);
const rootNode = tplnode.children[0];
if (rootNode) _mergeNode(rootNode, node, scanObj, exists);
$$(node, "[slot-id]").forEach((placeholder) => {
const slotName = placeholder.getAttribute("slot-id");
@ -504,7 +504,7 @@ const _unbindTree = (node) => {
});
node.childNodes && node.childNodes.forEach((child) => _unbindTree(child));
};
const RefreshState = _scanTree;
const ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree;
const Util = {
clone: window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj))),
base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))),
@ -599,7 +599,7 @@ const ApigoState = {
Component,
$,
$$,
RefreshState,
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios,
SetTranslator,
_scanTree,
_unbindTree,
@ -617,6 +617,7 @@ if (typeof window !== "undefined") {
}
if (typeof document !== "undefined") {
const init = () => {
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'");
@ -641,10 +642,10 @@ export {
Hash,
LocalStorage,
NewState,
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 = _scanTree
globalThis.__RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree
const _components = new Map()
const _pendingTemplates = []

View File

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

View File

@ -71,7 +71,7 @@ export function _makeComponent(name, node, scanObj, exists = {}) {
if (template) {
const tplnode = template.content.cloneNode(true);
if (tplnode.childNodes.length) {
const rootNode = Array.from(tplnode.childNodes).find(n => n.nodeType === Node.ELEMENT_NODE);
const rootNode = tplnode.children[0];
if (rootNode) _mergeNode(rootNode, node, scanObj, exists);
$$(node, '[slot-id]').forEach(placeholder => {
const slotName = placeholder.getAttribute('slot-id');

View File

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

View File

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

View File

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

View File

@ -1,19 +1,19 @@
// src/index.js
export { NewState } from './observer.js';
export { Component } from './component.js';
export { $, $$, RefreshState, SetTranslator, _scanTree, _unbindTree } from './dom.js';
export { $, $$, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, 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, SetTranslator } from './dom.js';
import { _scanTree, _unbindTree, $, $$, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, SetTranslator } from './dom.js';
import { LocalStorage, Hash, State } from './globals.js';
import { NewState, onNotifyUpdate, setActiveBinding } from './observer.js';
import { Util } from './utils.js';
import { _runCode, _returnCode } from './core.js';
const ApigoState = {
NewState, Component, $, $$, RefreshState, SetTranslator, _scanTree, _unbindTree, Util, Hash, LocalStorage, State,
NewState, Component, $, $$, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, SetTranslator, _scanTree, _unbindTree, Util, Hash, LocalStorage, State,
_runCode, _returnCode, onNotifyUpdate, setActiveBinding
};
@ -23,6 +23,9 @@ 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'");

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 } = await import('@apigo.cc/state');
36 | RefreshState(document.documentElement);
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);
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 } = await import('@apigo.cc/state');
54 | const { __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios } = await import('@apigo.cc/state');
55 | document.body.innerHTML = '<div id="extreme" $each="state.extreme"></div>';
56 | window.state.extreme = null;
57 | RefreshState(document.getElementById('extreme'));
57 | __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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 } = await import('@apigo.cc/state');
RefreshState(document.documentElement);
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);
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 } = await import('@apigo.cc/state');
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios } = await import('@apigo.cc/state');
document.body.innerHTML = '<div id="extreme" $each="state.extreme"></div>';
window.state.extreme = null;
RefreshState(document.getElementById('extreme'));
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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, $ } = ApigoState;
const { Component, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, $ } = 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(document.documentElement);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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, $, $$, NewState } = ApigoState;
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, $, $$, 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(document.documentElement);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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(document.documentElement);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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(document.documentElement);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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(document.documentElement);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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(document.documentElement);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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(root);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(root);
await wait();
if ($('#inner-node')) throw new Error('$$if failed: should be hidden initially');
console.log('Enabling inner node...');
doubleState.innerShow = true;
RefreshState(root);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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(nestedRoot);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(nestedRoot);
await wait();
if ($('#nested-inner')) throw new Error('nested $$if failed: should be hidden initially');
doubleState.innerShow = true;
RefreshState(nestedRoot);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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, Component, NewState, $, Util } = ApigoState;
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, 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(document.documentElement);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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, Component, NewState, $, Util } = ApigoState;
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, 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(document.documentElement);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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, $ } = ApigoState;
const { Component, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, $ } = 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(document.documentElement);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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, $, NewState } = ApigoState;
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, $, 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(document.documentElement);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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, Component, NewState, $, Util } = ApigoState;
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, 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(document.documentElement);
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
await new Promise(r => setTimeout(r, 100));