@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 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 library.
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 processes HTML files, inlines components, and handles assets while preserving directory structure.
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
- # Compile
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 --accessibility # Transform custom tags to divs
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 --watch # Watch mode (future)
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
- "vibe:compile": "vibe compile",
287
- "vibe:compile:init": "vibe compile --init"
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 `bun vibe:compile` or `npm run vibe:compile`.
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
- "accessibility": false,
308
- "excludeTags": [],
309
- "nodeModulesAsIs": false
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
- ### Accessibility Mode
456
+ ### Element Transformation
372
457
 
373
- `--accessibility` transforms custom HTML elements to divs with classes:
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
- Exclude specific tags (e.g., web components) via `excludeTags` config.
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
 
@@ -2061,7 +2061,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
2061
2061
 
2062
2062
  [[package]]
2063
2063
  name = "vibe-compiler"
2064
- version = "0.1.0"
2064
+ version = "1.6.1"
2065
2065
  dependencies = [
2066
2066
  "clap",
2067
2067
  "colored",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "vibe-compiler"
3
- version = "0.1.0"
3
+ version = "1.6.1"
4
4
  edition = "2021"
5
5
  description = "Vibe framework compiler - compiles Vibe source files into optimized output"
6
6
  authors = ["Kim Korte"]
@@ -502,18 +502,20 @@ impl Compiler {
502
502
  }
503
503
  }
504
504
 
505
- // Canonicalize output path once for reliable comparison
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(&self.config.source.clone(), &parser, "", &canonical_output, &mut stats)?;
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(&self.config.source.clone(), "", &canonical_output, &mut stats)?;
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, &self.config.source);
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, &self.config.source);
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, &self.config.source);
1554
+ logger.log(&path, FileOperation::Copied, canonical_source);
1550
1555
  }
1551
1556
  }
1552
1557
  }
@@ -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 = "0.1.0")]
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ape-egg/vibe",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "type": "module",
5
5
  "description": "Runtime-first reactivity with optional compiler",
6
6
  "main": "index.js",
@@ -135,7 +135,21 @@ const detectHyperspeed = async () => {
135
135
  hyperspeedDetectionAttempted = true;
136
136
 
137
137
  try {
138
- const pagePath = window.location.pathname;
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;