reroute-js 0.2.3 → 0.3.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.
Files changed (100) hide show
  1. package/README.md +24 -7
  2. package/_/basic/package.json +2 -1
  3. package/_/blog/package.json +2 -1
  4. package/_/store/package.json +2 -1
  5. package/_/store/src/client/components/ProductCard.tsx +6 -3
  6. package/_/store/src/client/lib/api.ts +66 -1
  7. package/_/store/src/client/routes/index.tsx +3 -3
  8. package/_/store/src/client/routes/products/[id].tsx +7 -2
  9. package/_/store/src/index.ts +1 -0
  10. package/cli/bin.d.ts +9 -1
  11. package/cli/bin.js +28 -23
  12. package/cli/bin.js.map +5 -5
  13. package/cli/index.d.ts +9 -0
  14. package/cli/index.js +12 -46
  15. package/cli/index.js.map +3 -3
  16. package/cli/src/cli.d.ts +9 -1
  17. package/cli/src/commands/build.d.ts +9 -1
  18. package/cli/src/commands/dev.d.ts +9 -1
  19. package/cli/src/commands/gen.d.ts +9 -1
  20. package/cli/src/commands/init.d.ts +9 -1
  21. package/cli/src/libs/index.d.ts +9 -0
  22. package/cli/src/libs/tailwind.d.ts +9 -34
  23. package/cli/src/libs/tailwind.d.ts.map +1 -1
  24. package/core/index.d.ts +9 -0
  25. package/core/index.js +24 -3
  26. package/core/index.js.map +4 -4
  27. package/core/src/bundler/hash.d.ts +9 -0
  28. package/core/src/bundler/index.d.ts +9 -0
  29. package/core/src/bundler/transpile.d.ts +9 -0
  30. package/core/src/bundler/transpile.d.ts.map +1 -1
  31. package/core/src/content/discovery.d.ts +9 -0
  32. package/core/src/content/index.d.ts +9 -0
  33. package/core/src/content/metadata.d.ts +9 -0
  34. package/core/src/content/registry.d.ts +9 -0
  35. package/core/src/index.d.ts +9 -0
  36. package/core/src/ssr/data.d.ts +9 -0
  37. package/core/src/ssr/index.d.ts +9 -0
  38. package/core/src/ssr/modules.d.ts +9 -0
  39. package/core/src/ssr/render.d.ts +9 -0
  40. package/core/src/ssr/seed.d.ts +9 -0
  41. package/core/src/template/html.d.ts +9 -0
  42. package/core/src/template/index.d.ts +9 -0
  43. package/core/src/types.d.ts +9 -0
  44. package/core/src/utils/cache.d.ts +9 -0
  45. package/core/src/utils/compression.d.ts +9 -0
  46. package/core/src/utils/index.d.ts +9 -0
  47. package/core/src/utils/mime.d.ts +9 -0
  48. package/core/src/utils/path.d.ts +9 -0
  49. package/elysia/index.d.ts +9 -0
  50. package/elysia/index.js +419 -98
  51. package/elysia/index.js.map +12 -10
  52. package/elysia/src/index.d.ts +9 -0
  53. package/elysia/src/{utils → libs}/http.d.ts +9 -0
  54. package/elysia/src/libs/http.d.ts.map +1 -0
  55. package/elysia/src/libs/image.d.ts +38 -0
  56. package/elysia/src/libs/image.d.ts.map +1 -0
  57. package/elysia/src/plugin.d.ts +9 -0
  58. package/elysia/src/plugin.d.ts.map +1 -1
  59. package/elysia/src/routes/artifacts.d.ts +9 -0
  60. package/elysia/src/routes/content.d.ts +9 -0
  61. package/elysia/src/routes/dev.d.ts +11 -0
  62. package/elysia/src/routes/dev.d.ts.map +1 -1
  63. package/elysia/src/routes/image.d.ts +23 -0
  64. package/elysia/src/routes/image.d.ts.map +1 -0
  65. package/elysia/src/routes/ssr.d.ts +11 -0
  66. package/elysia/src/routes/ssr.d.ts.map +1 -1
  67. package/elysia/src/routes/static.d.ts +9 -0
  68. package/elysia/src/routes/static.d.ts.map +1 -1
  69. package/elysia/src/types.d.ts +13 -0
  70. package/elysia/src/types.d.ts.map +1 -1
  71. package/package.json +5 -4
  72. package/react/index.d.ts +9 -0
  73. package/react/index.js +223 -5
  74. package/react/index.js.map +4 -3
  75. package/react/src/components/ContentRoute.d.ts +9 -0
  76. package/react/src/components/Image.d.ts +53 -0
  77. package/react/src/components/Image.d.ts.map +1 -0
  78. package/react/src/components/Link.d.ts +9 -0
  79. package/react/src/components/Outlet.d.ts +9 -0
  80. package/react/src/components/index.d.ts +10 -0
  81. package/react/src/components/index.d.ts.map +1 -1
  82. package/react/src/hooks/index.d.ts +9 -0
  83. package/react/src/hooks/useContent.d.ts +9 -0
  84. package/react/src/hooks/useData.d.ts +9 -0
  85. package/react/src/hooks/useNavigate.d.ts +9 -0
  86. package/react/src/hooks/useParams.d.ts +9 -0
  87. package/react/src/hooks/useRouter.d.ts +9 -0
  88. package/react/src/hooks/useSearchParams.d.ts +9 -0
  89. package/react/src/index.d.ts +9 -0
  90. package/react/src/providers/ContentProvider.d.ts +9 -0
  91. package/react/src/providers/RerouteProvider.d.ts +9 -0
  92. package/react/src/providers/RouterProvider.d.ts +9 -0
  93. package/react/src/providers/index.d.ts +9 -0
  94. package/react/src/types/any.d.ts +9 -0
  95. package/react/src/types/index.d.ts +9 -0
  96. package/react/src/types/router.d.ts +9 -0
  97. package/react/src/utils/content.d.ts +9 -0
  98. package/react/src/utils/head.d.ts +9 -0
  99. package/react/src/utils/index.d.ts +9 -0
  100. package/elysia/src/utils/http.d.ts.map +0 -1
