@ape-egg/vibe 1.0.5 → 1.1.2

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 (35) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +228 -23
  3. package/compiler/bin/vibe-compile.js +109 -0
  4. package/compiler/native/.gitkeep +0 -0
  5. package/compiler/native/vibe-compiler-darwin-arm64 +0 -0
  6. package/compiler/src/Cargo.lock +1885 -0
  7. package/compiler/src/Cargo.toml +29 -0
  8. package/compiler/src/compiler/compile.rs +1209 -0
  9. package/compiler/src/compiler/mod.rs +5 -0
  10. package/compiler/src/config.rs +184 -0
  11. package/compiler/src/main.rs +284 -0
  12. package/compiler/src/parser/element.rs +96 -0
  13. package/compiler/src/parser/html.rs +335 -0
  14. package/compiler/src/parser/mod.rs +8 -0
  15. package/index.js +2 -248
  16. package/package.json +26 -3
  17. package/{affected.js → runtime/affected.js} +64 -4
  18. package/runtime/cleanup.js +59 -0
  19. package/runtime/component.js +116 -0
  20. package/{conditionals.js → runtime/conditionals.js} +25 -11
  21. package/{constants.js → runtime/constants.js} +23 -3
  22. package/runtime/debug.js +91 -0
  23. package/{hydrate.js → runtime/hydrate.js} +57 -7
  24. package/runtime/index.js +614 -0
  25. package/{iterate.js → runtime/iterate.js} +53 -45
  26. package/{iteration-utils.js → runtime/iteration-utils.js} +11 -1
  27. package/{parse.js → runtime/parse.js} +37 -7
  28. package/runtime/state.js +52 -0
  29. package/ROADMAP.md +0 -289
  30. package/llms.txt +0 -279
  31. package/state.js +0 -26
  32. /package/{_vibe-compiled-iteration-batch.js → runtime/_vibe-compiled-iteration-batch.js} +0 -0
  33. /package/{link.js → runtime/manifest.js} +0 -0
  34. /package/{utils.js → runtime/utils.js} +0 -0
  35. /package/{vibe.css → runtime/vibe.css} +0 -0
