@ape-egg/vibe 1.6.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +117 -16
- package/compiler/native/vibe-compiler-darwin-arm64 +0 -0
- package/compiler/src/Cargo.lock +1 -1
- package/compiler/src/Cargo.toml +1 -1
- package/compiler/src/compiler/compile.rs +15 -10
- package/compiler/src/main.rs +6 -3
- package/package.json +1 -1
- package/runtime/pre-compiled-manifest.js +15 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.1] - 2026-02-12
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Compiler output directory handling** - Fixed duplicate file generation in output
|
|
8
|
+
- Compiler now properly handles source and output paths without preserving source directory structure
|
|
9
|
+
- Files from `./src` are now correctly written to root of output directory (e.g., `compiled/index.html`) instead of `compiled/src/index.html`
|
|
10
|
+
- Canonicalized source path for reliable comparison and path stripping
|
|
11
|
+
- Updated all processing functions to use canonical source path for relative path calculation
|
|
12
|
+
|
|
13
|
+
- **Manifest detection improvements** - Better handling of directory URLs and extensionless paths
|
|
14
|
+
- Directory URLs now normalized: `/compiled/` → `/compiled/index.html`
|
|
15
|
+
- Extensionless URLs now normalized: `/compiled/mypage` → `/compiled/mypage.html`
|
|
16
|
+
- Fixes manifest detection when visiting pages without explicit `.html` extension
|
|
17
|
+
- Enables proper hyperspeed loading for directory index pages
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
3
21
|
## [1.6.0] - 2026-02-12
|
|
4
22
|
|
|
5
23
|
### Added
|
package/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# Vibe
|
|
2
2
|
|
|
3
|
-
A runtime-first reactive
|
|
3
|
+
**Version 1.6.0 (Alpha)** — A runtime-first reactive framework with optional compilation.
|
|
4
4
|
|
|
5
|
-
No virtual DOM. No build step. Just modern JavaScript.
|
|
5
|
+
No virtual DOM. No build step required. Just modern JavaScript. When you need production optimizations, add the optional Rust-based compiler.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install @ape-egg/vibe
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
**Status:** Functional and ready to use, but expect bugs and breaking changes daily until stable. Use in production at your own risk.
|
|
12
|
+
|
|
11
13
|
---
|
|
12
14
|
|
|
13
15
|
## Vibe Runtime
|
|
@@ -246,7 +248,7 @@ No need for immutable update patterns or spread operators. Just mutate and Vibe
|
|
|
246
248
|
|
|
247
249
|
## Vibe Compiler
|
|
248
250
|
|
|
249
|
-
Optional build step for production optimization. The compiler
|
|
251
|
+
Optional build step for production optimization. The compiler provides component inlining, iteration optimization, watch mode, and hydration manifests while preserving directory structure.
|
|
250
252
|
|
|
251
253
|
### Installation
|
|
252
254
|
|
|
@@ -264,18 +266,27 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
|
|
264
266
|
# Initialize config in package.json
|
|
265
267
|
bunx vibe compile --init
|
|
266
268
|
|
|
267
|
-
#
|
|
269
|
+
# Basic compilation
|
|
268
270
|
bunx vibe compile
|
|
271
|
+
# or shorthand
|
|
272
|
+
bunx vibe c
|
|
273
|
+
|
|
274
|
+
# Watch mode (incremental compilation)
|
|
275
|
+
bunx vibe compile --watch
|
|
276
|
+
|
|
277
|
+
# Production build
|
|
278
|
+
bunx vibe compile --minify --source-maps
|
|
269
279
|
|
|
270
280
|
# With options
|
|
271
281
|
bunx vibe compile --verbose # Step-by-step logging
|
|
272
282
|
bunx vibe compile --minify # Minify output
|
|
273
|
-
bunx vibe compile --
|
|
283
|
+
bunx vibe compile --elements-as-is # Keep custom elements as-is
|
|
274
284
|
bunx vibe compile --validate # Validate HTML syntax
|
|
275
|
-
bunx vibe compile --create-manifest # Generate hydration manifest
|
|
276
285
|
bunx vibe compile --source-maps # Generate source maps
|
|
277
286
|
bunx vibe compile --node-modules-as-is # Copy node_modules as-is
|
|
278
|
-
bunx vibe compile --
|
|
287
|
+
bunx vibe compile --components-as-is # Skip component inlining
|
|
288
|
+
bunx vibe compile --runtime-as-is # Skip manifest generation
|
|
289
|
+
bunx vibe compile --iterations-as-is # Skip iteration optimization
|
|
279
290
|
```
|
|
280
291
|
|
|
281
292
|
Or via npm scripts:
|
|
@@ -283,13 +294,14 @@ Or via npm scripts:
|
|
|
283
294
|
```json
|
|
284
295
|
{
|
|
285
296
|
"scripts": {
|
|
286
|
-
"
|
|
287
|
-
"
|
|
297
|
+
"compile": "vibe compile",
|
|
298
|
+
"compile:watch": "vibe compile --watch",
|
|
299
|
+
"compile:prod": "vibe compile --minify --source-maps"
|
|
288
300
|
}
|
|
289
301
|
}
|
|
290
302
|
```
|
|
291
303
|
|
|
292
|
-
Then run with `
|
|
304
|
+
Then run with `npm run compile` or `bun compile`.
|
|
293
305
|
|
|
294
306
|
### Configuration
|
|
295
307
|
|
|
@@ -304,13 +316,26 @@ Add to your `package.json`:
|
|
|
304
316
|
"pages": "pages",
|
|
305
317
|
"assets": "assets",
|
|
306
318
|
"minify": false,
|
|
307
|
-
"
|
|
308
|
-
"
|
|
309
|
-
"
|
|
319
|
+
"elementsAsIs": false,
|
|
320
|
+
"reservedElements": [],
|
|
321
|
+
"sourceMaps": false,
|
|
322
|
+
"validate": false,
|
|
323
|
+
"nodeModulesAsIs": false,
|
|
324
|
+
"componentsAsIs": false,
|
|
325
|
+
"runtimeAsIs": false,
|
|
326
|
+
"iterationsAsIs": false
|
|
310
327
|
}
|
|
311
328
|
}
|
|
312
329
|
```
|
|
313
330
|
|
|
331
|
+
**Key Options:**
|
|
332
|
+
|
|
333
|
+
- `elementsAsIs: false` — Transform custom elements to divs (default)
|
|
334
|
+
- `reservedElements: []` — Additional element names to reserve (appends to built-in HTML5 elements + "component")
|
|
335
|
+
- `componentsAsIs: false` — Inline components (default) or keep separate for runtime
|
|
336
|
+
- `iterationsAsIs: false` — Optimize iterations (default) or use runtime rendering
|
|
337
|
+
- `runtimeAsIs: false` — Generate manifest (default) or skip for runtime-only
|
|
338
|
+
|
|
314
339
|
**Defaults** (when no config):
|
|
315
340
|
|
|
316
341
|
- `source`: `./`
|
|
@@ -351,6 +376,66 @@ Auto-detects based on lockfiles: `bun.lockb`, `pnpm-lock.yaml`, `yarn.lock`, `pa
|
|
|
351
376
|
**Opt-out:**
|
|
352
377
|
Set `nodeModulesAsIs: true` in config or use `--node-modules-as-is` flag to copy node_modules as-is.
|
|
353
378
|
|
|
379
|
+
### Watch Mode
|
|
380
|
+
|
|
381
|
+
Watch mode enables incremental compilation with intelligent dependency tracking:
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
bunx vibe compile --watch
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Features:
|
|
388
|
+
- **Incremental builds** — Only recompiles changed files (~100ms)
|
|
389
|
+
- **Dependency tracking** — Changes to components trigger recompilation of pages using them
|
|
390
|
+
- **Debounced** — 300ms debounce prevents excessive compilation during rapid changes
|
|
391
|
+
- **Delta output** — First compile shows full output, subsequent compiles show only changes
|
|
392
|
+
|
|
393
|
+
### Component System
|
|
394
|
+
|
|
395
|
+
Components are automatically inlined during compilation with full support for props and slots:
|
|
396
|
+
|
|
397
|
+
```html
|
|
398
|
+
<!-- Source: components/card.html -->
|
|
399
|
+
<div class="card">
|
|
400
|
+
<h2>@[title]</h2>
|
|
401
|
+
<p>@[description]</p>
|
|
402
|
+
<slot></slot>
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
<!-- Usage in page -->
|
|
406
|
+
<component src="/components/card.html" title="My Card" description="Card description">
|
|
407
|
+
<p>Slot content</p>
|
|
408
|
+
</component>
|
|
409
|
+
|
|
410
|
+
<!-- Compiled output -->
|
|
411
|
+
<component>
|
|
412
|
+
<div class="card">
|
|
413
|
+
<h2>My Card</h2>
|
|
414
|
+
<p>Card description</p>
|
|
415
|
+
<p>Slot content</p>
|
|
416
|
+
</div>
|
|
417
|
+
</component>
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
**Custom element syntax:** Components can also use custom element syntax (e.g., `<card>` → auto-converts to `<component src="/components/card.html">`).
|
|
421
|
+
|
|
422
|
+
**Opt-out:** Set `componentsAsIs: true` to keep components as separate files for runtime loading.
|
|
423
|
+
|
|
424
|
+
### Iteration Optimization
|
|
425
|
+
|
|
426
|
+
The compiler generates optimized batch functions for `<!-- each -->` loops, providing 2-3x faster rendering:
|
|
427
|
+
|
|
428
|
+
```html
|
|
429
|
+
<!-- Source -->
|
|
430
|
+
<!-- each items as item, index -->
|
|
431
|
+
<li>@[index]: @[item]</li>
|
|
432
|
+
<!-- /each -->
|
|
433
|
+
|
|
434
|
+
<!-- Compiled to optimized batch function -->
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**Opt-out:** Set `iterationsAsIs: true` to use runtime rendering for all iterations.
|
|
438
|
+
|
|
354
439
|
### Output Structure
|
|
355
440
|
|
|
356
441
|
Mirrors source structure:
|
|
@@ -368,9 +453,9 @@ src/ compiled/
|
|
|
368
453
|
└── vibe/ └── vibe/
|
|
369
454
|
```
|
|
370
455
|
|
|
371
|
-
###
|
|
456
|
+
### Element Transformation
|
|
372
457
|
|
|
373
|
-
|
|
458
|
+
By default (`elementsAsIs: false`), custom HTML elements are transformed to divs with classes for better HTML validity:
|
|
374
459
|
|
|
375
460
|
```html
|
|
376
461
|
<!-- input -->
|
|
@@ -380,7 +465,23 @@ src/ compiled/
|
|
|
380
465
|
<div class="counter-header">Count</div>
|
|
381
466
|
```
|
|
382
467
|
|
|
383
|
-
|
|
468
|
+
Set `elementsAsIs: true` or use `--elements-as-is` flag to keep custom elements unchanged.
|
|
469
|
+
|
|
470
|
+
**Note:** The `<component>` element is a framework element and is never transformed.
|
|
471
|
+
|
|
472
|
+
### Reserved Element Names
|
|
473
|
+
|
|
474
|
+
The compiler validates component filenames against reserved element names (all HTML5 elements + "component" + your custom list). This prevents confusing bugs where components share names with HTML elements.
|
|
475
|
+
|
|
476
|
+
```json
|
|
477
|
+
{
|
|
478
|
+
"vibe-compiler": {
|
|
479
|
+
"reservedElements": ["my-custom-element", "another-reserved-name"]
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
**Case-sensitive validation:** `nav.html` conflicts with `<nav>` and will error, but `Nav.html` is allowed.
|
|
384
485
|
|
|
385
486
|
### Building Native Binaries
|
|
386
487
|
|
|
Binary file
|
package/compiler/src/Cargo.lock
CHANGED
package/compiler/src/Cargo.toml
CHANGED
|
@@ -502,18 +502,20 @@ impl Compiler {
|
|
|
502
502
|
}
|
|
503
503
|
}
|
|
504
504
|
|
|
505
|
-
// Canonicalize output
|
|
505
|
+
// Canonicalize both output and source paths for reliable comparison
|
|
506
506
|
let canonical_output = self.config.output.canonicalize()
|
|
507
507
|
.unwrap_or_else(|_| self.config.output.clone());
|
|
508
|
+
let canonical_source = self.config.source.canonicalize()
|
|
509
|
+
.unwrap_or_else(|_| self.config.source.clone());
|
|
508
510
|
|
|
509
511
|
// Track compile and copy times separately
|
|
510
512
|
let compile_start = Instant::now();
|
|
511
513
|
|
|
512
514
|
// Process all HTML files in source (excluding components directory)
|
|
513
|
-
self.process_directory_html_only(&
|
|
515
|
+
self.process_directory_html_only(&canonical_source, &parser, "", &canonical_output, &canonical_source, &mut stats)?;
|
|
514
516
|
|
|
515
517
|
// Process source directory for assets (CSS, JS, images, etc.)
|
|
516
|
-
self.process_directory_assets_only(&
|
|
518
|
+
self.process_directory_assets_only(&canonical_source, "", &canonical_output, &canonical_source, &mut stats)?;
|
|
517
519
|
|
|
518
520
|
let process_time = compile_start.elapsed();
|
|
519
521
|
|
|
@@ -906,6 +908,7 @@ impl Compiler {
|
|
|
906
908
|
parser: &HtmlParser,
|
|
907
909
|
relative_path: &str,
|
|
908
910
|
canonical_output: &Path,
|
|
911
|
+
canonical_source: &Path,
|
|
909
912
|
stats: &mut CompileStats,
|
|
910
913
|
) -> Result<(), CompileError> {
|
|
911
914
|
let entries = fs::read_dir(dir).map_err(|e| CompileError::ReadError {
|
|
@@ -941,7 +944,7 @@ impl Compiler {
|
|
|
941
944
|
format!("{}/{}", relative_path, file_name)
|
|
942
945
|
};
|
|
943
946
|
|
|
944
|
-
self.process_directory_html_only(&path, parser, &new_relative, canonical_output, stats)?;
|
|
947
|
+
self.process_directory_html_only(&path, parser, &new_relative, canonical_output, canonical_source, stats)?;
|
|
945
948
|
} else if path.extension().and_then(|e| e.to_str()) == Some("html") {
|
|
946
949
|
let (internal, external, component_srcs) = self.compile_html_file(&path, parser, relative_path)?;
|
|
947
950
|
stats.files_compiled += 1;
|
|
@@ -962,7 +965,7 @@ impl Compiler {
|
|
|
962
965
|
};
|
|
963
966
|
|
|
964
967
|
if let Some(ref mut logger) = self.logger {
|
|
965
|
-
logger.log(&path, FileOperation::Compiled,
|
|
968
|
+
logger.log(&path, FileOperation::Compiled, canonical_source);
|
|
966
969
|
|
|
967
970
|
// Log each component occurrence (for counting)
|
|
968
971
|
if let Some(all_srcs) = all_srcs {
|
|
@@ -990,6 +993,7 @@ impl Compiler {
|
|
|
990
993
|
dir: &Path,
|
|
991
994
|
relative_path: &str,
|
|
992
995
|
canonical_output: &Path,
|
|
996
|
+
canonical_source: &Path,
|
|
993
997
|
stats: &mut CompileStats,
|
|
994
998
|
) -> Result<(), CompileError> {
|
|
995
999
|
let entries = fs::read_dir(dir).map_err(|e| CompileError::ReadError {
|
|
@@ -1027,7 +1031,7 @@ impl Compiler {
|
|
|
1027
1031
|
} else {
|
|
1028
1032
|
format!("{}/{}", relative_path, file_name)
|
|
1029
1033
|
};
|
|
1030
|
-
self.copy_directory(&path, &new_relative, stats)?;
|
|
1034
|
+
self.copy_directory(&path, &new_relative, canonical_source, stats)?;
|
|
1031
1035
|
}
|
|
1032
1036
|
// Skip further processing (don't recurse into components)
|
|
1033
1037
|
continue;
|
|
@@ -1040,7 +1044,7 @@ impl Compiler {
|
|
|
1040
1044
|
format!("{}/{}", relative_path, file_name)
|
|
1041
1045
|
};
|
|
1042
1046
|
|
|
1043
|
-
self.process_directory_assets_only(&path, &new_relative, canonical_output, stats)?;
|
|
1047
|
+
self.process_directory_assets_only(&path, &new_relative, canonical_output, canonical_source, stats)?;
|
|
1044
1048
|
} else if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
|
|
1045
1049
|
// Skip files matching skip patterns
|
|
1046
1050
|
if should_skip_path(&path, file_name) {
|
|
@@ -1052,7 +1056,7 @@ impl Compiler {
|
|
|
1052
1056
|
self.copy_file(&path, relative_path)?;
|
|
1053
1057
|
stats.files_copied += 1;
|
|
1054
1058
|
if let Some(ref mut logger) = self.logger {
|
|
1055
|
-
logger.log(&path, FileOperation::Copied,
|
|
1059
|
+
logger.log(&path, FileOperation::Copied, canonical_source);
|
|
1056
1060
|
}
|
|
1057
1061
|
}
|
|
1058
1062
|
}
|
|
@@ -1523,6 +1527,7 @@ impl Compiler {
|
|
|
1523
1527
|
&mut self,
|
|
1524
1528
|
dir: &Path,
|
|
1525
1529
|
relative_path: &str,
|
|
1530
|
+
canonical_source: &Path,
|
|
1526
1531
|
stats: &mut CompileStats,
|
|
1527
1532
|
) -> Result<(), CompileError> {
|
|
1528
1533
|
let entries = fs::read_dir(dir).map_err(|e| CompileError::ReadError {
|
|
@@ -1541,12 +1546,12 @@ impl Compiler {
|
|
|
1541
1546
|
|
|
1542
1547
|
if path.is_dir() {
|
|
1543
1548
|
let new_relative = format!("{}/{}", relative_path, file_name);
|
|
1544
|
-
self.copy_directory(&path, &new_relative, stats)?;
|
|
1549
|
+
self.copy_directory(&path, &new_relative, canonical_source, stats)?;
|
|
1545
1550
|
} else {
|
|
1546
1551
|
self.copy_file(&path, relative_path)?;
|
|
1547
1552
|
stats.files_copied += 1;
|
|
1548
1553
|
if let Some(ref mut logger) = self.logger {
|
|
1549
|
-
logger.log(&path, FileOperation::Copied,
|
|
1554
|
+
logger.log(&path, FileOperation::Copied, canonical_source);
|
|
1550
1555
|
}
|
|
1551
1556
|
}
|
|
1552
1557
|
}
|
package/compiler/src/main.rs
CHANGED
|
@@ -26,7 +26,7 @@ struct ConfigOverrides {
|
|
|
26
26
|
#[derive(ClapParser, Debug)]
|
|
27
27
|
#[command(name = "vibe-compile")]
|
|
28
28
|
#[command(author = "Kim Korte")]
|
|
29
|
-
#[command(version = "
|
|
29
|
+
#[command(version = "1.6.1")]
|
|
30
30
|
#[command(about = "Compiles Vibe source files into optimized output")]
|
|
31
31
|
struct Args {
|
|
32
32
|
/// Working directory (defaults to current directory)
|
|
@@ -163,7 +163,7 @@ fn main() {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
if args.verbose {
|
|
166
|
-
println!("{}", "Vibe Compiler".cyan().bold());
|
|
166
|
+
println!("{}", format!("Vibe Compiler v{}", env!("CARGO_PKG_VERSION")).cyan().bold());
|
|
167
167
|
println!();
|
|
168
168
|
println!(" {}: {}", "Working dir".cyan(), config.working_dir.display());
|
|
169
169
|
println!(" {}: {}", "Source".cyan(), config.source.display());
|
|
@@ -207,6 +207,9 @@ fn main() {
|
|
|
207
207
|
println!(" {}: {}", format!("{:<18}", "source-maps").cyan(), format_bool_with_flag(config.source_maps, overrides.source_maps, original_source_maps));
|
|
208
208
|
println!(" {}: {}", format!("{:<18}", "validate").cyan(), format_bool_with_flag(config.validate, overrides.validate, original_validate));
|
|
209
209
|
println!();
|
|
210
|
+
} else {
|
|
211
|
+
// Show version in non-verbose mode
|
|
212
|
+
println!("{}", format!("Vibe Compiler v{}", env!("CARGO_PKG_VERSION")).cyan());
|
|
210
213
|
}
|
|
211
214
|
|
|
212
215
|
// Handle watch mode
|
|
@@ -243,7 +246,7 @@ fn main() {
|
|
|
243
246
|
}
|
|
244
247
|
|
|
245
248
|
// Show success headline
|
|
246
|
-
println!("\n{}", "Compilation successful! ✅".green().bold());
|
|
249
|
+
println!("\n{}", format!("Compilation successful! (v{}) ✅", env!("CARGO_PKG_VERSION")).green().bold());
|
|
247
250
|
println!();
|
|
248
251
|
|
|
249
252
|
// Show individual phase timings (validation first, then components, HTML, manifests, copied)
|
package/package.json
CHANGED
|
@@ -135,7 +135,21 @@ const detectHyperspeed = async () => {
|
|
|
135
135
|
hyperspeedDetectionAttempted = true;
|
|
136
136
|
|
|
137
137
|
try {
|
|
138
|
-
|
|
138
|
+
let pagePath = window.location.pathname;
|
|
139
|
+
|
|
140
|
+
// Normalize path: handle directory URLs and missing extensions
|
|
141
|
+
if (pagePath.endsWith('/')) {
|
|
142
|
+
// /compiled/ -> /compiled/index.html
|
|
143
|
+
pagePath = pagePath + 'index.html';
|
|
144
|
+
} else if (!pagePath.includes('.')) {
|
|
145
|
+
// /compiled/mypage -> /compiled/mypage.html
|
|
146
|
+
const lastSlash = pagePath.lastIndexOf('/');
|
|
147
|
+
const lastSegment = pagePath.substring(lastSlash + 1);
|
|
148
|
+
if (lastSegment && !lastSegment.includes('.')) {
|
|
149
|
+
pagePath = pagePath + '.html';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
139
153
|
const pathSegments = pagePath.split('/').filter(s => s);
|
|
140
154
|
|
|
141
155
|
if (pathSegments.length === 0) return null;
|