@ape-egg/vibe 1.8.0 → 1.9.1

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/runtime/utils.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { THIS_PROP_REGEX } from './constants.js';
2
+
1
3
  // Fast incrementing counter instead of expensive random hash
2
4
  let hashCounter = 0;
3
5
  export const hash = () => `_${hashCounter++}`;
@@ -6,6 +8,31 @@ export const hash = () => `_${hashCounter++}`;
6
8
  // Key: normalized expression + '\0' + state keys joined by '\0'
7
9
  const fnCache = new Map();
8
10
 
11
+ // Built-ins and reserved words that must NEVER be pre-declared as `var`
12
+ // inside a compiled expression — they're either real globals we want to
13
+ // reach, or JS keywords that would be a SyntaxError to shadow.
14
+ const EVAL_IDENT_EXCLUDE = new Set([
15
+ // Primitive literals / special identifiers
16
+ 'true', 'false', 'null', 'undefined', 'NaN', 'Infinity', 'this',
17
+ // JS reserved words (shadowing any of these is a SyntaxError)
18
+ 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
19
+ 'default', 'delete', 'do', 'else', 'export', 'extends', 'finally',
20
+ 'for', 'function', 'if', 'import', 'in', 'instanceof', 'let', 'new',
21
+ 'of', 'return', 'super', 'switch', 'throw', 'try', 'typeof', 'var',
22
+ 'void', 'while', 'with', 'yield', 'async', 'await',
23
+ // Common runtime globals authors reach for
24
+ 'Math', 'Array', 'Object', 'String', 'Number', 'Boolean', 'Date',
25
+ 'JSON', 'RegExp', 'Error', 'Map', 'Set', 'WeakMap', 'WeakSet',
26
+ 'Promise', 'Symbol', 'parseInt', 'parseFloat', 'isNaN', 'isFinite',
27
+ 'window', 'document', 'console', 'globalThis',
28
+ ]);
29
+
30
+ // Match free identifiers (not property accesses like `obj.foo` or keys
31
+ // inside strings). The `(?<![.\w$'"])` lookbehind skips tokens preceded
32
+ // by a dot (property access), an identifier char (mid-identifier), or
33
+ // a quote (inside a string literal).
34
+ const FREE_IDENT_REGEX = /(?<![.\w$'"])[a-zA-Z_$][\w$]*/g;
35
+
9
36
  // Evaluate expression in the context of state
10
37
  // Handles @[this.property] for component state and @[property] for global state
11
38
  export const evalInScope = (expr, state, element = null) => {
@@ -18,7 +45,7 @@ export const evalInScope = (expr, state, element = null) => {
18
45
  const componentId = findComponentIdForElement(element);
19
46
  if (componentId) {
20
47
  // Replace this.property with $['componentId'].property
21
- normalized = normalized.replace(/\bthis\.(\w+)/g, `$['${componentId}'].$1`);
48
+ normalized = normalized.replace(THIS_PROP_REGEX, `$['${componentId}'].$1`);
22
49
  }
23
50
  }
24
51
 
@@ -34,7 +61,55 @@ export const evalInScope = (expr, state, element = null) => {
34
61
  const allKeys = new Array(keyCount + 1);
35
62
  for (let i = 0; i < keyCount; i++) allKeys[i] = stateKeys[i];
36
63
  allKeys[keyCount] = '$';
37
- fn = new Function(...allKeys, `'use strict'; return (${normalized})`);
64
+
65
+ // Rewrite free identifiers that are neither state keys nor known
66
+ // globals/reserved words so they resolve to `undefined` instead of
67
+ // throwing ReferenceError. `typeof x` is the only operator that can
68
+ // probe a name without reading it, so we guard each such reference:
69
+ //
70
+ // missingProp → (typeof missingProp==='undefined'?void 0:missingProp)
71
+ //
72
+ // This keeps author intuition working for absent state (`!undef` is
73
+ // `true`, `undef?.x` is `undefined`) while leaving real globals —
74
+ // functions the app exposes on `window` — reachable via normal
75
+ // identifier lookup. The previous approach (`var undef;` hoist) also
76
+ // shadowed those globals to `undefined`, so a template call like
77
+ // `getLevelByExperience(exp)` threw TypeError and silently rendered
78
+ // "undefined".
79
+ const known = new Set(allKeys);
80
+ for (const ex of EVAL_IDENT_EXCLUDE) known.add(ex);
81
+
82
+ // Arrow function parameters are bound locally — rewriting them breaks
83
+ // the arrow's parameter list syntax (e.g. `f =>` must stay a bare ident).
84
+ // Collect both `(a, b) =>` and `x =>` forms, strip defaults, and treat
85
+ // them as known so the main pass leaves them untouched.
86
+ normalized.replace(/\(([^()]*)\)\s*=>/g, (_, paramList) => {
87
+ for (const p of paramList.split(',')) {
88
+ const name = p.trim().split('=')[0].trim();
89
+ if (/^[a-zA-Z_$][\w$]*$/.test(name)) known.add(name);
90
+ }
91
+ return '';
92
+ });
93
+ const singleParamRe = /(?:^|[^\w$.])([a-zA-Z_$][\w$]*)\s*=>/g;
94
+ let paramMatch;
95
+ while ((paramMatch = singleParamRe.exec(normalized)) !== null) {
96
+ known.add(paramMatch[1]);
97
+ }
98
+
99
+ // Match string literals first so hyphens inside them (e.g. 'the-arena')
100
+ // don't cause the trailing word to be rewritten as a free identifier.
101
+ // Also capture object-literal keys (`{foo: ...}` / `, foo: ...`) as a
102
+ // second alternative — they're property names, not references, so
103
+ // rewriting them produces invalid JS (computed key without brackets).
104
+ const source = normalized.replace(
105
+ /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*")|((?<=[{,]\s*)[a-zA-Z_$][\w$]*(?=\s*:))|(?<![.\w$'"])[a-zA-Z_$][\w$]*/g,
106
+ (match, strLit, objKey) => {
107
+ if (strLit !== undefined) return match;
108
+ if (objKey !== undefined) return match;
109
+ return known.has(match) ? match : `(typeof ${match}==='undefined'?void 0:${match})`;
110
+ },
111
+ );
112
+ fn = new Function(...allKeys, `'use strict'; return (${source})`);
38
113
  fnCache.set(cacheKey, fn);
39
114
  }
40
115
 
package/vibe.css CHANGED
@@ -27,6 +27,8 @@
27
27
  * to make the wrapper invisible in the layout. Children render as if the wrapper doesn't exist.
28
28
  */
29
29
  component,
30
- div.component {
30
+ div.component,
31
+ slot,
32
+ div.slot {
31
33
  display: contents;
32
34
  }