@ape-egg/vibe-compiler 4.3.23 → 4.3.25
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/native/vibe-compiler-darwin-arm64 +0 -0
- package/native/vibe-compiler-linux-x64 +0 -0
- package/package.json +9 -3
- package/src/Cargo.toml +1 -1
- package/src/compiler/compile.rs +27 -24
- package/src/compiler/spa.rs +3 -3
- package/src/compiler/watcher.rs +2 -2
- package/src/parser/html.rs +47 -12
- package/src/parser/mod.rs +1 -1
- package/src/Cargo.lock +0 -2024
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ape-egg/vibe-compiler",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.25",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Optional Rust compiler for Vibe
|
|
5
|
+
"description": "Optional Rust compiler for Vibe — pre-renders, inlines components, and optimizes iterations. The runtime never needs it.",
|
|
6
6
|
"homepage": "https://vibe.korte.kim",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/kkortes/ape-egg.git",
|
|
10
|
+
"directory": "packages/vibe-compiler"
|
|
11
|
+
},
|
|
7
12
|
"bin": {
|
|
8
13
|
"vibe": "./bin/vibe-compile.js"
|
|
9
14
|
},
|
|
@@ -29,7 +34,8 @@
|
|
|
29
34
|
"author": "kkortes",
|
|
30
35
|
"license": "ISC",
|
|
31
36
|
"publishConfig": {
|
|
32
|
-
"access": "public"
|
|
37
|
+
"access": "public",
|
|
38
|
+
"registry": "https://registry.npmjs.org"
|
|
33
39
|
},
|
|
34
40
|
"dependencies": {}
|
|
35
41
|
}
|
package/src/Cargo.toml
CHANGED
package/src/compiler/compile.rs
CHANGED
|
@@ -11,7 +11,7 @@ use rayon::prelude::*;
|
|
|
11
11
|
use serde_json::{Map, Value};
|
|
12
12
|
|
|
13
13
|
use crate::config::Config;
|
|
14
|
-
use crate::parser::HtmlParser;
|
|
14
|
+
use crate::parser::{HtmlParser, component_key};
|
|
15
15
|
|
|
16
16
|
/// Map an output-relative HTML path to the URL path the runtime resolves a
|
|
17
17
|
/// manifest from. Strips the `root` dir (served at /) and collapses dynamic
|
|
@@ -239,13 +239,13 @@ fn extract_event_handlers(html: &str) -> Vec<String> {
|
|
|
239
239
|
/// the inliner's `src=` regex won't match the renamed attribute, so it stops
|
|
240
240
|
/// re-expanding. `process_html_with_cache` restores the attribute to plain
|
|
241
241
|
/// `src=` at the end of compilation so the runtime fetches it normally.
|
|
242
|
-
fn escape_recursive_src(content: &str,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
regex::
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
242
|
+
fn escape_recursive_src(content: &str, key: &str) -> String {
|
|
243
|
+
Regex::new(r#"(<(?:component|div)\s(?:[^>]*\s)?)src="([^"]+)""#)
|
|
244
|
+
.unwrap()
|
|
245
|
+
.replace_all(content, |cap: ®ex::Captures| match component_key(&cap[2]) == key {
|
|
246
|
+
true => format!(r#"{}{}="{}""#, &cap[1], RECURSIVE_SRC_ATTR, &cap[2]),
|
|
247
|
+
false => cap[0].to_string(),
|
|
248
|
+
})
|
|
249
249
|
.into_owned()
|
|
250
250
|
}
|
|
251
251
|
|
|
@@ -1875,19 +1875,19 @@ impl Compiler {
|
|
|
1875
1875
|
}
|
|
1876
1876
|
|
|
1877
1877
|
fn collect_component_children(&mut self, component_src: &str, relationships: &mut Vec<(String, Vec<String>)>, visited: &mut HashSet<String>) {
|
|
1878
|
-
|
|
1878
|
+
let key = component_key(component_src);
|
|
1879
|
+
if !visited.insert(key.clone()) {
|
|
1879
1880
|
return;
|
|
1880
1881
|
}
|
|
1881
|
-
visited.insert(component_src.to_string());
|
|
1882
1882
|
|
|
1883
1883
|
// Read component content
|
|
1884
|
-
let content = if
|
|
1885
|
-
match self.fetch_external_component_raw(
|
|
1884
|
+
let content = if key.starts_with("http://") || key.starts_with("https://") {
|
|
1885
|
+
match self.fetch_external_component_raw(&key) {
|
|
1886
1886
|
Ok(c) => c,
|
|
1887
1887
|
Err(_) => return,
|
|
1888
1888
|
}
|
|
1889
1889
|
} else {
|
|
1890
|
-
match self.resolve_component_path(
|
|
1890
|
+
match self.resolve_component_path(&key) {
|
|
1891
1891
|
Ok(path) => match fs::read_to_string(&path) {
|
|
1892
1892
|
Ok(c) => c,
|
|
1893
1893
|
Err(_) => return,
|
|
@@ -2050,17 +2050,7 @@ impl Compiler {
|
|
|
2050
2050
|
parser: &HtmlParser,
|
|
2051
2051
|
visiting: &mut std::collections::HashSet<String>,
|
|
2052
2052
|
) -> Option<String> {
|
|
2053
|
-
|
|
2054
|
-
let normalized_src = if component_src.starts_with("http://") || component_src.starts_with("https://") {
|
|
2055
|
-
component_src.to_string()
|
|
2056
|
-
} else {
|
|
2057
|
-
let without_prefix = component_src.trim_start_matches("./");
|
|
2058
|
-
if without_prefix.starts_with('/') {
|
|
2059
|
-
without_prefix.to_string()
|
|
2060
|
-
} else {
|
|
2061
|
-
format!("/{}", without_prefix)
|
|
2062
|
-
}
|
|
2063
|
-
};
|
|
2053
|
+
let normalized_src = component_key(component_src);
|
|
2064
2054
|
|
|
2065
2055
|
// Return cached if already fetched and fully resolved
|
|
2066
2056
|
if let Some(cached) = self.component_cache.get(&normalized_src) {
|
|
@@ -2744,6 +2734,19 @@ fn count_newlines(bytes: &[u8]) -> usize {
|
|
|
2744
2734
|
mod tests {
|
|
2745
2735
|
use super::*;
|
|
2746
2736
|
|
|
2737
|
+
#[test]
|
|
2738
|
+
fn escape_recursive_src_matches_a_cycle_written_without_extension() {
|
|
2739
|
+
let out = escape_recursive_src(
|
|
2740
|
+
r#"<component src="/tree"></component><component src="/leaf.html"></component>"#,
|
|
2741
|
+
"/tree.html",
|
|
2742
|
+
);
|
|
2743
|
+
assert_eq!(
|
|
2744
|
+
out,
|
|
2745
|
+
format!(r#"<component {RECURSIVE_SRC_ATTR}="/tree"></component><component src="/leaf.html"></component>"#),
|
|
2746
|
+
);
|
|
2747
|
+
assert_eq!(escape_recursive_src(&out, "/tree.html"), out, "an escaped src was escaped again");
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2747
2750
|
#[test]
|
|
2748
2751
|
fn minify_preserves_script_newlines_so_line_comments_dont_swallow_code() {
|
|
2749
2752
|
// A `//` line comment inside a component script relies on its trailing
|
package/src/compiler/spa.rs
CHANGED
|
@@ -622,10 +622,10 @@ mod tests {
|
|
|
622
622
|
#[test]
|
|
623
623
|
fn harvests_and_derives_shell_imports() {
|
|
624
624
|
assert_eq!(
|
|
625
|
-
harvest_vibe_import("import vibe from '/
|
|
626
|
-
Some("/
|
|
625
|
+
harvest_vibe_import("import vibe from '/node_modules/@ape-egg/vibe/index.js';"),
|
|
626
|
+
Some("/node_modules/@ape-egg/vibe/index.js".to_string())
|
|
627
627
|
);
|
|
628
|
-
assert_eq!(spa_import_for("/
|
|
628
|
+
assert_eq!(spa_import_for("/node_modules/@ape-egg/vibe/index.js"), "/node_modules/@ape-egg/vibe/spa.js");
|
|
629
629
|
assert_eq!(spa_import_for("@ape-egg/vibe"), "@ape-egg/vibe/spa");
|
|
630
630
|
}
|
|
631
631
|
|
package/src/compiler/watcher.rs
CHANGED
|
@@ -8,6 +8,7 @@ use regex::Regex;
|
|
|
8
8
|
|
|
9
9
|
use super::{Compiler, compile::should_skip_path, shutdown};
|
|
10
10
|
use crate::config::Config;
|
|
11
|
+
use crate::parser::component_key;
|
|
11
12
|
use crate::constants::{HYPERSPEED_DIR, MANIFEST_SUFFIX};
|
|
12
13
|
|
|
13
14
|
/// Tracks which pages and components depend on which components
|
|
@@ -159,8 +160,7 @@ pub fn extract_component_dependencies(html: &str, components_dir: &Path) -> Hash
|
|
|
159
160
|
let component_tag_re = Regex::new(r#"<component\s+[^>]*src=["']([^"']+)["']"#).unwrap();
|
|
160
161
|
for cap in component_tag_re.captures_iter(html) {
|
|
161
162
|
if let Some(src) = cap.get(1) {
|
|
162
|
-
|
|
163
|
-
deps.insert(PathBuf::from(src_path));
|
|
163
|
+
deps.insert(PathBuf::from(component_key(src.as_str()).trim_start_matches('/')));
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
|
package/src/parser/html.rs
CHANGED
|
@@ -7,6 +7,22 @@ use super::element::{Element, ElementCache};
|
|
|
7
7
|
use crate::inert::Inert;
|
|
8
8
|
use crate::constants::{RECURSIVE_SRC_ATTR, MODULE_SCRIPT_TYPE};
|
|
9
9
|
|
|
10
|
+
/// The key a `<component src>` is fetched and cached under: rooted at `/` unless
|
|
11
|
+
/// it is a URL, with `.html` appended when its last path segment has no
|
|
12
|
+
/// extension, so `/vibe/counter` and `/vibe/counter.html` are one component.
|
|
13
|
+
/// Mirrors `fetchComponentTemplate` in the vibe runtime.
|
|
14
|
+
pub fn component_key(src: &str) -> String {
|
|
15
|
+
let rooted = if src.starts_with("http://") || src.starts_with("https://") {
|
|
16
|
+
src.to_string()
|
|
17
|
+
} else {
|
|
18
|
+
format!("/{}", src.trim_start_matches("./").trim_start_matches('/'))
|
|
19
|
+
};
|
|
20
|
+
regex::Regex::new(r"^((?:[^?#]*/)?[^/?#.]+)([?#].*)?$")
|
|
21
|
+
.unwrap()
|
|
22
|
+
.replace(&rooted, "$1.html$2")
|
|
23
|
+
.into_owned()
|
|
24
|
+
}
|
|
25
|
+
|
|
10
26
|
#[derive(Error, Debug)]
|
|
11
27
|
pub enum ParseError {
|
|
12
28
|
#[error("Failed to read file {path}: {source}")]
|
|
@@ -449,18 +465,7 @@ impl HtmlParser {
|
|
|
449
465
|
|
|
450
466
|
let mut applied = false;
|
|
451
467
|
for (start, end, src, props, slot_content) in sorted {
|
|
452
|
-
let
|
|
453
|
-
src.to_string()
|
|
454
|
-
} else {
|
|
455
|
-
let without_prefix = src.trim_start_matches("./");
|
|
456
|
-
if without_prefix.starts_with('/') {
|
|
457
|
-
without_prefix.to_string()
|
|
458
|
-
} else {
|
|
459
|
-
format!("/{}", without_prefix)
|
|
460
|
-
}
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
if let Some(template) = external_cache.get(&normalized_src) {
|
|
468
|
+
if let Some(template) = external_cache.get(&component_key(&src)) {
|
|
464
469
|
// Iter-prop components stay as runtime `<component src>` tags so the
|
|
465
470
|
// runtime renders them via its __vibe.iterProps path — inlining
|
|
466
471
|
// breaks the enclosing loop's scope (see is_iter_prop_root).
|
|
@@ -891,6 +896,36 @@ fn transform_custom_tags(content: &str, reserved_elements: &[String]) -> String
|
|
|
891
896
|
mod tests {
|
|
892
897
|
use super::*;
|
|
893
898
|
|
|
899
|
+
#[test]
|
|
900
|
+
fn component_key_appends_html_to_an_extensionless_src() {
|
|
901
|
+
let cases = [
|
|
902
|
+
("/vibe/counter", "/vibe/counter.html"),
|
|
903
|
+
("vibe/counter", "/vibe/counter.html"),
|
|
904
|
+
("./vibe/counter", "/vibe/counter.html"),
|
|
905
|
+
("/vibe/counter.html", "/vibe/counter.html"),
|
|
906
|
+
("/vibe/counter/v2.html", "/vibe/counter/v2.html"),
|
|
907
|
+
("/vibe/counter?v=2", "/vibe/counter.html?v=2"),
|
|
908
|
+
("https://x.com/vibe/counter#a", "https://x.com/vibe/counter.html#a"),
|
|
909
|
+
("/v1.2/counter", "/v1.2/counter.html"),
|
|
910
|
+
("/a/b.html?x=/y", "/a/b.html?x=/y"),
|
|
911
|
+
("/dir/", "/dir/"),
|
|
912
|
+
];
|
|
913
|
+
for (src, key) in cases {
|
|
914
|
+
assert_eq!(component_key(src), key, "src {src}");
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
#[test]
|
|
919
|
+
fn inline_component_without_extension_uses_the_html_template() {
|
|
920
|
+
let parser = HtmlParser::new(std::path::PathBuf::from("."));
|
|
921
|
+
let mut cache = HashMap::new();
|
|
922
|
+
cache.insert("/components/Potion.html".to_string(), "<icon potion></icon>".to_string());
|
|
923
|
+
|
|
924
|
+
let out = parser.inline_component_elements("<wrap><component src=\"/components/Potion\"></component></wrap>", &cache);
|
|
925
|
+
|
|
926
|
+
assert_eq!(out, "<wrap><component><icon potion></icon></component></wrap>");
|
|
927
|
+
}
|
|
928
|
+
|
|
894
929
|
#[test]
|
|
895
930
|
fn find_matching_close_tolerates_whitespace_in_end_tag() {
|
|
896
931
|
// Whitespace-controlled markup splits an end tag across lines, e.g.
|