@ape-egg/vibe 1.3.0 → 1.3.2

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 CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.3.2] - 2025-02-06
4
+
5
+ ### Fixed
6
+
7
+ - **Package completeness**: Added missing `boot.js` to npm package
8
+ - File was missing from the "files" allowlist in package.json
9
+
10
+ ### Changed
11
+
12
+ - **Package strategy**: Switched from allowlist to denylist approach
13
+ - Removed "files" field from package.json
14
+ - Added `.npmignore` to exclude build artifacts (`target/`) and dev files
15
+ - Ensures all source files are included without manual maintenance
16
+
17
+ ---
18
+
19
+ ## [1.3.1] - 2025-02-06
20
+
21
+ ### Fixed
22
+
23
+ - **Package exports**: Added missing `./component` export to package.json
24
+ - Enables proper import: `import component from '@ape-egg/vibe/component'`
25
+ - Previously `component.js` was included in package files but not exposed via exports field
26
+
27
+ ---
28
+
3
29
  ## [1.3.0] - 2025-02-06
4
30
 
5
31
  ### Added
package/ROADMAP.md ADDED
@@ -0,0 +1,289 @@
1
+ # Vibe Roadmap
2
+
3
+ Feature proposals and improvements for Vibe's runtime-first reactive framework.
4
+
5
+ ---
6
+
7
+ ## Proposed: Manual Hydration API
8
+
9
+ **Status**: Proposal
10
+ **Priority**: Medium
11
+ **Category**: Core Runtime
12
+
13
+ ### Problem
14
+
15
+ Vibe's MutationObserver automatically hydrates `@[bindings]` on initial page load and incremental DOM changes, but fails when developers perform wholesale DOM replacement via `innerHTML`:
16
+
17
+ ```js
18
+ // This doesn't trigger Vibe's hydration:
19
+ element.innerHTML = '<h1>Welcome, @[race]!</h1>';
20
+ // Result: Literal text "@[race]" instead of evaluated "human"
21
+ ```
22
+
23
+ This happens because:
24
+ 1. `innerHTML` replacement destroys all old DOM nodes and creates new ones
25
+ 2. Vibe's MutationObserver is designed for incremental mutations, not complete replacement
26
+ 3. No mechanism exists to manually trigger re-hydration
27
+
28
+ ### Current Workaround
29
+
30
+ Users must manually evaluate bindings before setting innerHTML:
31
+
32
+ ```js
33
+ const evaluateBindings = (html) => {
34
+ return html.replace(/@\[([^\]]+)\]/g, (match, expression) => {
35
+ const keys = Object.keys(window.$);
36
+ const values = Object.values(window.$);
37
+ const result = new Function(...keys, `return ${expression}`)(...values);
38
+ return result ?? '';
39
+ });
40
+ };
41
+
42
+ element.innerHTML = evaluateBindings(html); // Manually evaluated
43
+ ```
44
+
45
+ **Issues with this approach:**
46
+ - Not DRY - duplicates Vibe's internal evaluation logic
47
+ - Fragile - user's regex might not match Vibe's parser exactly
48
+ - Knowledge burden - users need to know when manual evaluation is needed
49
+ - Inconsistent - some bindings auto-hydrate, others need manual work
50
+
51
+ ### Use Cases
52
+
53
+ This affects multiple real-world scenarios:
54
+
55
+ 1. **Dynamic content replacement** (tutorials, articles, modals)
56
+ 2. **Client-side routing** (replacing page sections with new HTML)
57
+ 3. **Lazy-loaded sections** (loading HTML from server with bindings)
58
+ 4. **Template cloning** (using `<template>` elements with `@[bindings]`)
59
+ 5. **Server-sent HTML** (SSR-like patterns where server sends HTML with bindings)
60
+
61
+ ### Proposed Solution
62
+
63
+ Add a manual hydration API that allows users to trigger Vibe's binding evaluation:
64
+
65
+ #### Option 1: Element Hydration
66
+ ```js
67
+ vibe.hydrate(element);
68
+ ```
69
+
70
+ **Usage:**
71
+ ```js
72
+ tutorial.innerHTML = '<h1>Welcome, @[race]!</h1>';
73
+ vibe.hydrate(tutorial); // Scan tutorial and children for @[bindings]
74
+ ```
75
+
76
+ **Pros:**
77
+ - Most flexible - works with any element
78
+ - Matches web component patterns (`connectedCallback()`)
79
+ - Clear intent - "scan this element"
80
+
81
+ **Cons:**
82
+ - Requires import/reference to vibe library
83
+ - Two-step process (set innerHTML, then hydrate)
84
+
85
+ #### Option 2: HTML String Evaluation
86
+ ```js
87
+ const evaluated = vibe.evaluate(html, state);
88
+ ```
89
+
90
+ **Usage:**
91
+ ```js
92
+ const html = '<h1>Welcome, @[race]!</h1>';
93
+ const evaluated = vibe.evaluate(html, window.$);
94
+ tutorial.innerHTML = evaluated;
95
+ ```
96
+
97
+ **Pros:**
98
+ - Pure function - easier to test
99
+ - Works without DOM access
100
+ - Can be used server-side or in workers
101
+
102
+ **Cons:**
103
+ - Users must manage state passing
104
+ - Doesn't handle nested/dynamic state updates
105
+
106
+ #### Option 3: Safe innerHTML Setter
107
+ ```js
108
+ vibe.setHTML(element, html);
109
+ ```
110
+
111
+ **Usage:**
112
+ ```js
113
+ vibe.setHTML(tutorial, '<h1>Welcome, @[race]!</h1>');
114
+ ```
115
+
116
+ **Pros:**
117
+ - Single operation - set and hydrate in one call
118
+ - Matches platform APIs (`element.setHTML()`)
119
+ - Simplest API surface
120
+
121
+ **Cons:**
122
+ - Yet another setter abstraction
123
+ - Might conflict with future platform APIs
124
+
125
+ ### Recommendation
126
+
127
+ **Implement Option 1** (`vibe.hydrate(element)`):
128
+ - Aligns with Vibe's runtime-first philosophy
129
+ - Gives users explicit control over hydration timing
130
+ - Most flexible for different scenarios
131
+ - Clear and predictable behavior
132
+
133
+ ### Implementation Notes
134
+
135
+ ```js
136
+ // Expose on the state proxy:
137
+ window.$ = state({ race: 'human' });
138
+ window.$.vibe.hydrate(element); // Scan element for @[bindings]
139
+
140
+ // Or as a module export:
141
+ import state, { hydrate } from '@ape-egg/vibe';
142
+ hydrate(element);
143
+ ```
144
+
145
+ Should support:
146
+ - Single element: `hydrate(tutorial)`
147
+ - Multiple elements: `hydrate([el1, el2])`
148
+ - Selector: `hydrate('tutorial')` (convenience)
149
+
150
+ ### Related
151
+
152
+ Compare to other frameworks:
153
+ - **Alpine.js**: `Alpine.initTree(el)` - manual initialization
154
+ - **Vue**: `app.mount(el)` - mount to element
155
+ - **Svelte**: Compiler handles this at build time
156
+ - **HTMX**: `htmx.process(el)` - process element for attributes
157
+
158
+ ---
159
+
160
+ ## Known Issues
161
+
162
+ ### innerHTML Replacement Corrupts Conditional Branches in Iterations
163
+
164
+ **Status**: Bug
165
+ **Priority**: Low (edge case)
166
+ **Category**: Core Runtime
167
+ **Discovered**: 2026-01-25
168
+
169
+ #### Problem
170
+
171
+ When a parent element containing iterations with nested conditionals has its `innerHTML` replaced multiple times with identical HTML, the conditional's `else` branches become `null` after 2-3 replacements. This causes conditionals to fail rendering.
172
+
173
+ **Root cause:**
174
+ 1. Iteration template's `branches` object is shared across all iteration instances (iterate.js:65: `branches, // Branch templates are reused`)
175
+ 2. When `innerHTML` replacement happens, something mutates the shared `branches.else` to `null`
176
+ 3. All instances reference the same corrupted branches object
177
+ 4. Subsequent renders have no `else` branch template to mount
178
+
179
+ #### Reproduction
180
+
181
+ Programmatic test case:
182
+
183
+ ```js
184
+ // HTML structure
185
+ const html = `
186
+ <item-list>
187
+ <!-- each items as item, i -->
188
+ <item-card>
189
+ <span>@[item]</span>
190
+ <!-- if i % 2 === 0 -->
191
+ <badge>Even</badge>
192
+ <!-- else -->
193
+ <badge secondary>Odd</badge>
194
+ <!-- /if -->
195
+ </item-card>
196
+ <!-- /each -->
197
+ </item-list>
198
+ `;
199
+
200
+ // State
201
+ window.$ = state({ items: ['Apple', 'Banana', 'Cherry'] });
202
+
203
+ // Trigger the bug
204
+ const container = document.querySelector('[vibe]');
205
+
206
+ // First replacement: works
207
+ container.innerHTML = html;
208
+ await new Promise(r => setTimeout(r, 100));
209
+
210
+ // Second replacement: works
211
+ container.innerHTML = html;
212
+ await new Promise(r => setTimeout(r, 100));
213
+
214
+ // Third replacement: branches.else becomes NULL
215
+ container.innerHTML = html;
216
+ await new Promise(r => setTimeout(r, 100));
217
+
218
+ // Result: Even/Odd badges fail to render in the third iteration
219
+ ```
220
+
221
+ #### Observations
222
+
223
+ 1. Cloning `branches` object during iteration (shallow copy) doesn't prevent the bug
224
+ 2. The mutation happens BEFORE cloning, meaning the original template is corrupted
225
+ 3. Setting a property trap on `branches.else` doesn't catch the mutation (already null when accessed)
226
+ 4. Only affects conditionals inside iterations - standalone conditionals work fine
227
+ 5. Only triggers with multiple innerHTML replacements - single replacement works
228
+
229
+ #### Affected Patterns
230
+
231
+ This bug only affects:
232
+ - Replacing innerHTML multiple times with identical HTML containing iterations + conditionals
233
+ - Demo infrastructure like tutorial.js that re-renders on state changes
234
+ - Not representative of typical usage patterns
235
+
236
+ Does NOT affect:
237
+ - Normal reactivity (state changes)
238
+ - Single innerHTML replacement
239
+ - Iterations without conditionals
240
+ - Conditionals outside iterations
241
+ - Incremental DOM mutations (appendChild, insertBefore, etc.)
242
+
243
+ #### Workaround
244
+
245
+ Avoid multiple innerHTML replacements on parents containing iteration+conditional templates. Instead:
246
+ 1. Use incremental DOM APIs (appendChild, createElement)
247
+ 2. Replace innerHTML once at initialization only
248
+ 3. Use Vibe's normal reactivity for updates
249
+ 4. Don't wrap demo content in `<tutorial>` that re-renders via innerHTML
250
+
251
+ #### Investigation Log
252
+
253
+ Debugging attempts (2026-01-25):
254
+ - ✓ Confirmed `branches.else` becomes `null` after 3rd innerHTML replacement
255
+ - ✓ Added deep cloning of branches object - didn't help (already null before clone)
256
+ - ✓ Added Object.defineProperty trap - didn't fire (already null)
257
+ - ✓ Checked parsing logic - correctly finds else comments
258
+ - ✗ Unable to identify where mutation occurs
259
+ - ✗ Unable to reproduce with simpler test case (needs tutorial.js pattern)
260
+
261
+ Likely related to:
262
+ - MutationObserver's removedNodes callback cleaning up references
263
+ - Template caching/reuse strategy in iterate.js
264
+ - Interaction between parse → clone → hydrate → render cycle
265
+
266
+ #### Resolution Path
267
+
268
+ **Phase 1 (Current)**: Document and work around
269
+ - Remove `<tutorial>` wrapper from demos
270
+ - Add note in CLAUDE.md about limitation
271
+ - Tests validate core reactivity works correctly
272
+
273
+ **Phase 2+**: Consider fixing if real-world need emerges
274
+ - Deep investigation into branch reference lifecycle
275
+ - Possibly: deep clone branches instead of sharing reference
276
+ - Possibly: rebuild conditional metadata on each innerHTML replacement
277
+ - Possibly: manual hydration API (see "Manual Hydration API" proposal above)
278
+
279
+ This is acceptable technical debt since:
280
+ 1. Edge case not representative of normal usage
281
+ 2. Core reactivity (the 99% case) works correctly
282
+ 3. Can be addressed when/if users report needing this pattern
283
+ 4. Phase 1 goals (iteration + conditionals) are met
284
+
285
+ ---
286
+
287
+ ## Future Proposals
288
+
289
+ *This section reserved for additional feature proposals*
package/boot.js ADDED
@@ -0,0 +1,46 @@
1
+ // Shared boot mechanism for vibe
2
+ // Used by both index.js (global state) and component.js (component state)
3
+
4
+ import main from './runtime/index.js';
5
+
6
+ let bootQueued = false;
7
+ let booted = false;
8
+
9
+ export const ensureBoot = () => {
10
+ if (booted || bootQueued) return;
11
+ bootQueued = true;
12
+
13
+ // Auto-boot in next microtask if no explicit boot
14
+ queueMicrotask(() => {
15
+ if (!booted) {
16
+ boot();
17
+ }
18
+ });
19
+ };
20
+
21
+ export const boot = () => {
22
+ if (booted) {
23
+ return window.$;
24
+ }
25
+ booted = true;
26
+
27
+ // Merge all state: global + components
28
+ const globalState = window.__vibeGlobalState || {};
29
+ const componentStates = window.__vibeComponents || {};
30
+
31
+ const mergedState = {
32
+ ...globalState,
33
+ ...componentStates,
34
+ };
35
+
36
+ // Get config and targetSelector (first caller wins)
37
+ const config = window.__vibeConfig || {};
38
+ const targetSelector = window.__vibeTargetSelector || '';
39
+
40
+ // Boot with merged state
41
+ window.$ = main(mergedState, config, targetSelector);
42
+
43
+ return window.$;
44
+ };
45
+
46
+ export const isBooted = () => booted;
package/component.js ADDED
@@ -0,0 +1,59 @@
1
+ // Component state entry point
2
+ // Usage:
3
+ // <component>
4
+ // <script type="module">
5
+ // import component from 'vibe/component.js';
6
+ // component({ count: 0 }, { debug: false }, 'body');
7
+ // </script>
8
+ // <div>@[this.count]</div>
9
+ // </component>
10
+ //
11
+ // Or with class:
12
+ // <div class="component">...</div>
13
+
14
+ import { generateComponentId } from './runtime/component-state.js';
15
+ import { ensureBoot } from './boot.js';
16
+
17
+ const component = (state = {}, config, targetSelector) => {
18
+ // Find the first unprocessed INLINE component wrapper
19
+ // Skip <component src=""> (fetched components) - they don't need state tagging
20
+ // Scripts execute in DOM order, so we claim wrappers in DOM order too
21
+ // Supports: <component> or <div class="component">
22
+ const allWrappers = Array.from(document.querySelectorAll('component:not([src]), div.component:not([src])'));
23
+ const wrapper = allWrappers.find(el => !el.hasAttribute('data-vibe-component-id'));
24
+
25
+ if (!wrapper) {
26
+ console.warn('[vibe] component() must be called inside <component> or <div class="component">');
27
+ return;
28
+ }
29
+
30
+ // Generate unique component ID
31
+ const componentId = generateComponentId();
32
+
33
+ // Tag only the wrapper element
34
+ // All descendants will find it via element.closest('[data-vibe-component-id]')
35
+ wrapper.setAttribute('data-vibe-component-id', componentId);
36
+
37
+ // Register component state in shared registry
38
+ if (!window.__vibeComponents) {
39
+ window.__vibeComponents = {};
40
+ }
41
+ window.__vibeComponents[componentId] = state;
42
+
43
+ // Store config (first caller wins)
44
+ if (config && !window.__vibeConfig) {
45
+ window.__vibeConfig = config;
46
+ }
47
+
48
+ // Store targetSelector (first caller wins)
49
+ if (targetSelector && !window.__vibeTargetSelector) {
50
+ window.__vibeTargetSelector = targetSelector;
51
+ }
52
+
53
+ // Ensure boot happens
54
+ ensureBoot();
55
+
56
+ return componentId;
57
+ };
58
+
59
+ export default component;
package/llms.txt ADDED
@@ -0,0 +1,304 @@
1
+ # Vibe - Complete Documentation
2
+
3
+ > Runtime-first reactivity. No virtual DOM. No build step.
4
+
5
+ ## Overview
6
+
7
+ Vibe is a lightweight reactive library that uses Proxy-based state and MutationObserver for fine-grained DOM updates. It works directly in the browser with zero compilation required.
8
+
9
+ **Key characteristics:**
10
+ - Proxy-based reactive state (`window.$`)
11
+ - Deep reactivity (nested mutations automatically trigger updates)
12
+ - Surgical DOM updates (only affected elements re-render)
13
+ - MutationObserver for dynamic element tracking
14
+ - Works with vanilla HTML - no special file format
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @ape-egg/vibe
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ```html
25
+ <script type="module">
26
+ import state from "@ape-egg/vibe";
27
+ window.$ = state({ name: "World", count: 0 });
28
+ </script>
29
+
30
+ <h1>Hello, @[name]!</h1>
31
+ <button onclick="$.count++">Clicked @[count] times</button>
32
+ ```
33
+
34
+ ## Core Syntax
35
+
36
+ ### Reactive Bindings
37
+
38
+ Use `@[property]` syntax anywhere in HTML or CSS:
39
+
40
+ ```html
41
+ <!-- Text content -->
42
+ <div>@[firstName]</div>
43
+
44
+ <!-- Expressions -->
45
+ <div>@[firstName + ' ' + lastName]</div>
46
+ <div>@[count * 2]</div>
47
+
48
+ <!-- Attributes -->
49
+ <input value="@[inputValue]">
50
+ <button disabled="@[isLoading]">Submit</button>
51
+
52
+ <!-- CSS -->
53
+ <style>
54
+ .box { background: @[themeColor]; }
55
+ </style>
56
+ ```
57
+
58
+ ⚠️ **Important**: Bindings are evaluated using `new Function()`. Do not bind untrusted user input.
59
+
60
+ ### State Access
61
+
62
+ State is accessed globally via `window.$`:
63
+
64
+ ```javascript
65
+ // Read
66
+ console.log($.firstName);
67
+
68
+ // Write (triggers re-render)
69
+ $.firstName = "John";
70
+
71
+ // Increment
72
+ $.count++;
73
+
74
+ // Deep mutations (also trigger re-render)
75
+ $.user.profile.name = "Alice";
76
+ $.todos[2].completed = true;
77
+ $.config.theme.colors.primary = "#007bff";
78
+ ```
79
+
80
+ Vibe uses recursive proxies to detect changes at any nesting level automatically.
81
+
82
+ ## Control Flow
83
+
84
+ ### Iteration
85
+
86
+ ```html
87
+ <!-- each items as item -->
88
+ <li>@[item]</li>
89
+ <!-- /each -->
90
+ ```
91
+
92
+ With index:
93
+
94
+ ```html
95
+ <!-- each items as item, index -->
96
+ <li>@[index]: @[item]</li>
97
+ <!-- /each -->
98
+ ```
99
+
100
+ ### Nested Iteration
101
+
102
+ Use dot paths for nested arrays:
103
+
104
+ ```html
105
+ <!-- each categories as category -->
106
+ <h2>@[category.name]</h2>
107
+ <!-- each category.items as item -->
108
+ <span>@[item.name]</span>
109
+ <!-- /each -->
110
+ <!-- /each -->
111
+ ```
112
+
113
+ ### Conditionals
114
+
115
+ ```html
116
+ <!-- if isLoggedIn -->
117
+ <span>Welcome, @[username]!</span>
118
+ <!-- else -->
119
+ <span>Please log in</span>
120
+ <!-- /if -->
121
+ ```
122
+
123
+ Conditionals can be nested inside iterations and vice versa.
124
+
125
+ ## Special Attributes
126
+
127
+ ### Dehydrate
128
+
129
+ Skip reactive processing for an element and its children:
130
+
131
+ ```html
132
+ <code dehydrate>@[this] displays literally, not parsed</code>
133
+ ```
134
+
135
+ Use cases:
136
+ - Displaying `@[...]` syntax in documentation
137
+ - Static content that shouldn't be reactive
138
+ - Performance optimization for large static sections
139
+
140
+ ### Boolean Attributes
141
+
142
+ Attributes not in the value whitelist are removed when falsy:
143
+
144
+ ```html
145
+ <button disabled="@[isLoading]">Submit</button>
146
+ <!-- When isLoading is false, disabled attribute is removed entirely -->
147
+ ```
148
+
149
+ ## Events
150
+
151
+ Use standard inline event handlers:
152
+
153
+ ```html
154
+ <button onclick="$.count++">Increment</button>
155
+ <input oninput="$.text = this.value">
156
+ <form onsubmit="event.preventDefault(); handleSubmit()">
157
+ ```
158
+
159
+ ## Styling
160
+
161
+ ### CSS Bindings
162
+
163
+ Reactive values work inside `<style>` tags:
164
+
165
+ ```html
166
+ <style>
167
+ .box {
168
+ background: @[backgroundColor];
169
+ color: @[textColor];
170
+ width: @[width]px;
171
+ }
172
+ </style>
173
+ ```
174
+
175
+ ### Preventing FOUC
176
+
177
+ Hide content until hydration completes:
178
+
179
+ ```html
180
+ <body style="visibility: hidden;">
181
+ ```
182
+
183
+ Or use the included CSS:
184
+
185
+ ```html
186
+ <link rel="stylesheet" href="@ape-egg/vibe/vibe.css">
187
+ <body vibe-fouc>
188
+ ```
189
+
190
+ ## Dynamic Elements
191
+
192
+ Elements added via JavaScript are automatically hydrated through MutationObserver:
193
+
194
+ ```javascript
195
+ const div = document.createElement('div');
196
+ div.innerHTML = '<span>Hello, @[name]!</span>';
197
+ document.body.appendChild(div);
198
+ // Automatically becomes reactive
199
+ ```
200
+
201
+ ## API Reference
202
+
203
+ ### `state(initialState, afterUpdate?)`
204
+
205
+ Creates reactive state and initializes the framework.
206
+
207
+ ```javascript
208
+ import state from "@ape-egg/vibe";
209
+
210
+ window.$ = state(
211
+ { count: 0, user: { name: "Alice" } },
212
+ (newState, oldState) => {
213
+ console.log("State updated:", newState);
214
+ }
215
+ );
216
+ ```
217
+
218
+ **Parameters:**
219
+ - `initialState` - Object containing initial state values
220
+ - `afterUpdate` - Optional callback after each state change (receives read-only snapshots)
221
+
222
+ **Returns:** Proxy object for reactive state access
223
+
224
+ ## Scoped Variables
225
+
226
+ Inside `<!-- each -->` blocks, these variables are available:
227
+ - `item` (or custom name) - current array element
228
+ - `index` (or custom name) - current index
229
+ - Parent state remains accessible via `$`
230
+
231
+ ```html
232
+ <!-- each users as user, i -->
233
+ <div>@[i]: @[user.name] (total: @[users.length])</div>
234
+ <!-- /each -->
235
+ ```
236
+
237
+ ### Scoped State (Internal)
238
+
239
+ Each iteration instance stores a `scopedState` - a Proxy wrapper that provides access to both local variables (`item`, `index`) and global state. This enables reactive updates inside iterations:
240
+
241
+ ```html
242
+ <!-- each menuItems as item -->
243
+ <a completed="@[tutorialProgress[item.id]]">
244
+ @[item.label]
245
+ </a>
246
+ <!-- /each -->
247
+ ```
248
+
249
+ When `tutorialProgress` changes, Vibe:
250
+ 1. Finds affected elements inside iteration instances
251
+ 2. Uses that instance's `scopedState` for evaluation
252
+ 3. Can access both `item` (local) and `tutorialProgress` (global)
253
+ 4. Updates only the affected elements without re-rendering the entire iteration
254
+
255
+ ## Architecture
256
+
257
+ Vibe consists of these core modules:
258
+
259
+ - **state.js** - Proxy-based reactive state container with recursive proxies for deep reactivity
260
+ - **parse.js** - DOM parser that finds `@[...]` bindings
261
+ - **link.js** - Maps elements to parsed tree nodes
262
+ - **hydrate.js** - Updates DOM with current state values (supports scoped state for iterations)
263
+ - **affected.js** - Determines which elements need updating (walks iteration instances with scoped state)
264
+ - **iterate.js** - Array rendering with efficient diffing (stores scoped state per instance)
265
+ - **conditionals.js** - Conditional block rendering
266
+
267
+ ## How It Works
268
+
269
+ ```
270
+ 1. state() initializes the Proxy and framework
271
+ 2. parse.js scans DOM for @[...], <!-- each -->, <!-- if -->
272
+ 3. link.js maps elements to the parsed tree
273
+ 4. hydrate.js replaces bindings with values
274
+ 5. iterate.js renders <!-- each --> loops
275
+ 6. conditionals.js renders <!-- if --> blocks
276
+ 7. MutationObserver watches for new elements
277
+ 8. On state change: affected.js finds changed elements → hydrate.js updates them
278
+ ```
279
+
280
+ ## Current Limitations
281
+
282
+ - **No computed values**: Derived state must be calculated manually
283
+ - **No two-way binding sugar**: Must wire input events manually
284
+ - **Expression security**: `new Function()` evaluation - don't bind untrusted input
285
+
286
+ ## Best Practices
287
+
288
+ 1. **Initialize state before DOM**: Place `<script>` in `<head>` or before reactive elements
289
+ 2. **Use dehydrate for docs**: When showing `@[...]` syntax examples
290
+ 3. **Prevent FOUC**: Use `visibility: hidden` on body until hydration
291
+ 4. **Keep expressions simple**: Complex logic belongs in JavaScript, not templates
292
+ 5. **Mutate freely**: Deep reactivity means `$.user.name = "New"` just works - no spread operators needed
293
+
294
+ ## Browser Support
295
+
296
+ Modern browsers with:
297
+ - Proxy (ES6)
298
+ - MutationObserver
299
+ - ES Modules
300
+
301
+ ## Resources
302
+
303
+ - **Homepage**: https://vibe.korte.kim
304
+ - **npm**: https://www.npmjs.com/package/@ape-egg/vibe
package/package.json CHANGED
@@ -1,33 +1,19 @@
1
1
  {
2
2
  "name": "@ape-egg/vibe",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "type": "module",
5
5
  "description": "Runtime-first reactivity with optional compiler",
6
6
  "main": "index.js",
7
7
  "homepage": "https://vibe.korte.kim",
8
8
  "exports": {
9
9
  ".": "./index.js",
10
+ "./component": "./component.js",
10
11
  "./runtime": "./runtime/index.js",
11
12
  "./compiler": "./compiler/bin/vibe-compile.js"
12
13
  },
13
14
  "bin": {
14
15
  "vibe": "./compiler/bin/vibe-compile.js"
15
16
  },
16
- "files": [
17
- "index.js",
18
- "vibe.css",
19
- "runtime/",
20
- "compiler/bin/",
21
- "compiler/native/",
22
- "compiler/src/Cargo.lock",
23
- "compiler/src/Cargo.toml",
24
- "compiler/src/compiler/",
25
- "compiler/src/config.rs",
26
- "compiler/src/main.rs",
27
- "compiler/src/parser/",
28
- "README.md",
29
- "CHANGELOG.md"
30
- ],
31
17
  "keywords": [
32
18
  "reactive",
33
19
  "framework",