@ape-egg/vibe 1.1.2 → 1.3.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.
- package/CHANGELOG.md +140 -0
- package/README.md +30 -23
- package/compiler/native/vibe-compiler-darwin-arm64 +0 -0
- package/compiler/src/compiler/compile.rs +204 -51
- package/compiler/src/compiler/manifest_builder.rs +432 -0
- package/compiler/src/compiler/mod.rs +3 -0
- package/compiler/src/compiler/state_extractor.rs +148 -0
- package/compiler/src/compiler/value_stamper.rs +222 -0
- package/compiler/src/config.rs +0 -5
- package/compiler/src/main.rs +45 -20
- package/compiler/src/parser/html.rs +22 -8
- package/index.js +40 -2
- package/package.json +2 -1
- package/runtime/affected.js +44 -24
- package/runtime/cleanup.js +45 -24
- package/runtime/component-state.js +63 -0
- package/runtime/component.js +59 -15
- package/runtime/conditionals.js +4 -4
- package/runtime/constants.js +46 -2
- package/runtime/debug.js +1 -0
- package/runtime/hydrate.js +23 -21
- package/runtime/hyperspeed.js +425 -0
- package/runtime/index.js +322 -69
- package/runtime/iterate.js +15 -4
- package/runtime/parse.js +48 -10
- package/runtime/scope.js +50 -0
- package/runtime/state.js +10 -5
- package/runtime/utils.js +63 -5
- package/{runtime/vibe.css → vibe.css} +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,145 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.3.0] - 2025-02-06
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Static Analysis Compiler**: Replaced browser-based manifest generation with pure Rust static analysis
|
|
8
|
+
- New compiler modules: `state_extractor.rs`, `manifest_builder.rs`, `value_stamper.rs`
|
|
9
|
+
- ~3000x performance improvement (10-100 seconds → ~6ms for manifest generation)
|
|
10
|
+
- Pre-rendering support for iterations with initial state values
|
|
11
|
+
- Graceful handling of unparseable state (skips files instead of failing entire compilation)
|
|
12
|
+
|
|
13
|
+
- **Enhanced vibe() and component() API**:
|
|
14
|
+
- Added `config` parameter (second argument) for runtime configuration
|
|
15
|
+
- Added `targetSelector` parameter (third argument) for custom root element selection
|
|
16
|
+
- Multiple calls accumulate state, config/targetSelector use "first wins" strategy
|
|
17
|
+
- Example: `vibe({ count: 0 }, { debug: true }, 'body')`
|
|
18
|
+
|
|
19
|
+
- **Expanded Compilation Scope**: Compiler now processes all HTML files recursively
|
|
20
|
+
- Compiles all `<source-root>/**/*.html` (excluding `components/` directory)
|
|
21
|
+
- Generates manifests for all `<output-dir>/**/*.html` (excluding `components/`)
|
|
22
|
+
- Previously limited to `<source-root>/pages/` only
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **Iteration Restoration**: Improved DOM restoration algorithm
|
|
27
|
+
- Uses TreeWalker to find iteration comment pairs (more robust)
|
|
28
|
+
- Avoids index-based lookup that breaks after DOM structure changes
|
|
29
|
+
- Runtime now fully controls iteration nodes (skipped during manifest merge)
|
|
30
|
+
|
|
31
|
+
- **Compiler Output**: Enhanced user experience with better formatting
|
|
32
|
+
- Manifest generation occurs before "Compilation successful!" message
|
|
33
|
+
- Verbose mode (`--verbose`) shows only warnings/errors for manifest generation
|
|
34
|
+
- Clean title: "Generating manifests (static analysis)"
|
|
35
|
+
- Summary format: `Generated manifests (X files, Y skipped) in Zms`
|
|
36
|
+
- Positioned between "Compiled HTML" and "Copied files" in output
|
|
37
|
+
|
|
38
|
+
- **Debug Logging**: Refined hyperspeed detection messages
|
|
39
|
+
- Removed redundant path-specific log
|
|
40
|
+
- Changed to: "Detected vibe-hyperspeed. Applying pre-compiled manifest."
|
|
41
|
+
|
|
42
|
+
### Removed
|
|
43
|
+
|
|
44
|
+
- **Browser Automation Dependencies**: Eliminated heavy runtime dependencies
|
|
45
|
+
- Removed: chromiumoxide, tiny_http, tokio, futures
|
|
46
|
+
- Deleted 414 lines of browser automation code (`manifest.rs`)
|
|
47
|
+
- Pure Rust implementation with no external processes or async complexity
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- Manifest merge conflicts between hyperspeed (pre-compiled) and runtime trees
|
|
52
|
+
- Index shifting bugs during restoration phase that caused key mismatches
|
|
53
|
+
- Iteration rendering producing duplicate items (3x3 instead of 3)
|
|
54
|
+
- Pre-rendered iteration content not being properly replaced with reactive templates
|
|
55
|
+
|
|
56
|
+
### Performance
|
|
57
|
+
|
|
58
|
+
- Manifest generation: 10-100 seconds → ~6ms (~3000x faster)
|
|
59
|
+
- Total compilation time: Typically completes in 200-300ms for medium projects
|
|
60
|
+
- Zero browser startup overhead
|
|
61
|
+
- Reduced memory footprint (no Chromium instance)
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## [1.2.0] - 2026-02-03
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
- **Component state isolation**: Components now have their own isolated state using `<script type="component">` blocks
|
|
70
|
+
- Each component gets a unique ID (`_cTIMESTAMP_RANDOM`) automatically generated and stored in `data-vibe-component-id` attribute
|
|
71
|
+
- Component state lives at `$[componentId].property` in the global state object
|
|
72
|
+
- Variable declarations (`let count = 0`) are automatically transformed to property assignments (`this.count = 0`)
|
|
73
|
+
- Clean separation between global state and component-specific state
|
|
74
|
+
- Example component (subject to change!):
|
|
75
|
+
```html
|
|
76
|
+
<script type="component">
|
|
77
|
+
let count = 0;
|
|
78
|
+
let increment = () => { this.count++; };
|
|
79
|
+
</script>
|
|
80
|
+
<button onclick="this.increment()">Clicked @[this.count] times</button>
|
|
81
|
+
```
|
|
82
|
+
- **`this.property` syntax**: Reference component-scoped state from anywhere inside a component
|
|
83
|
+
- Works in bindings: `@[this.count]`
|
|
84
|
+
- Works in attributes: `value="@[this.inputValue]"`
|
|
85
|
+
- Works in event handlers: `onclick="this.increment()"`
|
|
86
|
+
- Works in name bindings: `@[this.iconName]`
|
|
87
|
+
- Works in conditionals: `<!-- if this.isVisible -->`
|
|
88
|
+
- Works in iterations: `<!-- each this.items as item -->`
|
|
89
|
+
- Runtime automatically resolves `this.property` → `$['componentId'].property`
|
|
90
|
+
- **Component script execution**: `<script type="component">` blocks execute in their own scope
|
|
91
|
+
- Scripts run when component HTML is fetched (runtime `<component>` resolution)
|
|
92
|
+
- State is registered in global `$` object under component ID
|
|
93
|
+
- All siblings after the script tag inherit the component ID via `data-vibe-component-id` attribute
|
|
94
|
+
- Multiple component scripts in same HTML create separate component instances with unique IDs
|
|
95
|
+
- **Props and slots integration**: Component state works seamlessly with existing `<component>` features
|
|
96
|
+
- Props can set component state: `<component src="/card.html" theme="@[userTheme]">`
|
|
97
|
+
- Props work with `this.` references: `@[this.theme]` inside card.html
|
|
98
|
+
- Slots work inside component-scoped HTML
|
|
99
|
+
- Components can be nested with isolated state at each level
|
|
100
|
+
|
|
101
|
+
### Changed
|
|
102
|
+
|
|
103
|
+
- **Component ID tagging**: DOM elements are now tagged with `data-vibe-component-id` during component processing (previously only scripts had this attribute)
|
|
104
|
+
- All siblings after a `<script type="component">` get tagged with the same component ID
|
|
105
|
+
- Tagging stops when hitting another component script or end of HTML
|
|
106
|
+
- Enables `this.property` resolution in any context (bindings, events, conditionals, iterations)
|
|
107
|
+
- **Event handler rewriting**: Event handlers with `this.property` are now rewritten at parse time
|
|
108
|
+
- `onclick="this.increment()"` → `onclick="$['_c123_abc'].increment()"`
|
|
109
|
+
- DOM properties (like `this.value`, `this.checked`) are preserved and not rewritten
|
|
110
|
+
- Prevents conflicts between component state access and native DOM properties
|
|
111
|
+
|
|
112
|
+
### Technical Details
|
|
113
|
+
|
|
114
|
+
- **Component state lifecycle**:
|
|
115
|
+
1. `<component src="/path.html">` fetches HTML
|
|
116
|
+
2. HTML is parsed in temporary container
|
|
117
|
+
3. `<script type="component">` blocks are found and executed
|
|
118
|
+
4. Each script generates unique component ID
|
|
119
|
+
5. Script and following siblings are tagged with `data-vibe-component-id`
|
|
120
|
+
6. Component state is registered at `$[componentId]`
|
|
121
|
+
7. Props are applied (with `this.` reference rewriting)
|
|
122
|
+
8. Slots are replaced
|
|
123
|
+
9. Transformed HTML replaces `<component>` element
|
|
124
|
+
10. MutationObserver triggers reactive hydration with component context
|
|
125
|
+
- **`this.property` resolution**: Helper function `resolveThisPath()` in utils.js walks up DOM tree to find nearest `data-vibe-component-id`, then rewrites path from `this.property` → `componentId.property`
|
|
126
|
+
- **Expression evaluation**: `evalInScope()` in utils.js handles both global (`$.property`) and component-scoped (`$['componentId'].property`) state access, with case-insensitive fallback for HTML-lowercased attribute names
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## [1.1.3] - 2026-02-02
|
|
131
|
+
|
|
132
|
+
### Added
|
|
133
|
+
|
|
134
|
+
- **MutationObserver performance optimization**: Fast filter with short-circuit evaluation
|
|
135
|
+
- New `shouldProcessNode()` function checks for Vibe syntax before expensive processing
|
|
136
|
+
- Short-circuits on first match: most Vibe nodes contain `@[`, so check exits immediately
|
|
137
|
+
- Filters out third-party framework mutations (React, Vue, etc.) with cheap string operations
|
|
138
|
+
- Only walks DOM tree for nodes that actually contain Vibe syntax (`@[`, `<!-- each`, `<!-- if`, `<component>`)
|
|
139
|
+
- Enables efficient coexistence with other frameworks on the same page
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
3
143
|
## [1.1.2] - 2026-02-01
|
|
4
144
|
|
|
5
145
|
### Fixed
|
package/README.md
CHANGED
|
@@ -18,20 +18,18 @@ The core reactive runtime. Works directly in the browser without any build tools
|
|
|
18
18
|
|
|
19
19
|
```html
|
|
20
20
|
<html>
|
|
21
|
-
<head>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
</head>
|
|
29
|
-
<body vibe>
|
|
30
|
-
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
</body>
|
|
21
|
+
<head>
|
|
22
|
+
<link rel="stylesheet" href="./node_modules/@ape-egg/vibe/vibe.css" />
|
|
23
|
+
<script type="module">
|
|
24
|
+
import state from './node_modules/@ape-egg/vibe/runtime/index.js';
|
|
25
|
+
// Attaches to element with attribute "vibe" by default
|
|
26
|
+
window.$ = state({ name: 'World', count: 0 });
|
|
27
|
+
</script>
|
|
28
|
+
</head>
|
|
29
|
+
<body vibe-fouc>
|
|
30
|
+
<h1>Hello, @[name]!</h1>
|
|
31
|
+
<button onclick="$.count++">Clicked @[count] times</button>
|
|
32
|
+
</body>
|
|
35
33
|
</html>
|
|
36
34
|
```
|
|
37
35
|
|
|
@@ -49,12 +47,14 @@ The CSS targets `[vibe]` and hides it until hydration completes. Once Vibe finis
|
|
|
49
47
|
Vibe supports bindings in three positions:
|
|
50
48
|
|
|
51
49
|
**Text content** — Inside element tags:
|
|
50
|
+
|
|
52
51
|
```html
|
|
53
52
|
<div>@[firstName] @[lastName]</div>
|
|
54
53
|
<h1>Hello, @[name]!</h1>
|
|
55
54
|
```
|
|
56
55
|
|
|
57
56
|
**Attribute values** — In attribute value position:
|
|
57
|
+
|
|
58
58
|
```html
|
|
59
59
|
<input value="@[username]" />
|
|
60
60
|
<div class="@[theme]" style="color: @[color]"></div>
|
|
@@ -62,15 +62,19 @@ Vibe supports bindings in three positions:
|
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
**Attribute names** — In attribute name position (useful for dynamic attributes):
|
|
65
|
+
|
|
65
66
|
```html
|
|
66
67
|
<icon @[iconName]></icon>
|
|
67
68
|
<button @[state]>Click me</button>
|
|
68
69
|
```
|
|
69
70
|
|
|
70
71
|
**CSS** — Bindings also work in style tags:
|
|
72
|
+
|
|
71
73
|
```html
|
|
72
74
|
<style>
|
|
73
|
-
.box {
|
|
75
|
+
.box {
|
|
76
|
+
background: @[themeColor];
|
|
77
|
+
}
|
|
74
78
|
</style>
|
|
75
79
|
```
|
|
76
80
|
|
|
@@ -78,7 +82,7 @@ Vibe supports bindings in three positions:
|
|
|
78
82
|
|
|
79
83
|
```html
|
|
80
84
|
<!-- each items as item, index -->
|
|
81
|
-
|
|
85
|
+
<li>@[index]: @[item]</li>
|
|
82
86
|
<!-- /each -->
|
|
83
87
|
```
|
|
84
88
|
|
|
@@ -86,9 +90,9 @@ Nested iteration with dot paths:
|
|
|
86
90
|
|
|
87
91
|
```html
|
|
88
92
|
<!-- each categories as category -->
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
<!-- each category.items as item -->
|
|
94
|
+
<span>@[item.name]</span>
|
|
95
|
+
<!-- /each -->
|
|
92
96
|
<!-- /each -->
|
|
93
97
|
```
|
|
94
98
|
|
|
@@ -96,9 +100,9 @@ Nested iteration with dot paths:
|
|
|
96
100
|
|
|
97
101
|
```html
|
|
98
102
|
<!-- if user.isAdmin -->
|
|
99
|
-
|
|
103
|
+
<admin-badge>Admin</admin-badge>
|
|
100
104
|
<!-- else -->
|
|
101
|
-
|
|
105
|
+
<span>User</span>
|
|
102
106
|
<!-- /if -->
|
|
103
107
|
```
|
|
104
108
|
|
|
@@ -126,9 +130,9 @@ Vibe uses recursive proxies to detect changes at any nesting level:
|
|
|
126
130
|
|
|
127
131
|
```javascript
|
|
128
132
|
// All of these trigger reactive updates:
|
|
129
|
-
$.user.name =
|
|
133
|
+
$.user.name = 'Alice';
|
|
130
134
|
$.todos[2].completed = true;
|
|
131
|
-
$.config.theme.colors.primary =
|
|
135
|
+
$.config.theme.colors.primary = '#007bff';
|
|
132
136
|
```
|
|
133
137
|
|
|
134
138
|
No need for immutable update patterns or spread operators. Just mutate and Vibe handles the rest.
|
|
@@ -211,6 +215,7 @@ Add to your `package.json`:
|
|
|
211
215
|
```
|
|
212
216
|
|
|
213
217
|
**Defaults** (when no config):
|
|
218
|
+
|
|
214
219
|
- `source`: `./`
|
|
215
220
|
- `output`: `./compiled`
|
|
216
221
|
- `components`: `<source>/components`
|
|
@@ -238,6 +243,7 @@ By default, the compiler creates deployable output by installing production depe
|
|
|
238
243
|
3. Removes `package.json` and lockfile from output (cleanup)
|
|
239
244
|
|
|
240
245
|
This ensures:
|
|
246
|
+
|
|
241
247
|
- Compiled output only includes runtime dependencies (from `dependencies`, not `devDependencies`)
|
|
242
248
|
- Local `node_modules` is never modified
|
|
243
249
|
- Faster than copying (no intermediate copy step)
|
|
@@ -294,6 +300,7 @@ cp target/release/vibe-compiler ../native/vibe-compiler-darwin-arm64
|
|
|
294
300
|
```
|
|
295
301
|
|
|
296
302
|
Supported platforms:
|
|
303
|
+
|
|
297
304
|
- `vibe-compiler-darwin-arm64` (macOS Apple Silicon) ✅ Included
|
|
298
305
|
- `vibe-compiler-darwin-x64` (macOS Intel)
|
|
299
306
|
- `vibe-compiler-linux-x64`
|
|
Binary file
|
|
@@ -100,6 +100,12 @@ pub struct CompileStats {
|
|
|
100
100
|
pub components_as_is: bool,
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
pub struct ManifestStats {
|
|
104
|
+
pub pages_processed: usize,
|
|
105
|
+
pub pages_skipped: usize,
|
|
106
|
+
pub total_time_ms: f64,
|
|
107
|
+
}
|
|
108
|
+
|
|
103
109
|
#[derive(Debug, Clone, PartialEq)]
|
|
104
110
|
enum FileOperation {
|
|
105
111
|
Compiled,
|
|
@@ -420,7 +426,13 @@ impl Compiler {
|
|
|
420
426
|
|
|
421
427
|
// Track compile and copy times separately
|
|
422
428
|
let compile_start = Instant::now();
|
|
423
|
-
|
|
429
|
+
|
|
430
|
+
// Process all HTML files in source (excluding components directory)
|
|
431
|
+
self.process_directory_html_only(&self.config.source.clone(), &parser, "", &mut stats)?;
|
|
432
|
+
|
|
433
|
+
// Process source directory for assets (CSS, JS, images, etc.)
|
|
434
|
+
self.process_directory_assets_only(&self.config.source.clone(), "", &mut stats)?;
|
|
435
|
+
|
|
424
436
|
let process_time = compile_start.elapsed();
|
|
425
437
|
|
|
426
438
|
// Print verbose output after processing (components first, then HTML, then copied)
|
|
@@ -493,8 +505,112 @@ impl Compiler {
|
|
|
493
505
|
Ok(stats)
|
|
494
506
|
}
|
|
495
507
|
|
|
496
|
-
///
|
|
497
|
-
fn
|
|
508
|
+
/// Generate manifest for a single file
|
|
509
|
+
fn generate_file_manifest(
|
|
510
|
+
html: &str,
|
|
511
|
+
html_path: &Path,
|
|
512
|
+
output_dir: &Path,
|
|
513
|
+
relative_path: &str,
|
|
514
|
+
_verbose: bool,
|
|
515
|
+
) -> Result<(), String> {
|
|
516
|
+
use crate::compiler::state_extractor::StateExtractor;
|
|
517
|
+
use crate::compiler::manifest_builder::ManifestBuilder;
|
|
518
|
+
use crate::compiler::value_stamper::ValueStamper;
|
|
519
|
+
|
|
520
|
+
// Extract state
|
|
521
|
+
let state = StateExtractor::extract_from_html(html)?;
|
|
522
|
+
|
|
523
|
+
// Build manifest
|
|
524
|
+
let manifest_builder = ManifestBuilder::new();
|
|
525
|
+
let manifest = manifest_builder.build_from_html(html, &state)?;
|
|
526
|
+
|
|
527
|
+
// Stamp values (pre-render)
|
|
528
|
+
let stamper = ValueStamper::new(&state);
|
|
529
|
+
let pre_rendered = stamper.stamp_html(html.to_string())?;
|
|
530
|
+
|
|
531
|
+
// Write pre-rendered HTML
|
|
532
|
+
fs::write(html_path, pre_rendered)
|
|
533
|
+
.map_err(|e| format!("Failed to write HTML: {}", e))?;
|
|
534
|
+
|
|
535
|
+
// Write manifest
|
|
536
|
+
let manifest_path = output_dir
|
|
537
|
+
.join("vibe-hyperspeed")
|
|
538
|
+
.join(format!("{}.manifest.js", relative_path));
|
|
539
|
+
|
|
540
|
+
// Ensure directory exists
|
|
541
|
+
if let Some(parent) = manifest_path.parent() {
|
|
542
|
+
fs::create_dir_all(parent)
|
|
543
|
+
.map_err(|e| format!("Failed to create manifest directory: {}", e))?;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
let manifest_json = serde_json::to_string(&manifest)
|
|
547
|
+
.map_err(|e| format!("Failed to serialize manifest: {}", e))?;
|
|
548
|
+
|
|
549
|
+
let manifest_js = format!(
|
|
550
|
+
"// Pre-compiled manifest for /{}\n// Generated by Vibe compiler\n\nexport default {};\n",
|
|
551
|
+
relative_path,
|
|
552
|
+
manifest_json
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
fs::write(&manifest_path, manifest_js)
|
|
556
|
+
.map_err(|e| format!("Failed to write manifest: {}", e))?;
|
|
557
|
+
|
|
558
|
+
Ok(())
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/// Generate manifests (called separately from main.rs if needed)
|
|
562
|
+
pub fn generate_manifests(&self) -> Result<ManifestStats, CompileError> {
|
|
563
|
+
|
|
564
|
+
let start = Instant::now();
|
|
565
|
+
let mut pages_processed = 0;
|
|
566
|
+
let mut pages_skipped = 0;
|
|
567
|
+
|
|
568
|
+
if self.verbose {
|
|
569
|
+
println!("\nGenerating manifests (static analysis)");
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Find all HTML files in compiled output (excluding components directory)
|
|
573
|
+
let html_files = self.find_all_html_files(&self.config.output)?;
|
|
574
|
+
|
|
575
|
+
for html_path in html_files {
|
|
576
|
+
let relative_path = html_path.strip_prefix(&self.config.output)
|
|
577
|
+
.unwrap()
|
|
578
|
+
.to_str()
|
|
579
|
+
.unwrap();
|
|
580
|
+
|
|
581
|
+
// Read compiled HTML
|
|
582
|
+
let html = fs::read_to_string(&html_path)
|
|
583
|
+
.map_err(|e| CompileError::ReadError {
|
|
584
|
+
path: html_path.display().to_string(),
|
|
585
|
+
source: e,
|
|
586
|
+
})?;
|
|
587
|
+
|
|
588
|
+
// Try to generate manifest for this file (skip on error)
|
|
589
|
+
match Self::generate_file_manifest(&html, &html_path, &self.config.output, relative_path, self.verbose) {
|
|
590
|
+
Ok(()) => {
|
|
591
|
+
pages_processed += 1;
|
|
592
|
+
}
|
|
593
|
+
Err(e) => {
|
|
594
|
+
pages_skipped += 1;
|
|
595
|
+
if self.verbose {
|
|
596
|
+
println!(" Skipped ({}): {}", e, relative_path);
|
|
597
|
+
}
|
|
598
|
+
// Don't fail compilation, just skip this file's manifest
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
let total_time_ms = start.elapsed().as_millis() as f64;
|
|
604
|
+
|
|
605
|
+
Ok(ManifestStats {
|
|
606
|
+
pages_processed,
|
|
607
|
+
pages_skipped,
|
|
608
|
+
total_time_ms,
|
|
609
|
+
})
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/// Process directory for HTML files only (used for pages directory)
|
|
613
|
+
fn process_directory_html_only(
|
|
498
614
|
&mut self,
|
|
499
615
|
dir: &Path,
|
|
500
616
|
parser: &HtmlParser,
|
|
@@ -516,7 +632,84 @@ impl Compiler {
|
|
|
516
632
|
continue;
|
|
517
633
|
}
|
|
518
634
|
|
|
519
|
-
// Skip
|
|
635
|
+
// Skip components directory
|
|
636
|
+
if file_name == self.config.components {
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Recurse into subdirectory
|
|
641
|
+
let new_relative = if relative_path.is_empty() {
|
|
642
|
+
file_name.to_string()
|
|
643
|
+
} else {
|
|
644
|
+
format!("{}/{}", relative_path, file_name)
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
self.process_directory_html_only(&path, parser, &new_relative, stats)?;
|
|
648
|
+
} else if path.extension().and_then(|e| e.to_str()) == Some("html") {
|
|
649
|
+
let (internal, external, component_srcs) = self.compile_html_file(&path, parser, relative_path)?;
|
|
650
|
+
stats.files_compiled += 1;
|
|
651
|
+
stats.internal_components_total += internal;
|
|
652
|
+
stats.external_components_total += external;
|
|
653
|
+
|
|
654
|
+
// Track unique components
|
|
655
|
+
for src in &component_srcs {
|
|
656
|
+
self.unique_components.insert(src.clone());
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Build component relationships and count occurrences before borrowing logger
|
|
660
|
+
let (component_relationships, all_srcs) = if self.logger.is_some() && !component_srcs.is_empty() {
|
|
661
|
+
let relationships = self.build_component_relationships(&component_srcs);
|
|
662
|
+
(Some(relationships), Some(component_srcs))
|
|
663
|
+
} else {
|
|
664
|
+
(None, None)
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
if let Some(ref mut logger) = self.logger {
|
|
668
|
+
logger.log(&path, FileOperation::Compiled, &self.config.source);
|
|
669
|
+
|
|
670
|
+
// Log each component occurrence (for counting)
|
|
671
|
+
if let Some(all_srcs) = all_srcs {
|
|
672
|
+
for src in all_srcs {
|
|
673
|
+
logger.log_component_occurrence(src);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Log unique relationships (for tree structure)
|
|
678
|
+
if let Some(relationships) = component_relationships {
|
|
679
|
+
for (src, children) in relationships {
|
|
680
|
+
logger.log_component_children(src, children);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
Ok(())
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/// Process directory for assets only (CSS, JS, images, etc.) - skip HTML
|
|
691
|
+
fn process_directory_assets_only(
|
|
692
|
+
&mut self,
|
|
693
|
+
dir: &Path,
|
|
694
|
+
relative_path: &str,
|
|
695
|
+
stats: &mut CompileStats,
|
|
696
|
+
) -> Result<(), CompileError> {
|
|
697
|
+
let entries = fs::read_dir(dir).map_err(|e| CompileError::ReadError {
|
|
698
|
+
path: dir.display().to_string(),
|
|
699
|
+
source: e,
|
|
700
|
+
})?;
|
|
701
|
+
|
|
702
|
+
for entry in entries.flatten() {
|
|
703
|
+
let path = entry.path();
|
|
704
|
+
let file_name = path.file_name().unwrap().to_str().unwrap();
|
|
705
|
+
|
|
706
|
+
if path.is_dir() {
|
|
707
|
+
// Skip special directories
|
|
708
|
+
if SKIP_DIRECTORIES.contains(&file_name) {
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Skip node_modules (handled separately)
|
|
520
713
|
if file_name == "node_modules" {
|
|
521
714
|
continue;
|
|
522
715
|
}
|
|
@@ -543,55 +736,15 @@ impl Compiler {
|
|
|
543
736
|
format!("{}/{}", relative_path, file_name)
|
|
544
737
|
};
|
|
545
738
|
|
|
546
|
-
self.
|
|
739
|
+
self.process_directory_assets_only(&path, &new_relative, stats)?;
|
|
547
740
|
} else if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
// Track unique components
|
|
556
|
-
for src in &component_srcs {
|
|
557
|
-
self.unique_components.insert(src.clone());
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
// Build component relationships and count occurrences before borrowing logger
|
|
561
|
-
let (component_relationships, all_srcs) = if self.logger.is_some() && !component_srcs.is_empty() {
|
|
562
|
-
let relationships = self.build_component_relationships(&component_srcs);
|
|
563
|
-
(Some(relationships), Some(component_srcs))
|
|
564
|
-
} else {
|
|
565
|
-
(None, None)
|
|
566
|
-
};
|
|
567
|
-
|
|
568
|
-
if let Some(ref mut logger) = self.logger {
|
|
569
|
-
logger.log(&path, FileOperation::Compiled, &self.config.source);
|
|
570
|
-
|
|
571
|
-
// Log each component occurrence (for counting)
|
|
572
|
-
if let Some(all_srcs) = all_srcs {
|
|
573
|
-
for src in all_srcs {
|
|
574
|
-
logger.log_component_occurrence(src);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// Log unique relationships (for tree structure)
|
|
579
|
-
if let Some(relationships) = component_relationships {
|
|
580
|
-
for (src, children) in relationships {
|
|
581
|
-
logger.log_component_children(src, children);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
// MIRROR_MODE: Copy CSS/JS as-is
|
|
587
|
-
ext if MIRROR_EXTENSIONS.contains(&ext) => {
|
|
588
|
-
self.copy_file(&path, relative_path)?;
|
|
589
|
-
stats.files_copied += 1;
|
|
590
|
-
if let Some(ref mut logger) = self.logger {
|
|
591
|
-
logger.log(&path, FileOperation::Copied, &self.config.source);
|
|
592
|
-
}
|
|
741
|
+
// Only copy non-HTML assets
|
|
742
|
+
if ext != "html" && MIRROR_EXTENSIONS.contains(&ext) {
|
|
743
|
+
self.copy_file(&path, relative_path)?;
|
|
744
|
+
stats.files_copied += 1;
|
|
745
|
+
if let Some(ref mut logger) = self.logger {
|
|
746
|
+
logger.log(&path, FileOperation::Copied, &self.config.source);
|
|
593
747
|
}
|
|
594
|
-
_ => {}
|
|
595
748
|
}
|
|
596
749
|
}
|
|
597
750
|
}
|