package/README.md CHANGED
@@ -34,6 +34,7 @@ Reroute is a dead-simple file-based routing framework for building full-stack Re
34
34
  - `useContent()` - Load and manage content collections with sorting
35
35
  - 🧩 **Components**:
36
36
  - `<Link>` - Client-side navigation with automatic prefetching
37
+ - `<Image>` - Optimized images with automatic format negotiation and lazy loading
37
38
  - `<Outlet>` - Render nested route components
38
39
  - `<ContentRoute>` - Dynamic content rendering with metadata injection
39
40
  - 🎭 **Providers**:
@@ -44,6 +45,20 @@ Reroute is a dead-simple file-based routing framework for building full-stack Re
44
45
  - `useData()` - Read route-level SSR data without loaders
45
46
  - `export const ssr = { data() {} }` - Route data function executed on server
46
47
 
48
+ ### 🖼️ Image Optimization
49
+ - 🎨 **Format Control** - Support for auto, AVIF, WebP, JPEG, and PNG formats
50
+ - 📐 **Responsive Images** - Automatic srcset generation for multiple device sizes (640-3840px)
51
+ - ⚡ **On-Demand Processing** - Server-side image transformation using Sharp
52
+ - 💾 **Smart Caching** - In-memory and disk cache for optimized images (1-year cache headers)
53
+ - 🔍 **Lazy Loading** - Native lazy loading with IntersectionObserver fallback for older browsers
54
+ - ⏫ **Priority Loading** - Opt-in eager loading for above-the-fold images
55
+ - 🌫️ **Blur Placeholder** - Optional blur-up effect with custom blur data URL support
56
+ - 🎯 **Quality Control** - Configurable quality (1-100, default: 75)
57
+ - 🔄 **Loading States** - Built-in spinner with customization (size, color, background, custom component)
58
+ - 🎨 **Custom Loader** - Override default image URL generation
59
+ - 📏 **Callbacks** - onLoad and onError event handlers
60
+ - 📱 **Sizes Attribute** - Control responsive image selection with custom sizes
61
+
47
62
  ### 🚀 Performance Optimizations
48
63
  - 💾 **Smart Caching** - LRU cache for files and bundles
49
64
  - 🔗 **Link Prefetching** - Automatic content prefetching on hover/focus
@@ -113,12 +128,7 @@ cd my-app
113
128
  bun dev
114
129
  ```
115
130
 
116
- ## 📖 Learn More
117
-
118
- - [Examples](./examples) - Example projects to get started
119
- - [GitHub](https://github.com/stewones/reroute) - Source code and issues
120
-
121
- ## 🔌 SSR Data: External Requests Without Loaders
131
+ ## 🔌 External Requests SSR
122
132
 
123
133
  Reroute can execute route-level data fetching on the server and inline the result for instant hydration. Define an optional `ssr.data` in a route module and read it with `useData()`:
124
134
 
@@ -144,10 +154,17 @@ export default function Page() {
144
154
  ```
