@ape-egg/vibe 1.7.2 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +139 -0
- package/README.md +28 -0
- package/boot.js +5 -1
- package/compiler/native/vibe-compiler-darwin-arm64 +0 -0
- package/compiler/native/vibe-compiler-linux-x64 +0 -0
- package/compiler/src/Cargo.lock +85 -496
- package/compiler/src/Cargo.toml +1 -1
- package/compiler/src/compiler/compile.rs +3 -11
- package/compiler/src/parser/html.rs +15 -15
- package/index.js +15 -0
- package/package.json +2 -2
- package/runtime/_vibe-compiled-iteration-batch.js +18 -9
- package/runtime/affected.js +54 -16
- package/runtime/component.js +287 -110
- package/runtime/conditionals.js +99 -7
- package/runtime/constants.js +10 -6
- package/runtime/index.js +142 -47
- package/runtime/iterate.js +364 -142
- package/runtime/iteration-utils.js +5 -1
- package/runtime/manifest.js +2 -1
- package/runtime/parse.js +67 -80
- package/runtime/pre-compiled-iterations.js +34 -21
- package/runtime/pre-compiled-manifest.js +38 -13
- package/runtime/reconcile.js +621 -0
- package/runtime/state.js +26 -7
- package/runtime/utils.js +97 -5
- package/vibe.css +3 -1
package/runtime/utils.js
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
let hashCounter = 0;
|
|
3
3
|
export const hash = () => `_${hashCounter++}`;
|
|
4
4
|
|
|
5
|
+
// Function compilation cache: avoids creating new Function() for repeated expressions
|
|
6
|
+
// Key: normalized expression + '\0' + state keys joined by '\0'
|
|
7
|
+
const fnCache = new Map();
|
|
8
|
+
|
|
9
|
+
// Built-ins and reserved words that must NEVER be pre-declared as `var`
|
|
10
|
+
// inside a compiled expression — they're either real globals we want to
|
|
11
|
+
// reach, or JS keywords that would be a SyntaxError to shadow.
|
|
12
|
+
const EVAL_IDENT_EXCLUDE = new Set([
|
|
13
|
+
// Primitive literals / special identifiers
|
|
14
|
+
'true', 'false', 'null', 'undefined', 'NaN', 'Infinity', 'this',
|
|
15
|
+
// JS reserved words (shadowing any of these is a SyntaxError)
|
|
16
|
+
'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
|
|
17
|
+
'default', 'delete', 'do', 'else', 'export', 'extends', 'finally',
|
|
18
|
+
'for', 'function', 'if', 'import', 'in', 'instanceof', 'let', 'new',
|
|
19
|
+
'of', 'return', 'super', 'switch', 'throw', 'try', 'typeof', 'var',
|
|
20
|
+
'void', 'while', 'with', 'yield', 'async', 'await',
|
|
21
|
+
// Common runtime globals authors reach for
|
|
22
|
+
'Math', 'Array', 'Object', 'String', 'Number', 'Boolean', 'Date',
|
|
23
|
+
'JSON', 'RegExp', 'Error', 'Map', 'Set', 'WeakMap', 'WeakSet',
|
|
24
|
+
'Promise', 'Symbol', 'parseInt', 'parseFloat', 'isNaN', 'isFinite',
|
|
25
|
+
'window', 'document', 'console', 'globalThis',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
// Match free identifiers (not property accesses like `obj.foo` or keys
|
|
29
|
+
// inside strings). The `(?<![.\w$'"])` lookbehind skips tokens preceded
|
|
30
|
+
// by a dot (property access), an identifier char (mid-identifier), or
|
|
31
|
+
// a quote (inside a string literal).
|
|
32
|
+
const FREE_IDENT_REGEX = /(?<![.\w$'"])[a-zA-Z_$][\w$]*/g;
|
|
33
|
+
|
|
5
34
|
// Evaluate expression in the context of state
|
|
6
35
|
// Handles @[this.property] for component state and @[property] for global state
|
|
7
36
|
export const evalInScope = (expr, state, element = null) => {
|
|
@@ -18,13 +47,76 @@ export const evalInScope = (expr, state, element = null) => {
|
|
|
18
47
|
}
|
|
19
48
|
}
|
|
20
49
|
|
|
21
|
-
//
|
|
22
|
-
const
|
|
50
|
+
// Get state keys once (triggers ownKeys trap only once for Proxies)
|
|
51
|
+
const stateKeys = Object.keys(state);
|
|
52
|
+
const keyCount = stateKeys.length;
|
|
53
|
+
|
|
54
|
+
// Cache lookup: expression + key signature → compiled function
|
|
55
|
+
// All iteration instances share the same state shape, so this hits cache 999/1000 times
|
|
56
|
+
const cacheKey = normalized + '\0' + stateKeys.join('\0');
|
|
57
|
+
let fn = fnCache.get(cacheKey);
|
|
58
|
+
if (!fn) {
|
|
59
|
+
const allKeys = new Array(keyCount + 1);
|
|
60
|
+
for (let i = 0; i < keyCount; i++) allKeys[i] = stateKeys[i];
|
|
61
|
+
allKeys[keyCount] = '$';
|
|
62
|
+
|
|
63
|
+
// Rewrite free identifiers that are neither state keys nor known
|
|
64
|
+
// globals/reserved words so they resolve to `undefined` instead of
|
|
65
|
+
// throwing ReferenceError. `typeof x` is the only operator that can
|
|
66
|
+
// probe a name without reading it, so we guard each such reference:
|
|
67
|
+
//
|
|
68
|
+
// missingProp → (typeof missingProp==='undefined'?void 0:missingProp)
|
|
69
|
+
//
|
|
70
|
+
// This keeps author intuition working for absent state (`!undef` is
|
|
71
|
+
// `true`, `undef?.x` is `undefined`) while leaving real globals —
|
|
72
|
+
// functions the app exposes on `window` — reachable via normal
|
|
73
|
+
// identifier lookup. The previous approach (`var undef;` hoist) also
|
|
74
|
+
// shadowed those globals to `undefined`, so a template call like
|
|
75
|
+
// `getLevelByExperience(exp)` threw TypeError and silently rendered
|
|
76
|
+
// "undefined".
|
|
77
|
+
const known = new Set(allKeys);
|
|
78
|
+
for (const ex of EVAL_IDENT_EXCLUDE) known.add(ex);
|
|
79
|
+
|
|
80
|
+
// Arrow function parameters are bound locally — rewriting them breaks
|
|
81
|
+
// the arrow's parameter list syntax (e.g. `f =>` must stay a bare ident).
|
|
82
|
+
// Collect both `(a, b) =>` and `x =>` forms, strip defaults, and treat
|
|
83
|
+
// them as known so the main pass leaves them untouched.
|
|
84
|
+
normalized.replace(/\(([^()]*)\)\s*=>/g, (_, paramList) => {
|
|
85
|
+
for (const p of paramList.split(',')) {
|
|
86
|
+
const name = p.trim().split('=')[0].trim();
|
|
87
|
+
if (/^[a-zA-Z_$][\w$]*$/.test(name)) known.add(name);
|
|
88
|
+
}
|
|
89
|
+
return '';
|
|
90
|
+
});
|
|
91
|
+
const singleParamRe = /(?:^|[^\w$.])([a-zA-Z_$][\w$]*)\s*=>/g;
|
|
92
|
+
let paramMatch;
|
|
93
|
+
while ((paramMatch = singleParamRe.exec(normalized)) !== null) {
|
|
94
|
+
known.add(paramMatch[1]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Match string literals first so hyphens inside them (e.g. 'the-arena')
|
|
98
|
+
// don't cause the trailing word to be rewritten as a free identifier.
|
|
99
|
+
// Also capture object-literal keys (`{foo: ...}` / `, foo: ...`) as a
|
|
100
|
+
// second alternative — they're property names, not references, so
|
|
101
|
+
// rewriting them produces invalid JS (computed key without brackets).
|
|
102
|
+
const source = normalized.replace(
|
|
103
|
+
/('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*")|((?<=[{,]\s*)[a-zA-Z_$][\w$]*(?=\s*:))|(?<![.\w$'"])[a-zA-Z_$][\w$]*/g,
|
|
104
|
+
(match, strLit, objKey) => {
|
|
105
|
+
if (strLit !== undefined) return match;
|
|
106
|
+
if (objKey !== undefined) return match;
|
|
107
|
+
return known.has(match) ? match : `(typeof ${match}==='undefined'?void 0:${match})`;
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
fn = new Function(...allKeys, `'use strict'; return (${source})`);
|
|
111
|
+
fnCache.set(cacheKey, fn);
|
|
112
|
+
}
|
|
23
113
|
|
|
24
|
-
|
|
25
|
-
const values =
|
|
114
|
+
// Build values array matching the cached function's parameter order
|
|
115
|
+
const values = new Array(keyCount + 1);
|
|
116
|
+
for (let i = 0; i < keyCount; i++) values[i] = state[stateKeys[i]];
|
|
117
|
+
values[keyCount] = state;
|
|
26
118
|
|
|
27
|
-
const result =
|
|
119
|
+
const result = fn(...values);
|
|
28
120
|
|
|
29
121
|
// If result is undefined and we're accessing a component property, try case-insensitive match
|
|
30
122
|
// This handles HTML lowercasing attribute names like @[this.iconName] -> @[this.iconname]
|