@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.
|
|
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
package/src/Cargo.toml
CHANGED
|
@@ -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: ®ex::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";
|