@@ -0,0 +1,5 @@
1
+ pub mod compile;
2
+
3
+ pub use compile::Compiler;
4
+ #[allow(unused_imports)]
5
+ pub use compile::CompileStats;
@@ -0,0 +1,184 @@
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 accessibility: bool,
33
+ #[serde(default)]
34
+ pub manifest: bool,
35
+ #[serde(default)]
36
+ pub validate: bool,
37
+ #[serde(default)]
38
+ pub source_maps: bool,
39
+ #[serde(default)]
40
+ pub exclude_tags: Vec<String>,
41
+ #[serde(default)]
42
+ pub node_modules_as_is: bool,
43
+ #[serde(default)]
44
+ pub components_as_is: bool,
45
+ }
46
+
47
+ fn default_source() -> String { "./".to_string() }
48
+ fn default_output() -> String { "./compiled".to_string() }
49
+ fn default_components() -> String { "components".to_string() }
50
+ fn default_pages() -> String { "pages".to_string() }
51
+ fn default_assets() -> String { "assets".to_string() }
52
+
53
+ impl Default for VibeCompilerConfig {
54
+ fn default() -> Self {
55
+ Self {
56
+ source: default_source(),
57
+ output: default_output(),
58
+ components: default_components(),
59
+ pages: default_pages(),
60
+ assets: default_assets(),
61
+ root: None,
62
+ minify: false,
63
+ accessibility: false,
64
+ manifest: false,
65
+ validate: false,
66
+ source_maps: false,
67
+ exclude_tags: vec![],
68
+ node_modules_as_is: false,
69
+ components_as_is: false,
70
+ }
71
+ }
72
+ }
73
+
74
+ #[derive(Debug, Clone)]
75
+ pub struct Config {
76
+ pub source: PathBuf,
77
+ pub output: PathBuf,
78
+ pub _source_str: String, // Original config value for display
79
+ pub _output_str: String, // Original config value for display
80
+ pub components: String,
81
+ pub pages: String,
82
+ pub _assets: String,
83
+ pub _root: Option<String>,
84
+ pub minify: bool,
85
+ pub accessibility: bool,
86
+ pub manifest: bool,
87
+ pub validate: bool,
88
+ pub source_maps: bool,
89
+ pub exclude_tags: Vec<String>,
90
+ pub node_modules_as_is: bool,
91
+ pub components_as_is: bool,
92
+ pub working_dir: PathBuf,
93
+ }
94
+
95
+ impl Config {
96
+ /// Load config from package.json, or use defaults if not found
97
+ pub fn load(working_dir: PathBuf) -> Self {
98
+ let package_path = working_dir.join("package.json");
99
+
100
+ let config = if package_path.exists() {
101
+ fs::read_to_string(&package_path)
102
+ .ok()
103
+ .and_then(|content| serde_json::from_str::<serde_json::Value>(&content).ok())
104
+ .and_then(|package| package.get("vibe-compiler").cloned())
105
+ .and_then(|vibe_config| serde_json::from_value::<VibeCompilerConfig>(vibe_config).ok())
106
+ .unwrap_or_default()
107
+ } else {
108
+ VibeCompilerConfig::default()
109
+ };
110
+
111
+ let source_str = config.source.clone();
112
+ let output_str = config.output.clone();
113
+ let source = working_dir.join(&config.source);
114
+ let output = working_dir.join(&config.output);
115
+
116
+ Self {
117
+ source,
118
+ output,
119
+ _source_str: source_str,
120
+ _output_str: output_str,
121
+ components: config.components,
122
+ pages: config.pages,
123
+ _assets: config.assets,
124
+ _root: config.root,
125
+ minify: config.minify,
126
+ accessibility: config.accessibility,
127
+ manifest: config.manifest,
128
+ validate: config.validate,
129
+ source_maps: config.source_maps,
130
+ exclude_tags: config.exclude_tags,
131
+ node_modules_as_is: config.node_modules_as_is,
132
+ components_as_is: config.components_as_is,
133
+ working_dir,
134
+ }
135
+ }
136
+
137
+ pub fn components_path(&self) -> PathBuf {
138
+ self.source.join(&self.components)
139
+ }
140
+
141
+ pub fn _pages_path(&self) -> PathBuf {
142
+ self.source.join(&self.pages)
143
+ }
144
+
145
+ pub fn _assets_path(&self) -> PathBuf {
146
+ self.source.join(&self._assets)
147
+ }
148
+ }
149
+
150
+ pub fn init_config(working_dir: &PathBuf) -> Result<(), ConfigError> {
151
+ let package_path = working_dir.join("package.json");
152
+
153
+ if !package_path.exists() {
154
+ // Create minimal package.json with vibe-compiler config
155
+ let config = VibeCompilerConfig::default();
156
+ let package = serde_json::json!({
157
+ "vibe-compiler": config
158
+ });
159
+ let formatted = serde_json::to_string_pretty(&package)?;
160
+ fs::write(&package_path, formatted)?;
161
+ println!("Created package.json with vibe-compiler config");
162
+ return Ok(());
163
+ }
164
+
165
+ let content = fs::read_to_string(&package_path)?;
166
+ let mut package: serde_json::Value = serde_json::from_str(&content)?;
167
+
168
+ // Don't overwrite existing config
169
+ if package.get("vibe-compiler").is_some() {
170
+ println!("vibe-compiler config already exists in package.json");
171
+ return Ok(());
172
+ }
173
+
174
+ let default_config = VibeCompilerConfig::default();
175
+ let config_value = serde_json::to_value(&default_config)?;
176
+
177
+ package["vibe-compiler"] = config_value;
178
+
179
+ let formatted = serde_json::to_string_pretty(&package)?;
180
+ fs::write(&package_path, formatted)?;
181
+
182
+ println!("Added vibe-compiler config to package.json");
183
+ Ok(())
184
+ }
@@ -0,0 +1,284 @@
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
+ use std::time::Instant;
9
+
10
+ use config::Config;
11
+ use compiler::compile::CompileError;
12
+ use compiler::Compiler;
13
+
14
+ /// Track which config values were overridden by flags
15
+ #[derive(Debug, Default)]
16
+ struct ConfigOverrides {
17
+ minify: bool,
18
+ accessibility: bool,
19
+ manifest: bool,
20
+ validate: bool,
21
+ source_maps: bool,
22
+ node_modules_as_is: bool,
23
+ components_as_is: bool,
24
+ }
25
+
26
+ /// Vibe Compiler - Compiles Vibe source files into optimized output
27
+ #[derive(ClapParser, Debug)]
28
+ #[command(name = "vibe-compile")]
29
+ #[command(author = "Kim Korte")]
30
+ #[command(version = "0.1.0")]
31
+ #[command(about = "Compiles Vibe source files into optimized output")]
32
+ struct Args {
33
+ /// Working directory (defaults to current directory)
34
+ #[arg(long)]
35
+ cwd: Option<PathBuf>,
36
+
37
+ /// Watch for file changes
38
+ #[arg(long)]
39
+ watch: bool,
40
+
41
+ /// Minify output
42
+ #[arg(long)]
43
+ minify: bool,
44
+
45
+ /// Enable verbose logging with step-by-step output
46
+ #[arg(long)]
47
+ verbose: bool,
48
+
49
+ /// Transform custom HTML elements to divs with classes for accessibility
50
+ #[arg(long)]
51
+ accessibility: bool,
52
+
53
+ /// Generate hydration manifest
54
+ #[arg(long, name = "create-manifest")]
55
+ create_manifest: bool,
56
+
57
+ /// Generate source maps
58
+ #[arg(long, name = "source-maps")]
59
+ source_maps: bool,
60
+
61
+ /// Validate HTML syntax
62
+ #[arg(long)]
63
+ validate: bool,
64
+
65
+ /// Initialize configuration in package.json
66
+ #[arg(long)]
67
+ init: bool,
68
+
69
+ /// Custom source directory (overrides config)
70
+ #[arg(long)]
71
+ source: Option<PathBuf>,
72
+
73
+ /// Custom output directory (overrides config)
74
+ #[arg(long)]
75
+ output: Option<PathBuf>,
76
+
77
+ /// Copy node_modules as-is without production-only install (overrides config)
78
+ #[arg(long, name = "node-modules-as-is")]
79
+ node_modules_as_is: bool,
80
+
81
+ /// Keep components directory and <component> tags as-is (don't inline components)
82
+ #[arg(long, name = "components-as-is")]
83
+ components_as_is: bool,
84
+ }
85
+
86
+ fn main() {
87
+ let args = Args::parse();
88
+ let start = Instant::now();
89
+
90
+ // Determine working directory
91
+ let working_dir = args.cwd
92
+ .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
93
+
94
+ if args.init {
95
+ match config::init_config(&working_dir) {
96
+ Ok(_) => {
97
+ return;
98
+ }
99
+ Err(e) => {
100
+ eprintln!("{}: {}", "Error".red(), e);
101
+ std::process::exit(1);
102
+ }
103
+ }
104
+ }
105
+
106
+ // Load config from package.json (uses defaults if not found)
107
+ let mut config = Config::load(working_dir);
108
+
109
+ // Override config with CLI args
110
+ if let Some(source) = args.source {
111
+ config.source = config.working_dir.join(source);
112
+ }
113
+ if let Some(output) = args.output {
114
+ config.output = config.working_dir.join(output);
115
+ }
116
+
117
+ // Store original config values before applying flag overrides
118
+ let original_minify = config.minify;
119
+ let original_accessibility = config.accessibility;
120
+ let original_manifest = config.manifest;
121
+ let original_validate = config.validate;
122
+ let original_source_maps = config.source_maps;
123
+ let original_node_modules_as_is = config.node_modules_as_is;
124
+ let original_components_as_is = config.components_as_is;
125
+
126
+ // Track overrides for verbose output
127
+ let mut overrides = ConfigOverrides::default();
128
+
129
+ if args.minify {
130
+ overrides.minify = !original_minify;
131
+ config.minify = true;
132
+ }
133
+ if args.accessibility {
134
+ overrides.accessibility = !original_accessibility;
135
+ config.accessibility = true;
136
+ }
137
+ if args.create_manifest {
138
+ overrides.manifest = !original_manifest;
139
+ config.manifest = true;
140
+ }
141
+ if args.validate {
142
+ overrides.validate = !original_validate;
143
+ config.validate = true;
144
+ }
145
+ if args.source_maps {
146
+ overrides.source_maps = !original_source_maps;
147
+ config.source_maps = true;
148
+ }
149
+ if args.node_modules_as_is {
150
+ overrides.node_modules_as_is = !original_node_modules_as_is;
151
+ config.node_modules_as_is = true;
152
+ }
153
+ if args.components_as_is {
154
+ overrides.components_as_is = !original_components_as_is;
155
+ config.components_as_is = true;
156
+ }
157
+
158
+ if args.verbose {
159
+ println!("{}", "Vibe Compiler".cyan().bold());
160
+ println!();
161
+ println!(" {}: {}", "Working dir".cyan(), config.working_dir.display());
162
+ println!(" {}: {}", "Source".cyan(), config.source.display());
163
+ println!(" {}: {}", "Output".cyan(), config.output.display());
164
+ println!(" {}: {}", "Components".cyan(), config.components);
165
+ println!(" {}: {}", "Pages".cyan(), config.pages);
166
+ println!(" {}: {}", "Assets".cyan(), config._assets);
167
+ println!();
168
+
169
+ // Show config values (alphabetically ordered)
170
+ println!("{}", "Compiler config".cyan().bold());
171
+ println!();
172
+ println!(" CLI flags override package.json config");
173
+ println!();
174
+
175
+ fn format_bool_with_flag(final_value: bool, overridden: bool, original_value: bool) -> String {
176
+ if overridden {
177
+ format!("{} (config: {}, flag: {})", final_value.to_string().green(), original_value.to_string().yellow(), final_value.to_string().green())
178
+ } else {
179
+ format!("{} (config: {})", final_value.to_string().green(), original_value.to_string().yellow())
180
+ }
181
+ }
182
+
183
+ fn format_value_no_flag<T: std::fmt::Display>(value: T) -> String {
184
+ format!("{} (config: {})", value.to_string().green(), value.to_string().yellow())
185
+ }
186
+
187
+ // Alphabetically ordered with padding (longest key is "node-modules-as-is" = 18 chars)
188
+ println!(" {}: {}", format!("{:<18}", "accessibility").cyan(), format_bool_with_flag(config.accessibility, overrides.accessibility, original_accessibility));
189
+ println!(" {}: {}", format!("{:<18}", "assets").cyan(), format_value_no_flag(&config._assets));
190
+ println!(" {}: {}", format!("{:<18}", "components").cyan(), format_value_no_flag(&config.components));
191
+ println!(" {}: {}", format!("{:<18}", "components-as-is").cyan(), format_bool_with_flag(config.components_as_is, overrides.components_as_is, original_components_as_is));
192
+ println!(" {}: {}", format!("{:<18}", "exclude-tags").cyan(), format_value_no_flag(format!("{:?}", config.exclude_tags)));
193
+ println!(" {}: {}", format!("{:<18}", "manifest").cyan(), format_bool_with_flag(config.manifest, overrides.manifest, original_manifest));
194
+ println!(" {}: {}", format!("{:<18}", "minify").cyan(), format_bool_with_flag(config.minify, overrides.minify, original_minify));
195
+ println!(" {}: {}", format!("{:<18}", "node-modules-as-is").cyan(), format_bool_with_flag(config.node_modules_as_is, overrides.node_modules_as_is, original_node_modules_as_is));
196
+ println!(" {}: {}", format!("{:<18}", "output").cyan(), format_value_no_flag(&config._output_str));
197
+ println!(" {}: {}", format!("{:<18}", "pages").cyan(), format_value_no_flag(&config.pages));
198
+ println!(" {}: {}", format!("{:<18}", "root").cyan(), format_value_no_flag(config._root.as_ref().map(|s| s.as_str()).unwrap_or("null")));
199
+ println!(" {}: {}", format!("{:<18}", "source").cyan(), format_value_no_flag(&config._source_str));
200
+ println!(" {}: {}", format!("{:<18}", "source-maps").cyan(), format_bool_with_flag(config.source_maps, overrides.source_maps, original_source_maps));
201
+ println!(" {}: {}", format!("{:<18}", "validate").cyan(), format_bool_with_flag(config.validate, overrides.validate, original_validate));
202
+ println!();
203
+ }
204
+
205
+ let mut compiler = Compiler::new(config, args.verbose);
206
+
207
+ match compiler.compile() {
208
+ Ok(stats) => {
209
+ let duration = start.elapsed();
210
+
211
+ // Show success headline
212
+ println!("\n{}", "Compilation successful! ✅".green().bold());
213
+ println!();
214
+
215
+ // Show individual phase timings (validation first, then components, HTML, copied)
216
+
217
+ // Show validation time if it happened
218
+ if let Some(validation_time) = stats.validation_time_ms {
219
+ println!("* Validated components in {:.0}ms", validation_time);
220
+ }
221
+
222
+ let total_components_unique = stats.internal_components_unique + stats.external_components_unique;
223
+
224
+ // Always show components line
225
+ if stats.components_as_is {
226
+ println!("* Compiled components (0 internal, 0 external) - \"components-as-is\": true");
227
+ } else if total_components_unique > 0 {
228
+ println!("* Compiled components ({} internal, {} external) in {:.0}ms",
229
+ stats.internal_components_unique,
230
+ stats.external_components_unique,
231
+ stats.components_time_ms
232
+ );
233
+ }
234
+
235
+ if stats.files_compiled > 0 {
236
+ println!("* Compiled HTML ({} file{}) in {:.0}ms",
237
+ stats.files_compiled,
238
+ if stats.files_compiled == 1 { "" } else { "s" },
239
+ stats.compile_time_ms
240
+ );
241
+ }
242
+
243
+ if stats.files_copied > 0 {
244
+ println!("* Copied files ({} file{}) in {:.0}ms",
245
+ stats.files_copied,
246
+ if stats.files_copied == 1 { "" } else { "s" },
247
+ stats.copy_time_ms
248
+ );
249
+ }
250
+
251
+ // Show node_modules handling
252
+ if let Some(nm_time) = stats.node_modules_time_ms {
253
+ if stats.node_modules_copied_as_is {
254
+ println!("* copied node_modules in {:.0}ms", nm_time);
255
+ } else if let Some(ref pkg_manager) = stats.package_manager {
256
+ println!("* {} install in {:.0}ms", pkg_manager, nm_time);
257
+ }
258
+ }
259
+
260
+ println!("\n{} in {:.0}ms", "Compiled".green(), duration.as_secs_f64() * 1000.0);
261
+ }
262
+ Err(e) => {
263
+ match e {
264
+ CompileError::ComponentValidationFailed(errors) => {
265
+ eprintln!("\n{}: Component validation failed\n", "Error".red());
266
+ for err in errors {
267
+ eprintln!(" {} {}", "✗".red(), err.component_src);
268
+ eprintln!(" Referenced in: {}", err.referenced_in);
269
+ eprintln!(" Error: {}\n", err.error_message);
270
+ }
271
+ eprintln!("Fix the errors above and try again.");
272
+ }
273
+ _ => {
274
+ eprintln!("{}: {}", "Error".red(), e);
275
+ }
276
+ }
277
+ std::process::exit(1);
278
+ }
279
+ }
280
+
281
+ if args.watch {
282
+ println!("{}", "Watch mode not yet implemented".yellow());
283
+ }
284
+ }
@@ -0,0 +1,96 @@
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>;