@ape-egg/vibe-compiler 4.3.14 → 4.3.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.
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ape-egg/vibe-compiler",
3
- "version": "4.3.14",
3
+ "version": "4.3.16",
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.3.14"
1602
+ version = "4.3.16"
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.3.14"
3
+ version = "4.3.16"
4
4
  edition = "2021"
5
5
  description = "Vibe framework compiler - compiles Vibe source files into optimized output"
6
6
  authors = ["Kim Korte"]
@@ -1601,6 +1601,7 @@ impl Compiler {
1601
1601
 
1602
1602
  // Raw pre-pass: read every page and detect the shared-layout shape
1603
1603
  // BEFORE component inlining consumes the <component src> root.
1604
+ let component_names: Vec<&str> = parser.elements().keys().map(String::as_str).collect();
1604
1605
  let mut raw_pages = Vec::new();
1605
1606
  for path in &page_files {
1606
1607
  let rel = path
@@ -1612,7 +1613,7 @@ impl Compiler {
1612
1613
  path: path.display().to_string(),
1613
1614
  source: e,
1614
1615
  })?;
1615
- let candidate = spa::detect_layout_root(&content);
1616
+ let candidate = spa::detect_layout_root(&content, &component_names);
1616
1617
  raw_pages.push((rel, content, candidate));
1617
1618
  }
1618
1619
  let candidates: Vec<_> = raw_pages.iter().map(|(_, _, c)| c.clone()).collect();
@@ -260,10 +260,11 @@ pub fn dissect_page(rel_path: &str, html: &str) -> Result<SpaPage, String> {
260
260
 
261
261
  /// Scan a page body for the hoistable-layout shape: ignoring whitespace,
262
262
  /// comments and top-level <script> blocks, the body must be EXACTLY ONE
263
- /// `<component src="…">` element. Returns (open_tag, src, inner_html).
263
+ /// `<component src="…">` element or one tag named after a component
264
+ /// (`<Layout>`). Returns (open_tag, src or tag name, inner_html).
264
265
  /// In-body scripts stay page code (they ride the fragment prelude), so they
265
266
  /// don't break the shape.
266
- pub fn detect_layout_root(raw_source: &str) -> Option<(String, String, String)> {
267
+ pub fn detect_layout_root(raw_source: &str, component_names: &[&str]) -> Option<(String, String, String)> {
267
268
  let body_re = Regex::new(r"(?is)<body[^>]*>(.*)</body>").unwrap();
268
269
  let body_caps = body_re.captures(raw_source)?;
269
270
  let body_match = body_caps.get(1)?;
@@ -280,12 +281,21 @@ pub fn detect_layout_root(raw_source: &str) -> Option<(String, String, String)>
280
281
  let cleaned = page_style_re.replace_all(&cleaned, "");
281
282
  let trimmed = cleaned.trim();
282
283
 
283
- let open_re = Regex::new(r#"(?is)^<component\b((?:"[^"]*"|'[^']*'|[^>"'])*)>"#).unwrap();
284
+ let open_re = Regex::new(r#"(?s)^<([A-Za-z][\w-]*)\b((?:"[^"]*"|'[^']*'|[^>"'])*)>"#).unwrap();
284
285
  let caps = open_re.captures(trimmed)?;
285
286
  let open_tag = caps.get(0).unwrap().as_str().to_string();
286
- let attrs = caps.get(1).unwrap().as_str();
287
+ let name = caps.get(1).unwrap().as_str();
288
+ let attrs = caps.get(2).unwrap().as_str();
287
289
  let src_re = Regex::new(r#"(?i)src\s*=\s*"([^"]+)""#).unwrap();
288
- let src = src_re.captures(attrs)?.get(1).unwrap().as_str().to_string();
290
+ let (src, tag_re) = if name.eq_ignore_ascii_case("component") {
291
+ let src = src_re.captures(attrs)?.get(1).unwrap().as_str().to_string();
292
+ (src, Regex::new(r"(?i)<component\b|</component\s*>").unwrap())
293
+ } else if component_names.contains(&name) {
294
+ let name = regex::escape(name);
295
+ (name.clone(), Regex::new(&format!(r"<{name}\b|</{name}\s*>")).unwrap())
296
+ } else {
297
+ return None;
298
+ };
289
299
 
290
300
  // Find the matching </component> by depth over a MASKED copy of the body:
291
301
  // comments, scripts and styles are space-filled at identical byte offsets,
@@ -306,7 +316,6 @@ pub fn detect_layout_root(raw_source: &str) -> Option<(String, String, String)>
306
316
  );
307
317
  let start = masked_body.find(open_tag.as_str())?;
308
318
  let after_open = start + open_tag.len();
309
- let tag_re = Regex::new(r"(?i)<component\b|</component\s*>").unwrap();
310
319
  let mut depth = 1usize;
311
320
  let mut inner_end = None;
312
321
  for m in tag_re.find_iter(&masked_body[after_open..]) {
@@ -532,11 +541,12 @@ pub fn compose_shell(
532
541
  // state and timers persist, matching what a hand-written SPA shell does.
533
542
  match hoisted_layout {
534
543
  Some((open_tag, _src)) => {
544
+ let name = open_tag[1..].split(|c: char| c.is_whitespace() || c == '>').next().unwrap();
535
545
  html.push_str(" ");
536
546
  html.push_str(open_tag);
537
547
  html.push('\n');
538
548
  html.push_str(" <component src=\"@[page.src]\" key=\"@[page.path]\"></component>\n");
539
- html.push_str(" </component>\n");
549
+ html.push_str(&format!(" </{}>\n", name));
540
550
  }
541
551
  None => {
542
552
  html.push_str(" <component src=\"@[page.src]\" key=\"@[page.path]\"></component>\n");
@@ -94,7 +94,6 @@ impl HtmlParser {
94
94
  }
95
95
 
96
96
  /// Get all loaded elements
97
- #[allow(dead_code)]
98
97
  pub fn elements(&self) -> &ElementCache {
99
98
  &self.cache
100
99
  }