create-aziz-app 2.0.10 → 2.0.12
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/bin/create-aziz-app.js +36 -0
- package/package.json +1 -1
- package/template/README.md +9 -5
- package/template/api/hello.js +5 -6
- package/template/api/users.js +6 -8
- package/template/app.html +24 -25
- package/template/docs/API-ROUTES.md +538 -0
- package/template/docs/DEV-SERVER.md +108 -22
- package/template/docs/HOW-IT-WORKS.md +362 -0
- package/template/modules/api.js +20 -124
- package/template/package-lock.json +13 -0
- package/template/package.json +1 -1
- package/template/scripts/dev-server.js +263 -170
- package/template/scripts/generate-imports.js +0 -10
- package/template/scripts/live-reload-client.js +105 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
# API Routes Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
API routes run on the **SERVER** (Node.js), just like Next.js! They're real backend endpoints, not client-side mocks.
|
|
6
|
+
|
|
7
|
+
## How It Works
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Browser Dev Server (Node.js)
|
|
11
|
+
│ │
|
|
12
|
+
│ GET /api/users │
|
|
13
|
+
├─────────────────────────────►│
|
|
14
|
+
│ │ Loads api/users.js
|
|
15
|
+
│ │ Executes handler()
|
|
16
|
+
│ │
|
|
17
|
+
│ { users: [...] } │
|
|
18
|
+
│◄─────────────────────────────┤
|
|
19
|
+
│ │
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Creating an API Route
|
|
23
|
+
|
|
24
|
+
### File Structure
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
api/
|
|
28
|
+
├── hello.js → /api/hello
|
|
29
|
+
├── users.js → /api/users
|
|
30
|
+
└── posts.js → /api/posts
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Each file in `api/` becomes an endpoint!
|
|
34
|
+
|
|
35
|
+
### Basic Example
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
// api/hello.js
|
|
39
|
+
|
|
40
|
+
module.exports = async function handler(req, res) {
|
|
41
|
+
return res.status(200).json({
|
|
42
|
+
message: 'Hello from the server!'
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Request Object
|
|
48
|
+
|
|
49
|
+
The `req` object contains:
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
{
|
|
53
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
|
54
|
+
body: {}, // Parsed JSON body
|
|
55
|
+
query: {}, // URL query parameters
|
|
56
|
+
headers: {}, // Request headers
|
|
57
|
+
url: '/api/users?id=1'
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Response Object
|
|
62
|
+
|
|
63
|
+
The `res` object has these methods:
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
// Set status code
|
|
67
|
+
res.status(200)
|
|
68
|
+
|
|
69
|
+
// Send JSON response
|
|
70
|
+
res.json({ data: 'value' })
|
|
71
|
+
|
|
72
|
+
// Send text response
|
|
73
|
+
res.send('Hello')
|
|
74
|
+
|
|
75
|
+
// Set custom header
|
|
76
|
+
res.setHeader('X-Custom', 'value')
|
|
77
|
+
|
|
78
|
+
// Chain methods
|
|
79
|
+
res.status(201).json({ created: true })
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Examples
|
|
83
|
+
|
|
84
|
+
### GET Request
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
// api/users.js
|
|
88
|
+
|
|
89
|
+
const users = [
|
|
90
|
+
{ id: 1, name: 'Aziz' },
|
|
91
|
+
{ id: 2, name: 'John' }
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
module.exports = async function handler(req, res) {
|
|
95
|
+
if (req.method === 'GET') {
|
|
96
|
+
// Get specific user
|
|
97
|
+
if (req.query.id) {
|
|
98
|
+
const user = users.find(u => u.id === parseInt(req.query.id));
|
|
99
|
+
if (user) {
|
|
100
|
+
return res.status(200).json(user);
|
|
101
|
+
}
|
|
102
|
+
return res.status(404).json({ error: 'User not found' });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Get all users
|
|
106
|
+
return res.status(200).json(users);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return res.status(405).json({ error: 'Method not allowed' });
|
|
110
|
+
};
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Usage:**
|
|
114
|
+
```javascript
|
|
115
|
+
// Get all users
|
|
116
|
+
const response = await fetchApi('/api/users');
|
|
117
|
+
const users = await response.json();
|
|
118
|
+
|
|
119
|
+
// Get specific user
|
|
120
|
+
const response = await fetchApi('/api/users?id=1');
|
|
121
|
+
const user = await response.json();
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### POST Request
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
// api/users.js
|
|
128
|
+
|
|
129
|
+
module.exports = async function handler(req, res) {
|
|
130
|
+
if (req.method === 'POST') {
|
|
131
|
+
const { name, email } = req.body;
|
|
132
|
+
|
|
133
|
+
// Validate
|
|
134
|
+
if (!name || !email) {
|
|
135
|
+
return res.status(400).json({
|
|
136
|
+
error: 'Name and email are required'
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Create user
|
|
141
|
+
const newUser = {
|
|
142
|
+
id: Date.now(),
|
|
143
|
+
name,
|
|
144
|
+
email
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return res.status(201).json(newUser);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Usage:**
|
|
153
|
+
```javascript
|
|
154
|
+
const response = await fetchApi('/api/users', {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
body: JSON.stringify({
|
|
157
|
+
name: 'Aziz',
|
|
158
|
+
email: 'aziz@example.com'
|
|
159
|
+
})
|
|
160
|
+
});
|
|
161
|
+
const user = await response.json();
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### PUT Request
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
// api/users.js
|
|
168
|
+
|
|
169
|
+
module.exports = async function handler(req, res) {
|
|
170
|
+
if (req.method === 'PUT') {
|
|
171
|
+
const userId = req.query.id;
|
|
172
|
+
const updates = req.body;
|
|
173
|
+
|
|
174
|
+
// Update user logic here
|
|
175
|
+
|
|
176
|
+
return res.status(200).json({
|
|
177
|
+
message: 'User updated',
|
|
178
|
+
user: updatedUser
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Usage:**
|
|
185
|
+
```javascript
|
|
186
|
+
const response = await fetchApi('/api/users?id=1', {
|
|
187
|
+
method: 'PUT',
|
|
188
|
+
body: JSON.stringify({
|
|
189
|
+
name: 'New Name'
|
|
190
|
+
})
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### DELETE Request
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
// api/users.js
|
|
198
|
+
|
|
199
|
+
module.exports = async function handler(req, res) {
|
|
200
|
+
if (req.method === 'DELETE') {
|
|
201
|
+
const userId = req.query.id;
|
|
202
|
+
|
|
203
|
+
// Delete user logic here
|
|
204
|
+
|
|
205
|
+
return res.status(200).json({
|
|
206
|
+
message: 'User deleted'
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Usage:**
|
|
213
|
+
```javascript
|
|
214
|
+
const response = await fetchApi('/api/users?id=1', {
|
|
215
|
+
method: 'DELETE'
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Multiple Methods
|
|
220
|
+
|
|
221
|
+
```javascript
|
|
222
|
+
// api/users.js
|
|
223
|
+
|
|
224
|
+
module.exports = async function handler(req, res) {
|
|
225
|
+
switch (req.method) {
|
|
226
|
+
case 'GET':
|
|
227
|
+
// Handle GET
|
|
228
|
+
return res.status(200).json({ method: 'GET' });
|
|
229
|
+
|
|
230
|
+
case 'POST':
|
|
231
|
+
// Handle POST
|
|
232
|
+
return res.status(201).json({ method: 'POST' });
|
|
233
|
+
|
|
234
|
+
case 'PUT':
|
|
235
|
+
// Handle PUT
|
|
236
|
+
return res.status(200).json({ method: 'PUT' });
|
|
237
|
+
|
|
238
|
+
case 'DELETE':
|
|
239
|
+
// Handle DELETE
|
|
240
|
+
return res.status(200).json({ method: 'DELETE' });
|
|
241
|
+
|
|
242
|
+
default:
|
|
243
|
+
return res.status(405).json({
|
|
244
|
+
error: 'Method not allowed'
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Using in Components
|
|
251
|
+
|
|
252
|
+
### Basic Fetch
|
|
253
|
+
|
|
254
|
+
```jsx
|
|
255
|
+
function MyComponent() {
|
|
256
|
+
const [data, setData] = useState(null);
|
|
257
|
+
const [loading, setLoading] = useState(false);
|
|
258
|
+
|
|
259
|
+
const loadData = async () => {
|
|
260
|
+
setLoading(true);
|
|
261
|
+
try {
|
|
262
|
+
const response = await fetchApi('/api/users');
|
|
263
|
+
const users = await response.json();
|
|
264
|
+
setData(users);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.error('Error:', error);
|
|
267
|
+
}
|
|
268
|
+
setLoading(false);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<div>
|
|
273
|
+
<button onClick={loadData}>Load Users</button>
|
|
274
|
+
{loading && <p>Loading...</p>}
|
|
275
|
+
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
|
|
276
|
+
</div>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### With Error Handling
|
|
282
|
+
|
|
283
|
+
```jsx
|
|
284
|
+
const [error, setError] = useState(null);
|
|
285
|
+
|
|
286
|
+
const loadData = async () => {
|
|
287
|
+
try {
|
|
288
|
+
const response = await fetchApi('/api/users');
|
|
289
|
+
|
|
290
|
+
if (!response.ok) {
|
|
291
|
+
throw new Error('Failed to fetch');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const users = await response.json();
|
|
295
|
+
setData(users);
|
|
296
|
+
setError(null);
|
|
297
|
+
} catch (err) {
|
|
298
|
+
setError(err.message);
|
|
299
|
+
setData(null);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Database Integration
|
|
305
|
+
|
|
306
|
+
### Using a Real Database
|
|
307
|
+
|
|
308
|
+
```javascript
|
|
309
|
+
// api/users.js
|
|
310
|
+
|
|
311
|
+
// Example with a database (you need to install the package)
|
|
312
|
+
// npm install better-sqlite3
|
|
313
|
+
|
|
314
|
+
const Database = require('better-sqlite3');
|
|
315
|
+
const db = new Database('mydb.sqlite');
|
|
316
|
+
|
|
317
|
+
module.exports = async function handler(req, res) {
|
|
318
|
+
if (req.method === 'GET') {
|
|
319
|
+
const users = db.prepare('SELECT * FROM users').all();
|
|
320
|
+
return res.status(200).json(users);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (req.method === 'POST') {
|
|
324
|
+
const { name, email } = req.body;
|
|
325
|
+
const result = db.prepare(
|
|
326
|
+
'INSERT INTO users (name, email) VALUES (?, ?)'
|
|
327
|
+
).run(name, email);
|
|
328
|
+
|
|
329
|
+
return res.status(201).json({
|
|
330
|
+
id: result.lastInsertRowid,
|
|
331
|
+
name,
|
|
332
|
+
email
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Using External APIs
|
|
339
|
+
|
|
340
|
+
```javascript
|
|
341
|
+
// api/weather.js
|
|
342
|
+
|
|
343
|
+
module.exports = async function handler(req, res) {
|
|
344
|
+
const city = req.query.city || 'London';
|
|
345
|
+
|
|
346
|
+
try {
|
|
347
|
+
const response = await fetch(
|
|
348
|
+
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=YOUR_KEY`
|
|
349
|
+
);
|
|
350
|
+
const data = await response.json();
|
|
351
|
+
|
|
352
|
+
return res.status(200).json(data);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
return res.status(500).json({
|
|
355
|
+
error: 'Failed to fetch weather'
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Environment Variables
|
|
362
|
+
|
|
363
|
+
```javascript
|
|
364
|
+
// api/config.js
|
|
365
|
+
|
|
366
|
+
module.exports = async function handler(req, res) {
|
|
367
|
+
// Access environment variables
|
|
368
|
+
const apiKey = process.env.API_KEY;
|
|
369
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
370
|
+
|
|
371
|
+
return res.status(200).json({
|
|
372
|
+
hasApiKey: !!apiKey,
|
|
373
|
+
hasDbUrl: !!dbUrl
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Set environment variables:
|
|
379
|
+
```bash
|
|
380
|
+
# Windows
|
|
381
|
+
set API_KEY=your_key
|
|
382
|
+
npm run dev
|
|
383
|
+
|
|
384
|
+
# Mac/Linux
|
|
385
|
+
API_KEY=your_key npm run dev
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Hot Reload
|
|
389
|
+
|
|
390
|
+
API routes support hot reload! When you save an API file:
|
|
391
|
+
|
|
392
|
+
1. Dev server detects the change
|
|
393
|
+
2. Reloads the API handler
|
|
394
|
+
3. Next request uses the new code
|
|
395
|
+
4. No server restart needed!
|
|
396
|
+
|
|
397
|
+
## CORS
|
|
398
|
+
|
|
399
|
+
CORS is automatically enabled for all API routes:
|
|
400
|
+
```
|
|
401
|
+
Access-Control-Allow-Origin: *
|
|
402
|
+
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
|
|
403
|
+
Access-Control-Allow-Headers: Content-Type
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## Error Handling
|
|
407
|
+
|
|
408
|
+
### Server Errors
|
|
409
|
+
|
|
410
|
+
```javascript
|
|
411
|
+
module.exports = async function handler(req, res) {
|
|
412
|
+
try {
|
|
413
|
+
// Your code here
|
|
414
|
+
const data = await someAsyncOperation();
|
|
415
|
+
return res.status(200).json(data);
|
|
416
|
+
} catch (error) {
|
|
417
|
+
console.error('API Error:', error);
|
|
418
|
+
return res.status(500).json({
|
|
419
|
+
error: 'Internal server error',
|
|
420
|
+
message: error.message
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Validation Errors
|
|
427
|
+
|
|
428
|
+
```javascript
|
|
429
|
+
module.exports = async function handler(req, res) {
|
|
430
|
+
if (req.method === 'POST') {
|
|
431
|
+
const { name, email } = req.body;
|
|
432
|
+
|
|
433
|
+
if (!name) {
|
|
434
|
+
return res.status(400).json({
|
|
435
|
+
error: 'Name is required'
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (!email || !email.includes('@')) {
|
|
440
|
+
return res.status(400).json({
|
|
441
|
+
error: 'Valid email is required'
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Process valid data
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## Status Codes
|
|
451
|
+
|
|
452
|
+
Common HTTP status codes:
|
|
453
|
+
|
|
454
|
+
- `200` - OK (successful GET, PUT, DELETE)
|
|
455
|
+
- `201` - Created (successful POST)
|
|
456
|
+
- `400` - Bad Request (validation error)
|
|
457
|
+
- `401` - Unauthorized (authentication required)
|
|
458
|
+
- `403` - Forbidden (no permission)
|
|
459
|
+
- `404` - Not Found (resource doesn't exist)
|
|
460
|
+
- `405` - Method Not Allowed (wrong HTTP method)
|
|
461
|
+
- `500` - Internal Server Error (server error)
|
|
462
|
+
|
|
463
|
+
## Best Practices
|
|
464
|
+
|
|
465
|
+
### 1. Always Handle Methods
|
|
466
|
+
|
|
467
|
+
```javascript
|
|
468
|
+
module.exports = async function handler(req, res) {
|
|
469
|
+
if (req.method === 'GET') {
|
|
470
|
+
// Handle GET
|
|
471
|
+
} else {
|
|
472
|
+
return res.status(405).json({
|
|
473
|
+
error: 'Method not allowed'
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### 2. Validate Input
|
|
480
|
+
|
|
481
|
+
```javascript
|
|
482
|
+
if (!req.body.name || typeof req.body.name !== 'string') {
|
|
483
|
+
return res.status(400).json({ error: 'Invalid name' });
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### 3. Use Try-Catch
|
|
488
|
+
|
|
489
|
+
```javascript
|
|
490
|
+
try {
|
|
491
|
+
const data = await database.query();
|
|
492
|
+
return res.status(200).json(data);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
return res.status(500).json({ error: error.message });
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### 4. Return Early
|
|
499
|
+
|
|
500
|
+
```javascript
|
|
501
|
+
if (!user) {
|
|
502
|
+
return res.status(404).json({ error: 'Not found' });
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Continue with logic
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### 5. Use Async/Await
|
|
509
|
+
|
|
510
|
+
```javascript
|
|
511
|
+
module.exports = async function handler(req, res) {
|
|
512
|
+
const data = await fetchData();
|
|
513
|
+
return res.status(200).json(data);
|
|
514
|
+
};
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
## Comparison with Next.js
|
|
518
|
+
|
|
519
|
+
| Feature | Aziz App | Next.js |
|
|
520
|
+
|---------|----------|---------|
|
|
521
|
+
| File-based routing | ✅ | ✅ |
|
|
522
|
+
| Server-side execution | ✅ | ✅ |
|
|
523
|
+
| Hot reload | ✅ | ✅ |
|
|
524
|
+
| Request/Response API | ✅ | ✅ |
|
|
525
|
+
| Dynamic routes | ❌ | ✅ |
|
|
526
|
+
| Middleware | ❌ | ✅ |
|
|
527
|
+
| Edge runtime | ❌ | ✅ |
|
|
528
|
+
|
|
529
|
+
## Summary
|
|
530
|
+
|
|
531
|
+
- API routes run on the **server** (Node.js)
|
|
532
|
+
- Each file in `api/` becomes an endpoint
|
|
533
|
+
- Use `module.exports` to export the handler
|
|
534
|
+
- Access via `fetchApi('/api/routename')`
|
|
535
|
+
- Supports GET, POST, PUT, DELETE
|
|
536
|
+
- Hot reload enabled
|
|
537
|
+
- CORS enabled by default
|
|
538
|
+
- Just like Next.js API routes!
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
The dev server provides a smooth development experience with live reload, automatic import generation, and file watching.
|
|
5
|
+
The dev server provides a smooth development experience with live reload using WebSocket, automatic import generation, and file watching.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
### 🔄 Live Reload
|
|
9
|
+
### 🔄 Live Reload (WebSocket-based)
|
|
10
10
|
- **Automatic browser refresh** when you save files
|
|
11
11
|
- **Visual indicator** in bottom-right corner shows connection status
|
|
12
|
-
- **
|
|
12
|
+
- **WebSocket connection** - reliable, fast, no caching issues
|
|
13
13
|
- **CSS hot reload** - instant updates without regenerating imports
|
|
14
|
+
- **Always loaded** - script is part of your HTML, not injected
|
|
14
15
|
|
|
15
16
|
### 👀 File Watching
|
|
16
17
|
The dev server watches these directories:
|
|
@@ -41,22 +42,36 @@ Then open the URL shown in console (usually `http://localhost:3000/`)
|
|
|
41
42
|
## Visual Indicators
|
|
42
43
|
|
|
43
44
|
The live reload indicator in the bottom-right corner shows:
|
|
44
|
-
- 🔌 **Connecting...** - Initial connection
|
|
45
|
-
- ✅ **Live Reload
|
|
46
|
-
- 🔄 **Reloading...** - Changes detected, refreshing page
|
|
47
|
-
- ⚠️ **
|
|
45
|
+
- 🔌 **Connecting...** - Initial connection (gray/green)
|
|
46
|
+
- ✅ **Live Reload** - Connected and watching (green, fades to 30%)
|
|
47
|
+
- 🔄 **Reloading...** - Changes detected, refreshing page (blue)
|
|
48
|
+
- ⚠️ **Disconnected** - Connection lost, attempting to reconnect (orange)
|
|
49
|
+
- ❌ **Offline** - Max reconnection attempts reached (red)
|
|
50
|
+
|
|
51
|
+
**Click the indicator** to manually reload the page.
|
|
48
52
|
|
|
49
53
|
Hover over the indicator to make it fully visible.
|
|
50
54
|
|
|
51
55
|
## How It Works
|
|
52
56
|
|
|
53
57
|
1. **Server starts** and generates initial imports
|
|
54
|
-
2. **Browser
|
|
55
|
-
3. **
|
|
56
|
-
4. **
|
|
57
|
-
5. **
|
|
58
|
-
6. **
|
|
59
|
-
7. **
|
|
58
|
+
2. **Browser loads** `live-reload-client.js` (always in HTML)
|
|
59
|
+
3. **WebSocket connects** to dev server
|
|
60
|
+
4. **File watcher** detects changes in watched directories
|
|
61
|
+
5. **Debouncing** prevents multiple rapid reloads (300ms for CSS, 500ms for JS)
|
|
62
|
+
6. **Import regeneration** (for JS/JSX changes only)
|
|
63
|
+
7. **WebSocket message** sent to all connected browsers
|
|
64
|
+
8. **Page reload** happens immediately
|
|
65
|
+
|
|
66
|
+
## Why WebSocket?
|
|
67
|
+
|
|
68
|
+
Previous versions used Server-Sent Events (SSE) with runtime injection, which had caching issues. The new approach:
|
|
69
|
+
|
|
70
|
+
- ✅ **Always loaded** - Script is in HTML, not injected at runtime
|
|
71
|
+
- ✅ **No caching issues** - Browser always has the script
|
|
72
|
+
- ✅ **Bidirectional** - WebSocket allows two-way communication
|
|
73
|
+
- ✅ **Reliable** - Built-in reconnection logic
|
|
74
|
+
- ✅ **Fast** - Instant notifications, no polling
|
|
60
75
|
|
|
61
76
|
## Cache Control
|
|
62
77
|
|
|
@@ -67,28 +82,52 @@ Pragma: no-cache
|
|
|
67
82
|
Expires: 0
|
|
68
83
|
```
|
|
69
84
|
|
|
70
|
-
|
|
85
|
+
Plus, `app.html` has meta tags:
|
|
86
|
+
```html
|
|
87
|
+
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
|
88
|
+
<meta http-equiv="Pragma" content="no-cache">
|
|
89
|
+
<meta http-equiv="Expires" content="0">
|
|
90
|
+
```
|
|
71
91
|
|
|
72
92
|
## Troubleshooting
|
|
73
93
|
|
|
94
|
+
### Indicator stuck on "Connecting..."?
|
|
95
|
+
|
|
96
|
+
1. **Check dev server is running** - Terminal should show "✅ Dev server running"
|
|
97
|
+
2. **Check browser console** (F12) - Look for WebSocket errors
|
|
98
|
+
3. **Check port** - Browser URL should match server URL
|
|
99
|
+
4. **Firewall** - Allow Node.js through firewall
|
|
100
|
+
5. **Try different browser** - Rule out browser-specific issues
|
|
101
|
+
|
|
74
102
|
### Changes not showing?
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
103
|
+
|
|
104
|
+
1. **Check indicator is green** - "✅ Live Reload"
|
|
105
|
+
2. **Check terminal** - Should show "📝 Detected change: ..."
|
|
106
|
+
3. **Check file was saved** - No unsaved indicator in editor
|
|
107
|
+
4. **Check file location** - Must be in watched directories
|
|
108
|
+
5. **Check for errors** - Browser console (F12) for JavaScript errors
|
|
109
|
+
|
|
110
|
+
### WebSocket connection failed?
|
|
111
|
+
|
|
112
|
+
1. **Port mismatch** - Check browser URL matches server port
|
|
113
|
+
2. **Proxy/VPN** - May block WebSocket connections
|
|
114
|
+
3. **Browser extension** - Ad blockers can interfere
|
|
115
|
+
4. **Try incognito mode** - Rule out extensions
|
|
79
116
|
|
|
80
117
|
### Port already in use?
|
|
81
|
-
The server will automatically try the next port (3001, 3002, etc.)
|
|
82
118
|
|
|
83
|
-
|
|
84
|
-
|
|
119
|
+
The server automatically tries the next port. Check terminal for actual port:
|
|
120
|
+
```
|
|
121
|
+
🌐 Server running at http://localhost:3001/
|
|
122
|
+
```
|
|
85
123
|
|
|
86
124
|
## Performance
|
|
87
125
|
|
|
88
126
|
- **Debouncing** prevents reload spam when saving multiple files
|
|
89
127
|
- **CSS-only changes** skip import regeneration for faster updates
|
|
90
|
-
- **
|
|
128
|
+
- **WebSocket** is lightweight and efficient
|
|
91
129
|
- **Multiple browsers** can connect simultaneously
|
|
130
|
+
- **Automatic reconnection** if connection drops
|
|
92
131
|
|
|
93
132
|
## Development Tips
|
|
94
133
|
|
|
@@ -96,3 +135,50 @@ The page will automatically try to reconnect after 2 seconds.
|
|
|
96
135
|
2. The visual indicator helps confirm live reload is working
|
|
97
136
|
3. CSS changes are faster than JS changes (no import regeneration)
|
|
98
137
|
4. Multiple browser tabs will all reload simultaneously
|
|
138
|
+
5. Click the indicator to manually reload if needed
|
|
139
|
+
|
|
140
|
+
## Production
|
|
141
|
+
|
|
142
|
+
The live reload script only runs in development (localhost). In production:
|
|
143
|
+
- Script detects non-localhost hostname
|
|
144
|
+
- Exits immediately without connecting
|
|
145
|
+
- No performance impact
|
|
146
|
+
- No visual indicator
|
|
147
|
+
|
|
148
|
+
## Technical Details
|
|
149
|
+
|
|
150
|
+
### WebSocket Protocol
|
|
151
|
+
|
|
152
|
+
The dev server implements a simple WebSocket server without external dependencies:
|
|
153
|
+
- Uses Node.js `http` module's `upgrade` event
|
|
154
|
+
- Performs WebSocket handshake manually
|
|
155
|
+
- Sends text frames to connected clients
|
|
156
|
+
- Handles connection cleanup automatically
|
|
157
|
+
|
|
158
|
+
### Message Format
|
|
159
|
+
|
|
160
|
+
Server sends a single message type:
|
|
161
|
+
- `"reload"` - Triggers browser reload
|
|
162
|
+
|
|
163
|
+
### Reconnection Logic
|
|
164
|
+
|
|
165
|
+
Client attempts to reconnect:
|
|
166
|
+
- Max 10 attempts
|
|
167
|
+
- 2-second delay between attempts
|
|
168
|
+
- Exponential backoff could be added if needed
|
|
169
|
+
|
|
170
|
+
## Comparison with Other Tools
|
|
171
|
+
|
|
172
|
+
| Feature | Aziz App | Live Server | Vite |
|
|
173
|
+
|---------|----------|-------------|------|
|
|
174
|
+
| Setup | Zero config | Zero config | Config file |
|
|
175
|
+
| Speed | Fast | Fast | Very fast |
|
|
176
|
+
| WebSocket | ✅ | ✅ | ✅ |
|
|
177
|
+
| Auto-import | ✅ | ❌ | ✅ |
|
|
178
|
+
| Visual indicator | ✅ | ❌ | ❌ |
|
|
179
|
+
| Dependencies | None | None | Many |
|
|
180
|
+
|
|
181
|
+
## Summary
|
|
182
|
+
|
|
183
|
+
The dev server provides a reliable, fast live reload experience using WebSocket. The script is always loaded (not injected), eliminating caching issues. File watching, debouncing, and automatic reconnection make development smooth and efficient.
|
|
184
|
+
|