@ape-egg/vibe 2.1.18 → 2.1.19
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 +6 -0
- package/compiler/native/vibe-compiler-darwin-arm64 +0 -0
- package/compiler/native/vibe-compiler-linux-x64 +0 -0
- package/compiler/src/Cargo.lock +1 -1
- package/compiler/src/Cargo.toml +1 -1
- package/compiler/src/compiler/manifest_builder.rs +15 -5
- package/package.json +1 -1
- package/runtime/constants.js +9 -4
- package/runtime/iteration-utils.js +20 -1
- package/runtime/parse.js +3 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.1.19] - 2026-06-25
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **`<!-- each list as item, i (item.id) -->` (index alias before the key) silently failed to iterate** (`runtime/constants.js`, `runtime/iteration-utils.js`, `runtime/parse.js`, `compiler/src/compiler/manifest_builder.rs`) — the iteration header grammar only accepted the key before the index (`as item (item.id), i`). When the index came first, the trailing `(item.id)` made `ITERATION_REGEX` fail to match, so `parse.js` skipped the comment entirely and the body rendered once with an undefined alias instead of iterating. `ITERATION_REGEX` now accepts the `(key)` expression in either position (a second optional key group), and a new `parseIterationHeader` helper coalesces the two and is the single source of truth used by `parse.js`. Compiled pages re-parse the preserved each comment through the same helper, so they were fixed by the runtime change; additionally the Rust `manifest_builder` (compiler 2.0.0 → 2.0.1) now strips the key before the item/index split so the emitted manifest carries a clean `indexAlias` (was `"i (item.id)"`) for both orderings. Tests: `tests/unit/iteration-utils.test.js` (parseIterationHeader), `tests/compiler/iterations-index-key/`, `e2e-runtime/iteration-index-key.html` + `tests/e2e/iteration-index-key.spec.js` (runtime + compiled).
|
|
8
|
+
|
|
3
9
|
## [2.1.18] - 2026-06-22
|
|
4
10
|
|
|
5
11
|
### Fixed
|
|
Binary file
|
|
Binary file
|
package/compiler/src/Cargo.lock
CHANGED
package/compiler/src/Cargo.toml
CHANGED
|
@@ -202,16 +202,26 @@ impl ManifestBuilder {
|
|
|
202
202
|
// Extract expression: "each items as item" -> "items as item"
|
|
203
203
|
let expression = trimmed.strip_prefix("each ").unwrap_or("").to_string();
|
|
204
204
|
|
|
205
|
-
// Parse expression to extract parts: "items as item, index" or "items as item"
|
|
205
|
+
// Parse expression to extract parts: "items as item, index" or "items as item".
|
|
206
|
+
// The optional (key) expression may sit before or after the index — strip it
|
|
207
|
+
// first so the item/index split isn't polluted by it. The key itself isn't
|
|
208
|
+
// stored here; the runtime re-derives it from the preserved comment.
|
|
206
209
|
let parts: Vec<&str> = expression.split(" as ").collect();
|
|
207
210
|
let array_path = parts.get(0).unwrap_or(&"").trim().to_string();
|
|
208
211
|
let alias_part = parts.get(1).unwrap_or(&"").trim();
|
|
209
|
-
let
|
|
210
|
-
|
|
211
|
-
|
|
212
|
+
let alias_without_key = match (alias_part.find('('), alias_part.rfind(')')) {
|
|
213
|
+
(Some(open), Some(close)) if close > open => {
|
|
214
|
+
format!("{}{}", &alias_part[..open], &alias_part[close + 1..])
|
|
215
|
+
}
|
|
216
|
+
_ => alias_part.to_string(),
|
|
217
|
+
};
|
|
218
|
+
let alias_without_key = alias_without_key.trim();
|
|
219
|
+
let (item_alias, index_alias) = if let Some(comma_pos) = alias_without_key.find(',') {
|
|
220
|
+
let item = alias_without_key[..comma_pos].trim().to_string();
|
|
221
|
+
let index = alias_without_key[comma_pos + 1..].trim().to_string();
|
|
212
222
|
(item, index)
|
|
213
223
|
} else {
|
|
214
|
-
(
|
|
224
|
+
(alias_without_key.to_string(), "index".to_string())
|
|
215
225
|
};
|
|
216
226
|
|
|
217
227
|
// Find node index in siblings
|
package/package.json
CHANGED
package/runtime/constants.js
CHANGED
|
@@ -211,18 +211,23 @@ export const BINDING_REGEX = new RegExp(String.raw`\@\[(${BINDING_INNER})\]`, 'g
|
|
|
211
211
|
// Regex for detecting a pure binding (entire value is just @[expression])
|
|
212
212
|
export const PURE_BINDING_REGEX = new RegExp(String.raw`^\@\[(${BINDING_INNER})\]$`);
|
|
213
213
|
|
|
214
|
-
// Regex for parsing iteration comment syntax. Supported forms
|
|
214
|
+
// Regex for parsing iteration comment syntax. Supported forms — the index and
|
|
215
|
+
// the key are each optional and may appear in EITHER order:
|
|
215
216
|
// <!-- each items as item -->
|
|
216
217
|
// <!-- each items as item, index -->
|
|
217
218
|
// <!-- each items as item (item.id) --> // explicit key
|
|
218
|
-
// <!-- each items as item (item.id), index --> // key
|
|
219
|
-
//
|
|
219
|
+
// <!-- each items as item (item.id), index --> // key, then index
|
|
220
|
+
// <!-- each items as item, index (item.id) --> // index, then key
|
|
221
|
+
// Capture groups: arrayPath, itemAlias, keyBeforeIndex (optional), indexAlias
|
|
222
|
+
// (optional), keyAfterIndex (optional). The key lands in group 3 when written
|
|
223
|
+
// before the index and in group 5 when written after; parseIterationHeader
|
|
224
|
+
// coalesces the two — prefer that helper over destructuring the raw match.
|
|
220
225
|
// The array expression can be any JS: a state path, a window global, a method
|
|
221
226
|
// call, or an inline literal. The optional key expression is evaluated per
|
|
222
227
|
// item against scoped state to produce a stable identity for diffing — this
|
|
223
228
|
// keeps survivors stable when earlier items are removed (otherwise the
|
|
224
229
|
// fallback hash key embeds the index and triggers bulk re-render).
|
|
225
|
-
export const ITERATION_REGEX = /^each\s+(.+)\s+as\s+(\w+)(?:\s*\(\s*([^)]+?)\s*\))?(?:\s*,\s*(\w+))?\s*$/;
|
|
230
|
+
export const ITERATION_REGEX = /^each\s+(.+)\s+as\s+(\w+)(?:\s*\(\s*([^)]+?)\s*\))?(?:\s*,\s*(\w+))?(?:\s*\(\s*([^)]+?)\s*\))?\s*$/;
|
|
226
231
|
|
|
227
232
|
// Regex for detecting start of iteration comment
|
|
228
233
|
export const ITERATION_START_REGEX = /^each\s+/;
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
// Utility functions for array iteration
|
|
2
|
-
import { ITERATION_START_REGEX, CONDITIONAL_START_REGEX } from './constants.js';
|
|
2
|
+
import { ITERATION_REGEX, ITERATION_START_REGEX, CONDITIONAL_START_REGEX } from './constants.js';
|
|
3
|
+
|
|
4
|
+
// Parse an `each` directive body (the text inside `<!-- ... -->`, markers
|
|
5
|
+
// stripped) into its parts, or null when it isn't a valid each. The index alias
|
|
6
|
+
// and the (key) expression are both optional and may be written in either order
|
|
7
|
+
// — `as item, i (item.id)` and `as item (item.id), i` are equivalent. Single
|
|
8
|
+
// source of truth for the grammar, used by the runtime parser and (via DOM
|
|
9
|
+
// re-parse of the restored markers) by compiled pages.
|
|
10
|
+
export const parseIterationHeader = (text) => {
|
|
11
|
+
const match = text.match(ITERATION_REGEX);
|
|
12
|
+
if (!match) return null;
|
|
13
|
+
const [, arrayPath, itemAlias, keyBeforeIndex, indexAlias, keyAfterIndex] = match;
|
|
14
|
+
const keyExpr = keyBeforeIndex ?? keyAfterIndex;
|
|
15
|
+
return {
|
|
16
|
+
arrayPath,
|
|
17
|
+
itemAlias,
|
|
18
|
+
keyExpr: keyExpr != null ? keyExpr.trim() : null,
|
|
19
|
+
indexAlias: indexAlias ?? null,
|
|
20
|
+
};
|
|
21
|
+
};
|
|
3
22
|
|
|
4
23
|
// Resolve nested paths in state (e.g., "user.items" -> state.user.items)
|
|
5
24
|
// Supports bracket notation: "teams[0].combatants" -> state.teams[0].combatants
|
package/runtime/parse.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { findEndComment, findConditionalEnd } from './iteration-utils.js';
|
|
1
|
+
import { findEndComment, findConditionalEnd, parseIterationHeader } from './iteration-utils.js';
|
|
2
2
|
import {
|
|
3
3
|
NON_REACTIVE_ELEMENTS,
|
|
4
4
|
BINDING_REGEX,
|
|
5
|
-
ITERATION_REGEX,
|
|
6
5
|
CONDITIONAL_REGEX,
|
|
7
6
|
DOM_ELEMENT_PROPERTIES,
|
|
8
7
|
DEHYDRATE_CLASS_OR_ATTR,
|
|
@@ -150,10 +149,10 @@ const recursive = (children, rootKey = undefined, skipIndices = new Set(), stats
|
|
|
150
149
|
|
|
151
150
|
// Handle iteration comments
|
|
152
151
|
if (nodeName === '#comment') {
|
|
153
|
-
const iterationMatch = textContent.trim()
|
|
152
|
+
const iterationMatch = parseIterationHeader(textContent.trim());
|
|
154
153
|
|
|
155
154
|
if (iterationMatch) {
|
|
156
|
-
const
|
|
155
|
+
const { arrayPath, itemAlias, keyExpr, indexAlias } = iterationMatch;
|
|
157
156
|
|
|
158
157
|
try {
|
|
159
158
|
// Find matching end comment
|