@ape-egg/vibe-compiler 3.0.0 → 3.0.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": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "type": "module",
5
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",
package/src/Cargo.lock CHANGED
@@ -1599,7 +1599,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
1599
1599
 
1600
1600
  [[package]]
1601
1601
  name = "vibe-compiler"
1602
- version = "2.2.0"
1602
+ version = "3.0.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 = "2.2.0"
3
+ version = "3.0.1"
4
4
  edition = "2021"
5
5
  description = "Vibe framework compiler - compiles Vibe source files into optimized output"
6
6
  authors = ["Kim Korte"]
@@ -1516,6 +1516,16 @@ impl Compiler {
1516
1516
  )));
1517
1517
  }
1518
1518
 
1519
+ // The component cache may be partially invalidated (watch mode drops
1520
+ // an edited component plus its inlining ancestors before this pass).
1521
+ // Re-fetch everything these pages reference — a cache miss during
1522
+ // inlining is silent and would degrade the shell and fragments to raw
1523
+ // runtime references. Warm entries return immediately, so a full
1524
+ // build pays nothing.
1525
+ if !self.config.components_as_is {
1526
+ self.fetch_components_for_files(&page_files, parser)?;
1527
+ }
1528
+
1519
1529
  // Raw pre-pass: read every page and detect the shared-layout shape
1520
1530
  // BEFORE component inlining consumes the <component src> root.
1521
1531
  let mut raw_pages = Vec::new();
@@ -2831,6 +2841,111 @@ mod tests {
2831
2841
  let _ = fs::remove_dir_all(&dir);
2832
2842
  }
2833
2843
 
2844
+ // Watch mode invalidates an edited component plus its inlining ancestors,
2845
+ // then re-runs compile_spa through the same cache. The pass must re-fetch
2846
+ // what it needs: a partially invalidated cache silently degrades inlining
2847
+ // to raw runtime references (lean outlet-only shell, un-inlined
2848
+ // fragments), so incremental output must be byte-identical to a cold full
2849
+ // compile of the same source.
2850
+ #[test]
2851
+ fn spa_recompile_after_component_invalidation_matches_full_compile() {
2852
+ let dir = std::env::temp_dir().join("vibe-spa-invalidation-test");
2853
+ let _ = fs::remove_dir_all(&dir);
2854
+ let source = dir.join("src");
2855
+ fs::create_dir_all(source.join("pages")).unwrap();
2856
+ fs::create_dir_all(source.join("components")).unwrap();
2857
+ let page = |title: &str, content: &str| {
2858
+ format!(
2859
+ "<!DOCTYPE html>\n<html><head><title>{}</title></head>\n<body vibe-fouc>\
2860
+ <component src=\"/components/Layout.html\">{}</component></body></html>\n",
2861
+ title, content
2862
+ )
2863
+ };
2864
+ fs::write(
2865
+ source.join("components/Layout.html"),
2866
+ "<nav-chrome><component src=\"/components/Menu.html\"></component></nav-chrome>\n<slot></slot>\n",
2867
+ )
2868
+ .unwrap();
2869
+ fs::write(source.join("components/Menu.html"), "<menu-box>menu-v1</menu-box>\n").unwrap();
2870
+ fs::write(source.join("components/Widget.html"), "<widget-box>widget-v1</widget-box>\n").unwrap();
2871
+ fs::write(
2872
+ source.join("pages/index.html"),
2873
+ page("Home", "<h1>home</h1><component src=\"/components/Widget.html\"></component>"),
2874
+ )
2875
+ .unwrap();
2876
+ fs::write(source.join("pages/about.html"), page("About", "<h1>about</h1>")).unwrap();
2877
+
2878
+ let config = Config {
2879
+ source: source.clone(),
2880
+ output: dir.join("out"),
2881
+ _source_str: String::new(),
2882
+ _output_str: String::new(),
2883
+ components: "components".to_string(),
2884
+ pages: "pages".to_string(),
2885
+ _assets: String::new(),
2886
+ root: None,
2887
+ minify: false,
2888
+ elements_as_is: true,
2889
+ source_maps: false,
2890
+ reserved_elements: Vec::new(),
2891
+ skip_files: Vec::new(),
2892
+ node_modules_as_is: false,
2893
+ components_as_is: false,
2894
+ runtime_as_is: false,
2895
+ iterations_as_is: false,
2896
+ no_clean: false,
2897
+ fouc_as_is: false,
2898
+ spa: true,
2899
+ working_dir: dir.clone(),
2900
+ };
2901
+
2902
+ let mut compiler = Compiler::new(config.clone(), false);
2903
+ let mut parser = HtmlParser::new(config.components_path());
2904
+ parser.load_elements().unwrap();
2905
+ compiler.compile().unwrap();
2906
+ compiler.generate_manifests().unwrap();
2907
+
2908
+ let shell_path = config.output.join("index.html");
2909
+ let manifest_path = config.output.join("vibe-hyperspeed/index.html.manifest.js");
2910
+ let fragment_path = config.output.join("components/vibe-spa/index.html");
2911
+ assert!(fs::read_to_string(&shell_path).unwrap().contains("menu-v1"));
2912
+ assert!(fs::read_to_string(&fragment_path).unwrap().contains("widget-v1"));
2913
+
2914
+ // Edit both a layout-subtree component and a page-content component,
2915
+ // then mirror the watcher: invalidate the edited components + their
2916
+ // inlining ancestors, re-run the SPA pass, refresh the shell manifest.
2917
+ fs::write(source.join("components/Menu.html"), "<menu-box>menu-v2</menu-box>\n").unwrap();
2918
+ fs::write(source.join("components/Widget.html"), "<widget-box>widget-v2</widget-box>\n").unwrap();
2919
+ let stale: std::collections::HashSet<String> = [
2920
+ "/components/Menu.html",
2921
+ "/components/Layout.html",
2922
+ "/components/Widget.html",
2923
+ ]
2924
+ .into_iter()
2925
+ .map(String::from)
2926
+ .collect();
2927
+ compiler.invalidate_components(&stale);
2928
+ compiler.compile_spa(&parser).unwrap();
2929
+ compiler.generate_shell_manifest().unwrap();
2930
+
2931
+ let incremental_shell = fs::read_to_string(&shell_path).unwrap();
2932
+ let incremental_manifest = fs::read_to_string(&manifest_path).unwrap();
2933
+ let incremental_fragment = fs::read_to_string(&fragment_path).unwrap();
2934
+
2935
+ // Cold full compile of the identical source is the reference output.
2936
+ let mut full_compiler = Compiler::new(config.clone(), false);
2937
+ let mut full_parser = HtmlParser::new(config.components_path());
2938
+ full_parser.load_elements().unwrap();
2939
+ full_compiler.compile().unwrap();
2940
+ full_compiler.generate_manifests().unwrap();
2941
+
2942
+ assert_eq!(incremental_shell, fs::read_to_string(&shell_path).unwrap());
2943
+ assert_eq!(incremental_manifest, fs::read_to_string(&manifest_path).unwrap());
2944
+ assert_eq!(incremental_fragment, fs::read_to_string(&fragment_path).unwrap());
2945
+
2946
+ let _ = fs::remove_dir_all(&dir);
2947
+ }
2948
+
2834
2949
  fn manifest_test_project(name: &str) -> (Config, PathBuf, PathBuf, PathBuf) {
2835
2950
  let dir = std::env::temp_dir().join(format!("vibe_{}_test", name));
2836
2951
  let _ = fs::remove_dir_all(&dir);