perf: finalize core performance optimizations and stability fixes
This commit is contained in:
parent
8daff2b564
commit
e31c887227
26
dist/state.js
vendored
26
dist/state.js
vendored
@ -88,16 +88,17 @@ function setDisableRunCodeError(value) {
|
||||
}
|
||||
const _fnCache = /* @__PURE__ */ new Map();
|
||||
function _runCode(code, vars, thisObj, extendVars) {
|
||||
const argKeys = [...Object.keys(extendVars || {}), ...Object.keys(vars || {})];
|
||||
const argValues = [...Object.values(extendVars || {}), ...Object.values(vars || {})];
|
||||
const allVars = { ...extendVars || {}, ...vars || {} };
|
||||
const argKeys = Object.keys(allVars);
|
||||
const argValues = Object.values(allVars);
|
||||
const cacheKey = code + argKeys.join(",");
|
||||
try {
|
||||
let fn = _fnCache.get(cacheKey);
|
||||
if (!fn) {
|
||||
fn = new Function(...argKeys, code);
|
||||
fn = new Function("Hash", "LocalStorage", "State", ...argKeys, code);
|
||||
_fnCache.set(cacheKey, fn);
|
||||
}
|
||||
return fn.apply(thisObj, argValues);
|
||||
return fn.apply(thisObj, [globalThis.Hash, globalThis.LocalStorage, globalThis.State, ...argValues]);
|
||||
} catch (e) {
|
||||
if (!_disableRunCodeError) console.error(e, extendVars, [code, extendVars, vars, thisObj]);
|
||||
return null;
|
||||
@ -300,6 +301,9 @@ const _parseNode = (node, scanObj) => {
|
||||
if (node._bindings) {
|
||||
node._bindings.forEach((binding) => _updateBinding(binding));
|
||||
if (node._hasOnUpdate) node.dispatchEvent(new Event("update", { bubbles: false }));
|
||||
if (node.hasAttribute("onupdate")) {
|
||||
_runCode(node.getAttribute("onupdate"), { thisNode: node }, node._thisObj || node, node._ref || {});
|
||||
}
|
||||
return;
|
||||
}
|
||||
let attrs = [];
|
||||
@ -325,11 +329,12 @@ const _parseNode = (node, scanObj) => {
|
||||
_initBinding({ node, prop: realAttrName.split("."), tpl, exp });
|
||||
} else {
|
||||
if (realAttrName.startsWith("on")) {
|
||||
if (realAttrName === "onupdate") node._hasOnUpdate = true;
|
||||
if (realAttrName === "onload" && !["BODY", "IMG", "IFRAME"].includes(node.tagName)) node._hasOnLoad = true;
|
||||
if (realAttrName === "onunload" && !["BODY", "IMG", "IFRAME"].includes(node.tagName)) node._hasOnUnload = true;
|
||||
const eventName = realAttrName.slice(2);
|
||||
if (eventName === "update") node._hasOnUpdate = true;
|
||||
if (eventName === "load" && !["BODY", "IMG", "IFRAME"].includes(node.tagName)) node._hasOnLoad = true;
|
||||
if (eventName === "onunload" && !["BODY", "IMG", "IFRAME"].includes(node.tagName)) node._hasOnUnload = true;
|
||||
((node2, thisObj) => {
|
||||
node2.addEventListener(realAttrName.slice(2), (e) => {
|
||||
node2.addEventListener(eventName, (e) => {
|
||||
_runCode(tpl, { event: e, thisNode: node2, ...e.detail || {} }, thisObj || node2, node2._ref || {});
|
||||
});
|
||||
})(node, scanObj.thisObj);
|
||||
@ -368,17 +373,22 @@ const _parseNode = (node, scanObj) => {
|
||||
};
|
||||
const _scanTree = (node, scanObj = {}) => {
|
||||
if (node.nodeType === 3) {
|
||||
if (node._stTranslated) return;
|
||||
const translated = _translate(node.textContent);
|
||||
if (translated !== node.textContent) node.textContent = translated;
|
||||
node._stTranslated = true;
|
||||
return;
|
||||
}
|
||||
if (node.nodeType !== 1) return;
|
||||
if (!node._stTranslated) {
|
||||
Array.from(node.attributes).forEach((attr) => {
|
||||
if (!attr.name.startsWith("$") && !attr.name.startsWith("st-") && !attr.name.startsWith(".")) {
|
||||
const translated = _translate(attr.value);
|
||||
if (translated !== attr.value) attr.value = translated;
|
||||
}
|
||||
});
|
||||
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));
|
||||
|
||||
2
dist/state.min.js
vendored
2
dist/state.min.js
vendored
File diff suppressed because one or more lines are too long
@ -8,16 +8,17 @@ export function setDisableRunCodeError(value) {
|
||||
const _fnCache = new Map();
|
||||
|
||||
export function _runCode(code, vars, thisObj, extendVars) {
|
||||
const argKeys = [...Object.keys(extendVars || {}), ...Object.keys(vars || {})];
|
||||
const argValues = [...Object.values(extendVars || {}), ...Object.values(vars || {})];
|
||||
const allVars = { ...(extendVars || {}), ... (vars || {}) };
|
||||
const argKeys = Object.keys(allVars);
|
||||
const argValues = Object.values(allVars);
|
||||
const cacheKey = code + argKeys.join(',');
|
||||
try {
|
||||
let fn = _fnCache.get(cacheKey);
|
||||
if (!fn) {
|
||||
fn = new Function(...argKeys, code);
|
||||
fn = new Function('Hash', 'LocalStorage', 'State', ...argKeys, code);
|
||||
_fnCache.set(cacheKey, fn);
|
||||
}
|
||||
return fn.apply(thisObj, argValues);
|
||||
return fn.apply(thisObj, [globalThis.Hash, globalThis.LocalStorage, globalThis.State, ...argValues]);
|
||||
} catch (e) {
|
||||
if (!_disableRunCodeError) console.error(e, extendVars, [code, extendVars, vars, thisObj]);
|
||||
return null;
|
||||
|
||||
17
src/dom.js
17
src/dom.js
@ -210,6 +210,9 @@ export const _parseNode = (node, scanObj) => {
|
||||
if (node._bindings) {
|
||||
node._bindings.forEach(binding => _updateBinding(binding));
|
||||
if (node._hasOnUpdate) node.dispatchEvent(new Event('update', { bubbles: false }));
|
||||
if (node.hasAttribute('onupdate')) {
|
||||
_runCode(node.getAttribute('onupdate'), { thisNode: node }, node._thisObj || node, node._ref || {});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -238,11 +241,12 @@ export const _parseNode = (node, scanObj) => {
|
||||
_initBinding({ node: node, prop: realAttrName.split('.'), tpl, exp });
|
||||
} else {
|
||||
if (realAttrName.startsWith('on')) {
|
||||
if (realAttrName === 'onupdate') node._hasOnUpdate = true;
|
||||
if (realAttrName === 'onload' && !['BODY', 'IMG', 'IFRAME'].includes(node.tagName)) node._hasOnLoad = true;
|
||||
if (realAttrName === 'onunload' && !['BODY', 'IMG', 'IFRAME'].includes(node.tagName)) node._hasOnUnload = true;
|
||||
const eventName = realAttrName.slice(2);
|
||||
if (eventName === 'update') node._hasOnUpdate = true;
|
||||
if (eventName === 'load' && !['BODY', 'IMG', 'IFRAME'].includes(node.tagName)) node._hasOnLoad = true;
|
||||
if (eventName === 'onunload' && !['BODY', 'IMG', 'IFRAME'].includes(node.tagName)) node._hasOnUnload = true;
|
||||
((node, thisObj) => {
|
||||
node.addEventListener(realAttrName.slice(2), (e) => {
|
||||
node.addEventListener(eventName, (e) => {
|
||||
_runCode(tpl, { event: e, thisNode: node, ...(e.detail || {}) }, thisObj || node, node._ref || {});
|
||||
});
|
||||
})(node, scanObj.thisObj);
|
||||
@ -282,18 +286,23 @@ export const _parseNode = (node, scanObj) => {
|
||||
|
||||
export const _scanTree = (node, scanObj = {}) => {
|
||||
if (node.nodeType === 3) {
|
||||
if (node._stTranslated) return;
|
||||
const translated = _translate(node.textContent);
|
||||
if (translated !== node.textContent) node.textContent = translated;
|
||||
node._stTranslated = true;
|
||||
return;
|
||||
}
|
||||
if (node.nodeType !== 1) return;
|
||||
|
||||
if (!node._stTranslated) {
|
||||
Array.from(node.attributes).forEach(attr => {
|
||||
if (!attr.name.startsWith('$') && !attr.name.startsWith('st-') && !attr.name.startsWith('.')) {
|
||||
const translated = _translate(attr.value);
|
||||
if (translated !== attr.value) attr.value = translated;
|
||||
}
|
||||
});
|
||||
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');
|
||||
|
||||
36
test/perf.test.js
Normal file
36
test/perf.test.js
Normal file
@ -0,0 +1,36 @@
|
||||
import { NewState } from '../src/observer.js'
|
||||
|
||||
export async function profilePerformance() {
|
||||
console.log('--- Performance Profiling ---');
|
||||
|
||||
const count = 1000;
|
||||
const data = Array.from({ length: count }, (_, i) => ({
|
||||
id: i,
|
||||
name: 'User ' + i,
|
||||
active: i % 2 === 0,
|
||||
tags: ['a', 'b', 'c']
|
||||
}));
|
||||
|
||||
// 1. Measure NewState wrapping
|
||||
const t1 = performance.now();
|
||||
const wrapped = data.map(item => NewState(item));
|
||||
const t2 = performance.now();
|
||||
console.log(`Wrapping ${count} items in NewState: ${(t2 - t1).toFixed(2)}ms`);
|
||||
|
||||
// 2. Measure property access
|
||||
const t3 = performance.now();
|
||||
let sum = 0;
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (wrapped[i].active) sum++;
|
||||
}
|
||||
const t4 = performance.now();
|
||||
console.log(`Accessing 'active' property on ${count} proxies: ${(t4 - t3).toFixed(2)}ms`);
|
||||
|
||||
// 3. Measure update notification
|
||||
const t5 = performance.now();
|
||||
for (let i = 0; i < count; i++) {
|
||||
wrapped[i].name = 'New Name ' + i;
|
||||
}
|
||||
const t6 = performance.now();
|
||||
console.log(`Updating 'name' property on ${count} proxies: ${(t6 - t5).toFixed(2)}ms`);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user