@ape-egg/vibe-compiler 4.2.0 → 4.2.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.
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ape-egg/vibe-compiler",
3
- "version": "4.2.0",
3
+ "version": "4.2.1",
4
4
  "type": "module",
5
5
  "description": "Optional Rust compiler for Vibe \u2014 pre-renders, inlines components, and optimizes iterations. The runtime never needs it.",
6
6
  "homepage": "https://vibe.korte.kim",
package/src/Cargo.lock CHANGED
@@ -1599,7 +1599,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
1599
1599
 
1600
1600
  [[package]]
1601
1601
  name = "vibe-compiler"
1602
- version = "4.2.0"
1602
+ version = "4.2.1"
1603
1603
  dependencies = [
1604
1604
  "clap",
1605
1605
  "colored",
package/src/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "vibe-compiler"
3
- version = "4.2.0"
3
+ version = "4.2.1"
4
4
  edition = "2021"
5
5
  description = "Vibe framework compiler - compiles Vibe source files into optimized output"
6
6
  authors = ["Kim Korte"]
@@ -1,7 +1,7 @@
1
1
  use regex::Regex;
2
2
  use serde::{Serialize, Deserialize};
3
3
  use std::collections::HashMap;
4
- use crate::constants::NAMEBIND_ATTR;
4
+ use crate::constants::{NAMEBIND_ATTR, DEFER_ATTR_PREFIX, INTERNAL_ATTR_PREFIX};
5
5
 
6
6
  #[derive(Debug, Clone, Serialize, Deserialize)]
7
7
  #[serde(rename_all = "camelCase")]
@@ -180,6 +180,55 @@ fn restore_relocated_name_bindings(template: &str) -> String {
180
180
  .to_string()
181
181
  }
182
182
 
183
+ fn map_tag_spans(html: &str, f: impl Fn(&str) -> String) -> String {
184
+ let bytes = html.as_bytes();
185
+ let mut out = String::with_capacity(html.len());
186
+ let mut i = 0;
187
+ while i < bytes.len() {
188
+ let Some(rel) = html[i..].find('<') else {
189
+ out.push_str(&html[i..]);
190
+ break;
191
+ };
192
+ let lt = i + rel;
193
+ out.push_str(&html[i..lt]);
194
+ let mut j = lt + 1;
195
+ let mut quote = None;
196
+ while j < bytes.len() {
197
+ let c = bytes[j];
198
+ match quote {
199
+ Some(q) if c == q => quote = None,
200
+ Some(_) => {}
201
+ None if c == b'"' || c == b'\'' => quote = Some(c),
202
+ None if c == b'>' => break,
203
+ None => {}
204
+ }
205
+ j += 1;
206
+ }
207
+ let span = &html[lt..(j + 1).min(bytes.len())];
208
+ let is_tag = span.len() > 1
209
+ && span[1..].trim_start_matches('/').starts_with(|c: char| c.is_ascii_alphabetic());
210
+ if is_tag { out.push_str(&f(span)) } else { out.push_str(span) }
211
+ i = j + 1;
212
+ }
213
+ out
214
+ }
215
+
216
+ fn unpark_deferred_attrs(template: &str) -> String {
217
+ let deferred = Regex::new(&format!(r"(\s){}([\w-]+)", DEFER_ATTR_PREFIX)).unwrap();
218
+ let internal = &INTERNAL_ATTR_PREFIX[DEFER_ATTR_PREFIX.len()..];
219
+ map_tag_spans(template, |tag| {
220
+ deferred
221
+ .replace_all(tag, |caps: &regex::Captures| {
222
+ if caps[2].starts_with(internal) {
223
+ caps[0].to_string()
224
+ } else {
225
+ format!("{}{}", &caps[1], &caps[2])
226
+ }
227
+ })
228
+ .to_string()
229
+ })
230
+ }
231
+
183
232
  fn is_value_style_attr(name: &str) -> bool {
184
233
  VALUE_ATTRS.contains(&name)
185
234
  || name.starts_with("data-")
@@ -235,7 +284,7 @@ fn emit_template_literal(template: &str) -> String {
235
284
  // expression has whitespace, so it could not be an HTML attribute name) back to the
236
285
  // `@[expr]=""` form the binding pass below renders as ` resolvedName=""`. The compiled
237
286
  // twin of the same step in runtime/iterate.js compileBatchFn.
238
- let template = &restore_relocated_name_bindings(template);
287
+ let template = &unpark_deferred_attrs(&restore_relocated_name_bindings(template));
239
288
 
240
289
  let each_re = Regex::new(r"<!--\s*each\s+([^\s]+)\s+as\s+([^\s,]+)(?:\s*,\s*([^\s]+))?\s*-->").unwrap();
241
290
  let end_re = Regex::new(r"<!--\s*/each\s*-->").unwrap();
@@ -416,6 +465,27 @@ mod tests {
416
465
  assert!(compiled.batch_fn.contains(r#"value="${item.v}""#));
417
466
  }
418
467
 
468
+ #[test]
469
+ fn test_deferred_attr_binding_lands_under_its_real_name() {
470
+ let html = r#"
471
+ <!-- each shots as shot -->
472
+ <img data-vibe-src="@[shot.thumb]" data-vibe-glow="@[shot.on]" data-vibe-alt="shot @[shot.n]" data-vibe-internal-component-id="_c1">
473
+ <!-- /each -->
474
+ "#;
475
+
476
+ let opts = build_iteration_optimizations(html);
477
+ let iterations = &opts.unwrap().iterations;
478
+ let (_, compiled) = iterations.iter().next().unwrap();
479
+
480
+ assert!(compiled.batch_fn.contains(r#" src="${shot.thumb}""#));
481
+ assert!(compiled.batch_fn.contains(r#"${(shot.on) ? ' glow=""' : ''}"#));
482
+ assert!(compiled.batch_fn.contains(r#" alt="shot ${shot.n}""#));
483
+ assert!(compiled.batch_fn.contains(r#"data-vibe-internal-component-id="_c1""#));
484
+ assert!(!compiled.batch_fn.contains("data-vibe-src"));
485
+ assert!(!compiled.batch_fn.contains("data-vibe-glow"));
486
+ assert!(!compiled.batch_fn.contains("data-vibe-alt"));
487
+ }
488
+
419
489
  #[test]
420
490
  fn test_unsafe_template_skips_batch_fn() {
421
491
  let html = r#"
package/src/constants.rs CHANGED
@@ -1,7 +1,4 @@
1
1
  pub const DEFER_ATTR_PREFIX: &str = "data-vibe-";
2
- // Declared for the JS↔Rust drift check; the compiler only ever names the
3
- // derived attributes below, and narrowing to internal is DEFER_ATTR_PREFIX's job.
4
- #[allow(dead_code)]
5
2
  pub const INTERNAL_ATTR_PREFIX: &str = "data-vibe-internal-";
6
3
  pub const COMPONENT_ID_ATTR: &str = "data-vibe-internal-component-id";
7
4
  pub const NAMEBIND_ATTR: &str = "data-vibe-internal-namebind";