@ape-egg/vibe 2.1.22 → 3.0.0

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.
Files changed (56) hide show
  1. package/README.md +112 -5
  2. package/boot.js +4 -4
  3. package/component.js +27 -29
  4. package/hot-module-refresh.js +4 -4
  5. package/index.js +26 -17
  6. package/llms.txt +36 -5
  7. package/package.json +20 -14
  8. package/runtime/affected.js +159 -36
  9. package/runtime/cleanup.js +45 -1
  10. package/runtime/component.js +360 -98
  11. package/runtime/conditionals.js +111 -14
  12. package/runtime/debug.js +24 -0
  13. package/runtime/dispatch.js +172 -0
  14. package/runtime/hydrate.js +277 -110
  15. package/runtime/index.js +189 -65
  16. package/runtime/iterate.js +125 -50
  17. package/runtime/iteration-utils.js +59 -8
  18. package/runtime/manifest.js +77 -2
  19. package/runtime/parse.js +81 -11
  20. package/runtime/pre-compiled-iterations.js +19 -6
  21. package/runtime/pre-compiled-manifest.js +13 -4
  22. package/runtime/staging.js +153 -0
  23. package/runtime/state.js +31 -0
  24. package/runtime/tracking.js +173 -0
  25. package/runtime/utils.js +155 -78
  26. package/spa.js +206 -0
  27. package/vibe.css +8 -4
  28. package/CHANGELOG.md +0 -1159
  29. package/ROADMAP.md +0 -397
  30. package/compiler/bin/vibe-compile.js +0 -121
  31. package/compiler/native/.gitkeep +0 -0
  32. package/compiler/native/vibe-compiler-darwin-arm64 +0 -0
  33. package/compiler/native/vibe-compiler-linux-x64 +0 -0
  34. package/compiler/src/Cargo.lock +0 -2023
  35. package/compiler/src/Cargo.toml +0 -38
  36. package/compiler/src/compiler/PRE-RENDERING-IMPLEMENTATION.md +0 -241
  37. package/compiler/src/compiler/binding_case.rs +0 -88
  38. package/compiler/src/compiler/compile.rs +0 -2522
  39. package/compiler/src/compiler/component_tagger.rs +0 -469
  40. package/compiler/src/compiler/iteration_optimizer.rs +0 -455
  41. package/compiler/src/compiler/js_analyzer.rs +0 -715
  42. package/compiler/src/compiler/manifest_builder.rs +0 -693
  43. package/compiler/src/compiler/mod.rs +0 -15
  44. package/compiler/src/compiler/name_binding_protect.rs +0 -207
  45. package/compiler/src/compiler/reassignment_analyzer.rs +0 -456
  46. package/compiler/src/compiler/state_extractor.rs +0 -263
  47. package/compiler/src/compiler/value_stamper.rs +0 -921
  48. package/compiler/src/compiler/watcher.rs +0 -1147
  49. package/compiler/src/config.rs +0 -239
  50. package/compiler/src/main.rs +0 -347
  51. package/compiler/src/parser/element.rs +0 -96
  52. package/compiler/src/parser/html.rs +0 -1004
  53. package/compiler/src/parser/mod.rs +0 -8
  54. package/runtime/pre-compiled-manifest.test.mjs +0 -58
  55. package/runtime/scope.js +0 -50
  56. package/test-results/.last-run.json +0 -4