145
155
 
146
156
  Notes:
147
- - Works with Bun + Elysia SSR, no client loaders needed for initial render
157
+ - Works with Bun + Elysia, no client loaders needed for initial render
148
158
  - Data is injected as `window.__REROUTE_DATA__` and read during hydration
149
159
  - Use with existing content features (useContent); both can be seeded in the same page
150
160
 
161
+
162
+ ## 📖 Learn More
163
+
164
+ - [Examples](./examples) - Example projects to get started
165
+ - [Packages](./packages) - Reroute entrypoints
166
+ - [GitHub](https://github.com/stewones/reroute) - Source code and issues
167
+
151
168
  ## 📝 License
152
169
 
153
170
  MIT
@@ -12,7 +12,8 @@
12
12
  "reroute-js": "latest",
13
13
  "elysia": "^1.4.13",
14
14
  "react": "^19.2.0",
15
- "react-dom": "^19.2.0"
15
+ "react-dom": "^19.2.0",
16
+ "sharp": "^0.34.4"
16
17
  },
17
18
  "devDependencies": {
18
19
  "@types/bun": "latest",
@@ -12,7 +12,8 @@
12
12
  "reroute-js": "latest",
13
13
  "elysia": "^1.4.12",
14
14
  "react": "^19.2.0",
15
- "react-dom": "^19.2.0"
15
+ "react-dom": "^19.2.0",
16
+ "sharp": "^0.34.4"
16
17
  },
17
18
  "devDependencies": {
18
19
  "@types/bun": "latest",
@@ -12,7 +12,8 @@
12
12
  "reroute-js": "latest",
13
13
  "elysia": "^1.4.12",
14
14
  "react": "^19.2.0",
15
- "react-dom": "^19.2.0"
15
+ "react-dom": "^19.2.0",
16
+ "sharp": "^0.34.4"
16
17
  },
