@ape-egg/vibe-compiler 4.3.25 → 4.3.27
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-linux-x64 +0 -0
- package/package.json +1 -1
- package/src/Cargo.lock +2032 -0
- package/src/Cargo.toml +1 -1
- package/src/compiler/compile.rs +161 -165
- package/src/compiler/value_stamper.rs +115 -71
- package/src/compiler/watcher.rs +6 -3
- package/src/config.rs +10 -0
- package/src/main.rs +7 -2
- package/src/parser/html.rs +1 -1
|
@@ -1,35 +1,20 @@
|
|
|
1
1
|
use regex::{Regex, Captures};
|
|
2
2
|
use serde_json::{Map, Value};
|
|
3
3
|
use rquickjs::{Context, Runtime};
|
|
4
|
-
use std::collections::
|
|
5
|
-
|
|
6
|
-
/// Render a primitive state value the way an evaluated `@[expr]` would print it:
|
|
7
|
-
/// strings/numbers/bools as their text, null as empty. Mirrors `eval_with_state`.
|
|
8
|
-
fn primitive_to_text(value: &Value) -> String {
|
|
9
|
-
match value {
|
|
10
|
-
Value::String(s) => s.clone(),
|
|
11
|
-
Value::Number(n) => n.to_string(),
|
|
12
|
-
Value::Bool(b) => b.to_string(),
|
|
13
|
-
Value::Null => String::new(),
|
|
14
|
-
// Constants are primitive-only (see reassignment_analyzer); fall back safely.
|
|
15
|
-
other => other.to_string(),
|
|
16
|
-
}
|
|
17
|
-
}
|
|
4
|
+
use std::collections::HashSet;
|
|
18
5
|
|
|
19
6
|
use crate::constants::{NON_REACTIVE_ELEMENTS, VALUE_ATTRS, DEHYDRATE_ATTR, NAMEBIND_ATTR, DEFER_ATTR_PREFIX};
|
|
7
|
+
use crate::parser::html::decode_entities;
|
|
20
8
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
pub struct ValueStamper {
|
|
10
|
+
/// Stamp state plus the global keys reassignment analysis proved constant.
|
|
11
|
+
/// A dynamic global is never in here, so any expression reading one throws
|
|
12
|
+
/// a ReferenceError in QuickJS and its binding stays live for the runtime.
|
|
13
|
+
state: Value,
|
|
24
14
|
binding_regex: Regex,
|
|
25
15
|
_runtime: Runtime, // Must be kept alive for context to work
|
|
26
16
|
context: Context,
|
|
27
17
|
components_as_is: bool,
|
|
28
|
-
/// Global state keys proven constant by reassignment analysis, mapped to the
|
|
29
|
-
/// text they stamp to. Only an `@[key]` binding whose expression is EXACTLY
|
|
30
|
-
/// one of these keys is stamped from here — never a compound expression — so
|
|
31
|
-
/// there is no risk of a dynamic operand leaking into the value.
|
|
32
|
-
constant_texts: HashMap<String, String>,
|
|
33
18
|
/// Selective mode (the SPA shell): unresolvable state is runtime-owned, so
|
|
34
19
|
/// unresolved attribute markers are kept instead of stripped and a
|
|
35
20
|
/// conditional whose expression reads an unknown root stays raw (both
|
|
@@ -37,47 +22,46 @@ pub struct ValueStamper<'a> {
|
|
|
37
22
|
selective: bool,
|
|
38
23
|
}
|
|
39
24
|
|
|
40
|
-
impl
|
|
41
|
-
pub fn new(state: &
|
|
25
|
+
impl ValueStamper {
|
|
26
|
+
pub fn new(state: &Value, components_as_is: bool) -> Result<Self, String> {
|
|
42
27
|
Self::with_constants(state, components_as_is, &Map::new())
|
|
43
28
|
}
|
|
44
29
|
|
|
45
30
|
pub fn with_constants(
|
|
46
|
-
state: &
|
|
31
|
+
state: &Value,
|
|
47
32
|
components_as_is: bool,
|
|
48
33
|
constants: &Map<String, Value>,
|
|
49
34
|
) -> Result<Self, String> {
|
|
35
|
+
let mut state = state.clone();
|
|
36
|
+
state.as_object_mut().expect("stamp state is an object").extend(constants.clone());
|
|
37
|
+
|
|
50
38
|
let runtime = Runtime::new().map_err(|e| format!("Failed to create QuickJS runtime: {:?}", e))?;
|
|
51
39
|
let context = Context::full(&runtime).map_err(|e| format!("Failed to create QuickJS context: {:?}", e))?;
|
|
52
40
|
|
|
53
41
|
// Set up state ONCE when creating the stamper (not per expression)
|
|
54
42
|
context.with(|ctx| -> Result<(), String> {
|
|
55
|
-
let state_str = serde_json::to_string(state)
|
|
43
|
+
let state_str = serde_json::to_string(&state)
|
|
56
44
|
.map_err(|e| format!("Failed to serialize state: {}", e))?;
|
|
57
45
|
// Expose state both as bare globals (via Object.assign) and as a
|
|
58
46
|
// persistent `$` global carrying the runtime `unsafe` helper, so a
|
|
59
47
|
// `@[$.unsafe(expr)]` binding stamps its trusted string raw at build
|
|
60
|
-
// time — mirroring the runtime, where `$.unsafe`
|
|
48
|
+
// time — mirroring the runtime, where `$.unsafe` wraps it in RawHtml.
|
|
61
49
|
ctx.eval::<(), _>(format!(
|
|
62
|
-
"const $ = {}; Object.assign(globalThis, $);
|
|
50
|
+
"const $ = {}; Object.assign(globalThis, $); \
|
|
51
|
+
globalThis.RawHtml = class {{ constructor(html) {{ this.html = html == null ? '' : String(html) }} }}; \
|
|
52
|
+
$.unsafe = (html) => new RawHtml(html); globalThis.$ = $",
|
|
63
53
|
state_str
|
|
64
54
|
))
|
|
65
55
|
.map_err(|e| format!("Failed to set up state in QuickJS: {:?}", e))?;
|
|
66
56
|
Ok(())
|
|
67
57
|
})?;
|
|
68
58
|
|
|
69
|
-
let constant_texts = constants
|
|
70
|
-
.iter()
|
|
71
|
-
.map(|(key, value)| (key.clone(), primitive_to_text(value)))
|
|
72
|
-
.collect();
|
|
73
|
-
|
|
74
59
|
Ok(Self {
|
|
75
60
|
state,
|
|
76
61
|
binding_regex: Regex::new(r"@\[((?:[^\[\]]|\[[^\]]*\])+)\]").unwrap(),
|
|
77
62
|
_runtime: runtime,
|
|
78
63
|
context,
|
|
79
64
|
components_as_is,
|
|
80
|
-
constant_texts,
|
|
81
65
|
selective: false,
|
|
82
66
|
})
|
|
83
67
|
}
|
|
@@ -107,12 +91,40 @@ impl<'a> ValueStamper<'a> {
|
|
|
107
91
|
// Then, render conditionals (resolve if/else branches)
|
|
108
92
|
let html = self.render_conditionals(html)?;
|
|
109
93
|
|
|
94
|
+
// Stamp bindings only in regions that are NOT in skip_regions
|
|
95
|
+
let (mut result, unresolved) = self.stamp_bindings_tracked(&html, &self.skip_regions(&html));
|
|
96
|
+
|
|
97
|
+
// Clean up attributes that still contain unresolved markers. Selective
|
|
98
|
+
// mode keeps them: an unresolved marker there is runtime-owned state
|
|
99
|
+
// (the outlet's `src="@[page.src]"`), not an absent iteration property.
|
|
100
|
+
if !self.selective {
|
|
101
|
+
result = self.cleanup_unresolved_attributes(result, &unresolved);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Restore vibe-dehydrate content from placeholders
|
|
105
|
+
for (i, content) in dehydrate_placeholders.iter().enumerate() {
|
|
106
|
+
let placeholder = format!("<!--VIBE_DEHYDRATE_PLACEHOLDER_{}-->", i);
|
|
107
|
+
result = result.replace(&placeholder, content);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Finally, clean up boolean-like attributes with falsy values
|
|
111
|
+
// When components_as_is is true, skip cleanup inside component elements.
|
|
112
|
+
// Regions are measured again on the stamped HTML: stamping changes
|
|
113
|
+
// lengths, so the pre-stamp offsets point at the wrong attributes.
|
|
114
|
+
let skip_regions = self.skip_regions(&result);
|
|
115
|
+
Ok(self.cleanup_boolean_attributes(result, &skip_regions))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Byte ranges of the regions stamping and cleanup leave untouched:
|
|
119
|
+
/// non-reactive elements, vibe-dehydrate templates and, when
|
|
120
|
+
/// components_as_is is true, component elements.
|
|
121
|
+
fn skip_regions(&self, html: &str) -> Vec<(usize, usize)> {
|
|
110
122
|
// Find all NON_REACTIVE_ELEMENT regions to skip
|
|
111
123
|
let mut skip_regions: Vec<(usize, usize)> = Vec::new();
|
|
112
124
|
|
|
113
125
|
for element_type in NON_REACTIVE_ELEMENTS {
|
|
114
126
|
let regex = Regex::new(&format!(r"(?s)<{}[^>]*>.*?</{}>", element_type, element_type)).unwrap();
|
|
115
|
-
for mat in regex.find_iter(
|
|
127
|
+
for mat in regex.find_iter(html) {
|
|
116
128
|
skip_regions.push((mat.start(), mat.end()));
|
|
117
129
|
}
|
|
118
130
|
}
|
|
@@ -120,7 +132,7 @@ impl<'a> ValueStamper<'a> {
|
|
|
120
132
|
// Skip elements with vibe-dehydrate attribute (content should not be processed)
|
|
121
133
|
// This is a secondary protection in case placeholders aren't used
|
|
122
134
|
let dehydrate_regex = Regex::new(&format!(r"(?s)<template[^>]*\s{}[^>]*>.*?</template>", DEHYDRATE_ATTR)).unwrap();
|
|
123
|
-
for mat in dehydrate_regex.find_iter(
|
|
135
|
+
for mat in dehydrate_regex.find_iter(html) {
|
|
124
136
|
skip_regions.push((mat.start(), mat.end()));
|
|
125
137
|
}
|
|
126
138
|
|
|
@@ -129,55 +141,40 @@ impl<'a> ValueStamper<'a> {
|
|
|
129
141
|
if self.components_as_is {
|
|
130
142
|
// Match <component ...>...</component>
|
|
131
143
|
let component_regex = Regex::new(r"(?s)<component[^>]*>.*?</component>").unwrap();
|
|
132
|
-
for mat in component_regex.find_iter(
|
|
144
|
+
for mat in component_regex.find_iter(html) {
|
|
133
145
|
skip_regions.push((mat.start(), mat.end()));
|
|
134
146
|
}
|
|
135
147
|
|
|
136
148
|
// Match <div class="component" ...>...</div>
|
|
137
149
|
let div_component_regex = Regex::new(r#"(?s)<div\s+class="component"[^>]*>.*?</div>"#).unwrap();
|
|
138
|
-
for mat in div_component_regex.find_iter(
|
|
150
|
+
for mat in div_component_regex.find_iter(html) {
|
|
139
151
|
skip_regions.push((mat.start(), mat.end()));
|
|
140
152
|
}
|
|
141
153
|
}
|
|
142
154
|
|
|
143
155
|
// Sort and merge overlapping regions
|
|
144
156
|
skip_regions.sort_by_key(|r| r.0);
|
|
145
|
-
|
|
146
|
-
// Stamp bindings only in regions that are NOT in skip_regions
|
|
147
|
-
let (mut result, unresolved) = self.stamp_bindings_tracked(&html, &skip_regions);
|
|
148
|
-
|
|
149
|
-
// Clean up attributes that still contain unresolved markers. Selective
|
|
150
|
-
// mode keeps them: an unresolved marker there is runtime-owned state
|
|
151
|
-
// (the outlet's `src="@[page.src]"`), not an absent iteration property.
|
|
152
|
-
if !self.selective {
|
|
153
|
-
result = self.cleanup_unresolved_attributes(result, &unresolved);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Restore vibe-dehydrate content from placeholders
|
|
157
|
-
for (i, content) in dehydrate_placeholders.iter().enumerate() {
|
|
158
|
-
let placeholder = format!("<!--VIBE_DEHYDRATE_PLACEHOLDER_{}-->", i);
|
|
159
|
-
result = result.replace(&placeholder, content);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Finally, clean up boolean-like attributes with falsy values
|
|
163
|
-
// When components_as_is is true, skip cleanup inside component elements
|
|
164
|
-
Ok(self.cleanup_boolean_attributes(result, &skip_regions))
|
|
157
|
+
skip_regions
|
|
165
158
|
}
|
|
166
159
|
|
|
167
160
|
/// Evaluate a JavaScript expression with the current state
|
|
168
161
|
fn eval_expression(&self, expr: &str) -> Option<String> {
|
|
169
|
-
self.eval_with_state(expr, self.state)
|
|
162
|
+
self.eval_with_state(expr, &self.state)
|
|
170
163
|
}
|
|
171
164
|
|
|
172
165
|
/// Evaluate a JavaScript expression with custom state (for nested iterations)
|
|
166
|
+
/// into the HTML it stamps: escaped text, or the raw markup of `$.unsafe`.
|
|
173
167
|
fn eval_with_state(&self, expr: &str, _state: &Value) -> Option<String> {
|
|
174
168
|
// State is already set up in the context during new(), just evaluate the expression
|
|
175
169
|
self.context.with(|ctx| -> Option<String> {
|
|
176
|
-
// Evaluate expression and convert to string
|
|
177
170
|
let result: rquickjs::Value = ctx.eval(expr).ok()?;
|
|
171
|
+
let raw_html: rquickjs::Value = ctx.globals().get("RawHtml").ok()?;
|
|
172
|
+
if let Some(raw) = result.as_object().filter(|o| o.is_instance_of(&raw_html)) {
|
|
173
|
+
return raw.get("html").ok();
|
|
174
|
+
}
|
|
178
175
|
|
|
179
176
|
// Convert result to string
|
|
180
|
-
if result.is_string() {
|
|
177
|
+
let text = if result.is_string() {
|
|
181
178
|
result.as_string().and_then(|s| s.to_string().ok())
|
|
182
179
|
} else if result.is_number() {
|
|
183
180
|
result.as_number().map(|n| n.to_string())
|
|
@@ -191,7 +188,8 @@ impl<'a> ValueStamper<'a> {
|
|
|
191
188
|
// For objects/arrays, convert to JSON
|
|
192
189
|
ctx.json_stringify(result).ok()
|
|
193
190
|
.and_then(|v| v.and_then(|s| s.to_string().ok()))
|
|
194
|
-
}
|
|
191
|
+
};
|
|
192
|
+
text.map(|text| escape_html(&text))
|
|
195
193
|
})
|
|
196
194
|
}
|
|
197
195
|
|
|
@@ -301,7 +299,7 @@ impl<'a> ValueStamper<'a> {
|
|
|
301
299
|
|
|
302
300
|
context.with(|ctx| -> Option<Vec<Value>> {
|
|
303
301
|
// Set state properties in global scope
|
|
304
|
-
let state_str = serde_json::to_string(self.state).ok()?;
|
|
302
|
+
let state_str = serde_json::to_string(&self.state).ok()?;
|
|
305
303
|
ctx.eval::<(), _>(format!("const $ = {}; Object.assign(globalThis, $)", state_str)).ok()?;
|
|
306
304
|
|
|
307
305
|
// Evaluate path to get array
|
|
@@ -683,14 +681,10 @@ impl<'a> ValueStamper<'a> {
|
|
|
683
681
|
result.push_str(binding_match.as_str());
|
|
684
682
|
} else {
|
|
685
683
|
let caps = self.binding_regex.captures(binding_match.as_str()).unwrap();
|
|
686
|
-
|
|
687
|
-
//
|
|
688
|
-
//
|
|
689
|
-
|
|
690
|
-
// and the binding is left live for the runtime.
|
|
691
|
-
if let Some(text) = self.constant_texts.get(expr.trim()) {
|
|
692
|
-
result.push_str(text);
|
|
693
|
-
} else if let Some(stamped) = self.eval_expression(expr) {
|
|
684
|
+
// The HTML is serialized, so `=>` in a text binding reads `=>`
|
|
685
|
+
// and `"` in an attribute binding reads `"` — decode back to
|
|
686
|
+
// the JS the runtime evaluates.
|
|
687
|
+
if let Some(stamped) = self.eval_expression(&decode_entities(&caps[1])) {
|
|
694
688
|
result.push_str(&stamped);
|
|
695
689
|
} else {
|
|
696
690
|
unresolved.push(result.len());
|
|
@@ -791,6 +785,10 @@ impl<'a> ValueStamper<'a> {
|
|
|
791
785
|
}
|
|
792
786
|
}
|
|
793
787
|
|
|
788
|
+
fn escape_html(text: &str) -> String {
|
|
789
|
+
text.replace('&', "&").replace('<', "<").replace('>', ">").replace('"', """)
|
|
790
|
+
}
|
|
791
|
+
|
|
794
792
|
#[cfg(test)]
|
|
795
793
|
mod tests {
|
|
796
794
|
use super::*;
|
|
@@ -888,6 +886,16 @@ mod tests {
|
|
|
888
886
|
assert_eq!(result, "<p><em>x</em></p>");
|
|
889
887
|
}
|
|
890
888
|
|
|
889
|
+
#[test]
|
|
890
|
+
fn stamp_escapes_quotes_and_angle_brackets() {
|
|
891
|
+
let state = json!({ "snippet": r#"<component src="/card.html"></component> & more"# });
|
|
892
|
+
let stamper = ValueStamper::new(&state, false).unwrap();
|
|
893
|
+
let html = String::from(r#"<copy-button data-value="@[snippet]">@[snippet]</copy-button>"#);
|
|
894
|
+
let result = stamper.stamp_html(html).unwrap();
|
|
895
|
+
let escaped = "<component src="/card.html"></component> & more";
|
|
896
|
+
assert_eq!(result, format!(r#"<copy-button data-value="{escaped}">{escaped}</copy-button>"#));
|
|
897
|
+
}
|
|
898
|
+
|
|
891
899
|
#[test]
|
|
892
900
|
fn stamp_attribute_binding() {
|
|
893
901
|
let state = json!({ "firstName": "John" });
|
|
@@ -1051,5 +1059,41 @@ mod tests {
|
|
|
1051
1059
|
let result = stamper.stamp_html("<x>@[version + suffix]</x>".to_string()).unwrap();
|
|
1052
1060
|
assert!(!result.contains("0.1.5"), "constant leaked into compound: {result}");
|
|
1053
1061
|
}
|
|
1062
|
+
|
|
1063
|
+
#[test]
|
|
1064
|
+
fn compound_expression_over_constants_is_stamped_from_serialized_html() {
|
|
1065
|
+
// An inlined component's `@[name.split…]` with `name="@[team]"` arrives
|
|
1066
|
+
// as `@[(team).split…]`, its `=>` serialized to `=>`.
|
|
1067
|
+
let state = json!({});
|
|
1068
|
+
let consts = constants(&[("team", json!("Ada Lovelace"))]);
|
|
1069
|
+
let stamper = ValueStamper::with_constants(&state, false, &consts).unwrap();
|
|
1070
|
+
let result = stamper
|
|
1071
|
+
.stamp_html("<avatar-circle>@[(team).split(' ').map(part => part[0]).join('')]</avatar-circle>".to_string())
|
|
1072
|
+
.unwrap();
|
|
1073
|
+
assert_eq!(result, "<avatar-circle>AL</avatar-circle>");
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
#[test]
|
|
1077
|
+
fn cleanup_measures_skip_regions_after_a_binding_shrinks() {
|
|
1078
|
+
// The avatar's binding stamps 52 bytes down to `AL`, pulling the hint
|
|
1079
|
+
// box back into where the `<pre>` sat before stamping — cleanup must
|
|
1080
|
+
// still see its attributes as outside the `<pre>`.
|
|
1081
|
+
let state = json!({ "gone": false, "secondary": "" });
|
|
1082
|
+
let consts = constants(&[("team", json!("Ada Lovelace"))]);
|
|
1083
|
+
let stamper = ValueStamper::with_constants(&state, false, &consts).unwrap();
|
|
1084
|
+
let result = stamper
|
|
1085
|
+
.stamp_html(
|
|
1086
|
+
"<avatar-circle>@[(team).split(' ').map(part => part[0]).join('')]</avatar-circle>\
|
|
1087
|
+
<pre>npm install @ape-egg/vibe --save-dev</pre>\
|
|
1088
|
+
<hint-box gone=\"@[gone]\"><button secondary=\"@[secondary]\">Go</button></hint-box>"
|
|
1089
|
+
.to_string(),
|
|
1090
|
+
)
|
|
1091
|
+
.unwrap();
|
|
1092
|
+
assert_eq!(
|
|
1093
|
+
result,
|
|
1094
|
+
"<avatar-circle>AL</avatar-circle><pre>npm install @ape-egg/vibe --save-dev</pre>\
|
|
1095
|
+
<hint-box><button secondary>Go</button></hint-box>"
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1054
1098
|
}
|
|
1055
1099
|
|
package/src/compiler/watcher.rs
CHANGED
|
@@ -471,6 +471,10 @@ pub fn watch(config: Config, verbose: bool, launcher: Option<u32>) -> std::resul
|
|
|
471
471
|
}
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
+
if let Err(e) = compiler.remove_stale_output() {
|
|
475
|
+
eprintln!("\n{}: Removing stale output failed: {}", "Warning".yellow(), e);
|
|
476
|
+
}
|
|
477
|
+
|
|
474
478
|
// Show success headline
|
|
475
479
|
println!("\n{}", "Initial compilation complete!".green().bold());
|
|
476
480
|
println!();
|
|
@@ -525,8 +529,8 @@ pub fn watch(config: Config, verbose: bool, launcher: Option<u32>) -> std::resul
|
|
|
525
529
|
if let Some(nm_time) = stats.node_modules_time_ms {
|
|
526
530
|
if stats.node_modules_copied_as_is {
|
|
527
531
|
println!("* copied node_modules in {:.0}ms", nm_time);
|
|
528
|
-
} else
|
|
529
|
-
println!("*
|
|
532
|
+
} else {
|
|
533
|
+
println!("* copied dependencies in {:.0}ms", nm_time);
|
|
530
534
|
}
|
|
531
535
|
}
|
|
532
536
|
|
|
@@ -879,7 +883,6 @@ pub fn watch(config: Config, verbose: bool, launcher: Option<u32>) -> std::resul
|
|
|
879
883
|
components_time_ms: 0.0,
|
|
880
884
|
validation_time_ms: None,
|
|
881
885
|
node_modules_time_ms: None,
|
|
882
|
-
package_manager: None,
|
|
883
886
|
node_modules_copied_as_is: false,
|
|
884
887
|
components_as_is: config.components_as_is,
|
|
885
888
|
};
|
package/src/config.rs
CHANGED
|
@@ -38,6 +38,8 @@ pub struct VibeCompilerConfig {
|
|
|
38
38
|
pub skip_files: Vec<String>,
|
|
39
39
|
#[serde(default)]
|
|
40
40
|
pub node_modules_as_is: bool,
|
|
41
|
+
#[serde(default = "default_modules")]
|
|
42
|
+
pub modules: String,
|
|
41
43
|
#[serde(default)]
|
|
42
44
|
pub components_as_is: bool,
|
|
43
45
|
#[serde(default)]
|
|
@@ -73,6 +75,7 @@ fn default_output() -> String { "./compiled".to_string() }
|
|
|
73
75
|
fn default_components() -> String { "components".to_string() }
|
|
74
76
|
fn default_pages() -> String { "pages".to_string() }
|
|
75
77
|
fn default_assets() -> String { "assets".to_string() }
|
|
78
|
+
fn default_modules() -> String { "node_modules".to_string() }
|
|
76
79
|
|
|
77
80
|
/// Get list of standard HTML5 elements + "component"
|
|
78
81
|
/// Component names matching these are reserved and not allowed
|
|
@@ -118,6 +121,7 @@ impl Default for VibeCompilerConfig {
|
|
|
118
121
|
reserved_elements: vec![],
|
|
119
122
|
skip_files: vec![],
|
|
120
123
|
node_modules_as_is: false,
|
|
124
|
+
modules: default_modules(),
|
|
121
125
|
components_as_is: false,
|
|
122
126
|
runtime_as_is: false,
|
|
123
127
|
iterations_as_is: false,
|
|
@@ -145,6 +149,7 @@ pub struct Config {
|
|
|
145
149
|
pub reserved_elements: Vec<String>,
|
|
146
150
|
pub skip_files: Vec<String>,
|
|
147
151
|
pub node_modules_as_is: bool,
|
|
152
|
+
pub modules: String,
|
|
148
153
|
pub components_as_is: bool,
|
|
149
154
|
pub runtime_as_is: bool,
|
|
150
155
|
pub iterations_as_is: bool,
|
|
@@ -208,6 +213,7 @@ impl Config {
|
|
|
208
213
|
reserved_elements,
|
|
209
214
|
skip_files,
|
|
210
215
|
node_modules_as_is: config.node_modules_as_is,
|
|
216
|
+
modules: config.modules,
|
|
211
217
|
components_as_is: config.components_as_is,
|
|
212
218
|
runtime_as_is: config.runtime_as_is,
|
|
213
219
|
iterations_as_is: config.iterations_as_is,
|
|
@@ -219,6 +225,10 @@ impl Config {
|
|
|
219
225
|
}
|
|
220
226
|
}
|
|
221
227
|
|
|
228
|
+
pub fn modules_path(&self) -> PathBuf {
|
|
229
|
+
self.output.join(&self.modules)
|
|
230
|
+
}
|
|
231
|
+
|
|
222
232
|
pub fn components_path(&self) -> PathBuf {
|
|
223
233
|
self.source.join(&self.components)
|
|
224
234
|
}
|
package/src/main.rs
CHANGED
|
@@ -242,6 +242,7 @@ fn main() {
|
|
|
242
242
|
println!(" {}: {}", format!("{:<16}", "elementsAsIs").cyan(), format_bool_with_flag(config.elements_as_is, overrides.elements_as_is, original_elements_as_is));
|
|
243
243
|
println!(" {}: {}", format!("{:<16}", "iterationsAsIs").cyan(), format_bool_with_flag(config.iterations_as_is, overrides.iterations_as_is, original_iterations_as_is));
|
|
244
244
|
println!(" {}: {}", format!("{:<16}", "minify").cyan(), format_bool_with_flag(config.minify, overrides.minify, original_minify));
|
|
245
|
+
println!(" {}: {}", format!("{:<16}", "modules").cyan(), format_value_no_flag(&config.modules));
|
|
245
246
|
println!(" {}: {}", format!("{:<16}", "nodeModulesAsIs").cyan(), format_bool_with_flag(config.node_modules_as_is, overrides.node_modules_as_is, original_node_modules_as_is));
|
|
246
247
|
println!(" {}: {}", format!("{:<16}", "output").cyan(), format_value_no_flag(&config._output_str));
|
|
247
248
|
println!(" {}: {}", format!("{:<16}", "pages").cyan(), format_value_no_flag(&config.pages));
|
|
@@ -291,6 +292,10 @@ fn main() {
|
|
|
291
292
|
}
|
|
292
293
|
}
|
|
293
294
|
|
|
295
|
+
if let Err(e) = compiler.remove_stale_output() {
|
|
296
|
+
eprintln!("\n{}: Removing stale output failed: {}", "Warning".yellow(), e);
|
|
297
|
+
}
|
|
298
|
+
|
|
294
299
|
// Show success headline
|
|
295
300
|
println!("\n{}", format!("Compilation successful! (v{}) ✅", env!("CARGO_PKG_VERSION")).green().bold());
|
|
296
301
|
println!();
|
|
@@ -345,8 +350,8 @@ fn main() {
|
|
|
345
350
|
if let Some(nm_time) = stats.node_modules_time_ms {
|
|
346
351
|
if stats.node_modules_copied_as_is {
|
|
347
352
|
println!("* copied node_modules in {:.0}ms", nm_time);
|
|
348
|
-
} else
|
|
349
|
-
println!("*
|
|
353
|
+
} else {
|
|
354
|
+
println!("* copied dependencies in {:.0}ms", nm_time);
|
|
350
355
|
}
|
|
351
356
|
}
|
|
352
357
|
|
package/src/parser/html.rs
CHANGED
|
@@ -681,7 +681,7 @@ fn escape_attr_value(value: &str) -> String {
|
|
|
681
681
|
.replace('\'', "'")
|
|
682
682
|
}
|
|
683
683
|
|
|
684
|
-
fn decode_entities(value: &str) -> String {
|
|
684
|
+
pub(crate) fn decode_entities(value: &str) -> String {
|
|
685
685
|
value
|
|
686
686
|
.replace(""", "\"")
|
|
687
687
|
.replace(""", "\"")
|