@@ -1,239 +0,0 @@
1
- use serde::{Deserialize, Serialize};
2
- use std::fs;
3
- use std::path::PathBuf;
4
- use thiserror::Error;
5
-
6
- #[derive(Error, Debug)]
7
- pub enum ConfigError {
8
- #[error("Failed to read package.json: {0}")]
9
- ReadError(#[from] std::io::Error),
10
- #[error("Failed to parse package.json: {0}")]
11
- ParseError(#[from] serde_json::Error),
12
- }
13
-
14
- #[derive(Debug, Clone, Serialize, Deserialize)]
15
- #[serde(rename_all = "camelCase")]
16
- pub struct VibeCompilerConfig {
17
- #[serde(default = "default_source")]
18
- pub source: String,
19
- #[serde(default = "default_output")]
20
- pub output: String,
21
- #[serde(default = "default_components")]
22
- pub components: String,
23
- #[serde(default = "default_pages")]
24
- pub pages: String,
25
- #[serde(default = "default_assets")]
26
- pub assets: String,
27
- #[serde(default)]
28
- pub root: Option<String>,
29
- #[serde(default)]
30
- pub minify: bool,
31
- #[serde(default)]
32
- pub elements_as_is: bool,
33
- #[serde(default)]
34
- pub source_maps: bool,
35
- #[serde(default)]
36
- pub reserved_elements: Vec<String>,
37
- #[serde(default)]
38
- pub skip_files: Vec<String>,
39
- #[serde(default)]
40
- pub node_modules_as_is: bool,
41
- #[serde(default)]
42
- pub components_as_is: bool,
43
- #[serde(default)]
44
- pub runtime_as_is: bool,
45
- #[serde(default)]
46
- pub iterations_as_is: bool,
47
- #[serde(default)]
48
- pub no_clean: bool,
49
- #[serde(default)]
50
- pub fouc_as_is: bool,
51
- }
52
-
53
- fn default_source() -> String { "./".to_string() }
54
- fn default_output() -> String { "./compiled".to_string() }
55
- fn default_components() -> String { "components".to_string() }
56
- fn default_pages() -> String { "pages".to_string() }
57
- fn default_assets() -> String { "assets".to_string() }
58
-
59
- /// Get list of standard HTML5 elements + "component"
60
- /// Component names matching these are reserved and not allowed
61
- pub fn get_default_reserved_elements() -> Vec<String> {
62
- vec![
63
- "a", "abbr", "address", "area", "article", "aside", "audio",
64
- "b", "base", "bdi", "bdo", "blockquote", "body", "br", "button",
65
- "canvas", "caption", "cite", "code", "col", "colgroup",
66
- "data", "datalist", "dd", "del", "details", "dfn", "dialog", "div", "dl", "dt",
67
- "em", "embed",
68
- "fieldset", "figcaption", "figure", "footer", "form",
69
- "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html",
70
- "i", "iframe", "img", "input", "ins",
71
- "kbd",
72
- "label", "legend", "li", "link",
73
- "main", "map", "mark", "menu", "meta", "meter",
74
- "nav", "noscript",
75
- "object", "ol", "optgroup", "option", "output",
76
- "p", "param", "picture", "pre", "progress",
77
- "q",
78
- "rp", "rt", "ruby",
79
- "s", "samp", "script", "search", "section", "select", "slot", "small", "source", "span", "strong", "style", "sub", "summary", "sup", "svg",
80
- "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track",
81
- "u", "ul",
82
- "var", "video",
83
- "wbr",
84
- "component", // Always reserved
85
- ].into_iter().map(|s| s.to_string()).collect()
86
- }
87
-
88
- impl Default for VibeCompilerConfig {
89
- fn default() -> Self {
90
- Self {
91
- source: default_source(),
92
- output: default_output(),
93
- components: default_components(),
94
- pages: default_pages(),
95
- assets: default_assets(),
96
- root: None,
97
- minify: false,
98
- elements_as_is: false,
99
- source_maps: false,
100
- reserved_elements: vec![],
101
- skip_files: vec![],
102
- node_modules_as_is: false,
103
- components_as_is: false,
104
- runtime_as_is: false,
105
- iterations_as_is: false,
106
- no_clean: false,
107
- fouc_as_is: false,
108
- }
109
- }
110
- }
111
-
112
- #[derive(Debug, Clone)]
113
- pub struct Config {
114
- pub source: PathBuf,
115
- pub output: PathBuf,
116
- pub _source_str: String, // Original config value for display
117
- pub _output_str: String, // Original config value for display
118
- pub components: String,
119
- pub pages: String,
120
- pub _assets: String,
121
- pub root: Option<String>,
122
- pub minify: bool,
123
- pub elements_as_is: bool,
124
- pub source_maps: bool,
125
- pub reserved_elements: Vec<String>,
126
- pub skip_files: Vec<String>,
127
- pub node_modules_as_is: bool,
128
- pub components_as_is: bool,
129
- pub runtime_as_is: bool,
130
- pub iterations_as_is: bool,
131
- pub no_clean: bool,
132
- pub fouc_as_is: bool,
133
- pub working_dir: PathBuf,
134
- }
135
-
136
- impl Config {
137
- /// Load config from package.json, or use defaults if not found
138
- pub fn load(working_dir: PathBuf) -> Self {
139
- let package_path = working_dir.join("package.json");
140
-
141
- let config = if package_path.exists() {
142
- fs::read_to_string(&package_path)
143
- .ok()
144
- .and_then(|content| serde_json::from_str::<serde_json::Value>(&content).ok())
145
- .and_then(|package| package.get("vibe-compiler").cloned())
146
- .and_then(|vibe_config| serde_json::from_value::<VibeCompilerConfig>(vibe_config).ok())
147
- .unwrap_or_default()
148
- } else {
149
- VibeCompilerConfig::default()
150
- };
151
-
152
- let source_str = config.source.clone();
153
- let output_str = config.output.clone();
154
- let source = working_dir.join(&config.source);
155
- let output = working_dir.join(&config.output);
156
-
157
- // Combine default reserved elements with user-provided ones
158
- let mut reserved_elements = get_default_reserved_elements();
159
- reserved_elements.extend(config.reserved_elements);
160
-
161
- // Combine built-in skip patterns with user-provided ones (append, not replace)
162
- let mut skip_files: Vec<String> = crate::compiler::compile::SKIP_FILES
163
- .iter()
164
- .map(|s| s.to_string())
165
- .collect();
166
- skip_files.extend(config.skip_files);
167
-
168
- Self {
169
- source,
170
- output,
171
- _source_str: source_str,
172
- _output_str: output_str,
173
- components: config.components,
174
- pages: config.pages,
175
- _assets: config.assets,
176
- root: config.root,
177
- minify: config.minify,
178
- elements_as_is: config.elements_as_is,
179
- source_maps: config.source_maps,
180
- reserved_elements,
181
- skip_files,
182
- node_modules_as_is: config.node_modules_as_is,
183
- components_as_is: config.components_as_is,
184
- runtime_as_is: config.runtime_as_is,
185
- iterations_as_is: config.iterations_as_is,
186
- no_clean: config.no_clean,
187
- fouc_as_is: config.fouc_as_is,
188
- working_dir,
189
- }
190
- }
191
-
192
- pub fn components_path(&self) -> PathBuf {
193
- self.source.join(&self.components)
194
- }
195
-
196
- pub fn _pages_path(&self) -> PathBuf {
197
- self.source.join(&self.pages)
198
- }
199
-
200
- pub fn _assets_path(&self) -> PathBuf {
201
- self.source.join(&self._assets)
202
- }
203
- }
204
-
205
- pub fn init_config(working_dir: &PathBuf) -> Result<(), ConfigError> {
206
- let package_path = working_dir.join("package.json");
207
-
208
- if !package_path.exists() {
209
- // Create minimal package.json with vibe-compiler config
210
- let config = VibeCompilerConfig::default();
211
- let package = serde_json::json!({
212
- "vibe-compiler": config
213
- });
214
- let formatted = serde_json::to_string_pretty(&package)?;
215
- fs::write(&package_path, formatted)?;
216
- println!("Created package.json with vibe-compiler config");
217
- return Ok(());
218
- }
219
-
220
- let content = fs::read_to_string(&package_path)?;
221
- let mut package: serde_json::Value = serde_json::from_str(&content)?;
222
-
223
- // Don't overwrite existing config
224
- if package.get("vibe-compiler").is_some() {
225
- println!("vibe-compiler config already exists in package.json");
226
- return Ok(());
227
- }
228
-
229
- let default_config = VibeCompilerConfig::default();
230
- let config_value = serde_json::to_value(&default_config)?;
231
-
232
- package["vibe-compiler"] = config_value;
233
-
234
- let formatted = serde_json::to_string_pretty(&package)?;
235
- fs::write(&package_path, formatted)?;
236
-
237
- println!("Added vibe-compiler config to package.json");
238
- Ok(())
239
- }
@@ -1,347 +0,0 @@
1
- mod config;
2
- mod parser;
3
- mod compiler;
4
-
5
- use clap::Parser as ClapParser;
6
- use colored::Colorize;
7
- use std::path::PathBuf;
8
-
9
- use config::Config;
10
- use compiler::Compiler;
11
-
12
- /// Track which config values were overridden by flags
13
- #[derive(Debug, Default)]
14
- struct ConfigOverrides {
15
- minify: bool,
16
- elements_as_is: bool,
17
- source_maps: bool,
18
- node_modules_as_is: bool,
19
- components_as_is: bool,
20
- runtime_as_is: bool,
21
- iterations_as_is: bool,
22
- }
23
-
24
- /// Vibe Compiler - Compiles Vibe source files into optimized output
25
- #[derive(ClapParser, Debug)]
26
- #[command(name = "vibe-compile")]
27
- #[command(author = "Kim Korte")]
28
- #[command(version)]
29
- #[command(about = "Compiles Vibe source files into optimized output")]
30
- struct Args {
31
- /// Working directory (defaults to current directory)
32
- #[arg(long)]
33
- cwd: Option<PathBuf>,
34
-
35
- /// Watch for file changes
36
- #[arg(long)]
37
- watch: bool,
38
-
39
- /// Minify output
40
- #[arg(long)]
41
- minify: bool,
42
-
43
- /// Enable verbose logging with step-by-step output
44
- #[arg(long)]
45
- verbose: bool,
46
-
47
- /// Keep custom HTML elements as-is (don't transform to divs for accessibility)
48
- #[arg(long, name = "elements-as-is")]
49
- elements_as_is: bool,
50
-
51
- /// Generate source maps
52
- #[arg(long, name = "source-maps")]
53
- source_maps: bool,
54
-
55
- /// Initialize configuration in package.json
56
- #[arg(long)]
57
- init: bool,
58
-
59
- /// Custom source directory (overrides config)
60
- #[arg(long)]
61
- source: Option<PathBuf>,
62
-
63
- /// Custom output directory (overrides config)
64
- #[arg(long)]
65
- output: Option<PathBuf>,
66
-
67
- /// Copy node_modules as-is without production-only install (overrides config)
68
- #[arg(long, name = "node-modules-as-is")]
69
- node_modules_as_is: bool,
70
-
71
- /// Keep components directory and <component> tags as-is (don't inline components)
72
- #[arg(long, name = "components-as-is")]
73
- components_as_is: bool,
74
-
75
- /// Skip manifest generation - output will use runtime-only mode (no FOUC prevention)
76
- #[arg(long, name = "runtime-as-is")]
77
- runtime_as_is: bool,
78
-
79
- /// Skip iteration optimization - use runtime DOM cloning instead of compiled batch functions
80
- #[arg(long, name = "iterations-as-is")]
81
- iterations_as_is: bool,
82
-
83
- /// Skip cleaning output directory before compilation
84
- #[arg(long, name = "no-clean", hide = true)]
85
- no_clean: bool,
86
-
87
- /// Keep FOUC prevention class/attribute in compiled output
88
- #[arg(long, name = "fouc-as-is")]
89
- fouc_as_is: bool,
90
- }
91
-
92
- fn main() {
93
- let args = Args::parse();
94
-
95
- // Determine working directory
96
- let working_dir = args.cwd
97
- .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
98
-
99
- if args.init {
100
- match config::init_config(&working_dir) {
101
- Ok(_) => {
102
- return;
103
- }
104
- Err(e) => {
105
- eprintln!("{}: {}", "Error".red(), e);
106
- std::process::exit(1);
107
- }
108
- }
109
- }
110
-
111
- // Load config from package.json (uses defaults if not found)
112
- let mut config = Config::load(working_dir);
113
-
114
- // Override config with CLI args
115
- if let Some(source) = args.source {
116
- config.source = config.working_dir.join(source);
117
- }
118
- if let Some(output) = args.output {
119
- config.output = config.working_dir.join(output);
120
- }
121
-
122
- // Store original config values before applying flag overrides
123
- let original_minify = config.minify;
124
- let original_elements_as_is = config.elements_as_is;
125
- let original_source_maps = config.source_maps;
126
- let original_node_modules_as_is = config.node_modules_as_is;
127
- let original_components_as_is = config.components_as_is;
128
- let original_runtime_as_is = config.runtime_as_is;
129
- let original_iterations_as_is = config.iterations_as_is;
130
-
131
- // Track overrides for verbose output
132
- let mut overrides = ConfigOverrides::default();
133
-
134
- if args.minify {
135
- overrides.minify = !original_minify;
136
- config.minify = true;
137
- }
138
- if args.elements_as_is {
139
- overrides.elements_as_is = !original_elements_as_is;
140
- config.elements_as_is = true;
141
- }
142
- if args.source_maps {
143
- overrides.source_maps = !original_source_maps;
144
- config.source_maps = true;
145
- }
146
- if args.node_modules_as_is {
147
- overrides.node_modules_as_is = !original_node_modules_as_is;
148
- config.node_modules_as_is = true;
149
- }
150
- if args.components_as_is {
151
- overrides.components_as_is = !original_components_as_is;
152
- config.components_as_is = true;
153
- }
154
- if args.runtime_as_is {
155
- overrides.runtime_as_is = !original_runtime_as_is;
156
- config.runtime_as_is = true;
157
- }
158
- if args.iterations_as_is {
159
- overrides.iterations_as_is = !original_iterations_as_is;
160
- config.iterations_as_is = true;
161
- }
162
- if args.no_clean {
163
- config.no_clean = true;
164
- }
165
- if args.fouc_as_is {
166
- config.fouc_as_is = true;
167
- }
168
-
169
- if args.verbose {
170
- println!("{}", format!("Vibe Compiler v{}", env!("CARGO_PKG_VERSION")).cyan().bold());
171
- println!();
172
- println!(" {}: {}", "Working dir".cyan(), config.working_dir.display());
173
- println!(" {}: {}", "Source".cyan(), config.source.display());
174
- println!(" {}: {}", "Output".cyan(), config.output.display());
175
- println!(" {}: {}", "Components".cyan(), config.components);
176
- println!(" {}: {}", "Pages".cyan(), config.pages);
177
- println!(" {}: {}", "Assets".cyan(), config._assets);
178
- println!();
179
-
180
- // Show config values (alphabetically ordered)
181
- println!("{}", "Compiler config".cyan().bold());
182
- println!();
183
- println!(" CLI flags override package.json config");
184
- println!();
185
-
186
- fn format_bool_with_flag(final_value: bool, overridden: bool, original_value: bool) -> String {
187
- if overridden {
188
- format!("{} (config: {}, flag: {})", final_value.to_string().green(), original_value.to_string().yellow(), final_value.to_string().green())
189
- } else {
190
- format!("{} (config: {})", final_value.to_string().green(), original_value.to_string().yellow())
191
- }
192
- }
193
-
194
- fn format_value_no_flag<T: std::fmt::Display>(value: T) -> String {
195
- format!("{} (config: {})", value.to_string().green(), value.to_string().yellow())
196
- }
197
-
198
- fn format_reserved_elements(elements: &[String]) -> String {
199
- if elements.len() <= 2 {
200
- format!("{:?}", elements)
201
- } else {
202
- let remaining = elements.len() - 2;
203
- format!("[\"component\", \"div\", ... + {} more]", remaining)
204
- }
205
- }
206
-
207
- fn format_list_preview(items: &[String]) -> String {
208
- if items.len() <= 2 {
209
- format!("{:?}", items)
210
- } else {
211
- format!("[{:?}, {:?}, ... + {} more]", items[0], items[1], items.len() - 2)
212
- }
213
- }
214
-
215
- // Alphabetically ordered with padding (longest key is "reservedElements" = 16 chars)
216
- println!(" {}: {}", format!("{:<16}", "assets").cyan(), format_value_no_flag(&config._assets));
217
- println!(" {}: {}", format!("{:<16}", "components").cyan(), format_value_no_flag(&config.components));
218
- println!(" {}: {}", format!("{:<16}", "componentsAsIs").cyan(), format_bool_with_flag(config.components_as_is, overrides.components_as_is, original_components_as_is));
219
- println!(" {}: {}", format!("{:<16}", "elementsAsIs").cyan(), format_bool_with_flag(config.elements_as_is, overrides.elements_as_is, original_elements_as_is));
220
- println!(" {}: {}", format!("{:<16}", "iterationsAsIs").cyan(), format_bool_with_flag(config.iterations_as_is, overrides.iterations_as_is, original_iterations_as_is));
221
- println!(" {}: {}", format!("{:<16}", "minify").cyan(), format_bool_with_flag(config.minify, overrides.minify, original_minify));
222
- println!(" {}: {}", format!("{:<16}", "nodeModulesAsIs").cyan(), format_bool_with_flag(config.node_modules_as_is, overrides.node_modules_as_is, original_node_modules_as_is));
223
- println!(" {}: {}", format!("{:<16}", "output").cyan(), format_value_no_flag(&config._output_str));
224
- println!(" {}: {}", format!("{:<16}", "pages").cyan(), format_value_no_flag(&config.pages));
225
- println!(" {}: {}", format!("{:<16}", "reservedElements").cyan(), format_value_no_flag(format_reserved_elements(&config.reserved_elements)));
226
- println!(" {}: {}", format!("{:<16}", "root").cyan(), format_value_no_flag(config.root.as_ref().map(|s| s.as_str()).unwrap_or("null")));
227
- println!(" {}: {}", format!("{:<16}", "runtimeAsIs").cyan(), format_bool_with_flag(config.runtime_as_is, overrides.runtime_as_is, original_runtime_as_is));
228
- println!(" {}: {}", format!("{:<16}", "skipFiles").cyan(), format_value_no_flag(format_list_preview(&config.skip_files)));
229
- println!(" {}: {}", format!("{:<16}", "source").cyan(), format_value_no_flag(&config._source_str));
230
- println!(" {}: {}", format!("{:<16}", "sourceMaps").cyan(), format_bool_with_flag(config.source_maps, overrides.source_maps, original_source_maps));
231
- println!();
232
- } else {
233
- // Show version in non-verbose mode
234
- println!("{}", format!("Vibe Compiler v{}", env!("CARGO_PKG_VERSION")).cyan());
235
- }
236
-
237
- // Handle watch mode
238
- if args.watch {
239
- if let Err(e) = compiler::watcher::watch(config, args.verbose) {
240
- eprintln!("{}: {}", "Error".red(), e);
241
- std::process::exit(1);
242
- }
243
- return;
244
- }
245
-
246
- // Save runtime_as_is before moving config
247
- let runtime_as_is = config.runtime_as_is;
248
-
249
- let mut compiler = Compiler::new(config, args.verbose);
250
-
251
- match compiler.compile() {
252
- Ok(stats) => {
253
- // Generate manifests BEFORE showing success (unless runtime-as-is is enabled)
254
- let mut manifest_time_ms = 0.0;
255
- let mut manifest_stats_result = None;
256
- if !runtime_as_is {
257
- match compiler.generate_manifests() {
258
- Ok(manifest_stats) => {
259
- manifest_time_ms = manifest_stats.total_time_ms;
260
- manifest_stats_result = Some(manifest_stats);
261
- }
262
- Err(e) => {
263
- eprintln!("\n{}: Manifest generation failed: {}", "Warning".yellow(), e);
264
- eprintln!("Compilation succeeded but manifests were not generated.");
265
- eprintln!("Use --runtime-as-is to skip manifest generation.");
266
- }
267
- }
268
- }
269
-
270
- // Show success headline
271
- println!("\n{}", format!("Compilation successful! (v{}) ✅", env!("CARGO_PKG_VERSION")).green().bold());
272
- println!();
273
-
274
- // Show individual phase timings (validation first, then components, HTML, manifests, copied)
275
-
276
- // Show validation time if it happened
277
- if let Some(validation_time) = stats.validation_time_ms {
278
- println!("* Validated components in {:.0}ms", validation_time);
279
- }
280
-
281
- let total_components_unique = stats.internal_components_unique + stats.external_components_unique;
282
-
283
- // Always show components line
284
- if stats.components_as_is {
285
- println!("* Compiled components (0 internal, 0 external) - \"components-as-is\": true");
286
- } else if total_components_unique > 0 {
287
- println!("* Compiled components ({} internal, {} external) in {:.0}ms",
288
- stats.internal_components_unique,
289
- stats.external_components_unique,
290
- stats.components_time_ms
291
- );
292
- }
293
-
294
- if stats.files_compiled > 0 {
295
- println!("* Compiled HTML ({} file{}) in {:.0}ms",
296
- stats.files_compiled,
297
- if stats.files_compiled == 1 { "" } else { "s" },
298
- stats.compile_time_ms
299
- );
300
- }
301
-
302
- // Show manifest stats between HTML and Copied files
303
- if let Some(manifest_stats) = manifest_stats_result {
304
- println!("* Generated manifests ({} file{}, {} skipped) in {:.0}ms",
305
- manifest_stats.pages_processed,
306
- if manifest_stats.pages_processed == 1 { "" } else { "s" },
307
- manifest_stats.pages_skipped,
308
- manifest_stats.total_time_ms
309
- );
310
- }
311
-
312
- if stats.files_copied > 0 {
313
- println!("* Copied files ({} file{}) in {:.0}ms",
314
- stats.files_copied,
315
- if stats.files_copied == 1 { "" } else { "s" },
316
- stats.copy_time_ms
317
- );
318
- }
319
-
320
- // Show node_modules handling
321
- if let Some(nm_time) = stats.node_modules_time_ms {
322
- if stats.node_modules_copied_as_is {
323
- println!("* copied node_modules in {:.0}ms", nm_time);
324
- } else if let Some(ref pkg_manager) = stats.package_manager {
325
- println!("* {} install in {:.0}ms", pkg_manager, nm_time);
326
- }
327
- }
328
-
329
- // Calculate total duration as sum of all individual operations
330
- let total_duration_ms = stats.validation_time_ms.unwrap_or(0.0)
331
- + stats.components_time_ms
332
- + stats.compile_time_ms
333
- + stats.copy_time_ms
334
- + stats.node_modules_time_ms.unwrap_or(0.0)
335
- + manifest_time_ms;
336
-
337
- println!("\n{} in {:.0}ms", "Compiled".green(), total_duration_ms);
338
-
339
- // Exit explicitly to kill background server thread
340
- std::process::exit(0);
341
- }
342
- Err(e) => {
343
- eprintln!("{}: {}", "Error".red(), e);
344
- std::process::exit(1);
345
- }
346
- }
347
- }
@@ -1,96 +0,0 @@
1
- use std::collections::HashMap;
2
- use std::path::PathBuf;
3
-
4
- /// Represents a parsed Vibe element/component
5
- #[derive(Debug, Clone)]
6
- pub struct Element {
7
- /// Tag name (derived from filename, e.g., "counter" from counter.html)
8
- pub _tag_name: String,
9
- /// Original file path
10
- pub _path: PathBuf,
11
- /// Raw HTML content (the inner content of the element)
12
- pub content: String,
13
- /// Bindings found in this element (@[property])
14
- pub _bindings: Vec<String>,
15
- /// Nested element references (custom tags used within this element)
16
- pub _nested_elements: Vec<String>,
17
- }
18
-
19
- impl Element {
20
- pub fn new(tag_name: String, path: PathBuf, content: String) -> Self {
21
- let bindings = extract_bindings(&content);
22
- let nested_elements = extract_custom_tags(&content);
23
-
24
- Self {
25
- _tag_name: tag_name,
26
- _path: path,
27
- content,
28
- _bindings: bindings,
29
- _nested_elements: nested_elements,
30
- }
31
- }
32
- }
33
-
34
- /// Extract all @[property] bindings from content
35
- fn extract_bindings(content: &str) -> Vec<String> {
36
- let mut bindings = Vec::new();
37
- let re = regex::Regex::new(r"@\[([^\]]+)\]").unwrap();
38
-
39
- for cap in re.captures_iter(content) {
40
- if let Some(m) = cap.get(1) {
41
- let binding = m.as_str().to_string();
42
- if !bindings.contains(&binding) {
43
- bindings.push(binding);
44
- }
45
- }
46
- }
47
-
48
- bindings
49
- }
50
-
51
- /// Extract custom HTML tags (potential element references)
52
- /// Custom tags are lowercase with hyphens, or single-word lowercase non-standard tags
53
- fn extract_custom_tags(content: &str) -> Vec<String> {
54
- let mut tags = Vec::new();
55
- // Match opening tags: <tag-name> or <tagname (self-closing handled too)
56
- let re = regex::Regex::new(r"<([a-z][a-z0-9-]*)[>\s/]").unwrap();
57
-
58
- // Standard HTML5 elements to exclude
59
- let standard_tags: std::collections::HashSet<&str> = [
60
- "a", "abbr", "address", "area", "article", "aside", "audio",
61
- "b", "base", "bdi", "bdo", "blockquote", "body", "br", "button",
62
- "canvas", "caption", "cite", "code", "col", "colgroup",
63
- "data", "datalist", "dd", "del", "details", "dfn", "dialog", "div", "dl", "dt",
64
- "em", "embed",
65
- "fieldset", "figcaption", "figure", "footer", "form",
66
- "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html",
67
- "i", "iframe", "img", "input", "ins",
68
- "kbd",
69
- "label", "legend", "li", "link",
70
- "main", "map", "mark", "menu", "meta", "meter",
71
- "nav", "noscript",
72
- "object", "ol", "optgroup", "option", "output",
73
- "p", "param", "picture", "pre", "progress",
74
- "q",
75
- "rp", "rt", "ruby",
76
- "s", "samp", "script", "search", "section", "select", "slot", "small", "source", "span", "strong", "style", "sub", "summary", "sup", "svg",
77
- "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track",
78
- "u", "ul",
79
- "var", "video",
80
- "wbr",
81
- ].into_iter().collect();
82
-
83
- for cap in re.captures_iter(content) {
84
- if let Some(m) = cap.get(1) {
85
- let tag = m.as_str().to_string();
86
- if !standard_tags.contains(tag.as_str()) && !tags.contains(&tag) {
87
- tags.push(tag);
88
- }
89
- }
90
- }
91
-
92
- tags
93
- }
94
-
95
- /// Cache of loaded elements by tag name
96
- pub type ElementCache = HashMap<String, Element>;