@flexireact/core 1.0.1 → 2.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/README.md +117 -116
- package/bin/flexireact.js +23 -0
- package/cli/index.ts +9 -21
- package/core/cli/{logger.js → logger.ts} +8 -2
- package/core/client/{hydration.js → hydration.ts} +10 -0
- package/core/client/{islands.js → islands.ts} +6 -1
- package/core/client/{navigation.js → navigation.ts} +10 -2
- package/core/client/{runtime.js → runtime.ts} +16 -0
- package/core/{index.js → index.ts} +2 -1
- package/core/islands/{index.js → index.ts} +16 -4
- package/core/{logger.js → logger.ts} +1 -1
- package/core/middleware/{index.js → index.ts} +32 -9
- package/core/plugins/{index.js → index.ts} +9 -6
- package/core/render/index.ts +1069 -0
- package/core/{render.js → render.ts} +7 -5
- package/core/router/index.ts +543 -0
- package/core/rsc/{index.js → index.ts} +6 -5
- package/core/server/{index.js → index.ts} +25 -6
- package/core/{server.js → server.ts} +8 -2
- package/core/ssg/{index.js → index.ts} +30 -5
- package/core/start-dev.ts +6 -0
- package/core/start-prod.ts +6 -0
- package/core/tsconfig.json +28 -0
- package/core/types.ts +239 -0
- package/package.json +19 -14
- package/cli/index.js +0 -992
- package/core/render/index.js +0 -765
- package/core/router/index.js +0 -296
- /package/core/{api.js → api.ts} +0 -0
- /package/core/build/{index.js → index.ts} +0 -0
- /package/core/client/{index.js → index.ts} +0 -0
- /package/core/{config.js → config.ts} +0 -0
- /package/core/{context.js → context.ts} +0 -0
- /package/core/{dev.js → dev.ts} +0 -0
- /package/core/{loader.js → loader.ts} +0 -0
- /package/core/{router.js → router.ts} +0 -0
- /package/core/{utils.js → utils.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="./assets/
|
|
2
|
+
<img src="./assets/flexireact.webp" alt="FlexiReact Logo" width="400" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
<h1 align="center">FlexiReact</h1>
|
|
5
|
+
<h1 align="center">FlexiReact v2</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<strong>The Modern React Framework</strong>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
A blazing-fast React framework with TypeScript, Tailwind CSS, SSR, SSG, Islands architecture, and file-based routing.<br/>
|
|
12
|
+
A blazing-fast React framework with TypeScript, Tailwind CSS v4, SSR, SSG, Islands architecture, and file-based routing.<br/>
|
|
13
13
|
Inspired by Next.js, Remix, Astro, and TanStack Start — but simpler and lighter.
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
16
|
<p align="center">
|
|
17
|
-
<a href="https://www.npmjs.com/package/
|
|
17
|
+
<a href="https://www.npmjs.com/package/@flexireact/core"><img src="https://img.shields.io/npm/v/@flexireact/core.svg" alt="npm version" /></a>
|
|
18
18
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a>
|
|
19
|
-
<a href="#"><img src="https://img.shields.io/badge/TypeScript-
|
|
20
|
-
<a href="#"><img src="https://img.shields.io/badge/Tailwind-
|
|
19
|
+
<a href="#"><img src="https://img.shields.io/badge/TypeScript-Native-blue.svg" alt="TypeScript Native" /></a>
|
|
20
|
+
<a href="#"><img src="https://img.shields.io/badge/Tailwind-v4-38B2AC.svg" alt="Tailwind CSS v4" /></a>
|
|
21
21
|
</p>
|
|
22
22
|
|
|
23
|
+
## 🆕 What's New in v2
|
|
24
|
+
|
|
25
|
+
- **TypeScript Native** — Core rewritten in TypeScript for better DX
|
|
26
|
+
- **Tailwind CSS v4** — New `@import "tailwindcss"` and `@theme` syntax
|
|
27
|
+
- **Routes Directory** — New `routes/` directory with route groups, dynamic segments
|
|
28
|
+
- **Modern 404 Page** — Beautiful, interactive error pages
|
|
29
|
+
- **Enhanced DevTools** — Precise error messages with color-coded render times
|
|
30
|
+
- **Improved CLI** — TypeScript-based CLI with better templates
|
|
31
|
+
|
|
23
32
|
## ✨ Features
|
|
24
33
|
|
|
25
34
|
### 🏗️ Core Framework
|
|
@@ -140,8 +149,9 @@
|
|
|
140
149
|
|
|
141
150
|
```bash
|
|
142
151
|
# Create a new project
|
|
143
|
-
npx flexireact
|
|
144
|
-
cd
|
|
152
|
+
npx create-flexireact@latest my-app
|
|
153
|
+
cd my-app
|
|
154
|
+
npm install
|
|
145
155
|
|
|
146
156
|
# Start development server
|
|
147
157
|
npm run dev
|
|
@@ -155,51 +165,73 @@ npm run start
|
|
|
155
165
|
|
|
156
166
|
Open http://localhost:3000
|
|
157
167
|
|
|
158
|
-
## 📁 Project Structure
|
|
168
|
+
## 📁 Project Structure (v2)
|
|
169
|
+
|
|
170
|
+
FlexiReact v2 introduces a new `routes/` directory with enhanced routing capabilities:
|
|
159
171
|
|
|
160
172
|
```
|
|
161
173
|
myapp/
|
|
162
|
-
├── app/
|
|
163
|
-
│ ├── components/
|
|
164
|
-
│ │ ├──
|
|
165
|
-
│ │
|
|
166
|
-
│ │ ├── Navbar.tsx # Navigation bar
|
|
167
|
-
│ │ └── index.ts # Component exports
|
|
174
|
+
├── app/ # App directory (layout, components, styles)
|
|
175
|
+
│ ├── components/
|
|
176
|
+
│ │ ├── ui/ # UI components (Button, Card, etc.)
|
|
177
|
+
│ │ └── layout/ # Layout components (Navbar, Footer)
|
|
168
178
|
│ ├── styles/
|
|
169
|
-
│ │ └── globals.css
|
|
170
|
-
│
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
│ ├──
|
|
179
|
+
│ │ └── globals.css # Global styles + Tailwind v4
|
|
180
|
+
│ ├── providers/ # React context providers
|
|
181
|
+
│ └── layout.tsx # Root layout
|
|
182
|
+
├── routes/ # FlexiReact v2 file-based routing
|
|
183
|
+
│ ├── (public)/ # Route groups (don't affect URL)
|
|
184
|
+
│ │ ├── home.tsx # → /
|
|
185
|
+
│ │ └── about.tsx # → /about
|
|
174
186
|
│ ├── blog/
|
|
175
|
-
│ │ ├── index.tsx
|
|
176
|
-
│ │ └── [slug].tsx
|
|
187
|
+
│ │ ├── index.tsx # → /blog
|
|
188
|
+
│ │ └── [slug].tsx # → /blog/:slug
|
|
177
189
|
│ └── api/
|
|
178
|
-
│ └── hello.ts
|
|
179
|
-
├──
|
|
180
|
-
|
|
181
|
-
├──
|
|
182
|
-
├──
|
|
190
|
+
│ └── hello.ts # → /api/hello
|
|
191
|
+
├── lib/ # Utilities
|
|
192
|
+
│ └── utils.ts
|
|
193
|
+
├── public/ # Static assets
|
|
194
|
+
├── tsconfig.json # TypeScript configuration
|
|
195
|
+
├── flexireact.config.ts # FlexiReact configuration
|
|
183
196
|
└── package.json
|
|
184
197
|
```
|
|
185
198
|
|
|
186
|
-
## 🛣️ Routing
|
|
199
|
+
## 🛣️ Routing (v2)
|
|
200
|
+
|
|
201
|
+
FlexiReact v2 supports three routing conventions (in priority order):
|
|
202
|
+
|
|
203
|
+
### 1. Routes Directory (Recommended)
|
|
204
|
+
|
|
205
|
+
| File | Route |
|
|
206
|
+
|------|-------|
|
|
207
|
+
| `routes/(public)/home.tsx` | `/` |
|
|
208
|
+
| `routes/(public)/about.tsx` | `/about` |
|
|
209
|
+
| `routes/blog/index.tsx` | `/blog` |
|
|
210
|
+
| `routes/blog/[slug].tsx` | `/blog/:slug` |
|
|
211
|
+
| `routes/[...path].tsx` | Catch-all route |
|
|
212
|
+
| `routes/api/hello.ts` | `/api/hello` |
|
|
213
|
+
|
|
214
|
+
### 2. App Directory (Next.js style)
|
|
215
|
+
|
|
216
|
+
| File | Route |
|
|
217
|
+
|------|-------|
|
|
218
|
+
| `app/page.tsx` | `/` |
|
|
219
|
+
| `app/about/page.tsx` | `/about` |
|
|
220
|
+
| `app/blog/[slug]/page.tsx` | `/blog/:slug` |
|
|
187
221
|
|
|
188
|
-
###
|
|
222
|
+
### 3. Pages Directory (Legacy)
|
|
189
223
|
|
|
190
224
|
| File | Route |
|
|
191
225
|
|------|-------|
|
|
192
|
-
| `pages/index.
|
|
193
|
-
| `pages/about.
|
|
194
|
-
| `pages/blog/[slug].jsx` | `/blog/:slug` |
|
|
195
|
-
| `pages/[...path].jsx` | Catch-all route |
|
|
226
|
+
| `pages/index.tsx` | `/` |
|
|
227
|
+
| `pages/about.tsx` | `/about` |
|
|
196
228
|
|
|
197
229
|
### Dynamic Routes
|
|
198
230
|
|
|
199
|
-
```
|
|
200
|
-
//
|
|
201
|
-
export default function
|
|
202
|
-
return <h1>
|
|
231
|
+
```tsx
|
|
232
|
+
// routes/blog/[slug].tsx
|
|
233
|
+
export default function BlogPost({ params }: { params: { slug: string } }) {
|
|
234
|
+
return <h1>Post: {params.slug}</h1>;
|
|
203
235
|
}
|
|
204
236
|
```
|
|
205
237
|
|
|
@@ -208,42 +240,62 @@ export default function User({ params }) {
|
|
|
208
240
|
Use parentheses to group routes without affecting the URL:
|
|
209
241
|
|
|
210
242
|
```
|
|
211
|
-
|
|
212
|
-
(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
(
|
|
216
|
-
|
|
243
|
+
routes/
|
|
244
|
+
(public)/
|
|
245
|
+
home.tsx # → /
|
|
246
|
+
about.tsx # → /about
|
|
247
|
+
(dashboard)/
|
|
248
|
+
settings.tsx # → /settings
|
|
217
249
|
```
|
|
218
250
|
|
|
219
251
|
## 📐 Layouts
|
|
220
252
|
|
|
221
|
-
Create
|
|
253
|
+
Create layouts in `app/layout.tsx` or within route directories:
|
|
222
254
|
|
|
223
|
-
```
|
|
224
|
-
//
|
|
225
|
-
|
|
255
|
+
```tsx
|
|
256
|
+
// app/layout.tsx
|
|
257
|
+
import { Navbar } from './components/layout/Navbar';
|
|
258
|
+
import { Footer } from './components/layout/Footer';
|
|
259
|
+
|
|
260
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
226
261
|
return (
|
|
227
|
-
<
|
|
228
|
-
<
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
262
|
+
<html lang="en" className="dark">
|
|
263
|
+
<head>
|
|
264
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
265
|
+
</head>
|
|
266
|
+
<body className="bg-background text-foreground">
|
|
267
|
+
<Navbar />
|
|
268
|
+
<main>{children}</main>
|
|
269
|
+
<Footer />
|
|
270
|
+
</body>
|
|
271
|
+
</html>
|
|
232
272
|
);
|
|
233
273
|
}
|
|
234
274
|
```
|
|
235
275
|
|
|
236
276
|
## ⏳ Loading & Error States
|
|
237
277
|
|
|
238
|
-
```
|
|
239
|
-
//
|
|
278
|
+
```tsx
|
|
279
|
+
// routes/loading.tsx
|
|
240
280
|
export default function Loading() {
|
|
241
|
-
return
|
|
281
|
+
return (
|
|
282
|
+
<div className="flex items-center justify-center min-h-screen">
|
|
283
|
+
<div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
|
|
284
|
+
</div>
|
|
285
|
+
);
|
|
242
286
|
}
|
|
243
287
|
|
|
244
|
-
//
|
|
245
|
-
export default function Error({ error }) {
|
|
246
|
-
return
|
|
288
|
+
// routes/error.tsx
|
|
289
|
+
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
|
|
290
|
+
return (
|
|
291
|
+
<div className="flex flex-col items-center justify-center min-h-screen">
|
|
292
|
+
<h1 className="text-4xl font-bold text-red-500">Something went wrong</h1>
|
|
293
|
+
<p className="text-gray-400 mt-4">{error.message}</p>
|
|
294
|
+
<button onClick={reset} className="mt-8 px-6 py-3 bg-primary text-black rounded-lg">
|
|
295
|
+
Try again
|
|
296
|
+
</button>
|
|
297
|
+
</div>
|
|
298
|
+
);
|
|
247
299
|
}
|
|
248
300
|
```
|
|
249
301
|
|
|
@@ -479,63 +531,6 @@ Islands provide partial hydration:
|
|
|
479
531
|
- **Better performance** — less code to parse/execute
|
|
480
532
|
- **Selective loading** — hydrate on visibility, interaction, etc.
|
|
481
533
|
|
|
482
|
-
## 🎨 FlexiUI - Official UI Library
|
|
483
|
-
|
|
484
|
-
FlexiReact comes with an official UI component library: **@flexireact/flexi-ui**
|
|
485
|
-
|
|
486
|
-
```bash
|
|
487
|
-
npm install @flexireact/flexi-ui
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
### Features
|
|
491
|
-
- 🌙 **Dark-first design** with neon emerald accents
|
|
492
|
-
- ♿ **Fully accessible** (ARIA-compliant, Radix UI primitives)
|
|
493
|
-
- 🎯 **TypeScript native** with full type safety
|
|
494
|
-
- 🌳 **Tree-shakeable** — import only what you need
|
|
495
|
-
- ⚡ **SSR ready** — works with FlexiReact SSR
|
|
496
|
-
|
|
497
|
-
### Quick Setup
|
|
498
|
-
|
|
499
|
-
```js
|
|
500
|
-
// tailwind.config.js
|
|
501
|
-
const { flexiUIPlugin } = require('@flexireact/flexi-ui/tailwind');
|
|
502
|
-
|
|
503
|
-
module.exports = {
|
|
504
|
-
darkMode: 'class',
|
|
505
|
-
content: [
|
|
506
|
-
'./pages/**/*.{js,ts,jsx,tsx}',
|
|
507
|
-
'./node_modules/@flexireact/flexi-ui/dist/**/*.js',
|
|
508
|
-
],
|
|
509
|
-
plugins: [flexiUIPlugin],
|
|
510
|
-
};
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
### Usage
|
|
514
|
-
|
|
515
|
-
```jsx
|
|
516
|
-
import { Button, Card, Badge, Input } from '@flexireact/flexi-ui';
|
|
517
|
-
|
|
518
|
-
export default function MyPage() {
|
|
519
|
-
return (
|
|
520
|
-
<Card>
|
|
521
|
-
<Badge variant="success">New</Badge>
|
|
522
|
-
<h2>Welcome!</h2>
|
|
523
|
-
<Input placeholder="Enter your email" />
|
|
524
|
-
<Button>Get Started</Button>
|
|
525
|
-
</Card>
|
|
526
|
-
);
|
|
527
|
-
}
|
|
528
|
-
```
|
|
529
|
-
|
|
530
|
-
### Available Components
|
|
531
|
-
- **Core**: Button, Input, Textarea, Checkbox, Switch, Select
|
|
532
|
-
- **Display**: Card, Badge, Avatar, Tooltip
|
|
533
|
-
- **Feedback**: Alert, Toast, Spinner, Skeleton, Progress
|
|
534
|
-
- **Overlay**: Modal, Drawer, Dropdown
|
|
535
|
-
- **Layout**: Separator, Tabs
|
|
536
|
-
|
|
537
|
-
📖 [FlexiUI Documentation](https://github.com/flexireact/flexi-ui)
|
|
538
|
-
|
|
539
534
|
---
|
|
540
535
|
|
|
541
536
|
## 📋 Requirements
|
|
@@ -543,7 +538,13 @@ export default function MyPage() {
|
|
|
543
538
|
- Node.js 18+
|
|
544
539
|
- React 18+
|
|
545
540
|
|
|
541
|
+
## 🔗 Links
|
|
542
|
+
|
|
543
|
+
- [GitHub Repository](https://github.com/flexireact/flexireact)
|
|
544
|
+
- [npm Package](https://www.npmjs.com/package/@flexireact/core)
|
|
545
|
+
- [Issues](https://github.com/flexireact/flexireact/issues)
|
|
546
|
+
|
|
546
547
|
## 📄 License
|
|
547
548
|
|
|
548
|
-
MIT
|
|
549
|
+
MIT © [FlexiReact Team](https://github.com/flexireact)
|
|
549
550
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FlexiReact CLI Entry Point
|
|
5
|
+
* Uses tsx to run TypeScript CLI directly
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawnSync } from 'child_process';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { dirname, join } from 'path';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
|
|
15
|
+
const cliPath = join(__dirname, '..', 'cli', 'index.ts');
|
|
16
|
+
|
|
17
|
+
// Run the CLI with tsx
|
|
18
|
+
const result = spawnSync('npx', ['tsx', cliPath, ...process.argv.slice(2)], {
|
|
19
|
+
stdio: 'inherit',
|
|
20
|
+
shell: true,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
process.exit(result.status ?? 0);
|
package/cli/index.ts
CHANGED
|
@@ -112,7 +112,7 @@ async function createProject(projectName?: string): Promise<void> {
|
|
|
112
112
|
name: 'projectName',
|
|
113
113
|
message: 'Project name:',
|
|
114
114
|
initial: 'my-flexi-app',
|
|
115
|
-
validate: (value) => value.length > 0 || 'Project name is required'
|
|
115
|
+
validate: (value: string) => value.length > 0 || 'Project name is required'
|
|
116
116
|
});
|
|
117
117
|
name = response.projectName;
|
|
118
118
|
if (!name) process.exit(1);
|
|
@@ -830,21 +830,15 @@ ${pc.cyan(' │')} ${pc.cyan('│')}
|
|
|
830
830
|
${pc.cyan(' ╰─────────────────────────────────────────╯')}
|
|
831
831
|
`);
|
|
832
832
|
|
|
833
|
-
const
|
|
834
|
-
const serverPath = path.join(__dirname, '..', 'core', 'server', 'index.js');
|
|
835
|
-
const loaderUrl = pathToFileURL(loaderPath).href;
|
|
833
|
+
const startDevPath = path.join(__dirname, '..', 'core', 'start-dev.ts');
|
|
836
834
|
|
|
837
835
|
const child = spawn(
|
|
838
|
-
|
|
839
|
-
[
|
|
840
|
-
'--import',
|
|
841
|
-
`data:text/javascript,import { register } from 'node:module'; register('${loaderUrl}', import.meta.url);`,
|
|
842
|
-
'-e',
|
|
843
|
-
`import('${pathToFileURL(serverPath).href}').then(m => m.createServer({ mode: 'development' }))`
|
|
844
|
-
],
|
|
836
|
+
'npx',
|
|
837
|
+
['tsx', startDevPath],
|
|
845
838
|
{
|
|
846
839
|
stdio: 'inherit',
|
|
847
840
|
cwd: process.cwd(),
|
|
841
|
+
shell: true,
|
|
848
842
|
env: { ...process.env, NODE_ENV: 'development', FORCE_COLOR: '1' }
|
|
849
843
|
}
|
|
850
844
|
);
|
|
@@ -909,21 +903,15 @@ async function runStart(): Promise<void> {
|
|
|
909
903
|
log.info('Starting production server...');
|
|
910
904
|
log.blank();
|
|
911
905
|
|
|
912
|
-
const
|
|
913
|
-
const serverPath = path.join(__dirname, '..', 'core', 'server', 'index.js');
|
|
914
|
-
const loaderUrl = pathToFileURL(loaderPath).href;
|
|
906
|
+
const startProdPath = path.join(__dirname, '..', 'core', 'start-prod.ts');
|
|
915
907
|
|
|
916
908
|
const child = spawn(
|
|
917
|
-
|
|
918
|
-
[
|
|
919
|
-
'--import',
|
|
920
|
-
`data:text/javascript,import { register } from 'node:module'; register('${loaderUrl}', import.meta.url);`,
|
|
921
|
-
'-e',
|
|
922
|
-
`import('${pathToFileURL(serverPath).href}').then(m => m.createServer({ mode: 'production' }))`
|
|
923
|
-
],
|
|
909
|
+
'npx',
|
|
910
|
+
['tsx', startProdPath],
|
|
924
911
|
{
|
|
925
912
|
stdio: 'inherit',
|
|
926
913
|
cwd: process.cwd(),
|
|
914
|
+
shell: true,
|
|
927
915
|
env: { ...process.env, NODE_ENV: 'production' }
|
|
928
916
|
}
|
|
929
917
|
);
|
|
@@ -65,7 +65,13 @@ const box = {
|
|
|
65
65
|
verticalLeft: '┤',
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
interface BoxOptions {
|
|
69
|
+
padding?: number;
|
|
70
|
+
borderColor?: (s: string) => string;
|
|
71
|
+
width?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function createBox(content: string, options: BoxOptions = {}) {
|
|
69
75
|
const {
|
|
70
76
|
padding = 1,
|
|
71
77
|
borderColor = colors.primary,
|
|
@@ -180,7 +186,7 @@ function pluginLoader(plugins = []) {
|
|
|
180
186
|
// HTTP Request Logger
|
|
181
187
|
// ============================================================================
|
|
182
188
|
|
|
183
|
-
function request(method, path, statusCode, duration, options = {}) {
|
|
189
|
+
function request(method: string, path: string, statusCode: number, duration: number, options: { type?: string } = {}) {
|
|
184
190
|
const { type = 'dynamic' } = options;
|
|
185
191
|
|
|
186
192
|
// Method colors
|
|
@@ -6,6 +6,16 @@
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import { hydrateRoot, createRoot } from 'react-dom/client';
|
|
8
8
|
|
|
9
|
+
// Extend Window interface for __FLEXI_DATA__
|
|
10
|
+
declare global {
|
|
11
|
+
interface Window {
|
|
12
|
+
__FLEXI_DATA__?: {
|
|
13
|
+
islands?: any[];
|
|
14
|
+
props?: Record<string, any>;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
9
19
|
/**
|
|
10
20
|
* Hydrates a specific island component
|
|
11
21
|
*/
|
|
@@ -40,7 +40,12 @@ export function IslandBoundary({ children, fallback = null, name = 'island' }) {
|
|
|
40
40
|
/**
|
|
41
41
|
* Creates a lazy-loaded island
|
|
42
42
|
*/
|
|
43
|
-
|
|
43
|
+
interface LazyIslandOptions {
|
|
44
|
+
fallback?: React.ReactNode;
|
|
45
|
+
name?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createClientIsland(loader: () => Promise<any>, options: LazyIslandOptions = {}) {
|
|
44
49
|
const { fallback = null, name = 'lazy-island' } = options;
|
|
45
50
|
|
|
46
51
|
return function LazyIsland(props) {
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
|
|
8
8
|
// Navigation state
|
|
9
|
-
const navigationState
|
|
9
|
+
const navigationState: {
|
|
10
|
+
listeners: Set<(url: string) => void>;
|
|
11
|
+
prefetched: Set<string>;
|
|
12
|
+
} = {
|
|
10
13
|
listeners: new Set(),
|
|
11
14
|
prefetched: new Set()
|
|
12
15
|
};
|
|
@@ -14,7 +17,12 @@ const navigationState = {
|
|
|
14
17
|
/**
|
|
15
18
|
* Navigates to a new URL
|
|
16
19
|
*/
|
|
17
|
-
|
|
20
|
+
interface NavigateOptions {
|
|
21
|
+
replace?: boolean;
|
|
22
|
+
scroll?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function navigate(url: string, options: NavigateOptions = {}) {
|
|
18
26
|
const { replace = false, scroll = true } = options;
|
|
19
27
|
|
|
20
28
|
if (replace) {
|
|
@@ -6,6 +6,22 @@
|
|
|
6
6
|
import { hydrateAllIslands, setupProgressiveHydration } from './hydration.js';
|
|
7
7
|
import { navigate, prefetch } from './navigation.js';
|
|
8
8
|
|
|
9
|
+
// Extend Window interface
|
|
10
|
+
declare global {
|
|
11
|
+
interface Window {
|
|
12
|
+
FlexiReact: {
|
|
13
|
+
navigate: typeof navigate;
|
|
14
|
+
prefetch: typeof prefetch;
|
|
15
|
+
hydrateAllIslands: typeof hydrateAllIslands;
|
|
16
|
+
setupProgressiveHydration: typeof setupProgressiveHydration;
|
|
17
|
+
};
|
|
18
|
+
__FLEXI_DATA__?: {
|
|
19
|
+
islands?: any[];
|
|
20
|
+
props?: Record<string, any>;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
9
25
|
// Expose to global scope
|
|
10
26
|
window.FlexiReact = {
|
|
11
27
|
navigate,
|
|
@@ -15,7 +15,8 @@ export { buildRouteTree, matchRoute, findRouteLayouts, RouteType } from './route
|
|
|
15
15
|
export { renderPage, renderError, renderLoading } from './render/index.js';
|
|
16
16
|
|
|
17
17
|
// Server
|
|
18
|
-
|
|
18
|
+
import { createServer } from './server/index.js';
|
|
19
|
+
export { createServer };
|
|
19
20
|
|
|
20
21
|
// Build
|
|
21
22
|
export { build, buildDev, BuildMode } from './build/index.js';
|
|
@@ -63,8 +63,13 @@ export function getRegisteredIslands() {
|
|
|
63
63
|
/**
|
|
64
64
|
* Creates an island component wrapper
|
|
65
65
|
*/
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
interface IslandOptions {
|
|
67
|
+
name?: string;
|
|
68
|
+
clientPath?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function createIsland(Component: React.ComponentType<any>, options: IslandOptions = {}) {
|
|
72
|
+
const { name = (Component as any).name || 'Island', clientPath } = options;
|
|
68
73
|
|
|
69
74
|
function IslandWrapper(props) {
|
|
70
75
|
return Island({
|
|
@@ -148,9 +153,16 @@ export const LoadStrategy = {
|
|
|
148
153
|
/**
|
|
149
154
|
* Creates a lazy island that hydrates based on strategy
|
|
150
155
|
*/
|
|
151
|
-
|
|
156
|
+
interface LazyIslandOptions {
|
|
157
|
+
name?: string;
|
|
158
|
+
clientPath?: string;
|
|
159
|
+
strategy?: string;
|
|
160
|
+
media?: string | null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function createLazyIsland(Component: React.ComponentType<any>, options: LazyIslandOptions = {}) {
|
|
152
164
|
const {
|
|
153
|
-
name = Component.name || 'LazyIsland',
|
|
165
|
+
name = (Component as any).name || 'LazyIsland',
|
|
154
166
|
clientPath,
|
|
155
167
|
strategy = LoadStrategy.VISIBLE,
|
|
156
168
|
media = null
|
|
@@ -122,7 +122,7 @@ export const logger = {
|
|
|
122
122
|
},
|
|
123
123
|
|
|
124
124
|
// HTTP request log - Compact single line like Next.js
|
|
125
|
-
request(method, path, status, time, extra = {}) {
|
|
125
|
+
request(method: string, path: string, status: number, time: number, extra: { type?: string } = {}) {
|
|
126
126
|
const methodColor = getMethodColor(method);
|
|
127
127
|
const statusColor = getStatusColor(status);
|
|
128
128
|
const timeStr = formatTime(time);
|
|
@@ -29,8 +29,22 @@ import { pathToFileURL } from 'url';
|
|
|
29
29
|
/**
|
|
30
30
|
* Middleware response helpers
|
|
31
31
|
*/
|
|
32
|
+
interface MiddlewareResponseOptions {
|
|
33
|
+
type?: string;
|
|
34
|
+
status?: number;
|
|
35
|
+
headers?: Record<string, string>;
|
|
36
|
+
body?: any;
|
|
37
|
+
url?: string | null;
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
export class MiddlewareResponse {
|
|
33
|
-
|
|
41
|
+
type: string;
|
|
42
|
+
status: number;
|
|
43
|
+
headers: Map<string, string>;
|
|
44
|
+
body: any;
|
|
45
|
+
url: string | null;
|
|
46
|
+
|
|
47
|
+
constructor(options: MiddlewareResponseOptions = {}) {
|
|
34
48
|
this.type = options.type || 'next';
|
|
35
49
|
this.status = options.status || 200;
|
|
36
50
|
this.headers = new Map(Object.entries(options.headers || {}));
|
|
@@ -69,7 +83,7 @@ export class MiddlewareResponse {
|
|
|
69
83
|
/**
|
|
70
84
|
* Return a JSON response
|
|
71
85
|
*/
|
|
72
|
-
static json(data, options = {}) {
|
|
86
|
+
static json(data: any, options: { status?: number; headers?: Record<string, string> } = {}) {
|
|
73
87
|
return new MiddlewareResponse({
|
|
74
88
|
type: 'response',
|
|
75
89
|
status: options.status || 200,
|
|
@@ -81,7 +95,7 @@ export class MiddlewareResponse {
|
|
|
81
95
|
/**
|
|
82
96
|
* Return an HTML response
|
|
83
97
|
*/
|
|
84
|
-
static html(content, options = {}) {
|
|
98
|
+
static html(content: string, options: { status?: number; headers?: Record<string, string> } = {}) {
|
|
85
99
|
return new MiddlewareResponse({
|
|
86
100
|
type: 'response',
|
|
87
101
|
status: options.status || 200,
|
|
@@ -95,7 +109,16 @@ export class MiddlewareResponse {
|
|
|
95
109
|
* Middleware request wrapper
|
|
96
110
|
*/
|
|
97
111
|
export class MiddlewareRequest {
|
|
98
|
-
|
|
112
|
+
raw: any;
|
|
113
|
+
method: string;
|
|
114
|
+
url: string;
|
|
115
|
+
headers: Map<string, string>;
|
|
116
|
+
pathname: string;
|
|
117
|
+
searchParams: URLSearchParams;
|
|
118
|
+
query: Record<string, string>;
|
|
119
|
+
cookies: Map<string, string>;
|
|
120
|
+
|
|
121
|
+
constructor(req: any) {
|
|
99
122
|
this.raw = req;
|
|
100
123
|
this.method = req.method;
|
|
101
124
|
this.url = req.url;
|
|
@@ -105,7 +128,7 @@ export class MiddlewareRequest {
|
|
|
105
128
|
const parsedUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
|
|
106
129
|
this.pathname = parsedUrl.pathname;
|
|
107
130
|
this.searchParams = parsedUrl.searchParams;
|
|
108
|
-
this.query = Object.fromEntries(parsedUrl.searchParams)
|
|
131
|
+
this.query = Object.fromEntries(parsedUrl.searchParams) as Record<string, string>;
|
|
109
132
|
|
|
110
133
|
// Parse cookies
|
|
111
134
|
this.cookies = this._parseCookies(req.headers.cookie || '');
|
|
@@ -274,7 +297,7 @@ export const middlewares = {
|
|
|
274
297
|
/**
|
|
275
298
|
* CORS middleware
|
|
276
299
|
*/
|
|
277
|
-
cors(options = {}) {
|
|
300
|
+
cors(options: { origin?: string; methods?: string; headers?: string; credentials?: boolean } = {}) {
|
|
278
301
|
const {
|
|
279
302
|
origin = '*',
|
|
280
303
|
methods = 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
|
@@ -294,7 +317,7 @@ export const middlewares = {
|
|
|
294
317
|
|
|
295
318
|
// Handle preflight
|
|
296
319
|
if (request.method === 'OPTIONS') {
|
|
297
|
-
return MiddlewareResponse.json({}, { status: 204, headers: response.headers });
|
|
320
|
+
return MiddlewareResponse.json({}, { status: 204, headers: Object.fromEntries(response.headers) });
|
|
298
321
|
}
|
|
299
322
|
|
|
300
323
|
return response;
|
|
@@ -330,7 +353,7 @@ export const middlewares = {
|
|
|
330
353
|
/**
|
|
331
354
|
* Rate limiting middleware
|
|
332
355
|
*/
|
|
333
|
-
rateLimit(options = {}) {
|
|
356
|
+
rateLimit(options: { windowMs?: number; max?: number } = {}) {
|
|
334
357
|
const { windowMs = 60000, max = 100 } = options;
|
|
335
358
|
const requests = new Map();
|
|
336
359
|
|
|
@@ -369,7 +392,7 @@ export const middlewares = {
|
|
|
369
392
|
/**
|
|
370
393
|
* Logging middleware
|
|
371
394
|
*/
|
|
372
|
-
logger(options = {}) {
|
|
395
|
+
logger(options: { format?: string } = {}) {
|
|
373
396
|
const { format = 'combined' } = options;
|
|
374
397
|
|
|
375
398
|
return (request) => {
|