bini-router 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Binidu Ranasinghe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/Readme.md ADDED
@@ -0,0 +1,359 @@
1
+ # bini-router
2
+
3
+ <div align="center">
4
+
5
+ [![npm version](https://img.shields.io/npm/v/bini-router?color=00CFFF&labelColor=0a0a0a&style=flat-square)](https://www.npmjs.com/package/bini-router)
6
+ [![license](https://img.shields.io/badge/license-MIT-00CFFF?labelColor=0a0a0a&style=flat-square)](./LICENSE)
7
+ [![vite](https://img.shields.io/badge/vite-6.x-646cff?labelColor=0a0a0a&style=flat-square)](https://vitejs.dev)
8
+ [![react](https://img.shields.io/badge/react-18%2B-61dafb?labelColor=0a0a0a&style=flat-square)](https://react.dev)
9
+ [![hono](https://img.shields.io/badge/hono-powered-e36002?labelColor=0a0a0a&style=flat-square)](https://hono.dev)
10
+ [![typescript](https://img.shields.io/badge/typescript-ready-3178c6?labelColor=0a0a0a&style=flat-square)](https://www.typescriptlang.org)
11
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-00CFFF?labelColor=0a0a0a&style=flat-square)](https://github.com/binidu/bini-router/pulls)
12
+
13
+ **File-based routing, nested layouts, per-route metadata, and Hono-powered API routes for Vite.**
14
+ Like Next.js — but pure SPA, zero server required.
15
+
16
+ </div>
17
+
18
+ ---
19
+
20
+ ## Features
21
+
22
+ - 🗂️ **File-based routing** — `page.tsx` files map directly to URLs
23
+ - 🪆 **Nested layouts** — layouts wrap their segment and all children
24
+ - 🏷️ **Per-route metadata** — `export const metadata` in any layout or page
25
+ - 🔀 **Dynamic segments** — `[id]/page.tsx` → `/:id`
26
+ - 🌐 **API routes** — Hono-powered, pure `Request → Response` handlers
27
+ - 🛡️ **Built-in error boundaries** — per-layout crash isolation
28
+ - ⏳ **Lazy loading** — every route is code-split automatically
29
+ - 🔄 **HMR** — file watcher with smart debounce and dedup
30
+ - 📦 **Zero config** — works out of the box, all platforms emitted on build
31
+ - 🚀 **Deploy anywhere** — Vercel, Netlify, Cloudflare Workers, Apache, Node
32
+
33
+ ---
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ npm install bini-router hono
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Setup
44
+
45
+ ### `vite.config.ts`
46
+
47
+ ```ts
48
+ import { defineConfig } from 'vite'
49
+ import react from '@vitejs/plugin-react'
50
+ import { biniroute } from 'bini-router'
51
+
52
+ export default defineConfig({
53
+ plugins: [react(), biniroute()],
54
+ })
55
+ ```
56
+
57
+ ### `index.html`
58
+
59
+ ```html
60
+ <!DOCTYPE html>
61
+ <html lang="en">
62
+ <head>
63
+ <!-- bini-router injects all meta tags here automatically -->
64
+ </head>
65
+ <body>
66
+ <div id="root"></div>
67
+ <script type="module" src="/src/main.tsx"></script>
68
+ </body>
69
+ </html>
70
+ ```
71
+
72
+ > You do **not** need to manually add `<title>`, `<meta>`, favicons, or Open Graph tags.
73
+ > bini-router reads your `metadata` export and injects everything at build time.
74
+
75
+ ---
76
+
77
+ ## File Structure
78
+
79
+ ```
80
+ src/
81
+ main.tsx ← mounts <App /> as usual
82
+ App.tsx ← auto-generated by bini-router — do not edit
83
+ app/
84
+ globals.css ← global styles
85
+ layout.tsx ← root layout + global metadata
86
+ page.tsx ← /
87
+
88
+ dashboard/
89
+ layout.tsx ← nested layout for /dashboard/*
90
+ page.tsx ← /dashboard
91
+ [id]/
92
+ page.tsx ← /dashboard/:id
93
+
94
+ blog/
95
+ [slug]/
96
+ page.tsx ← /blog/:slug
97
+
98
+ api/
99
+ users.ts ← /api/users
100
+ posts/
101
+ index.ts ← /api/posts
102
+ [id].ts ← /api/posts/:id
103
+ [...catch].ts ← /api/* catch-all
104
+
105
+ not-found.tsx ← custom 404 page (optional)
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Pages
111
+
112
+ ```tsx
113
+ // src/app/dashboard/page.tsx
114
+ export default function Dashboard() {
115
+ return <h1>Dashboard</h1>
116
+ }
117
+ ```
118
+
119
+ ### Dynamic routes
120
+
121
+ ```tsx
122
+ // src/app/blog/[slug]/page.tsx
123
+ import { useParams } from 'react-router-dom'
124
+
125
+ export default function Post() {
126
+ const { slug } = useParams()
127
+ return <h1>Post: {slug}</h1>
128
+ }
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Layouts
134
+
135
+ Layouts wrap all pages in their directory and subdirectories.
136
+
137
+ ```tsx
138
+ // src/app/layout.tsx — root layout
139
+ // ✅ Use {children} — this is a React wrapper, not an Outlet-based route
140
+ export const metadata = {
141
+ title : 'My App',
142
+ description: 'Built with bini-router',
143
+ }
144
+
145
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
146
+ return <>{children}</>
147
+ }
148
+ ```
149
+
150
+ ```tsx
151
+ // src/app/dashboard/layout.tsx — nested layout
152
+ // ✅ Use <Outlet /> — this IS a React Router route wrapper
153
+ import { Outlet } from 'react-router-dom'
154
+
155
+ export const metadata = {
156
+ title: 'Dashboard',
157
+ }
158
+
159
+ export default function DashboardLayout() {
160
+ return (
161
+ <div className="dashboard">
162
+ <aside>Sidebar</aside>
163
+ <main>
164
+ <Outlet />
165
+ </main>
166
+ </div>
167
+ )
168
+ }
169
+ ```
170
+
171
+ > **Root layout** (`src/app/layout.tsx`) uses `{children}` — it wraps `<BrowserRouter>` from outside.
172
+ > **Nested layouts** use `<Outlet />` — they are React Router route wrappers.
173
+
174
+ ### Layouts with `<html>` are skipped
175
+
176
+ If a layout renders `<html><body>`, bini-router automatically skips it as a route wrapper.
177
+ The `<html>` shell belongs in `index.html`, not in React. Keep your root layout as a clean provider/context wrapper.
178
+
179
+ ---
180
+
181
+ ## Metadata
182
+
183
+ Export `metadata` from **any** `layout.tsx`. The title is applied via `document.title` when the layout mounts. Root layout metadata is injected into `index.html` at build time.
184
+
185
+ ```ts
186
+ export const metadata = {
187
+ // Core
188
+ title : 'Dashboard',
189
+ description : 'Your personal dashboard',
190
+ viewport : 'width=device-width, initial-scale=1.0',
191
+ themeColor : '#00CFFF',
192
+ charset : 'UTF-8',
193
+ robots : 'index, follow',
194
+ manifest : '/site.webmanifest',
195
+
196
+ // Keywords
197
+ keywords : ['react', 'vite', 'dashboard'],
198
+
199
+ // Author
200
+ authors : [{ name: 'Your Name', url: 'https://example.com' }],
201
+
202
+ // Canonical / base
203
+ metadataBase: new URL('https://myapp.com'),
204
+
205
+ // Open Graph
206
+ openGraph: {
207
+ title : 'Dashboard',
208
+ description: 'Your personal dashboard',
209
+ url : 'https://myapp.com/dashboard',
210
+ siteName : 'My App',
211
+ type : 'website',
212
+ images : [{ url: '/og.png', width: 1200, height: 630, alt: 'Dashboard' }],
213
+ },
214
+
215
+ // Twitter / X
216
+ twitter: {
217
+ card : 'summary_large_image',
218
+ title : 'Dashboard',
219
+ description: 'Your personal dashboard',
220
+ creator : '@yourhandle',
221
+ images : ['/og.png'],
222
+ },
223
+
224
+ // Icons
225
+ icons: {
226
+ icon : [{ url: '/favicon.svg', type: 'image/svg+xml' }],
227
+ shortcut: [{ url: '/favicon.png' }],
228
+ apple : [{ url: '/apple-touch-icon.png', sizes: '180x180' }],
229
+ },
230
+ }
231
+ ```
232
+
233
+ All fields are optional. `metadata` is stripped from the browser bundle automatically — it never ships to the client.
234
+
235
+ ---
236
+
237
+ ## API Routes
238
+
239
+ API handlers are pure Hono context handlers. Export a default Hono app or a single handler function.
240
+
241
+ ```ts
242
+ // src/app/api/users.ts
243
+ import { Hono } from 'hono'
244
+
245
+ const app = new Hono()
246
+
247
+ app.get('/api/users', (c) => c.json({ users: ['alice', 'bob'] }))
248
+
249
+ app.post('/api/users', async (c) => {
250
+ const body = await c.req.json()
251
+ return c.json(body, 201)
252
+ })
253
+
254
+ export default app
255
+ ```
256
+
257
+ ### Dynamic API route
258
+
259
+ ```ts
260
+ // src/app/api/posts/[id].ts
261
+ import { Hono } from 'hono'
262
+
263
+ const app = new Hono()
264
+
265
+ app.get('/api/posts/:id', (c) => c.json({ id: c.req.param('id') }))
266
+
267
+ export default app
268
+ ```
269
+
270
+ ### Simple handler shorthand
271
+
272
+ ```ts
273
+ // src/app/api/hello.ts
274
+ export default (c: any) => c.json({ message: 'Hello!' })
275
+ ```
276
+
277
+ ### Catch-all API route
278
+
279
+ ```ts
280
+ // src/app/api/[...catch].ts
281
+ export default (c: any) => c.json({ message: 'Not found', path: c.req.path }, 404)
282
+ ```
283
+
284
+ ---
285
+
286
+ ## Custom 404
287
+
288
+ Create `src/app/not-found.tsx` with a default export. If the file is empty or missing, bini-router renders a built-in 404 page.
289
+
290
+ ```tsx
291
+ // src/app/not-found.tsx
292
+ export default function NotFound() {
293
+ return (
294
+ <div>
295
+ <h1>404 — Page not found</h1>
296
+ <a href="/">Go home</a>
297
+ </div>
298
+ )
299
+ }
300
+ ```
301
+
302
+ ---
303
+
304
+ ## Build output
305
+
306
+ Every `npm run build` automatically emits adapters for **all** platforms — no config needed:
307
+
308
+ | File | Platform |
309
+ |---|---|
310
+ | `dist/api.js` | Universal Hono handler (Node, Bun, Deno) |
311
+ | `dist/worker.js` | Cloudflare Workers |
312
+ | `dist/_redirects` | Netlify (SPA fallback + API rewrite) |
313
+ | `dist/vercel.json` | Vercel (SPA fallback + API rewrite) |
314
+ | `dist/.htaccess` | Apache (SPA fallback) |
315
+ | `netlify/functions/api.js` | Netlify Functions adapter |
316
+ | `api/index.js` | Vercel Edge Function adapter |
317
+
318
+ ---
319
+
320
+ ## Options
321
+
322
+ ```ts
323
+ biniroute({
324
+ appDir: 'src/app', // Custom app directory (default: src/app)
325
+ apiDir: 'src/app/api', // Custom API directory (default: src/app/api)
326
+ api : true, // Enable API routes (default: true)
327
+ cors : true, // Auto CORS headers on API routes (default: true)
328
+ })
329
+ ```
330
+
331
+ ---
332
+
333
+ ## How it works
334
+
335
+ ```
336
+ vite dev / build
337
+ ├── bini-router scans src/app/
338
+ ├── generates src/App.tsx (lazy routes + error boundaries)
339
+ ├── strips `export const metadata` from all files (never ships to browser)
340
+ ├── injects <title>, <meta>, OG, Twitter, icons into index.html
341
+ └── watches for file changes → debounced regen → HMR full-reload
342
+
343
+ Request /dashboard/123
344
+ ├── BrowserRouter matches → DashboardLayout > Page
345
+ ├── TitleSetter fires document.title = 'Dashboard'
346
+ ├── React.lazy loads the page chunk on demand
347
+ └── ErrorBoundary catches any render crash per layout segment
348
+
349
+ Request /api/users
350
+ ├── Vite middleware intercepts /api/*
351
+ ├── bini-router loads src/app/api/users.ts via Hono
352
+ └── returns Response — pure web standard, no framework lock-in
353
+ ```
354
+
355
+ ---
356
+
357
+ ## License
358
+
359
+ MIT © [Binidu Ranasinghe](https://bini.js.org)