@ape-egg/vibe 2.1.15 → 2.1.16

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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.1.16] - 2026-06-22
4
+
5
+ ### Fixed
6
+
7
+ - **Compiler 1.9.9 → 2.0.0 — bare prop substitution corrupted matching text inside string literals** (`compiler/src/parser/html.rs`, `runtime/component.js`) — the bare-prop-identifier rewrite added in 2.1.14 (so `onclick="pick(item)"` resolves the live prop) replaced *every* whole-word occurrence of the prop name, including ones inside a quoted string. A prop named `slot` rewrote the selector in `closest('brawler-slot')`, corrupting it. Both the compiler's `substitute_identifier` and the runtime's `substituteInExpr` now track string-literal boundaries (`'`, `"`, and backtick, honouring escapes; template `${…}` counts as part of the literal) and substitute only the code spans between them, leaving identifiers inside string literals intact. The two sides stay byte-for-byte identical, preserving compiled/runtime parity.
8
+
3
9
  ## [2.1.15] - 2026-06-22
4
10
 
5
11
  ### Fixed
@@ -1599,7 +1599,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
1599
1599
 
1600
1600
  [[package]]
1601
1601
  name = "vibe-compiler"
1602
- version = "1.9.9"
1602
+ version = "2.0.0"
1603
1603
  dependencies = [
1604
1604
  "clap",
1605
1605
  "colored",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "vibe-compiler"
3
- version = "1.9.9"
3
+ version = "2.0.0"
4
4
  edition = "2021"
5
5
  description = "Vibe framework compiler - compiles Vibe source files into optimized output"
6
6
  authors = ["Kim Korte"]
@@ -542,7 +542,32 @@ fn substitute_identifier(expr: &str, name: &str, replacement: &str) -> String {
542
542
  }
543
543
  let mut out: Vec<u8> = Vec::with_capacity(bytes.len());
544
544
  let mut i = 0;
545
+ // Delimiter of the string literal we're currently inside, or 0 when in code.
546
+ // A prop identifier that appears inside a quoted string (e.g. the selector in
547
+ // `closest('brawler-slot')`) must be left intact, mirroring the runtime's
548
+ // substituteInExpr. Template `${…}` counts as part of the literal here.
549
+ let mut quote: u8 = 0;
545
550
  while i < bytes.len() {
551
+ let b = bytes[i];
552
+ if quote != 0 {
553
+ out.push(b);
554
+ if b == b'\\' && i + 1 < bytes.len() {
555
+ out.push(bytes[i + 1]);
556
+ i += 2;
557
+ continue;
558
+ }
559
+ if b == quote {
560
+ quote = 0;
561
+ }
562
+ i += 1;
563
+ continue;
564
+ }
565
+ if b == b'\'' || b == b'"' || b == b'`' {
566
+ quote = b;
567
+ out.push(b);
568
+ i += 1;
569
+ continue;
570
+ }
546
571
  if i + nlen <= bytes.len()
547
572
  && bytes[i..i + nlen].eq_ignore_ascii_case(nb)
548
573
  && (i == 0 || (!is_ident_byte(bytes[i - 1]) && bytes[i - 1] != b'.'))
@@ -551,7 +576,7 @@ fn substitute_identifier(expr: &str, name: &str, replacement: &str) -> String {
551
576
  out.extend_from_slice(replacement.as_bytes());
552
577
  i += nlen;
553
578
  } else {
554
- out.push(bytes[i]);
579
+ out.push(b);
555
580
  i += 1;
556
581
  }
557
582
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ape-egg/vibe",
3
- "version": "2.1.15",
3
+ "version": "2.1.16",
4
4
  "type": "module",
5
5
  "description": "Runtime-first reactivity with optional compiler",
6
6
  "main": "index.js",
@@ -350,7 +350,23 @@ const renderPropsAndSlot = (temp, props, slotHtml) => {
350
350
  'gi'
351
351
  );
352
352
 
353
- const substituteInExpr = (expr, replacement) => expr.replace(idRegex, replacement);
353
+ // Rewrite the identifier only outside string literals. A prop/alias that also
354
+ // appears inside a quoted string — e.g. the selector in `closest('brawler-slot')`
355
+ // when the prop is `slot` — must be left intact. Split on string literals
356
+ // (single/double/backtick, honouring escapes) and substitute only the code spans
357
+ // between them. Template `${…}` interpolation counts as part of the literal here.
358
+ const stringLiteral = /(['"`])(?:\\.|(?!\1)[^\\])*\1/g;
359
+ const substituteInExpr = (expr, replacement) => {
360
+ let out = '';
361
+ let last = 0;
362
+ let m;
363
+ stringLiteral.lastIndex = 0;
364
+ while ((m = stringLiteral.exec(expr))) {
365
+ out += expr.slice(last, m.index).replace(idRegex, replacement) + m[0];
366
+ last = m.index + m[0].length;
367
+ }
368
+ return out + expr.slice(last).replace(idRegex, replacement);
369
+ };
354
370
 
355
371
  if (bindingMatch) {
356
372
  const path = bindingMatch[1];