17
18
  "devDependencies": {
18
19
  "@types/bun": "latest",
@@ -1,5 +1,5 @@
1
1
  /** biome-ignore-all lint/a11y/noStaticElementInteractions: who cares, this is just an example */
2
- import { Link } from 'reroute-js/react';
2
+ import { Image, Link } from 'reroute-js/react';
3
3
  import type { Product } from '../lib/api';
4
4
 
5
5
  interface ProductCardProps {
@@ -14,11 +14,14 @@ export default function ProductCard({ product }: ProductCardProps) {
14
14
  className='block h-full no-underline text-inherit'
15
15
  >
16
16
  <div className='w-full h-60 bg-gray-50 flex items-center justify-center p-4'>
17
- <img
17
+ <Image
18
18
  src={product.image}
19
19
  alt={product.title}
20
+ width={180}
21
+ height={180}
22
+ quality={75}
23
+ sizes='(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 180px'
20
24
  className='max-w-full max-h-full object-contain'
21
- loading='lazy'
22
25
  style={{ viewTransitionName: `product-image-${product.id}` }}
23
26
  />
24
27
  </div>
@@ -3,6 +3,15 @@
3
3
 
4
4
  const API_BASE_URL = 'https://fakestoreapi.com';
5
5
 
6
+ // In-memory cache for API responses
7
+ interface CacheEntry<T> {
8
+ data: T;
9
+ timestamp: number;
10
+ }
11
+
12
+ const cache = new Map<string, CacheEntry<unknown>>();
13
+ const CACHE_TTL = 60000; // 1 minute cache
14
+
6
15
  interface Product {
7
16
  id: number;
8
17
  title: string;
@@ -65,11 +74,67 @@ function ensureSerializable<T>(data: T): T {
65
74
  return JSON.parse(JSON.stringify(data));
66
75
  }
67
76
 
68
- // Generic fetch wrapper with error handling
77
+ // Cache helper functions
78
+ function getCached<T>(key: string): T | null {
79
+ const entry = cache.get(key) as CacheEntry<T> | undefined;
80
+ if (!entry) return null;
81
+
82
+ const isExpired = Date.now() - entry.timestamp > CACHE_TTL;
83
+ if (isExpired) {
84
+ cache.delete(key);
85
+ return null;
86
+ }
87
+
88
+ return entry.data;
89
+ }
90
+
91
+ function setCache<T>(key: string, data: T): void {
92
+ cache.set(key, {
93
+ data,
94
+ timestamp: Date.now(),
95
+ });
96
+ }
97
+
98
+ // Generic fetch wrapper with error handling and caching
69
99
  async function apiFetch<T>(
70
100
  endpoint: string,
71
101
  options?: RequestInit,
72
102
  ): Promise<T> {
103
+ // Check cache first (only for GET requests)
104
+ if (!options || !options.method || options.method === 'GET') {
105
+ const cacheKey = `${endpoint}:${JSON.stringify(options || {})}`;
106
+ const cached = getCached<T>(cacheKey);
107
+ if (cached) {
108
+ return cached;
109
+ }
110
+
111
+ try {
112
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
113
+ ...options,
114
+ headers: {
115
+ 'Content-Type': 'application/json',
116
+ ...options?.headers,
117
+ },
118
+ });
119
+
120
+ if (!response.ok) {
121
+ throw new Error(`API error: ${response.status} ${response.statusText}`);
122
+ }
123
+
124
+ const data = await response.json();
125
+ const serialized = ensureSerializable(data);
126
+
127
+ // Cache the result
128
+ setCache(cacheKey, serialized);
129
+
130
+ return serialized;
131
+ } catch (error) {
132
+ console.error(`API request failed: ${endpoint}`, error);
133
+ throw error;
134
+ }
135
+ }
136
+
137
+ // Non-GET requests - no caching
73
138
  try {
74
139
  const response = await fetch(`${API_BASE_URL}${endpoint}`, {
75
140
  ...options,
@@ -80,8 +80,8 @@ function HomePage() {
80
80
  Lightning Fast
81
81
  </h3>
82
82
  <p className='text-gray-600 leading-relaxed'>
83
- Built on Bun for blazing fast performance and server-side
84
- rendering
83
+ Built on Bun for blazing fast performance, server-side rendering
84
+ and image caching
85
85
  </p>
86
86
  </div>
87
87
 
@@ -102,7 +102,7 @@ function HomePage() {
102
102
  SSR Support
103
103
  </h3>
104
104
  <p className='text-gray-600 leading-relaxed'>
105
- SEO-friendly server-side rendering with automatic hydration
105
+ SEO-friendly with automatic hydration
106
106
  </p>
107
107
  </div>
108
108
 
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useState } from 'react';
2
- import { Link, useData, useParams } from 'reroute-js/react';
2
+ import { Image, Link, useData, useParams } from 'reroute-js/react';
3
3
  import Header from '../../components/Header';
4
4
  import ProductCard from '../../components/ProductCard';
5
5
  import { getProduct, getProductsByCategory, type Product } from '../../lib/api';
@@ -141,9 +141,14 @@ function ProductDetailPage() {
141
141
  <div className='grid grid-cols-1 lg:grid-cols-2 gap-12 mb-16'>
142
142
  {/* Product Image */}
143
143
  <div className='card flex items-center justify-center p-8 min-h-[500px]'>
144
- <img
144
+ <Image
145
145
  src={product.image}
146
146
  alt={product.title}
147
+ width={350}
148
+ height={350}
149
+ quality={85}
150
+ sizes='(max-width: 1024px) 90vw, 350px'
151
+ priority
147
152
  className='max-w-full max-h-[500px] object-contain'
148
153
  style={{ viewTransitionName: `product-image-${product.id}` }}
149
154
  />
@@ -10,6 +10,7 @@ const app = new Elysia()
10
10
  reroute({
11
11
  app: createElement(App),
12
12
  minify: IS_PRODUCTION,
13
+ imageAllowedDomains: ['fakestoreapi.com'],
13
14
  }),
14
15
  )
15
16
  .listen(Number(Bun.env.PORT || '3001'));
package/cli/bin.d.ts CHANGED
@@ -1,3 +1,11 @@
1
- #!/usr/bin/env bun
1
+ /**
2
+ * reroute-js v0.2.2
3
+ *
4
+ * @license MIT
5
+ * @copyright 2025 stewones <hi@stewan.io>
6
+ * @see https://github.com/stewones/reroute
7
+ *
8
+ * Built with Bun <3
9
+ */
2
10
  export * from './src/cli';
3
11
  //# sourceMappingURL=bin.d.ts.map
package/cli/bin.js CHANGED
@@ -1,5 +1,14 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
+ /**
4
+ * reroute-js v0.2.2
5
+ *
6
+ * @license MIT
7
+ * @copyright 2025 stewones <hi@stewan.io>
8
+ * @see https://github.com/stewones/reroute
9
+ *
10
+ * Built with Bun <3
11
+ */
3
12
  var __defProp = Object.defineProperty;
4
13
  var __export = (target, all) => {
5
14
  for (var name in all)
@@ -288,21 +297,17 @@ async function buildTailwind(cwd) {
288
297
  });
289
298
  });
290
299
  }
291
- function initTailwind(cwd) {
292
- if (!isTailwindAvailable(cwd)) {
293
- return null;
294
- }
295
- if (!hasTailwindInput(cwd)) {
296
- console.log('[reroute/tailwind] theme.css not found or missing @import "tailwindcss", skipping...');
297
- return null;
300
+ async function buildTailwindIfConfigured(cwd) {
301
+ try {
302
+ if (isTailwindAvailable(cwd) && hasTailwindInput(cwd)) {
303
+ await buildTailwind(cwd);
304
+ return true;
305
+ }
306
+ return false;
307
+ } catch (e) {
308
+ console.warn("[reroute/tailwind] build failed:", e);
309
+ return true;
298
310
  }
299
- console.log("[reroute/tailwind] Detected Tailwind CSS v4");
300
- buildTailwind(cwd).then(() => {
301
- console.log("[reroute/tailwind] Built successfully");
302
- }).catch((error) => {
303
- console.error("[reroute/tailwind] Build failed:", error);
304
- });
305
- return null;
306
311
  }
307
312
  var init_tailwind = () => {};
308
313
 
@@ -719,14 +724,14 @@ async function gen(args) {
719
724
  const watchMode = args.includes("--watch") || args.includes("-w");
720
725
  if (!watchMode) {
721
726
  await generate(cwd);
722
- initTailwind(cwd);
727
+ await buildTailwindIfConfigured(cwd);
723
728
  return;
724
729
  }
725
730
  console.log("[reroute/gen] Watch mode enabled");
726
731
  console.log("[reroute/gen] Initial generation...");
727
732
  await generate(cwd);
728
733
  try {
729
- await buildTailwind(cwd);
734
+ await buildTailwindIfConfigured(cwd);
730
735
  } catch {}
731
736
  const notifyReload = async (reason) => {
732
737
  const ports = [
@@ -757,7 +762,7 @@ async function gen(args) {
757
762
  console.log("[reroute/gen] Change detected, regenerating...");
758
763
  try {
759
764
  await generate(cwd);
760
- await buildTailwind(cwd);
765
+ await buildTailwindIfConfigured(cwd);
761
766
  await notifyReload("routes change");
762
767
  } catch (e) {
763
768
  console.error("[reroute/gen] Error during regeneration:", e);
@@ -781,7 +786,7 @@ async function gen(args) {
781
786
  clearTimeout(twDebounce);
782
787
  twDebounce = setTimeout(async () => {
783
788
  try {
784
- await buildTailwind(cwd);
789
+ await buildTailwindIfConfigured(cwd);
785
790
  await notifyReload("client change");
786
791
  } catch {}
787
792
  }, 150);
@@ -819,14 +824,14 @@ async function main() {
819
824
  process.exit(0);
820
825
  }
821
826
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
822
- printHelp2();
827
+ await printHelp2();
823
828
  process.exit(0);
824
829
  }
825
830
  const command = args[0];
826
831
  if (!commands[command]) {
827
832
  console.error(`Unknown command: ${command}`);
828
833
  console.error("");
829
- printHelp2();
834
+ await printHelp2();
830
835
  process.exit(1);
831
836
  }
832
837
  try {
@@ -837,8 +842,8 @@ async function main() {
837
842
  process.exit(1);
838
843
  }
839
844
  }
840
- function printHelp2() {
841
- console.log("Reroute CLI - File-based routing framework");
845
+ async function printHelp2() {
846
+ console.log(`Reroute v${await getVersion()}`);
842
847
  console.log("");
843
848
  console.log("Usage:");
844
849
  console.log(" reroute <command> [options]");
@@ -875,4 +880,4 @@ async function getVersion() {
875
880
  }
876
881
  main();
877
882
 
878
- //# debugId=6AFDAF7BCD23E07864756E2164756E21
883
+ //# debugId=2E434D31D2F3337264756E2164756E21