create-aziz-app 2.0.12 → 2.0.13
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/package.json +3 -2
- package/template/README.md +18 -24
- package/template/api/hello.js +19 -6
- package/template/api/users.js +132 -37
- package/template/components/Navbar.jsx +7 -6
- package/template/docs/NEXTJS-COMPARISON.md +473 -0
- package/template/modules/state.js +225 -31
- package/template/pages/HooksDemo.jsx +194 -0
- package/template/scripts/dev-server.js +5 -5
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-aziz-app",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.13",
|
|
4
4
|
"description": "A React-like framework with state management, routing, and API routes - Create apps with auto-import, live reload, and Next.js-like features",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
7
|
+
"full-stack",
|
|
7
8
|
"framework",
|
|
8
9
|
"cli",
|
|
9
10
|
"create-app",
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
"api-routes",
|
|
13
14
|
"live-reload"
|
|
14
15
|
],
|
|
15
|
-
"license": "
|
|
16
|
+
"license": "AZIZ",
|
|
16
17
|
"author": "Syed Muhammad Aziz Ahmed",
|
|
17
18
|
"type": "commonjs",
|
|
18
19
|
"files": [
|
package/template/README.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
# Aziz App -
|
|
1
|
+
# Aziz App - Lightweight Next.js Clone
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A **lightweight Next.js clone** with React hooks, API routes, and file-based routing. All the benefits of Next.js, but **6x smaller** with **zero dependencies**!
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Next.js: ~400KB + 20 dependencies
|
|
7
|
+
Aziz App: ~50KB + 0 dependencies
|
|
8
|
+
```
|
|
4
9
|
|
|
5
10
|
## 📁 Project Structure
|
|
6
11
|
|
|
@@ -96,33 +101,23 @@ function ProfilePage() {
|
|
|
96
101
|
|
|
97
102
|
## ✨ Features
|
|
98
103
|
|
|
99
|
-
###
|
|
100
|
-
```javascript
|
|
101
|
-
// Just name your function with "Page" suffix
|
|
102
|
-
function AboutPage() {
|
|
103
|
-
return <div><h1>About</h1></div>;
|
|
104
|
-
}
|
|
105
|
-
// Automatically becomes route "#/about"
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### State Management
|
|
104
|
+
### React Hooks (Identical to Next.js!)
|
|
109
105
|
```javascript
|
|
110
106
|
const [count, setCount] = useState(0);
|
|
111
107
|
const [name, setName] = useState('');
|
|
112
|
-
```
|
|
113
108
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
function HomePage() { } // → #/home
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
console.log('Mounted!');
|
|
111
|
+
}, []);
|
|
118
112
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
navigate("about");
|
|
113
|
+
const memoized = useMemo(() => expensive(), [dep]);
|
|
114
|
+
const callback = useCallback(() => action(), [dep]);
|
|
115
|
+
const ref = useRef(null);
|
|
116
|
+
const [state, dispatch] = useReducer(reducer, initial);
|
|
124
117
|
```
|
|
125
118
|
|
|
119
|
+
**All hooks work exactly like React/Next.js!**
|
|
120
|
+
|
|
126
121
|
### API Routes
|
|
127
122
|
```javascript
|
|
128
123
|
// api/todos.js - Runs on SERVER (Node.js)!
|
|
@@ -152,15 +147,14 @@ The CLI will automatically set up your chosen framework!
|
|
|
152
147
|
|
|
153
148
|
## 📚 Documentation
|
|
154
149
|
|
|
150
|
+
- **[Next.js Comparison](docs/NEXTJS-COMPARISON.md)** - How it compares to Next.js
|
|
155
151
|
- **[Getting Started](docs/GETTING-STARTED.md)** - Complete setup guide
|
|
156
152
|
- **[Auto-Registration](docs/AUTO-REGISTRATION.md)** - How pages auto-register
|
|
157
153
|
- **[API Routes](docs/API-ROUTES.md)** - Server-side API endpoints (like Next.js!)
|
|
158
|
-
- **[API Guide](docs/API-GUIDE.md)** - Legacy API guide
|
|
159
154
|
- **[Auto-Import](docs/AUTO-IMPORT.md)** - Auto-import system details
|
|
160
155
|
- **[Dev Server](docs/DEV-SERVER.md)** - Live reload and development
|
|
161
156
|
- **[How It Works](docs/HOW-IT-WORKS.md)** - System architecture
|
|
162
157
|
- **[Troubleshooting](docs/TROUBLESHOOTING.md)** - Fix common issues
|
|
163
|
-
- **[Quick Start](docs/QUICK-START.md)** - Quick examples
|
|
164
158
|
|
|
165
159
|
## 🛠️ Commands
|
|
166
160
|
|
package/template/api/hello.js
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
// API Route: /api/hello
|
|
2
|
-
//
|
|
2
|
+
// Simple Next.js-style API route
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
export default async function handler(req, res) {
|
|
5
|
+
const { method, query } = req;
|
|
6
|
+
|
|
7
|
+
if (method !== 'GET') {
|
|
8
|
+
res.setHeader('Allow', ['GET']);
|
|
9
|
+
return res.status(405).json({
|
|
10
|
+
error: 'Method not allowed',
|
|
11
|
+
message: `Method ${method} is not allowed. Use GET instead.`
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const name = query.name || 'World';
|
|
7
16
|
|
|
8
17
|
return res.status(200).json({
|
|
9
18
|
message: `Hello, ${name}!`,
|
|
10
19
|
timestamp: new Date().toISOString(),
|
|
11
|
-
method:
|
|
20
|
+
method: method,
|
|
21
|
+
query: query
|
|
12
22
|
});
|
|
13
|
-
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// CommonJS export for Node.js compatibility
|
|
26
|
+
module.exports = handler;
|
package/template/api/users.js
CHANGED
|
@@ -1,60 +1,155 @@
|
|
|
1
1
|
// API Route: /api/users
|
|
2
|
-
//
|
|
2
|
+
// Next.js-style API route handler
|
|
3
3
|
|
|
4
|
-
// Mock database (in
|
|
5
|
-
|
|
6
|
-
{ id: 1, name: 'Aziz Ahmed', email: 'aziz@example.com' },
|
|
7
|
-
{ id: 2, name: 'John Doe', email: 'john@example.com' },
|
|
8
|
-
{ id: 3, name: 'Jane Smith', email: 'jane@example.com' }
|
|
4
|
+
// Mock database (in production, use a real database)
|
|
5
|
+
let users = [
|
|
6
|
+
{ id: 1, name: 'Aziz Ahmed', email: 'aziz@example.com', createdAt: new Date().toISOString() },
|
|
7
|
+
{ id: 2, name: 'John Doe', email: 'john@example.com', createdAt: new Date().toISOString() },
|
|
8
|
+
{ id: 3, name: 'Jane Smith', email: 'jane@example.com', createdAt: new Date().toISOString() }
|
|
9
9
|
];
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
// Next.js-style default export
|
|
12
|
+
export default async function handler(req, res) {
|
|
13
|
+
const { method, query, body } = req;
|
|
14
|
+
|
|
15
|
+
switch (method) {
|
|
15
16
|
case 'GET':
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
// GET /api/users - Get all users
|
|
18
|
+
// GET /api/users?id=1 - Get specific user
|
|
19
|
+
if (query.id) {
|
|
20
|
+
const user = users.find(u => u.id === parseInt(query.id));
|
|
21
|
+
if (!user) {
|
|
22
|
+
return res.status(404).json({
|
|
23
|
+
error: 'User not found',
|
|
24
|
+
message: `No user found with id ${query.id}`
|
|
25
|
+
});
|
|
23
26
|
}
|
|
27
|
+
return res.status(200).json(user);
|
|
24
28
|
}
|
|
25
|
-
|
|
29
|
+
|
|
30
|
+
// Return all users
|
|
31
|
+
return res.status(200).json({
|
|
32
|
+
users,
|
|
33
|
+
total: users.length,
|
|
34
|
+
timestamp: new Date().toISOString()
|
|
35
|
+
});
|
|
26
36
|
|
|
27
37
|
case 'POST':
|
|
28
|
-
// Create
|
|
38
|
+
// POST /api/users - Create new user
|
|
39
|
+
const { name, email } = body;
|
|
40
|
+
|
|
41
|
+
// Validation
|
|
42
|
+
if (!name || !email) {
|
|
43
|
+
return res.status(400).json({
|
|
44
|
+
error: 'Validation error',
|
|
45
|
+
message: 'Name and email are required',
|
|
46
|
+
fields: {
|
|
47
|
+
name: !name ? 'Name is required' : null,
|
|
48
|
+
email: !email ? 'Email is required' : null
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!email.includes('@')) {
|
|
54
|
+
return res.status(400).json({
|
|
55
|
+
error: 'Validation error',
|
|
56
|
+
message: 'Invalid email format'
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check if email already exists
|
|
61
|
+
if (users.find(u => u.email === email)) {
|
|
62
|
+
return res.status(409).json({
|
|
63
|
+
error: 'Conflict',
|
|
64
|
+
message: 'User with this email already exists'
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Create new user
|
|
29
69
|
const newUser = {
|
|
30
|
-
id: users.length + 1,
|
|
31
|
-
name
|
|
32
|
-
email
|
|
70
|
+
id: users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1,
|
|
71
|
+
name,
|
|
72
|
+
email,
|
|
73
|
+
createdAt: new Date().toISOString()
|
|
33
74
|
};
|
|
75
|
+
|
|
34
76
|
users.push(newUser);
|
|
35
|
-
|
|
77
|
+
|
|
78
|
+
return res.status(201).json({
|
|
79
|
+
message: 'User created successfully',
|
|
80
|
+
user: newUser
|
|
81
|
+
});
|
|
36
82
|
|
|
37
83
|
case 'PUT':
|
|
38
|
-
// Update
|
|
39
|
-
const userId =
|
|
84
|
+
// PUT /api/users?id=1 - Update user
|
|
85
|
+
const userId = query.id || body.id;
|
|
86
|
+
|
|
87
|
+
if (!userId) {
|
|
88
|
+
return res.status(400).json({
|
|
89
|
+
error: 'Bad request',
|
|
90
|
+
message: 'User ID is required'
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
40
94
|
const userIndex = users.findIndex(u => u.id === parseInt(userId));
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return res.status(
|
|
95
|
+
|
|
96
|
+
if (userIndex === -1) {
|
|
97
|
+
return res.status(404).json({
|
|
98
|
+
error: 'User not found',
|
|
99
|
+
message: `No user found with id ${userId}`
|
|
100
|
+
});
|
|
44
101
|
}
|
|
45
|
-
|
|
102
|
+
|
|
103
|
+
// Update user
|
|
104
|
+
users[userIndex] = {
|
|
105
|
+
...users[userIndex],
|
|
106
|
+
...body,
|
|
107
|
+
id: users[userIndex].id, // Prevent ID change
|
|
108
|
+
updatedAt: new Date().toISOString()
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return res.status(200).json({
|
|
112
|
+
message: 'User updated successfully',
|
|
113
|
+
user: users[userIndex]
|
|
114
|
+
});
|
|
46
115
|
|
|
47
116
|
case 'DELETE':
|
|
48
|
-
// Delete
|
|
49
|
-
const deleteId =
|
|
117
|
+
// DELETE /api/users?id=1 - Delete user
|
|
118
|
+
const deleteId = query.id;
|
|
119
|
+
|
|
120
|
+
if (!deleteId) {
|
|
121
|
+
return res.status(400).json({
|
|
122
|
+
error: 'Bad request',
|
|
123
|
+
message: 'User ID is required'
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
50
127
|
const deleteIndex = users.findIndex(u => u.id === parseInt(deleteId));
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return res.status(
|
|
128
|
+
|
|
129
|
+
if (deleteIndex === -1) {
|
|
130
|
+
return res.status(404).json({
|
|
131
|
+
error: 'User not found',
|
|
132
|
+
message: `No user found with id ${deleteId}`
|
|
133
|
+
});
|
|
54
134
|
}
|
|
55
|
-
|
|
135
|
+
|
|
136
|
+
const deletedUser = users.splice(deleteIndex, 1)[0];
|
|
137
|
+
|
|
138
|
+
return res.status(200).json({
|
|
139
|
+
message: 'User deleted successfully',
|
|
140
|
+
user: deletedUser
|
|
141
|
+
});
|
|
56
142
|
|
|
57
143
|
default:
|
|
58
|
-
|
|
144
|
+
// Method not allowed
|
|
145
|
+
res.setHeader('Allow', ['GET', 'POST', 'PUT', 'DELETE']);
|
|
146
|
+
return res.status(405).json({
|
|
147
|
+
error: 'Method not allowed',
|
|
148
|
+
message: `Method ${method} is not allowed for this endpoint`,
|
|
149
|
+
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE']
|
|
150
|
+
});
|
|
59
151
|
}
|
|
60
|
-
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// CommonJS export for Node.js compatibility
|
|
155
|
+
module.exports = handler;
|
|
@@ -5,12 +5,13 @@ function Navbar() {
|
|
|
5
5
|
|
|
6
6
|
return (
|
|
7
7
|
<nav>
|
|
8
|
-
<span class="brand" onClick={() => navigate("home")}>⚡
|
|
9
|
-
<a href="#/home"
|
|
10
|
-
<a href="#/about"
|
|
11
|
-
<a href="#/
|
|
12
|
-
<a href="#/
|
|
13
|
-
<a href="#/api-demo"
|
|
8
|
+
<span class="brand" onClick={() => navigate("home")}>⚡ AzizApp</span>
|
|
9
|
+
<a href="#/home" class={path === "home" ? "active" : ""}>Home</a>
|
|
10
|
+
<a href="#/about" class={path === "about" ? "active" : ""}>About</a>
|
|
11
|
+
<a href="#/test" class={path === "test" ? "active" : ""}>Test</a>
|
|
12
|
+
<a href="#/hooks-demo" class={path === "hooks-demo" ? "active" : ""}>Hooks</a>
|
|
13
|
+
<a href="#/api-demo" class={path === "api-demo" ? "active" : ""}>API Demo</a>
|
|
14
|
+
<a href="#/contact" class={path === "contact" ? "active" : ""}>Contact</a>
|
|
14
15
|
</nav>
|
|
15
16
|
);
|
|
16
17
|
}
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
# Next.js Comparison
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Aziz App is a **lightweight Next.js clone** with all the core benefits but zero dependencies and minimal complexity.
|
|
6
|
+
|
|
7
|
+
## Feature Comparison
|
|
8
|
+
|
|
9
|
+
| Feature | Aziz App | Next.js | Notes |
|
|
10
|
+
|---------|----------|---------|-------|
|
|
11
|
+
| **React Hooks** | ✅ | ✅ | useState, useEffect, useMemo, useCallback, useRef, useReducer |
|
|
12
|
+
| **File-based Routing** | ✅ | ✅ | pages/ folder → routes |
|
|
13
|
+
| **API Routes** | ✅ | ✅ | api/ folder → endpoints |
|
|
14
|
+
| **Server-side API** | ✅ | ✅ | Real Node.js backend |
|
|
15
|
+
| **Hot Reload** | ✅ | ✅ | WebSocket-based |
|
|
16
|
+
| **Auto Import** | ✅ | ✅ | No manual imports needed |
|
|
17
|
+
| **Zero Config** | ✅ | ❌ | No config files needed |
|
|
18
|
+
| **Bundle Size** | **~50KB** | ~300KB+ | 6x smaller! |
|
|
19
|
+
| **Dependencies** | **0** | 20+ | No node_modules bloat |
|
|
20
|
+
| **Build Time** | **Instant** | Slow | No build step |
|
|
21
|
+
| **SSR** | ❌ | ✅ | Client-side only |
|
|
22
|
+
| **SSG** | ❌ | ✅ | Client-side only |
|
|
23
|
+
| **Image Optimization** | ❌ | ✅ | Use CDN instead |
|
|
24
|
+
| **TypeScript** | ❌ | ✅ | Pure JavaScript |
|
|
25
|
+
| **Middleware** | ❌ | ✅ | Keep it simple |
|
|
26
|
+
|
|
27
|
+
## React Hooks - Identical API
|
|
28
|
+
|
|
29
|
+
### useState
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
// Next.js
|
|
33
|
+
import { useState } from 'react';
|
|
34
|
+
const [count, setCount] = useState(0);
|
|
35
|
+
|
|
36
|
+
// Aziz App - EXACTLY THE SAME!
|
|
37
|
+
const [count, setCount] = useState(0);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### useEffect
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
// Next.js
|
|
44
|
+
import { useEffect } from 'react';
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
console.log('Mounted!');
|
|
47
|
+
return () => console.log('Cleanup!');
|
|
48
|
+
}, [dependency]);
|
|
49
|
+
|
|
50
|
+
// Aziz App - EXACTLY THE SAME!
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
console.log('Mounted!');
|
|
53
|
+
return () => console.log('Cleanup!');
|
|
54
|
+
}, [dependency]);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### useMemo
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
// Next.js
|
|
61
|
+
import { useMemo } from 'react';
|
|
62
|
+
const value = useMemo(() => expensiveCalc(), [dep]);
|
|
63
|
+
|
|
64
|
+
// Aziz App - EXACTLY THE SAME!
|
|
65
|
+
const value = useMemo(() => expensiveCalc(), [dep]);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### useCallback
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
// Next.js
|
|
72
|
+
import { useCallback } from 'react';
|
|
73
|
+
const callback = useCallback(() => doSomething(), [dep]);
|
|
74
|
+
|
|
75
|
+
// Aziz App - EXACTLY THE SAME!
|
|
76
|
+
const callback = useCallback(() => doSomething(), [dep]);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### useRef
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
// Next.js
|
|
83
|
+
import { useRef } from 'react';
|
|
84
|
+
const ref = useRef(null);
|
|
85
|
+
|
|
86
|
+
// Aziz App - EXACTLY THE SAME!
|
|
87
|
+
const ref = useRef(null);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### useReducer
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
// Next.js
|
|
94
|
+
import { useReducer } from 'react';
|
|
95
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
96
|
+
|
|
97
|
+
// Aziz App - EXACTLY THE SAME!
|
|
98
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## API Routes - Identical API
|
|
102
|
+
|
|
103
|
+
### Basic Route
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
// Next.js (pages/api/hello.js)
|
|
107
|
+
export default async function handler(req, res) {
|
|
108
|
+
res.status(200).json({ message: 'Hello' });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Aziz App (api/hello.js) - EXACTLY THE SAME!
|
|
112
|
+
export default async function handler(req, res) {
|
|
113
|
+
res.status(200).json({ message: 'Hello' });
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Request Object
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
// Both Next.js and Aziz App
|
|
121
|
+
{
|
|
122
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
|
123
|
+
body: {}, // Parsed JSON
|
|
124
|
+
query: {}, // URL parameters
|
|
125
|
+
headers: {}, // Request headers
|
|
126
|
+
url: string // Full URL
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Response Methods
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
// Both Next.js and Aziz App
|
|
134
|
+
res.status(200) // Set status
|
|
135
|
+
res.json({ data: 'value' }) // Send JSON
|
|
136
|
+
res.send('text') // Send text
|
|
137
|
+
res.setHeader('X-Custom', 'value') // Set header
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### CRUD Example
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
// Works identically in both!
|
|
144
|
+
export default async function handler(req, res) {
|
|
145
|
+
switch (req.method) {
|
|
146
|
+
case 'GET':
|
|
147
|
+
return res.status(200).json({ users: [] });
|
|
148
|
+
case 'POST':
|
|
149
|
+
return res.status(201).json({ created: true });
|
|
150
|
+
case 'PUT':
|
|
151
|
+
return res.status(200).json({ updated: true });
|
|
152
|
+
case 'DELETE':
|
|
153
|
+
return res.status(200).json({ deleted: true });
|
|
154
|
+
default:
|
|
155
|
+
return res.status(405).json({ error: 'Method not allowed' });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## File-based Routing
|
|
161
|
+
|
|
162
|
+
### Pages
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
// Next.js
|
|
166
|
+
pages/
|
|
167
|
+
├── index.js → /
|
|
168
|
+
├── about.js → /about
|
|
169
|
+
└── contact.js → /contact
|
|
170
|
+
|
|
171
|
+
// Aziz App - Same concept!
|
|
172
|
+
pages/
|
|
173
|
+
├── Home.jsx → #/home
|
|
174
|
+
├── About.jsx → #/about
|
|
175
|
+
└── Contact.jsx → #/contact
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Auto-Registration
|
|
179
|
+
|
|
180
|
+
```javascript
|
|
181
|
+
// Next.js - Automatic
|
|
182
|
+
// Just create the file
|
|
183
|
+
|
|
184
|
+
// Aziz App - Automatic too!
|
|
185
|
+
// Just name your function: AboutPage()
|
|
186
|
+
function AboutPage() {
|
|
187
|
+
return <div><h1>About</h1></div>;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Data Fetching
|
|
192
|
+
|
|
193
|
+
### Client-side Fetching
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
// Next.js
|
|
197
|
+
import { useState, useEffect } from 'react';
|
|
198
|
+
|
|
199
|
+
function Page() {
|
|
200
|
+
const [data, setData] = useState(null);
|
|
201
|
+
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
fetch('/api/users')
|
|
204
|
+
.then(res => res.json())
|
|
205
|
+
.then(setData);
|
|
206
|
+
}, []);
|
|
207
|
+
|
|
208
|
+
return <div>{data?.message}</div>;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Aziz App - EXACTLY THE SAME!
|
|
212
|
+
function Page() {
|
|
213
|
+
const [data, setData] = useState(null);
|
|
214
|
+
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
fetch('/api/users')
|
|
217
|
+
.then(res => res.json())
|
|
218
|
+
.then(setData);
|
|
219
|
+
}, []);
|
|
220
|
+
|
|
221
|
+
return <div>{data?.message}</div>;
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Custom Hook
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
// Both Next.js and Aziz App
|
|
229
|
+
function useFetch(url) {
|
|
230
|
+
const [data, setData] = useState(null);
|
|
231
|
+
const [loading, setLoading] = useState(true);
|
|
232
|
+
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
fetch(url)
|
|
235
|
+
.then(res => res.json())
|
|
236
|
+
.then(data => {
|
|
237
|
+
setData(data);
|
|
238
|
+
setLoading(false);
|
|
239
|
+
});
|
|
240
|
+
}, [url]);
|
|
241
|
+
|
|
242
|
+
return { data, loading };
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## JSX Syntax
|
|
247
|
+
|
|
248
|
+
### Components
|
|
249
|
+
|
|
250
|
+
```jsx
|
|
251
|
+
// Next.js
|
|
252
|
+
export default function Button({ onClick, children }) {
|
|
253
|
+
return <button onClick={onClick}>{children}</button>;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Aziz App - Same JSX!
|
|
257
|
+
function Button({ onClick, children }) {
|
|
258
|
+
return <button onClick={onClick}>{children}</button>;
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Props
|
|
263
|
+
|
|
264
|
+
```jsx
|
|
265
|
+
// Both support the same props
|
|
266
|
+
<div
|
|
267
|
+
class="container"
|
|
268
|
+
style={{ color: 'red' }}
|
|
269
|
+
onClick={() => alert('Hi')}
|
|
270
|
+
>
|
|
271
|
+
Content
|
|
272
|
+
</div>
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Development Experience
|
|
276
|
+
|
|
277
|
+
### Hot Reload
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
// Next.js
|
|
281
|
+
1. Save file
|
|
282
|
+
2. Fast Refresh
|
|
283
|
+
3. State preserved
|
|
284
|
+
|
|
285
|
+
// Aziz App
|
|
286
|
+
1. Save file
|
|
287
|
+
2. WebSocket reload
|
|
288
|
+
3. Full page reload (state reset)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Error Messages
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
// Next.js
|
|
295
|
+
- Detailed error overlay
|
|
296
|
+
- Stack traces
|
|
297
|
+
- Suggestions
|
|
298
|
+
|
|
299
|
+
// Aziz App
|
|
300
|
+
- Browser console errors
|
|
301
|
+
- Simpler debugging
|
|
302
|
+
- Less magic
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## When to Use Each
|
|
306
|
+
|
|
307
|
+
### Use Aziz App When:
|
|
308
|
+
|
|
309
|
+
✅ Building simple SPAs
|
|
310
|
+
✅ Learning React concepts
|
|
311
|
+
✅ Prototyping quickly
|
|
312
|
+
✅ Want zero dependencies
|
|
313
|
+
✅ Don't need SSR/SSG
|
|
314
|
+
✅ Want instant startup
|
|
315
|
+
✅ Prefer simplicity
|
|
316
|
+
|
|
317
|
+
### Use Next.js When:
|
|
318
|
+
|
|
319
|
+
✅ Need SEO (SSR/SSG)
|
|
320
|
+
✅ Building large apps
|
|
321
|
+
✅ Need image optimization
|
|
322
|
+
✅ Want TypeScript
|
|
323
|
+
✅ Need middleware
|
|
324
|
+
✅ Want edge functions
|
|
325
|
+
✅ Building production apps
|
|
326
|
+
|
|
327
|
+
## Migration Path
|
|
328
|
+
|
|
329
|
+
### From Aziz App to Next.js
|
|
330
|
+
|
|
331
|
+
1. **Pages** - Rename files, adjust exports
|
|
332
|
+
2. **API Routes** - Copy as-is (same API!)
|
|
333
|
+
3. **Hooks** - No changes needed (same API!)
|
|
334
|
+
4. **Components** - Minor adjustments
|
|
335
|
+
5. **Styling** - Add CSS modules if needed
|
|
336
|
+
|
|
337
|
+
### From Next.js to Aziz App
|
|
338
|
+
|
|
339
|
+
1. **Remove SSR/SSG** - Convert to client-side
|
|
340
|
+
2. **Pages** - Adjust file names
|
|
341
|
+
3. **API Routes** - Copy as-is (same API!)
|
|
342
|
+
4. **Hooks** - No changes needed (same API!)
|
|
343
|
+
5. **Remove dependencies** - Delete node_modules
|
|
344
|
+
|
|
345
|
+
## Performance
|
|
346
|
+
|
|
347
|
+
### Bundle Size
|
|
348
|
+
|
|
349
|
+
```
|
|
350
|
+
Next.js Production Build:
|
|
351
|
+
- Framework: ~300KB
|
|
352
|
+
- Runtime: ~100KB
|
|
353
|
+
- Total: ~400KB+
|
|
354
|
+
|
|
355
|
+
Aziz App:
|
|
356
|
+
- Framework: ~50KB
|
|
357
|
+
- Runtime: 0KB (browser native)
|
|
358
|
+
- Total: ~50KB
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Startup Time
|
|
362
|
+
|
|
363
|
+
```
|
|
364
|
+
Next.js:
|
|
365
|
+
- npm install: 2-5 minutes
|
|
366
|
+
- npm run dev: 10-30 seconds
|
|
367
|
+
- First load: 2-5 seconds
|
|
368
|
+
|
|
369
|
+
Aziz App:
|
|
370
|
+
- npm install: 5 seconds
|
|
371
|
+
- npm run dev: <1 second
|
|
372
|
+
- First load: <1 second
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Build Time
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
Next.js:
|
|
379
|
+
- Development: 10-30 seconds
|
|
380
|
+
- Production: 1-5 minutes
|
|
381
|
+
|
|
382
|
+
Aziz App:
|
|
383
|
+
- Development: Instant
|
|
384
|
+
- Production: No build needed
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Code Examples
|
|
388
|
+
|
|
389
|
+
### Counter Component
|
|
390
|
+
|
|
391
|
+
```jsx
|
|
392
|
+
// Works identically in both!
|
|
393
|
+
function Counter() {
|
|
394
|
+
const [count, setCount] = useState(0);
|
|
395
|
+
|
|
396
|
+
return (
|
|
397
|
+
<div>
|
|
398
|
+
<p>Count: {count}</p>
|
|
399
|
+
<button onClick={() => setCount(count + 1)}>
|
|
400
|
+
Increment
|
|
401
|
+
</button>
|
|
402
|
+
</div>
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Form with Validation
|
|
408
|
+
|
|
409
|
+
```jsx
|
|
410
|
+
// Works identically in both!
|
|
411
|
+
function ContactForm() {
|
|
412
|
+
const [name, setName] = useState('');
|
|
413
|
+
const [email, setEmail] = useState('');
|
|
414
|
+
const [error, setError] = useState('');
|
|
415
|
+
|
|
416
|
+
const handleSubmit = async (e) => {
|
|
417
|
+
e.preventDefault();
|
|
418
|
+
|
|
419
|
+
if (!name || !email) {
|
|
420
|
+
setError('All fields required');
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const res = await fetch('/api/contact', {
|
|
425
|
+
method: 'POST',
|
|
426
|
+
headers: { 'Content-Type': 'application/json' },
|
|
427
|
+
body: JSON.stringify({ name, email })
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
if (res.ok) {
|
|
431
|
+
alert('Sent!');
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
return (
|
|
436
|
+
<form onSubmit={handleSubmit}>
|
|
437
|
+
<input
|
|
438
|
+
value={name}
|
|
439
|
+
onChange={(e) => setName(e.target.value)}
|
|
440
|
+
placeholder="Name"
|
|
441
|
+
/>
|
|
442
|
+
<input
|
|
443
|
+
value={email}
|
|
444
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
445
|
+
placeholder="Email"
|
|
446
|
+
/>
|
|
447
|
+
{error && <p>{error}</p>}
|
|
448
|
+
<button type="submit">Submit</button>
|
|
449
|
+
</form>
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
## Summary
|
|
455
|
+
|
|
456
|
+
Aziz App is a **lightweight Next.js clone** that provides:
|
|
457
|
+
|
|
458
|
+
- ✅ **Same React Hooks API** - useState, useEffect, etc.
|
|
459
|
+
- ✅ **Same API Routes** - Identical req/res objects
|
|
460
|
+
- ✅ **Same JSX Syntax** - Write components the same way
|
|
461
|
+
- ✅ **Same Development Flow** - File-based routing, hot reload
|
|
462
|
+
- ✅ **Zero Dependencies** - No node_modules bloat
|
|
463
|
+
- ✅ **Instant Startup** - No build step
|
|
464
|
+
- ✅ **6x Smaller** - Minimal bundle size
|
|
465
|
+
|
|
466
|
+
Perfect for:
|
|
467
|
+
- 🎓 Learning React/Next.js concepts
|
|
468
|
+
- 🚀 Rapid prototyping
|
|
469
|
+
- 📱 Simple SPAs
|
|
470
|
+
- 🎨 Portfolio sites
|
|
471
|
+
- 🧪 Experiments
|
|
472
|
+
|
|
473
|
+
**It's Next.js, but lightweight!** 🎉
|
|
@@ -1,54 +1,248 @@
|
|
|
1
1
|
/** @jsx h */
|
|
2
2
|
|
|
3
3
|
// ============================================================
|
|
4
|
-
// STATE MANAGEMENT
|
|
5
|
-
//
|
|
4
|
+
// STATE MANAGEMENT
|
|
5
|
+
// React-like hooks system (useState, useEffect, etc.)
|
|
6
|
+
// Exactly like React/Next.js!
|
|
6
7
|
// ============================================================
|
|
7
8
|
|
|
8
|
-
// Component state storage - keyed by component instance
|
|
9
|
-
const componentStates = new Map();
|
|
10
9
|
let currentComponent = null;
|
|
11
|
-
let
|
|
10
|
+
let currentHookIndex = 0;
|
|
11
|
+
const componentStates = new Map();
|
|
12
|
+
|
|
13
|
+
// Get or create state for current component
|
|
14
|
+
function getComponentState() {
|
|
15
|
+
if (!currentComponent) {
|
|
16
|
+
currentComponent = { hooks: [] };
|
|
17
|
+
}
|
|
18
|
+
return currentComponent;
|
|
19
|
+
}
|
|
12
20
|
|
|
13
|
-
//
|
|
21
|
+
// useState - Exactly like React!
|
|
14
22
|
function useState(initialValue) {
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
const component = getComponentState();
|
|
24
|
+
const hookIndex = currentHookIndex++;
|
|
25
|
+
|
|
26
|
+
// Initialize state if first render
|
|
27
|
+
if (component.hooks[hookIndex] === undefined) {
|
|
28
|
+
component.hooks[hookIndex] = {
|
|
29
|
+
value: typeof initialValue === 'function' ? initialValue() : initialValue
|
|
30
|
+
};
|
|
23
31
|
}
|
|
32
|
+
|
|
33
|
+
const hook = component.hooks[hookIndex];
|
|
34
|
+
|
|
35
|
+
const setState = (newValue) => {
|
|
36
|
+
const nextValue = typeof newValue === 'function'
|
|
37
|
+
? newValue(hook.value)
|
|
38
|
+
: newValue;
|
|
39
|
+
|
|
40
|
+
if (hook.value !== nextValue) {
|
|
41
|
+
hook.value = nextValue;
|
|
42
|
+
// Trigger re-render
|
|
43
|
+
if (typeof renderPage === 'function') {
|
|
44
|
+
renderPage();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return [hook.value, setState];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// useEffect - Exactly like React!
|
|
53
|
+
function useEffect(callback, dependencies) {
|
|
54
|
+
const component = getComponentState();
|
|
55
|
+
const hookIndex = currentHookIndex++;
|
|
56
|
+
|
|
57
|
+
const oldDeps = component.hooks[hookIndex]?.deps;
|
|
24
58
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
//
|
|
32
|
-
hookIndex
|
|
33
|
-
|
|
34
|
-
if (typeof renderPage === 'function') {
|
|
35
|
-
renderPage();
|
|
59
|
+
let hasChanged = true;
|
|
60
|
+
if (oldDeps) {
|
|
61
|
+
hasChanged = dependencies.some((dep, i) => dep !== oldDeps[i]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (hasChanged) {
|
|
65
|
+
// Clean up previous effect
|
|
66
|
+
if (component.hooks[hookIndex]?.cleanup) {
|
|
67
|
+
component.hooks[hookIndex].cleanup();
|
|
36
68
|
}
|
|
69
|
+
|
|
70
|
+
// Run effect after render
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
const cleanup = callback();
|
|
73
|
+
if (component.hooks[hookIndex]) {
|
|
74
|
+
component.hooks[hookIndex].cleanup = cleanup;
|
|
75
|
+
}
|
|
76
|
+
}, 0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
component.hooks[hookIndex] = {
|
|
80
|
+
deps: dependencies,
|
|
81
|
+
cleanup: component.hooks[hookIndex]?.cleanup
|
|
37
82
|
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// useMemo - Exactly like React!
|
|
86
|
+
function useMemo(factory, dependencies) {
|
|
87
|
+
const component = getComponentState();
|
|
88
|
+
const hookIndex = currentHookIndex++;
|
|
89
|
+
|
|
90
|
+
const oldDeps = component.hooks[hookIndex]?.deps;
|
|
38
91
|
|
|
39
|
-
|
|
92
|
+
let hasChanged = true;
|
|
93
|
+
if (oldDeps) {
|
|
94
|
+
hasChanged = dependencies.some((dep, i) => dep !== oldDeps[i]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (hasChanged || !component.hooks[hookIndex]) {
|
|
98
|
+
const value = factory();
|
|
99
|
+
component.hooks[hookIndex] = {
|
|
100
|
+
value,
|
|
101
|
+
deps: dependencies
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return component.hooks[hookIndex].value;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// useCallback - Exactly like React!
|
|
109
|
+
function useCallback(callback, dependencies) {
|
|
110
|
+
return useMemo(() => callback, dependencies);
|
|
40
111
|
}
|
|
41
112
|
|
|
42
|
-
//
|
|
113
|
+
// useRef - Exactly like React!
|
|
114
|
+
function useRef(initialValue) {
|
|
115
|
+
const component = getComponentState();
|
|
116
|
+
const hookIndex = currentHookIndex++;
|
|
117
|
+
|
|
118
|
+
if (!component.hooks[hookIndex]) {
|
|
119
|
+
component.hooks[hookIndex] = {
|
|
120
|
+
current: initialValue
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return component.hooks[hookIndex];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// useReducer - Exactly like React!
|
|
128
|
+
function useReducer(reducer, initialState, init) {
|
|
129
|
+
const [state, setState] = useState(
|
|
130
|
+
init ? init(initialState) : initialState
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const dispatch = (action) => {
|
|
134
|
+
setState(prevState => reducer(prevState, action));
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return [state, dispatch];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Custom hook: useLocalStorage
|
|
141
|
+
function useLocalStorage(key, initialValue) {
|
|
142
|
+
const [storedValue, setStoredValue] = useState(() => {
|
|
143
|
+
try {
|
|
144
|
+
const item = window.localStorage.getItem(key);
|
|
145
|
+
return item ? JSON.parse(item) : initialValue;
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error(error);
|
|
148
|
+
return initialValue;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const setValue = (value) => {
|
|
153
|
+
try {
|
|
154
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
155
|
+
setStoredValue(valueToStore);
|
|
156
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error(error);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
return [storedValue, setValue];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Custom hook: useFetch
|
|
166
|
+
function useFetch(url, options = {}) {
|
|
167
|
+
const [data, setData] = useState(null);
|
|
168
|
+
const [loading, setLoading] = useState(true);
|
|
169
|
+
const [error, setError] = useState(null);
|
|
170
|
+
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
let cancelled = false;
|
|
173
|
+
|
|
174
|
+
const fetchData = async () => {
|
|
175
|
+
try {
|
|
176
|
+
setLoading(true);
|
|
177
|
+
const response = await fetch(url, options);
|
|
178
|
+
|
|
179
|
+
if (!response.ok) {
|
|
180
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const json = await response.json();
|
|
184
|
+
|
|
185
|
+
if (!cancelled) {
|
|
186
|
+
setData(json);
|
|
187
|
+
setError(null);
|
|
188
|
+
}
|
|
189
|
+
} catch (e) {
|
|
190
|
+
if (!cancelled) {
|
|
191
|
+
setError(e.message);
|
|
192
|
+
setData(null);
|
|
193
|
+
}
|
|
194
|
+
} finally {
|
|
195
|
+
if (!cancelled) {
|
|
196
|
+
setLoading(false);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
fetchData();
|
|
202
|
+
|
|
203
|
+
return () => {
|
|
204
|
+
cancelled = true;
|
|
205
|
+
};
|
|
206
|
+
}, [url, JSON.stringify(options)]);
|
|
207
|
+
|
|
208
|
+
return { data, loading, error };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Reset hooks before each render
|
|
43
212
|
function resetHooks() {
|
|
44
|
-
|
|
213
|
+
currentHookIndex = 0;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Initialize component state
|
|
217
|
+
function initComponent() {
|
|
218
|
+
currentComponent = { hooks: [] };
|
|
219
|
+
currentHookIndex = 0;
|
|
45
220
|
}
|
|
46
221
|
|
|
47
|
-
// Export to global scope
|
|
222
|
+
// Export to global scope (like React)
|
|
48
223
|
window.useState = useState;
|
|
224
|
+
window.useEffect = useEffect;
|
|
225
|
+
window.useMemo = useMemo;
|
|
226
|
+
window.useCallback = useCallback;
|
|
227
|
+
window.useRef = useRef;
|
|
228
|
+
window.useReducer = useReducer;
|
|
229
|
+
window.useLocalStorage = useLocalStorage;
|
|
230
|
+
window.useFetch = useFetch;
|
|
49
231
|
window.resetHooks = resetHooks;
|
|
50
|
-
|
|
232
|
+
window.initComponent = initComponent;
|
|
233
|
+
|
|
234
|
+
// Also make available without window prefix
|
|
51
235
|
if (typeof globalThis !== 'undefined') {
|
|
52
236
|
globalThis.useState = useState;
|
|
237
|
+
globalThis.useEffect = useEffect;
|
|
238
|
+
globalThis.useMemo = useMemo;
|
|
239
|
+
globalThis.useCallback = useCallback;
|
|
240
|
+
globalThis.useRef = useRef;
|
|
241
|
+
globalThis.useReducer = useReducer;
|
|
242
|
+
globalThis.useLocalStorage = useLocalStorage;
|
|
243
|
+
globalThis.useFetch = useFetch;
|
|
53
244
|
globalThis.resetHooks = resetHooks;
|
|
54
|
-
|
|
245
|
+
globalThis.initComponent = initComponent;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.log('✅ React-like hooks loaded: useState, useEffect, useMemo, useCallback, useRef, useReducer');
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
|
|
3
|
+
// Demo page showing all React-like hooks
|
|
4
|
+
function HooksDemoPage() {
|
|
5
|
+
// useState - Basic state
|
|
6
|
+
const [count, setCount] = useState(0);
|
|
7
|
+
const [name, setName] = useState('');
|
|
8
|
+
|
|
9
|
+
// useEffect - Side effects
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
console.log('Component mounted!');
|
|
12
|
+
document.title = `Count: ${count}`;
|
|
13
|
+
|
|
14
|
+
return () => {
|
|
15
|
+
console.log('Cleanup!');
|
|
16
|
+
};
|
|
17
|
+
}, [count]);
|
|
18
|
+
|
|
19
|
+
// useMemo - Memoized value
|
|
20
|
+
const expensiveValue = useMemo(() => {
|
|
21
|
+
console.log('Computing expensive value...');
|
|
22
|
+
return count * 2;
|
|
23
|
+
}, [count]);
|
|
24
|
+
|
|
25
|
+
// useCallback - Memoized function
|
|
26
|
+
const handleIncrement = useCallback(() => {
|
|
27
|
+
setCount(c => c + 1);
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
// useRef - Mutable ref
|
|
31
|
+
const inputRef = useRef(null);
|
|
32
|
+
|
|
33
|
+
const focusInput = () => {
|
|
34
|
+
if (inputRef.current) {
|
|
35
|
+
inputRef.current.focus();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// useReducer - Complex state
|
|
40
|
+
const [state, dispatch] = useReducer((state, action) => {
|
|
41
|
+
switch (action.type) {
|
|
42
|
+
case 'increment':
|
|
43
|
+
return { ...state, value: state.value + 1 };
|
|
44
|
+
case 'decrement':
|
|
45
|
+
return { ...state, value: state.value - 1 };
|
|
46
|
+
case 'reset':
|
|
47
|
+
return { ...state, value: 0 };
|
|
48
|
+
default:
|
|
49
|
+
return state;
|
|
50
|
+
}
|
|
51
|
+
}, { value: 0 });
|
|
52
|
+
|
|
53
|
+
// useLocalStorage - Custom hook
|
|
54
|
+
const [savedName, setSavedName] = useLocalStorage('userName', '');
|
|
55
|
+
|
|
56
|
+
// useFetch - Custom hook
|
|
57
|
+
const { data, loading, error } = useFetch('/api/hello?name=Hooks');
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div>
|
|
61
|
+
<h1>React Hooks Demo 🪝</h1>
|
|
62
|
+
|
|
63
|
+
<div class="card">
|
|
64
|
+
<h2>useState</h2>
|
|
65
|
+
<p>Basic state management</p>
|
|
66
|
+
<div class="count-display">{count}</div>
|
|
67
|
+
<div class="counter-actions">
|
|
68
|
+
<button class="btn btn-primary" onClick={() => setCount(count + 1)}>
|
|
69
|
+
Increment
|
|
70
|
+
</button>
|
|
71
|
+
<button class="btn btn-danger" onClick={() => setCount(count - 1)}>
|
|
72
|
+
Decrement
|
|
73
|
+
</button>
|
|
74
|
+
<button class="btn btn-grey" onClick={() => setCount(0)}>
|
|
75
|
+
Reset
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div class="card">
|
|
81
|
+
<h2>useEffect</h2>
|
|
82
|
+
<p>Check the browser console and page title!</p>
|
|
83
|
+
<p>Current count: <strong>{count}</strong></p>
|
|
84
|
+
<p style={{ color: '#666', fontSize: '14px' }}>
|
|
85
|
+
useEffect runs after render and updates the document title
|
|
86
|
+
</p>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div class="card">
|
|
90
|
+
<h2>useMemo</h2>
|
|
91
|
+
<p>Memoized expensive computation</p>
|
|
92
|
+
<p>Count × 2 = <strong>{expensiveValue}</strong></p>
|
|
93
|
+
<p style={{ color: '#666', fontSize: '14px' }}>
|
|
94
|
+
Check console - computation only runs when count changes
|
|
95
|
+
</p>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div class="card">
|
|
99
|
+
<h2>useCallback</h2>
|
|
100
|
+
<p>Memoized callback function</p>
|
|
101
|
+
<button class="btn btn-primary" onClick={handleIncrement}>
|
|
102
|
+
Increment (Memoized)
|
|
103
|
+
</button>
|
|
104
|
+
<p style={{ color: '#666', fontSize: '14px', marginTop: '8px' }}>
|
|
105
|
+
Function reference stays the same between renders
|
|
106
|
+
</p>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<div class="card">
|
|
110
|
+
<h2>useRef</h2>
|
|
111
|
+
<p>Mutable reference to DOM element</p>
|
|
112
|
+
<input
|
|
113
|
+
ref={inputRef}
|
|
114
|
+
type="text"
|
|
115
|
+
placeholder="Click button to focus me"
|
|
116
|
+
value={name}
|
|
117
|
+
onInput={(e) => setName(e.target.value)}
|
|
118
|
+
/>
|
|
119
|
+
<button class="btn btn-primary" onClick={focusInput} style={{ marginTop: '8px' }}>
|
|
120
|
+
Focus Input
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div class="card">
|
|
125
|
+
<h2>useReducer</h2>
|
|
126
|
+
<p>Complex state management with actions</p>
|
|
127
|
+
<div class="count-display">{state.value}</div>
|
|
128
|
+
<div class="counter-actions">
|
|
129
|
+
<button class="btn btn-primary" onClick={() => dispatch({ type: 'increment' })}>
|
|
130
|
+
+
|
|
131
|
+
</button>
|
|
132
|
+
<button class="btn btn-danger" onClick={() => dispatch({ type: 'decrement' })}>
|
|
133
|
+
−
|
|
134
|
+
</button>
|
|
135
|
+
<button class="btn btn-grey" onClick={() => dispatch({ type: 'reset' })}>
|
|
136
|
+
Reset
|
|
137
|
+
</button>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div class="card">
|
|
142
|
+
<h2>useLocalStorage</h2>
|
|
143
|
+
<p>Persistent state in localStorage</p>
|
|
144
|
+
<input
|
|
145
|
+
type="text"
|
|
146
|
+
placeholder="Enter your name"
|
|
147
|
+
value={savedName}
|
|
148
|
+
onInput={(e) => setSavedName(e.target.value)}
|
|
149
|
+
/>
|
|
150
|
+
<p style={{ marginTop: '8px' }}>
|
|
151
|
+
Saved name: <strong>{savedName || 'Not set'}</strong>
|
|
152
|
+
</p>
|
|
153
|
+
<p style={{ color: '#666', fontSize: '14px' }}>
|
|
154
|
+
Refresh the page - your name persists!
|
|
155
|
+
</p>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<div class="card">
|
|
159
|
+
<h2>useFetch</h2>
|
|
160
|
+
<p>Data fetching with loading and error states</p>
|
|
161
|
+
{loading && <p>Loading...</p>}
|
|
162
|
+
{error && <p style={{ color: 'red' }}>Error: {error}</p>}
|
|
163
|
+
{data && (
|
|
164
|
+
<div style={{
|
|
165
|
+
background: '#f0f0f0',
|
|
166
|
+
padding: '12px',
|
|
167
|
+
borderRadius: '8px',
|
|
168
|
+
fontFamily: 'monospace',
|
|
169
|
+
fontSize: '14px'
|
|
170
|
+
}}>
|
|
171
|
+
<pre>{JSON.stringify(data, null, 2)}</pre>
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<div class="card">
|
|
177
|
+
<h2>All Hooks Available</h2>
|
|
178
|
+
<ul style={{ lineHeight: '1.8', marginLeft: '20px' }}>
|
|
179
|
+
<li><code>useState</code> - State management</li>
|
|
180
|
+
<li><code>useEffect</code> - Side effects</li>
|
|
181
|
+
<li><code>useMemo</code> - Memoized values</li>
|
|
182
|
+
<li><code>useCallback</code> - Memoized functions</li>
|
|
183
|
+
<li><code>useRef</code> - Mutable refs</li>
|
|
184
|
+
<li><code>useReducer</code> - Complex state</li>
|
|
185
|
+
<li><code>useLocalStorage</code> - Persistent state</li>
|
|
186
|
+
<li><code>useFetch</code> - Data fetching</li>
|
|
187
|
+
</ul>
|
|
188
|
+
<p style={{ marginTop: '12px', color: '#666' }}>
|
|
189
|
+
Just like React/Next.js! 🎉
|
|
190
|
+
</p>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
@@ -31,16 +31,16 @@ function loadApiRoutes() {
|
|
|
31
31
|
delete require.cache[require.resolve(filePath)];
|
|
32
32
|
|
|
33
33
|
// Load the API handler
|
|
34
|
-
const
|
|
34
|
+
const module = require(filePath);
|
|
35
|
+
|
|
36
|
+
// Support both default export and module.exports
|
|
37
|
+
const handler = module.default || module;
|
|
35
38
|
|
|
36
39
|
if (typeof handler === 'function') {
|
|
37
40
|
apiHandlers[routeName] = handler;
|
|
38
41
|
console.log(` ✅ /api/${routeName}`);
|
|
39
|
-
} else if (handler.default && typeof handler.default === 'function') {
|
|
40
|
-
apiHandlers[routeName] = handler.default;
|
|
41
|
-
console.log(` ✅ /api/${routeName}`);
|
|
42
42
|
} else {
|
|
43
|
-
console.log(` ⚠️ /api/${routeName} - No
|
|
43
|
+
console.log(` ⚠️ /api/${routeName} - No handler function exported`);
|
|
44
44
|
}
|
|
45
45
|
} catch (error) {
|
|
46
46
|
console.error(` ❌ /api/${routeName} - ${error.message}`);
|