koa-classic-server 2.0.0 → 2.1.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 +31 -17
- package/__tests__/directory-sorting-links.test.js +135 -0
- package/__tests__/ejs.test.js +299 -0
- package/__tests__/performance.test.js +75 -6
- package/__tests__/publicWwwTest/ejs-templates/complex.ejs +56 -0
- package/__tests__/publicWwwTest/ejs-templates/index.ejs +30 -0
- package/__tests__/publicWwwTest/ejs-templates/simple.ejs +13 -0
- package/__tests__/publicWwwTest/ejs-templates/with-conditional.ejs +28 -0
- package/__tests__/publicWwwTest/ejs-templates/with-escaping.ejs +26 -0
- package/__tests__/publicWwwTest/ejs-templates/with-loop.ejs +16 -0
- package/{scripts → __tests__}/setup-benchmark.js +1 -1
- package/docs/CODE_REVIEW.md +298 -0
- package/docs/FLOW_DIAGRAM.md +952 -0
- package/docs/template-engine/TEMPLATE_ENGINE_GUIDE.md +1734 -0
- package/docs/template-engine/esempi-incrementali.js +192 -0
- package/docs/template-engine/examples/esempio1-nessun-dato.ejs +12 -0
- package/docs/template-engine/examples/esempio2-una-variabile.ejs +11 -0
- package/docs/template-engine/examples/esempio3-piu-variabili.ejs +15 -0
- package/docs/template-engine/examples/esempio4-condizionale.ejs +18 -0
- package/docs/template-engine/examples/esempio5-loop.ejs +18 -0
- package/docs/template-engine/examples/index-esempi.html +181 -0
- package/docs/template-engine/examples/index.html +40 -0
- package/docs/template-engine/examples/test.ejs +64 -0
- package/index.cjs +186 -35
- package/package.json +9 -6
- package/CREATE_RELEASE.sh +0 -53
- package/publish-to-npm.sh +0 -65
- /package/{benchmark-results-baseline-v1.2.0.txt → __tests__/benchmark-results-baseline-v1.2.0.txt} +0 -0
- /package/{benchmark-results-optimized-v2.0.0.txt → __tests__/benchmark-results-optimized-v2.0.0.txt} +0 -0
- /package/{benchmark.js → __tests__/benchmark.js} +0 -0
- /package/{customTest → __tests__/customTest}/README.md +0 -0
- /package/{customTest → __tests__/customTest}/loadConfig.util.js +0 -0
- /package/{customTest → __tests__/customTest}/serversToLoad.util.js +0 -0
- /package/{demo-regex-index.js → __tests__/demo-regex-index.js} +0 -0
- /package/{test-regex-quick.js → __tests__/test-regex-quick.js} +0 -0
- /package/{BENCHMARKS.md → docs/BENCHMARKS.md} +0 -0
- /package/{CHANGELOG.md → docs/CHANGELOG.md} +0 -0
- /package/{DEBUG_REPORT.md → docs/DEBUG_REPORT.md} +0 -0
- /package/{DOCUMENTATION.md → docs/DOCUMENTATION.md} +0 -0
- /package/{EXAMPLES_INDEX_OPTION.md → docs/EXAMPLES_INDEX_OPTION.md} +0 -0
- /package/{INDEX_OPTION_PRIORITY.md → docs/INDEX_OPTION_PRIORITY.md} +0 -0
- /package/{OPTIMIZATION_HTTP_CACHING.md → docs/OPTIMIZATION_HTTP_CACHING.md} +0 -0
- /package/{PERFORMANCE_ANALYSIS.md → docs/PERFORMANCE_ANALYSIS.md} +0 -0
- /package/{PERFORMANCE_COMPARISON.md → docs/PERFORMANCE_COMPARISON.md} +0 -0
- /package/{noteExports.md → docs/noteExports.md} +0 -0
|
@@ -0,0 +1,1734 @@
|
|
|
1
|
+
# 📚 Template Engine - Guida Completa
|
|
2
|
+
|
|
3
|
+
Guida completa all'integrazione di template engine con koa-classic-server, con esempi progressivi da zero a configurazioni enterprise.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📑 Indice
|
|
8
|
+
|
|
9
|
+
- [Introduzione](#introduzione)
|
|
10
|
+
- [Quick Start](#quick-start)
|
|
11
|
+
- [Esempi Incrementali](#esempi-incrementali) - Da zero a loop
|
|
12
|
+
- [Guida Progressiva](#guida-progressiva) - Da semplice a enterprise
|
|
13
|
+
- [Integrazione Template Engine](#integrazione-template-engine)
|
|
14
|
+
- [EJS](#ejs)
|
|
15
|
+
- [Pug](#pug)
|
|
16
|
+
- [Handlebars](#handlebars)
|
|
17
|
+
- [Nunjucks](#nunjucks)
|
|
18
|
+
- [Esempi Avanzati](#esempi-avanzati)
|
|
19
|
+
- [Best Practices](#best-practices)
|
|
20
|
+
- [Troubleshooting](#troubleshooting)
|
|
21
|
+
- [Performance Tips](#performance-tips)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Introduzione
|
|
26
|
+
|
|
27
|
+
koa-classic-server supporta l'integrazione con qualsiasi template engine JavaScript (EJS, Pug, Handlebars, Nunjucks, etc.) tramite una configurazione flessibile.
|
|
28
|
+
|
|
29
|
+
### Come Funziona
|
|
30
|
+
|
|
31
|
+
Quando una richiesta arriva per un file con estensione specificata in `template.ext`, il middleware:
|
|
32
|
+
|
|
33
|
+
1. Verifica che l'estensione del file sia nell'array `template.ext`
|
|
34
|
+
2. Chiama la funzione `template.render` con il path del file
|
|
35
|
+
3. La funzione render processa il template e imposta `ctx.body`
|
|
36
|
+
4. Il middleware serve la risposta
|
|
37
|
+
|
|
38
|
+
### Configurazione Base
|
|
39
|
+
|
|
40
|
+
La configurazione minima richiede due elementi: l'array `ext` con le estensioni da processare e la funzione `render`.
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
const Koa = require('koa');
|
|
44
|
+
const koaClassicServer = require('koa-classic-server');
|
|
45
|
+
const ejs = require('ejs');
|
|
46
|
+
|
|
47
|
+
const app = new Koa();
|
|
48
|
+
|
|
49
|
+
app.use(koaClassicServer(__dirname + '/public', {
|
|
50
|
+
template: {
|
|
51
|
+
// Array di estensioni da processare
|
|
52
|
+
ext: ['ejs', 'EJS'],
|
|
53
|
+
|
|
54
|
+
// Funzione di rendering (configurazione minima)
|
|
55
|
+
render: async (ctx, next, filePath) => {
|
|
56
|
+
try {
|
|
57
|
+
// Nessun dato passato - il template deve essere statico
|
|
58
|
+
ctx.body = await ejs.renderFile(filePath, {});
|
|
59
|
+
ctx.type = 'text/html';
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('Template error:', error);
|
|
62
|
+
ctx.status = 500;
|
|
63
|
+
ctx.body = 'Template Error';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
app.listen(3000);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Note:**
|
|
73
|
+
- Questa configurazione base passa un oggetto vuoto `{}` al template
|
|
74
|
+
- Funziona solo con template che non usano variabili
|
|
75
|
+
- Per passare dati al template, vedi le sezioni successive: [Quick Start](#quick-start) e [Esempi Incrementali](#esempi-incrementali)
|
|
76
|
+
|
|
77
|
+
### Parametri
|
|
78
|
+
|
|
79
|
+
#### `template.ext` (Array)
|
|
80
|
+
|
|
81
|
+
Array di estensioni file che devono essere processate dal template engine.
|
|
82
|
+
|
|
83
|
+
**Note importanti:**
|
|
84
|
+
- **Case-sensitive**: `'ejs'` e `'EJS'` sono diversi
|
|
85
|
+
- **Senza punto**: usa `'ejs'` non `'.ejs'`
|
|
86
|
+
- **Sintassi array**: puoi usare sia `['ejs', 'EJS']` che `Array('ejs', 'EJS')` (equivalenti)
|
|
87
|
+
|
|
88
|
+
#### `template.render` (Function)
|
|
89
|
+
|
|
90
|
+
Funzione async che riceve il file da renderizzare e imposta il corpo della risposta.
|
|
91
|
+
|
|
92
|
+
**Signature:**
|
|
93
|
+
```javascript
|
|
94
|
+
async function render(ctx, next, filePath) { }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Parametri:**
|
|
98
|
+
- `ctx`: Contesto Koa completo con request, response, state, etc.
|
|
99
|
+
- `next`: Middleware successivo (raramente utilizzato)
|
|
100
|
+
- `filePath`: Path assoluto del file template da renderizzare
|
|
101
|
+
|
|
102
|
+
**Responsabilità:**
|
|
103
|
+
- Leggere/processare il file template
|
|
104
|
+
- Impostare `ctx.body` con l'HTML renderizzato
|
|
105
|
+
- Impostare `ctx.type = 'text/html'`
|
|
106
|
+
- Gestire errori di rendering con try/catch
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Quick Start
|
|
111
|
+
|
|
112
|
+
### 1. Installazione
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npm install koa-classic-server ejs
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 2. Crea un Server
|
|
119
|
+
|
|
120
|
+
**server.js:**
|
|
121
|
+
```javascript
|
|
122
|
+
const Koa = require('koa');
|
|
123
|
+
const koaClassicServer = require('koa-classic-server');
|
|
124
|
+
const ejs = require('ejs');
|
|
125
|
+
const path = require('path');
|
|
126
|
+
|
|
127
|
+
const app = new Koa();
|
|
128
|
+
|
|
129
|
+
app.use(koaClassicServer(path.join(__dirname, 'public'), {
|
|
130
|
+
showDirContents: true,
|
|
131
|
+
template: {
|
|
132
|
+
ext: ['ejs'],
|
|
133
|
+
render: async (ctx, next, filePath) => {
|
|
134
|
+
try {
|
|
135
|
+
const html = await ejs.renderFile(filePath, {
|
|
136
|
+
title: 'My Application',
|
|
137
|
+
user: ctx.state.user || { name: 'Guest' },
|
|
138
|
+
path: ctx.path,
|
|
139
|
+
timestamp: new Date().toISOString()
|
|
140
|
+
});
|
|
141
|
+
ctx.type = 'text/html';
|
|
142
|
+
ctx.body = html;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error('Template error:', error);
|
|
145
|
+
ctx.status = 500;
|
|
146
|
+
ctx.body = `<h1>Error</h1><pre>${error.message}</pre>`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}));
|
|
151
|
+
|
|
152
|
+
app.listen(3000, () => {
|
|
153
|
+
console.log('Server running on http://localhost:3000');
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 3. Crea un Template
|
|
158
|
+
|
|
159
|
+
**public/index.ejs:**
|
|
160
|
+
```html
|
|
161
|
+
<!DOCTYPE html>
|
|
162
|
+
<html lang="it">
|
|
163
|
+
<head>
|
|
164
|
+
<meta charset="UTF-8">
|
|
165
|
+
<title><%= title %></title>
|
|
166
|
+
</head>
|
|
167
|
+
<body>
|
|
168
|
+
<h1>Benvenuto <%= user.name %>!</h1>
|
|
169
|
+
<p>Path: <%= path %></p>
|
|
170
|
+
<p>Generato: <%= timestamp %></p>
|
|
171
|
+
</body>
|
|
172
|
+
</html>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 4. Avvia il Server
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
node server.js
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Apri http://localhost:3000/index.ejs
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Esempi Incrementali
|
|
186
|
+
|
|
187
|
+
Questa sezione mostra 5 esempi progressivi, dal template più semplice (senza dati) al più complesso (con loop).
|
|
188
|
+
|
|
189
|
+
> **💡 Regola fondamentale:** Devi passare esattamente i dati che il template usa!
|
|
190
|
+
|
|
191
|
+
### Esempio 1: Nessun Dato
|
|
192
|
+
|
|
193
|
+
**Quando usare:** Template completamente statici senza contenuto dinamico.
|
|
194
|
+
|
|
195
|
+
**Template: `esempio1-nessun-dato.ejs`**
|
|
196
|
+
```html
|
|
197
|
+
<!DOCTYPE html>
|
|
198
|
+
<html lang="it">
|
|
199
|
+
<head>
|
|
200
|
+
<meta charset="UTF-8">
|
|
201
|
+
<title>Esempio 1</title>
|
|
202
|
+
</head>
|
|
203
|
+
<body>
|
|
204
|
+
<h1>Ciao Mondo!</h1>
|
|
205
|
+
<p>Questo è un template EJS che non usa nessuna variabile.</p>
|
|
206
|
+
<p>È equivalente a un normale file HTML.</p>
|
|
207
|
+
</body>
|
|
208
|
+
</html>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Server:**
|
|
212
|
+
```javascript
|
|
213
|
+
render: async (ctx, next, filePath) => {
|
|
214
|
+
try {
|
|
215
|
+
// Nessun dato passato
|
|
216
|
+
ctx.body = await ejs.renderFile(filePath, {});
|
|
217
|
+
ctx.type = 'text/html';
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('Template error:', error);
|
|
220
|
+
ctx.status = 500;
|
|
221
|
+
ctx.body = `<h1>Error</h1><pre>${error.message}</pre>`;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Caratteristiche:**
|
|
227
|
+
- ✅ Nessuna variabile dinamica
|
|
228
|
+
- ✅ Passa oggetto vuoto `{}`
|
|
229
|
+
- ✅ Template rendering gestito con try/catch
|
|
230
|
+
- ✅ Content-type impostato correttamente
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
### Esempio 2: Una Variabile
|
|
235
|
+
|
|
236
|
+
**Quando usare:** Template con un singolo valore dinamico.
|
|
237
|
+
|
|
238
|
+
**Template: `esempio2-una-variabile.ejs`**
|
|
239
|
+
```html
|
|
240
|
+
<!DOCTYPE html>
|
|
241
|
+
<html>
|
|
242
|
+
<head>
|
|
243
|
+
<title>Esempio 2</title>
|
|
244
|
+
</head>
|
|
245
|
+
<body>
|
|
246
|
+
<h1>Ciao!</h1>
|
|
247
|
+
<p>Il tuo nome è: <strong><%= nome %></strong></p>
|
|
248
|
+
</body>
|
|
249
|
+
</html>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**Server:**
|
|
253
|
+
```javascript
|
|
254
|
+
render: async (ctx, next, filePath) => {
|
|
255
|
+
try {
|
|
256
|
+
ctx.body = await ejs.renderFile(filePath, {
|
|
257
|
+
nome: 'Mario' // ✅ Passa UNA variabile
|
|
258
|
+
});
|
|
259
|
+
ctx.type = 'text/html';
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error('Template error:', error);
|
|
262
|
+
ctx.status = 500;
|
|
263
|
+
ctx.body = `<h1>Error</h1><pre>${error.message}</pre>`;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
### Esempio 3: Più Variabili
|
|
271
|
+
|
|
272
|
+
**Quando usare:** Template con più valori dinamici.
|
|
273
|
+
|
|
274
|
+
**Template: `esempio3-piu-variabili.ejs`**
|
|
275
|
+
```html
|
|
276
|
+
<!DOCTYPE html>
|
|
277
|
+
<html>
|
|
278
|
+
<head>
|
|
279
|
+
<title>Esempio 3</title>
|
|
280
|
+
</head>
|
|
281
|
+
<body>
|
|
282
|
+
<h1>Profilo Utente</h1>
|
|
283
|
+
<ul>
|
|
284
|
+
<li>Nome: <%= nome %></li>
|
|
285
|
+
<li>Età: <%= eta %></li>
|
|
286
|
+
<li>Città: <%= citta %></li>
|
|
287
|
+
</ul>
|
|
288
|
+
</body>
|
|
289
|
+
</html>
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Server:**
|
|
293
|
+
```javascript
|
|
294
|
+
render: async (ctx, next, filePath) => {
|
|
295
|
+
try {
|
|
296
|
+
ctx.body = await ejs.renderFile(filePath, {
|
|
297
|
+
nome: 'Mario', // ✅ Passa
|
|
298
|
+
eta: 30, // ✅ PIÙ
|
|
299
|
+
citta: 'Roma' // ✅ variabili
|
|
300
|
+
});
|
|
301
|
+
ctx.type = 'text/html';
|
|
302
|
+
} catch (error) {
|
|
303
|
+
console.error('Template error:', error);
|
|
304
|
+
ctx.status = 500;
|
|
305
|
+
ctx.body = `<h1>Error</h1><pre>${error.message}</pre>`;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
### Esempio 4: Condizionale
|
|
313
|
+
|
|
314
|
+
**Quando usare:** Template con logica condizionale (login, permessi, etc.).
|
|
315
|
+
|
|
316
|
+
**Template: `esempio4-condizionale.ejs`**
|
|
317
|
+
```html
|
|
318
|
+
<!DOCTYPE html>
|
|
319
|
+
<html>
|
|
320
|
+
<head>
|
|
321
|
+
<title>Esempio 4</title>
|
|
322
|
+
</head>
|
|
323
|
+
<body>
|
|
324
|
+
<h1>Area Utente</h1>
|
|
325
|
+
|
|
326
|
+
<% if (autenticato) { %>
|
|
327
|
+
<p>Benvenuto <%= nome %>!</p>
|
|
328
|
+
<p>Hai accesso completo al sistema.</p>
|
|
329
|
+
<a href="/logout">Logout</a>
|
|
330
|
+
<% } else { %>
|
|
331
|
+
<p>Non sei autenticato.</p>
|
|
332
|
+
<a href="/login">Effettua il login</a>
|
|
333
|
+
<% } %>
|
|
334
|
+
</body>
|
|
335
|
+
</html>
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Server:**
|
|
339
|
+
```javascript
|
|
340
|
+
render: async (ctx, next, filePath) => {
|
|
341
|
+
try {
|
|
342
|
+
ctx.body = await ejs.renderFile(filePath, {
|
|
343
|
+
autenticato: true, // ✅ Passa dati
|
|
344
|
+
nome: 'Mario' // ✅ per la logica
|
|
345
|
+
});
|
|
346
|
+
ctx.type = 'text/html';
|
|
347
|
+
} catch (error) {
|
|
348
|
+
console.error('Template error:', error);
|
|
349
|
+
ctx.status = 500;
|
|
350
|
+
ctx.body = `<h1>Error</h1><pre>${error.message}</pre>`;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
### Esempio 5: Loop
|
|
358
|
+
|
|
359
|
+
**Quando usare:** Template con liste/tabelle dinamiche.
|
|
360
|
+
|
|
361
|
+
**Template: `esempio5-loop.ejs`**
|
|
362
|
+
```html
|
|
363
|
+
<!DOCTYPE html>
|
|
364
|
+
<html>
|
|
365
|
+
<head>
|
|
366
|
+
<title>Esempio 5</title>
|
|
367
|
+
</head>
|
|
368
|
+
<body>
|
|
369
|
+
<h1>Lista Prodotti</h1>
|
|
370
|
+
<ul>
|
|
371
|
+
<% prodotti.forEach(function(prodotto) { %>
|
|
372
|
+
<li><%= prodotto %></li>
|
|
373
|
+
<% }); %>
|
|
374
|
+
</ul>
|
|
375
|
+
<p>Totale: <%= prodotti.length %> prodotti</p>
|
|
376
|
+
</body>
|
|
377
|
+
</html>
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**Server:**
|
|
381
|
+
```javascript
|
|
382
|
+
render: async (ctx, next, filePath) => {
|
|
383
|
+
try {
|
|
384
|
+
ctx.body = await ejs.renderFile(filePath, {
|
|
385
|
+
prodotti: ['Laptop', 'Mouse', 'Tastiera'] // ✅ Passa un array
|
|
386
|
+
});
|
|
387
|
+
ctx.type = 'text/html';
|
|
388
|
+
} catch (error) {
|
|
389
|
+
console.error('Template error:', error);
|
|
390
|
+
ctx.status = 500;
|
|
391
|
+
ctx.body = `<h1>Error</h1><pre>${error.message}</pre>`;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
### Riepilogo Esempi Incrementali
|
|
399
|
+
|
|
400
|
+
| Esempio | Dati Passati | Usa |
|
|
401
|
+
|---------|--------------|-----|
|
|
402
|
+
| 1 | `{}` | Nessuna variabile |
|
|
403
|
+
| 2 | `{ nome: '...' }` | 1 variabile |
|
|
404
|
+
| 3 | `{ nome: '...', eta: ..., citta: '...' }` | N variabili |
|
|
405
|
+
| 4 | `{ autenticato: true, nome: '...' }` | Condizionali (if/else) |
|
|
406
|
+
| 5 | `{ prodotti: [...] }` | Loop (forEach) |
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Guida Progressiva
|
|
411
|
+
|
|
412
|
+
Questa sezione mostra la progressione da configurazioni semplici a configurazioni enterprise con plugin system e theme system.
|
|
413
|
+
|
|
414
|
+
### Esempio 1: Configurazione Semplice - Nessun Dato
|
|
415
|
+
|
|
416
|
+
**Quando usare:** Template completamente statici senza contenuto dinamico.
|
|
417
|
+
|
|
418
|
+
**Server:**
|
|
419
|
+
```javascript
|
|
420
|
+
const Koa = require('koa');
|
|
421
|
+
const koaClassicServer = require('koa-classic-server');
|
|
422
|
+
const ejs = require('ejs');
|
|
423
|
+
|
|
424
|
+
const app = new Koa();
|
|
425
|
+
|
|
426
|
+
app.use(
|
|
427
|
+
koaClassicServer(
|
|
428
|
+
__dirname + '/public',
|
|
429
|
+
{
|
|
430
|
+
showDirContents: true,
|
|
431
|
+
template: {
|
|
432
|
+
render: async (ctx, next, filePath) => {
|
|
433
|
+
try {
|
|
434
|
+
// Nessun dato passato
|
|
435
|
+
ctx.body = await ejs.renderFile(filePath, {});
|
|
436
|
+
ctx.type = 'text/html';
|
|
437
|
+
} catch (error) {
|
|
438
|
+
console.error('Template error:', error);
|
|
439
|
+
ctx.status = 500;
|
|
440
|
+
ctx.body = `<h1>Error</h1><pre>${error.message}</pre>`;
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
ext: ['ejs', 'EJS'],
|
|
444
|
+
},
|
|
445
|
+
}
|
|
446
|
+
)
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
app.listen(3000);
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
### Esempio 2: Configurazione Base - Dati Semplici
|
|
455
|
+
|
|
456
|
+
**Quando usare:** Pagine con poche informazioni dinamiche (titolo, messaggio, timestamp).
|
|
457
|
+
|
|
458
|
+
**Server:**
|
|
459
|
+
```javascript
|
|
460
|
+
const Koa = require('koa');
|
|
461
|
+
const koaClassicServer = require('koa-classic-server');
|
|
462
|
+
const ejs = require('ejs');
|
|
463
|
+
|
|
464
|
+
const app = new Koa();
|
|
465
|
+
|
|
466
|
+
app.use(
|
|
467
|
+
koaClassicServer(
|
|
468
|
+
__dirname + '/public',
|
|
469
|
+
{
|
|
470
|
+
showDirContents: true,
|
|
471
|
+
template: {
|
|
472
|
+
render: async (ctx, next, filePath) => {
|
|
473
|
+
try {
|
|
474
|
+
// Pochi dati semplici
|
|
475
|
+
ctx.body = await ejs.renderFile(filePath, {
|
|
476
|
+
titolo: 'Il Mio Sito',
|
|
477
|
+
messaggio: 'Benvenuto nel mio sito web',
|
|
478
|
+
href: ctx.href,
|
|
479
|
+
path: ctx.path,
|
|
480
|
+
timestamp: new Date().toISOString()
|
|
481
|
+
});
|
|
482
|
+
ctx.type = 'text/html';
|
|
483
|
+
} catch (error) {
|
|
484
|
+
console.error('Template error:', error);
|
|
485
|
+
ctx.status = 500;
|
|
486
|
+
ctx.body = `<h1>Error</h1><pre>${error.message}</pre>`;
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
ext: ['ejs', 'EJS'],
|
|
490
|
+
},
|
|
491
|
+
}
|
|
492
|
+
)
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
app.listen(3000);
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
**Template: `public/pagina.ejs`**
|
|
499
|
+
```html
|
|
500
|
+
<!DOCTYPE html>
|
|
501
|
+
<html lang="it">
|
|
502
|
+
<head>
|
|
503
|
+
<meta charset="UTF-8">
|
|
504
|
+
<title><%= titolo %></title>
|
|
505
|
+
</head>
|
|
506
|
+
<body>
|
|
507
|
+
<h1><%= messaggio %></h1>
|
|
508
|
+
<div class="info">
|
|
509
|
+
<p><strong>URL corrente:</strong> <%= href %></p>
|
|
510
|
+
<p><strong>Path:</strong> <%= path %></p>
|
|
511
|
+
<p><strong>Generato:</strong> <%= timestamp %></p>
|
|
512
|
+
</div>
|
|
513
|
+
</body>
|
|
514
|
+
</html>
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
### Esempio 3: Configurazione Intermedia - Dati Organizzati
|
|
520
|
+
|
|
521
|
+
**Quando usare:** Applicazioni con più dati da organizzare logicamente.
|
|
522
|
+
|
|
523
|
+
**Server:**
|
|
524
|
+
```javascript
|
|
525
|
+
const Koa = require('koa');
|
|
526
|
+
const koaClassicServer = require('koa-classic-server');
|
|
527
|
+
const ejs = require('ejs');
|
|
528
|
+
|
|
529
|
+
const app = new Koa();
|
|
530
|
+
|
|
531
|
+
// Configurazione applicazione
|
|
532
|
+
const appConfig = {
|
|
533
|
+
siteName: 'Il Mio Sito',
|
|
534
|
+
version: '1.0.0',
|
|
535
|
+
apiPrefix: 'api',
|
|
536
|
+
adminPrefix: 'admin'
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
app.use(
|
|
540
|
+
koaClassicServer(
|
|
541
|
+
__dirname + '/public',
|
|
542
|
+
{
|
|
543
|
+
showDirContents: true,
|
|
544
|
+
template: {
|
|
545
|
+
render: async (ctx, next, filePath) => {
|
|
546
|
+
try {
|
|
547
|
+
// Dati organizzati in oggetti
|
|
548
|
+
ctx.body = await ejs.renderFile(filePath, {
|
|
549
|
+
config: {
|
|
550
|
+
siteName: appConfig.siteName,
|
|
551
|
+
version: appConfig.version,
|
|
552
|
+
apiPrefix: appConfig.apiPrefix
|
|
553
|
+
// NON passiamo adminPrefix per sicurezza
|
|
554
|
+
},
|
|
555
|
+
request: {
|
|
556
|
+
href: ctx.href,
|
|
557
|
+
path: ctx.path,
|
|
558
|
+
query: ctx.query,
|
|
559
|
+
method: ctx.method
|
|
560
|
+
},
|
|
561
|
+
user: ctx.state.user || null,
|
|
562
|
+
timestamp: new Date().toISOString()
|
|
563
|
+
});
|
|
564
|
+
ctx.type = 'text/html';
|
|
565
|
+
} catch (error) {
|
|
566
|
+
console.error('Template error:', error);
|
|
567
|
+
ctx.status = 500;
|
|
568
|
+
ctx.body = `<h1>Error</h1><pre>${error.message}</pre>`;
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
ext: ['ejs', 'EJS'],
|
|
572
|
+
},
|
|
573
|
+
}
|
|
574
|
+
)
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
app.listen(3000);
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
**Template: `public/pagina.ejs`**
|
|
581
|
+
```html
|
|
582
|
+
<!DOCTYPE html>
|
|
583
|
+
<html lang="it">
|
|
584
|
+
<head>
|
|
585
|
+
<meta charset="UTF-8">
|
|
586
|
+
<title><%= config.siteName %> - v<%= config.version %></title>
|
|
587
|
+
</head>
|
|
588
|
+
<body>
|
|
589
|
+
<header>
|
|
590
|
+
<h1><%= config.siteName %></h1>
|
|
591
|
+
<p>Versione: <%= config.version %></p>
|
|
592
|
+
</header>
|
|
593
|
+
|
|
594
|
+
<main>
|
|
595
|
+
<h2>Informazioni Richiesta</h2>
|
|
596
|
+
<ul>
|
|
597
|
+
<li>Metodo: <%= request.method %></li>
|
|
598
|
+
<li>Path: <%= request.path %></li>
|
|
599
|
+
<li>URL completo: <%= request.href %></li>
|
|
600
|
+
</ul>
|
|
601
|
+
|
|
602
|
+
<% if (Object.keys(request.query).length > 0) { %>
|
|
603
|
+
<h3>Query Parameters</h3>
|
|
604
|
+
<ul>
|
|
605
|
+
<% Object.keys(request.query).forEach(function(key) { %>
|
|
606
|
+
<li><%= key %>: <%= request.query[key] %></li>
|
|
607
|
+
<% }); %>
|
|
608
|
+
</ul>
|
|
609
|
+
<% } %>
|
|
610
|
+
|
|
611
|
+
<% if (user) { %>
|
|
612
|
+
<p>Benvenuto, <strong><%= user.name %></strong>!</p>
|
|
613
|
+
<% } else { %>
|
|
614
|
+
<p>Non sei autenticato.</p>
|
|
615
|
+
<% } %>
|
|
616
|
+
|
|
617
|
+
<!-- Esempio chiamata API -->
|
|
618
|
+
<button onclick="fetch('/<%= config.apiPrefix %>/data')">
|
|
619
|
+
Chiama API
|
|
620
|
+
</button>
|
|
621
|
+
</main>
|
|
622
|
+
|
|
623
|
+
<footer>
|
|
624
|
+
<p>Generato: <%= timestamp %></p>
|
|
625
|
+
</footer>
|
|
626
|
+
</body>
|
|
627
|
+
</html>
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
### Esempio 4: Configurazione Enterprise Completa
|
|
633
|
+
|
|
634
|
+
**Quando usare:** Applicazioni enterprise con plugin system, theme system, sessioni e configurazione avanzata.
|
|
635
|
+
|
|
636
|
+
**Server:**
|
|
637
|
+
```javascript
|
|
638
|
+
const Koa = require('koa');
|
|
639
|
+
const koaClassicServer = require('koa-classic-server');
|
|
640
|
+
const ejs = require('ejs');
|
|
641
|
+
|
|
642
|
+
const app = new Koa();
|
|
643
|
+
|
|
644
|
+
// Configurazione completa applicazione
|
|
645
|
+
const ital8Conf = {
|
|
646
|
+
wwwPath: '/public',
|
|
647
|
+
apiPrefix: 'api',
|
|
648
|
+
adminPrefix: 'admin',
|
|
649
|
+
viewsPrefix: 'views',
|
|
650
|
+
baseThemePath: '../themes/default'
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
// Sistema Plugin (esempio semplificato)
|
|
654
|
+
const pluginSys = {
|
|
655
|
+
getPluginList: () => ['plugin1', 'plugin2'],
|
|
656
|
+
isPluginActive: (name) => true,
|
|
657
|
+
// ... altri metodi
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
// Oggetti dei plugin da condividere nelle pagine web
|
|
661
|
+
const getObjectsToShareInWebPages = {
|
|
662
|
+
simpleAccess: {
|
|
663
|
+
isLoggedIn: (ctx) => !!ctx.state.user,
|
|
664
|
+
getUserRole: (ctx) => ctx.state.user?.role || 'guest'
|
|
665
|
+
},
|
|
666
|
+
analytics: {
|
|
667
|
+
trackPageView: () => { /* ... */ }
|
|
668
|
+
}
|
|
669
|
+
// ... altri plugin
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
// Sistema Temi
|
|
673
|
+
const themeSys = {
|
|
674
|
+
getCurrentTheme: () => 'default',
|
|
675
|
+
getThemePath: () => ital8Conf.baseThemePath,
|
|
676
|
+
// ... altri metodi
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
app.use(
|
|
680
|
+
koaClassicServer(
|
|
681
|
+
__dirname + `${ital8Conf.wwwPath}`,
|
|
682
|
+
{
|
|
683
|
+
showDirContents: true,
|
|
684
|
+
urlsReserved: ['/' + ital8Conf.adminPrefix, '/' + ital8Conf.apiPrefix, '/' + ital8Conf.viewsPrefix],
|
|
685
|
+
template: {
|
|
686
|
+
render: async (ctx, next, filePath) => {
|
|
687
|
+
try {
|
|
688
|
+
ctx.body = await ejs.renderFile(filePath, {
|
|
689
|
+
passData: {
|
|
690
|
+
// Configurazione API
|
|
691
|
+
apiPrefix: ital8Conf.apiPrefix,
|
|
692
|
+
|
|
693
|
+
// Sistema Plugin
|
|
694
|
+
pluginSys: pluginSys,
|
|
695
|
+
plugin: getObjectsToShareInWebPages,
|
|
696
|
+
|
|
697
|
+
// Sistema Temi
|
|
698
|
+
themeSys: themeSys,
|
|
699
|
+
|
|
700
|
+
// Informazioni File
|
|
701
|
+
filePath: filePath,
|
|
702
|
+
|
|
703
|
+
// Informazioni Richiesta
|
|
704
|
+
href: ctx.href,
|
|
705
|
+
query: ctx.query,
|
|
706
|
+
path: ctx.path,
|
|
707
|
+
method: ctx.method,
|
|
708
|
+
|
|
709
|
+
// Contesto Koa (usa con cautela)
|
|
710
|
+
ctx: ctx,
|
|
711
|
+
|
|
712
|
+
// Sessione (se disponibile)
|
|
713
|
+
session: ctx.session || undefined,
|
|
714
|
+
|
|
715
|
+
// Utility
|
|
716
|
+
timestamp: new Date().toISOString(),
|
|
717
|
+
env: process.env.NODE_ENV || 'development'
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
ctx.type = 'text/html';
|
|
722
|
+
|
|
723
|
+
} catch (error) {
|
|
724
|
+
console.error('❌ Template rendering error:', error);
|
|
725
|
+
ctx.status = 500;
|
|
726
|
+
ctx.type = 'text/html';
|
|
727
|
+
|
|
728
|
+
// Messaggio diverso in base all'ambiente
|
|
729
|
+
if (process.env.NODE_ENV === 'production') {
|
|
730
|
+
ctx.body = '<h1>Internal Server Error</h1>';
|
|
731
|
+
} else {
|
|
732
|
+
ctx.body = `
|
|
733
|
+
<h1>Template Error</h1>
|
|
734
|
+
<pre>${error.message}</pre>
|
|
735
|
+
<p><strong>File:</strong> ${filePath}</p>
|
|
736
|
+
<pre>${error.stack}</pre>
|
|
737
|
+
`;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
},
|
|
741
|
+
ext: ['ejs', 'EJS'],
|
|
742
|
+
},
|
|
743
|
+
}
|
|
744
|
+
)
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
app.listen(3000, () => {
|
|
748
|
+
console.log('✅ Server started on http://localhost:3000');
|
|
749
|
+
console.log(`📁 Serving from: ${__dirname}${ital8Conf.wwwPath}`);
|
|
750
|
+
console.log(`🔒 Reserved paths: /${ital8Conf.adminPrefix}, /${ital8Conf.apiPrefix}`);
|
|
751
|
+
});
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
**Template: `public/pagina-completa.ejs`**
|
|
755
|
+
```html
|
|
756
|
+
<!DOCTYPE html>
|
|
757
|
+
<html lang="it">
|
|
758
|
+
<head>
|
|
759
|
+
<meta charset="UTF-8">
|
|
760
|
+
<title>Pagina Completa con Plugin System</title>
|
|
761
|
+
|
|
762
|
+
<!-- Carica tema corrente -->
|
|
763
|
+
<link rel="stylesheet" href="<%= passData.themeSys.getThemePath() %>/style.css">
|
|
764
|
+
</head>
|
|
765
|
+
<body>
|
|
766
|
+
<header>
|
|
767
|
+
<h1>Sistema Completo</h1>
|
|
768
|
+
|
|
769
|
+
<!-- Verifica login tramite plugin -->
|
|
770
|
+
<% if (passData.plugin.simpleAccess.isLoggedIn(passData.ctx)) { %>
|
|
771
|
+
<p>👤 Utente: <%= passData.ctx.state.user.name %></p>
|
|
772
|
+
<p>🎭 Ruolo: <%= passData.plugin.simpleAccess.getUserRole(passData.ctx) %></p>
|
|
773
|
+
<% } else { %>
|
|
774
|
+
<p><a href="/login">Login</a></p>
|
|
775
|
+
<% } %>
|
|
776
|
+
</header>
|
|
777
|
+
|
|
778
|
+
<nav>
|
|
779
|
+
<!-- Chiamate API con prefix configurabile -->
|
|
780
|
+
<button onclick="fetchData('/<%= passData.apiPrefix %>/users')">
|
|
781
|
+
Carica Utenti
|
|
782
|
+
</button>
|
|
783
|
+
<button onclick="fetchData('/<%= passData.apiPrefix %>/posts')">
|
|
784
|
+
Carica Post
|
|
785
|
+
</button>
|
|
786
|
+
</nav>
|
|
787
|
+
|
|
788
|
+
<main>
|
|
789
|
+
<h2>Plugin Attivi</h2>
|
|
790
|
+
<ul>
|
|
791
|
+
<% passData.pluginSys.getPluginList().forEach(function(plugin) { %>
|
|
792
|
+
<li>
|
|
793
|
+
<%= plugin %>
|
|
794
|
+
<% if (passData.pluginSys.isPluginActive(plugin)) { %>
|
|
795
|
+
✅ Attivo
|
|
796
|
+
<% } else { %>
|
|
797
|
+
❌ Disattivo
|
|
798
|
+
<% } %>
|
|
799
|
+
</li>
|
|
800
|
+
<% }); %>
|
|
801
|
+
</ul>
|
|
802
|
+
|
|
803
|
+
<h2>Informazioni Richiesta</h2>
|
|
804
|
+
<dl>
|
|
805
|
+
<dt>Path:</dt>
|
|
806
|
+
<dd><%= passData.path %></dd>
|
|
807
|
+
|
|
808
|
+
<dt>Metodo:</dt>
|
|
809
|
+
<dd><%= passData.method %></dd>
|
|
810
|
+
|
|
811
|
+
<dt>Query:</dt>
|
|
812
|
+
<dd><pre><%= JSON.stringify(passData.query, null, 2) %></pre></dd>
|
|
813
|
+
|
|
814
|
+
<% if (passData.session) { %>
|
|
815
|
+
<dt>Session ID:</dt>
|
|
816
|
+
<dd><%= passData.session.id || 'N/A' %></dd>
|
|
817
|
+
<% } %>
|
|
818
|
+
</dl>
|
|
819
|
+
|
|
820
|
+
<h2>Tema Corrente</h2>
|
|
821
|
+
<p>Tema: <strong><%= passData.themeSys.getCurrentTheme() %></strong></p>
|
|
822
|
+
<p>Path: <%= passData.themeSys.getThemePath() %></p>
|
|
823
|
+
</main>
|
|
824
|
+
|
|
825
|
+
<footer>
|
|
826
|
+
<p>Ambiente: <%= passData.env %></p>
|
|
827
|
+
<p>Generato: <%= passData.timestamp %></p>
|
|
828
|
+
<p>File: <%= passData.filePath %></p>
|
|
829
|
+
</footer>
|
|
830
|
+
|
|
831
|
+
<script>
|
|
832
|
+
// Funzione per chiamate API
|
|
833
|
+
function fetchData(endpoint) {
|
|
834
|
+
fetch(endpoint)
|
|
835
|
+
.then(res => res.json())
|
|
836
|
+
.then(data => console.log(data))
|
|
837
|
+
.catch(err => console.error(err));
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Track page view (se plugin analytics attivo)
|
|
841
|
+
<% if (passData.plugin.analytics) { %>
|
|
842
|
+
passData.plugin.analytics.trackPageView();
|
|
843
|
+
<% } %>
|
|
844
|
+
</script>
|
|
845
|
+
</body>
|
|
846
|
+
</html>
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
---
|
|
850
|
+
|
|
851
|
+
### Confronto Configurazioni
|
|
852
|
+
|
|
853
|
+
| Feature | Es. 1 | Es. 2 | Es. 3 | Es. 4 |
|
|
854
|
+
|---------|-------|-------|-------|-------|
|
|
855
|
+
| Dati passati | ❌ No | ✅ Sì | ✅ Sì | ✅ Sì |
|
|
856
|
+
| Organizzazione | - | Piatta | Oggetti | `passData` |
|
|
857
|
+
| Plugin System | ❌ | ❌ | ❌ | ✅ |
|
|
858
|
+
| Theme System | ❌ | ❌ | ❌ | ✅ |
|
|
859
|
+
| Sessioni | ❌ | ❌ | ❌ | ✅ |
|
|
860
|
+
| URL Riservati | ❌ | ❌ | ❌ | ✅ |
|
|
861
|
+
| Gestione Errori | Base | Base | Base | Avanzata |
|
|
862
|
+
| Complessità | ⭐ | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
|
|
863
|
+
|
|
864
|
+
### Quale Configurazione Usare?
|
|
865
|
+
|
|
866
|
+
**Usa Esempio 1 se:**
|
|
867
|
+
- Sito completamente statico
|
|
868
|
+
- Nessun dato dinamico
|
|
869
|
+
- Pagine semplici HTML
|
|
870
|
+
|
|
871
|
+
**Usa Esempio 2 se:**
|
|
872
|
+
- Poche informazioni dinamiche
|
|
873
|
+
- Nessun plugin/tema
|
|
874
|
+
- Applicazione semplice
|
|
875
|
+
|
|
876
|
+
**Usa Esempio 3 se:**
|
|
877
|
+
- Più dati da organizzare
|
|
878
|
+
- Serve configurazione base
|
|
879
|
+
- Applicazione media complessità
|
|
880
|
+
|
|
881
|
+
**Usa Esempio 4 se:**
|
|
882
|
+
- Sistema con plugin
|
|
883
|
+
- Sistema con temi
|
|
884
|
+
- Applicazione enterprise
|
|
885
|
+
- Sessioni e autenticazione
|
|
886
|
+
- Configurazione avanzata
|
|
887
|
+
|
|
888
|
+
---
|
|
889
|
+
|
|
890
|
+
## Integrazione Template Engine
|
|
891
|
+
|
|
892
|
+
### EJS
|
|
893
|
+
|
|
894
|
+
EJS (Embedded JavaScript) è uno dei template engine più popolari per Node.js.
|
|
895
|
+
|
|
896
|
+
#### Installazione
|
|
897
|
+
|
|
898
|
+
```bash
|
|
899
|
+
npm install ejs
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
#### Esempio Completo con Loop e Condizionali
|
|
903
|
+
|
|
904
|
+
**views/products.ejs:**
|
|
905
|
+
```html
|
|
906
|
+
<!DOCTYPE html>
|
|
907
|
+
<html lang="it">
|
|
908
|
+
<head>
|
|
909
|
+
<meta charset="UTF-8">
|
|
910
|
+
<title>Catalogo Prodotti</title>
|
|
911
|
+
<style>
|
|
912
|
+
.product { border: 1px solid #ddd; padding: 10px; margin: 10px 0; }
|
|
913
|
+
.price { color: green; font-weight: bold; }
|
|
914
|
+
.discount { color: red; }
|
|
915
|
+
.out-of-stock { color: gray; }
|
|
916
|
+
</style>
|
|
917
|
+
</head>
|
|
918
|
+
<body>
|
|
919
|
+
<h1>Catalogo Prodotti</h1>
|
|
920
|
+
|
|
921
|
+
<% if (products && products.length > 0) { %>
|
|
922
|
+
<div class="products">
|
|
923
|
+
<% products.forEach(function(product) { %>
|
|
924
|
+
<div class="product" data-id="<%= product.id %>">
|
|
925
|
+
<h3><%= product.name %></h3>
|
|
926
|
+
<p><%= product.description %></p>
|
|
927
|
+
<p class="price">€<%= product.price.toFixed(2) %></p>
|
|
928
|
+
|
|
929
|
+
<% if (product.discount > 0) { %>
|
|
930
|
+
<p class="discount">Sconto: <%= product.discount %>%</p>
|
|
931
|
+
<p>Prezzo finale: €<%= (product.price * (1 - product.discount / 100)).toFixed(2) %></p>
|
|
932
|
+
<% } %>
|
|
933
|
+
|
|
934
|
+
<% if (product.inStock) { %>
|
|
935
|
+
<button onclick="addToCart(<%= product.id %>)">Aggiungi al carrello</button>
|
|
936
|
+
<% } else { %>
|
|
937
|
+
<p class="out-of-stock">Non disponibile</p>
|
|
938
|
+
<% } %>
|
|
939
|
+
</div>
|
|
940
|
+
<% }); %>
|
|
941
|
+
</div>
|
|
942
|
+
|
|
943
|
+
<p>Totale prodotti: <%= products.length %></p>
|
|
944
|
+
<% } else { %>
|
|
945
|
+
<p>Nessun prodotto disponibile.</p>
|
|
946
|
+
<% } %>
|
|
947
|
+
</body>
|
|
948
|
+
</html>
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
**Server con dati prodotti:**
|
|
952
|
+
```javascript
|
|
953
|
+
const path = require('path');
|
|
954
|
+
|
|
955
|
+
app.use(koaClassicServer(path.join(__dirname, 'views'), {
|
|
956
|
+
template: {
|
|
957
|
+
ext: ['ejs'],
|
|
958
|
+
render: async (ctx, next, filePath) => {
|
|
959
|
+
const basename = path.basename(filePath, '.ejs');
|
|
960
|
+
|
|
961
|
+
// Dati specifici per template
|
|
962
|
+
const dataMap = {
|
|
963
|
+
'products': {
|
|
964
|
+
products: [
|
|
965
|
+
{ id: 1, name: 'Laptop', description: 'High-performance laptop', price: 999.99, discount: 10, inStock: true },
|
|
966
|
+
{ id: 2, name: 'Mouse', description: 'Wireless mouse', price: 29.99, discount: 0, inStock: true },
|
|
967
|
+
{ id: 3, name: 'Keyboard', description: 'Mechanical keyboard', price: 149.99, discount: 15, inStock: false }
|
|
968
|
+
]
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
const data = dataMap[basename] || {};
|
|
973
|
+
const html = await ejs.renderFile(filePath, data);
|
|
974
|
+
|
|
975
|
+
ctx.type = 'text/html';
|
|
976
|
+
ctx.body = html;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}));
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
#### HTML Escaping e Sicurezza XSS
|
|
983
|
+
|
|
984
|
+
EJS fornisce protezione XSS automatica con l'escaping HTML:
|
|
985
|
+
|
|
986
|
+
```html
|
|
987
|
+
<!-- Escaped (safe) - use <%= %> -->
|
|
988
|
+
<p>User input: <%= userInput %></p>
|
|
989
|
+
<!-- Output: <p>User input: <script>alert('XSS')</script></p> -->
|
|
990
|
+
|
|
991
|
+
<!-- Unescaped (unsafe) - use <%- %> -->
|
|
992
|
+
<p>HTML content: <%- htmlContent %></p>
|
|
993
|
+
<!-- Output: <p>HTML content: <strong>Bold text</strong></p> -->
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
**Best Practice:**
|
|
997
|
+
- **Usa sempre `<%= %>`** per output di dati utente
|
|
998
|
+
- **Usa `<%- %>` solo** per HTML fidato (es. da CMS, markdown processato)
|
|
999
|
+
- **Valida e sanitizza** input utente prima del rendering
|
|
1000
|
+
|
|
1001
|
+
---
|
|
1002
|
+
|
|
1003
|
+
### Pug
|
|
1004
|
+
|
|
1005
|
+
Pug (ex-Jade) è un template engine con sintassi minimalista.
|
|
1006
|
+
|
|
1007
|
+
#### Installazione
|
|
1008
|
+
|
|
1009
|
+
```bash
|
|
1010
|
+
npm install pug
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
#### Configurazione
|
|
1014
|
+
|
|
1015
|
+
```javascript
|
|
1016
|
+
const pug = require('pug');
|
|
1017
|
+
|
|
1018
|
+
app.use(koaClassicServer(path.join(__dirname, 'views'), {
|
|
1019
|
+
template: {
|
|
1020
|
+
ext: ['pug', 'jade'],
|
|
1021
|
+
render: async (ctx, next, filePath) => {
|
|
1022
|
+
try {
|
|
1023
|
+
const html = pug.renderFile(filePath, {
|
|
1024
|
+
title: 'My App',
|
|
1025
|
+
user: ctx.state.user,
|
|
1026
|
+
pretty: process.env.NODE_ENV === 'development' // HTML formattato solo in dev
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
ctx.type = 'text/html';
|
|
1030
|
+
ctx.body = html;
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
console.error('Pug error:', error);
|
|
1033
|
+
ctx.status = 500;
|
|
1034
|
+
ctx.body = 'Template Error';
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}));
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
---
|
|
1042
|
+
|
|
1043
|
+
### Handlebars
|
|
1044
|
+
|
|
1045
|
+
Handlebars è un template engine con sintassi mustache.
|
|
1046
|
+
|
|
1047
|
+
#### Installazione
|
|
1048
|
+
|
|
1049
|
+
```bash
|
|
1050
|
+
npm install handlebars
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
#### Configurazione
|
|
1054
|
+
|
|
1055
|
+
```javascript
|
|
1056
|
+
const handlebars = require('handlebars');
|
|
1057
|
+
const fs = require('fs').promises;
|
|
1058
|
+
|
|
1059
|
+
app.use(koaClassicServer(path.join(__dirname, 'views'), {
|
|
1060
|
+
template: {
|
|
1061
|
+
ext: ['hbs', 'handlebars'],
|
|
1062
|
+
render: async (ctx, next, filePath) => {
|
|
1063
|
+
try {
|
|
1064
|
+
const source = await fs.readFile(filePath, 'utf-8');
|
|
1065
|
+
const template = handlebars.compile(source);
|
|
1066
|
+
const html = template({
|
|
1067
|
+
title: 'My App',
|
|
1068
|
+
items: ['Item 1', 'Item 2', 'Item 3']
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
ctx.type = 'text/html';
|
|
1072
|
+
ctx.body = html;
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
console.error('Handlebars error:', error);
|
|
1075
|
+
ctx.status = 500;
|
|
1076
|
+
ctx.body = 'Template Error';
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}));
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
---
|
|
1084
|
+
|
|
1085
|
+
### Nunjucks
|
|
1086
|
+
|
|
1087
|
+
Nunjucks è un template engine potente e flessibile ispirato a Jinja2.
|
|
1088
|
+
|
|
1089
|
+
#### Installazione
|
|
1090
|
+
|
|
1091
|
+
```bash
|
|
1092
|
+
npm install nunjucks
|
|
1093
|
+
```
|
|
1094
|
+
|
|
1095
|
+
#### Configurazione
|
|
1096
|
+
|
|
1097
|
+
```javascript
|
|
1098
|
+
const nunjucks = require('nunjucks');
|
|
1099
|
+
const path = require('path');
|
|
1100
|
+
|
|
1101
|
+
// Configura Nunjucks
|
|
1102
|
+
const env = nunjucks.configure(path.join(__dirname, 'views'), {
|
|
1103
|
+
autoescape: true,
|
|
1104
|
+
noCache: process.env.NODE_ENV === 'development'
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
app.use(koaClassicServer(path.join(__dirname, 'views'), {
|
|
1108
|
+
template: {
|
|
1109
|
+
ext: ['njk', 'html'],
|
|
1110
|
+
render: async (ctx, next, filePath) => {
|
|
1111
|
+
try {
|
|
1112
|
+
const html = await new Promise((resolve, reject) => {
|
|
1113
|
+
nunjucks.render(path.relative(path.join(__dirname, 'views'), filePath), {
|
|
1114
|
+
title: 'My App',
|
|
1115
|
+
user: ctx.state.user
|
|
1116
|
+
}, (err, result) => {
|
|
1117
|
+
if (err) reject(err);
|
|
1118
|
+
else resolve(result);
|
|
1119
|
+
});
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
ctx.type = 'text/html';
|
|
1123
|
+
ctx.body = html;
|
|
1124
|
+
} catch (error) {
|
|
1125
|
+
console.error('Nunjucks error:', error);
|
|
1126
|
+
ctx.status = 500;
|
|
1127
|
+
ctx.body = 'Template Error';
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}));
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
---
|
|
1135
|
+
|
|
1136
|
+
## Esempi Avanzati
|
|
1137
|
+
|
|
1138
|
+
### Integrazione con Database
|
|
1139
|
+
|
|
1140
|
+
```javascript
|
|
1141
|
+
const { Pool } = require('pg');
|
|
1142
|
+
const pool = new Pool({ /* config */ });
|
|
1143
|
+
|
|
1144
|
+
app.use(koaClassicServer(path.join(__dirname, 'views'), {
|
|
1145
|
+
template: {
|
|
1146
|
+
ext: ['ejs'],
|
|
1147
|
+
render: async (ctx, next, filePath) => {
|
|
1148
|
+
try {
|
|
1149
|
+
const templateName = path.basename(filePath, '.ejs');
|
|
1150
|
+
let data = {};
|
|
1151
|
+
|
|
1152
|
+
// Carica dati dal database in base al template
|
|
1153
|
+
if (templateName === 'users') {
|
|
1154
|
+
const result = await pool.query('SELECT * FROM users');
|
|
1155
|
+
data.users = result.rows;
|
|
1156
|
+
} else if (templateName === 'user-profile') {
|
|
1157
|
+
const userId = ctx.query.id;
|
|
1158
|
+
const result = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
|
|
1159
|
+
data.user = result.rows[0];
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
const html = await ejs.renderFile(filePath, {
|
|
1163
|
+
...getCommonData(ctx),
|
|
1164
|
+
...data
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
ctx.type = 'text/html';
|
|
1168
|
+
ctx.body = html;
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
console.error('Database/Template error:', error);
|
|
1171
|
+
ctx.status = 500;
|
|
1172
|
+
ctx.body = 'Error loading data';
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}));
|
|
1177
|
+
```
|
|
1178
|
+
|
|
1179
|
+
---
|
|
1180
|
+
|
|
1181
|
+
### Template con Layouts
|
|
1182
|
+
|
|
1183
|
+
Implementazione di un sistema di layout per riutilizzare strutture comuni.
|
|
1184
|
+
|
|
1185
|
+
**views/layout.ejs:**
|
|
1186
|
+
```html
|
|
1187
|
+
<!DOCTYPE html>
|
|
1188
|
+
<html lang="it">
|
|
1189
|
+
<head>
|
|
1190
|
+
<meta charset="UTF-8">
|
|
1191
|
+
<title><%= pageTitle %></title>
|
|
1192
|
+
<link rel="stylesheet" href="/css/style.css">
|
|
1193
|
+
</head>
|
|
1194
|
+
<body>
|
|
1195
|
+
<header>
|
|
1196
|
+
<nav>
|
|
1197
|
+
<a href="/">Home</a>
|
|
1198
|
+
<a href="/about">About</a>
|
|
1199
|
+
<% if (isAuthenticated) { %>
|
|
1200
|
+
<a href="/profile">Profile</a>
|
|
1201
|
+
<a href="/logout">Logout</a>
|
|
1202
|
+
<% } else { %>
|
|
1203
|
+
<a href="/login">Login</a>
|
|
1204
|
+
<% } %>
|
|
1205
|
+
</nav>
|
|
1206
|
+
</header>
|
|
1207
|
+
|
|
1208
|
+
<main>
|
|
1209
|
+
<%- content %>
|
|
1210
|
+
</main>
|
|
1211
|
+
|
|
1212
|
+
<footer>
|
|
1213
|
+
<p>© <%= currentYear %> <%= appName %></p>
|
|
1214
|
+
</footer>
|
|
1215
|
+
</body>
|
|
1216
|
+
</html>
|
|
1217
|
+
```
|
|
1218
|
+
|
|
1219
|
+
**Server con layout:**
|
|
1220
|
+
```javascript
|
|
1221
|
+
const ejs = require('ejs');
|
|
1222
|
+
const fs = require('fs').promises;
|
|
1223
|
+
const path = require('path');
|
|
1224
|
+
|
|
1225
|
+
const layoutPath = path.join(__dirname, 'views', 'layout.ejs');
|
|
1226
|
+
|
|
1227
|
+
app.use(koaClassicServer(path.join(__dirname, 'views'), {
|
|
1228
|
+
template: {
|
|
1229
|
+
ext: ['ejs'],
|
|
1230
|
+
render: async (ctx, next, filePath) => {
|
|
1231
|
+
try {
|
|
1232
|
+
// Renderizza il contenuto
|
|
1233
|
+
const content = await ejs.renderFile(filePath, {
|
|
1234
|
+
...getCommonData(ctx),
|
|
1235
|
+
title: 'Page Title'
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
// Renderizza il layout con il contenuto
|
|
1239
|
+
const html = await ejs.renderFile(layoutPath, {
|
|
1240
|
+
...getCommonData(ctx),
|
|
1241
|
+
content: content,
|
|
1242
|
+
pageTitle: 'My App - Page Title'
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
ctx.type = 'text/html';
|
|
1246
|
+
ctx.body = html;
|
|
1247
|
+
} catch (error) {
|
|
1248
|
+
console.error('Layout/Template error:', error);
|
|
1249
|
+
ctx.status = 500;
|
|
1250
|
+
ctx.body = 'Error rendering page';
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
}));
|
|
1255
|
+
```
|
|
1256
|
+
|
|
1257
|
+
---
|
|
1258
|
+
|
|
1259
|
+
## Best Practices
|
|
1260
|
+
|
|
1261
|
+
### 1. Gestione Errori
|
|
1262
|
+
|
|
1263
|
+
Implementa sempre gestione errori robusta nella funzione render:
|
|
1264
|
+
|
|
1265
|
+
```javascript
|
|
1266
|
+
render: async (ctx, next, filePath) => {
|
|
1267
|
+
try {
|
|
1268
|
+
const html = await ejs.renderFile(filePath, data);
|
|
1269
|
+
ctx.type = 'text/html';
|
|
1270
|
+
ctx.body = html;
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
console.error('Template rendering error:', error);
|
|
1273
|
+
|
|
1274
|
+
// In development: mostra errore dettagliato
|
|
1275
|
+
if (process.env.NODE_ENV === 'development') {
|
|
1276
|
+
ctx.status = 500;
|
|
1277
|
+
ctx.type = 'text/html';
|
|
1278
|
+
ctx.body = `
|
|
1279
|
+
<h1>Template Rendering Error</h1>
|
|
1280
|
+
<pre>${error.stack}</pre>
|
|
1281
|
+
`;
|
|
1282
|
+
} else {
|
|
1283
|
+
// In production: messaggio generico
|
|
1284
|
+
ctx.status = 500;
|
|
1285
|
+
ctx.body = 'Internal Server Error';
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
---
|
|
1292
|
+
|
|
1293
|
+
### 2. Cache dei Template
|
|
1294
|
+
|
|
1295
|
+
In produzione, abilita il caching per performance migliori:
|
|
1296
|
+
|
|
1297
|
+
```javascript
|
|
1298
|
+
const ejs = require('ejs');
|
|
1299
|
+
|
|
1300
|
+
const ejsOptions = {
|
|
1301
|
+
cache: process.env.NODE_ENV === 'production',
|
|
1302
|
+
filename: filePath
|
|
1303
|
+
};
|
|
1304
|
+
|
|
1305
|
+
render: async (ctx, next, filePath) => {
|
|
1306
|
+
const html = await ejs.renderFile(filePath, data, ejsOptions);
|
|
1307
|
+
ctx.type = 'text/html';
|
|
1308
|
+
ctx.body = html;
|
|
1309
|
+
}
|
|
1310
|
+
```
|
|
1311
|
+
|
|
1312
|
+
---
|
|
1313
|
+
|
|
1314
|
+
### 3. Dati Comuni
|
|
1315
|
+
|
|
1316
|
+
Crea una funzione helper per dati comuni a tutti i template:
|
|
1317
|
+
|
|
1318
|
+
```javascript
|
|
1319
|
+
function getCommonData(ctx) {
|
|
1320
|
+
return {
|
|
1321
|
+
// Request info
|
|
1322
|
+
path: ctx.path,
|
|
1323
|
+
query: ctx.query,
|
|
1324
|
+
method: ctx.method,
|
|
1325
|
+
|
|
1326
|
+
// User info
|
|
1327
|
+
user: ctx.state.user || null,
|
|
1328
|
+
isAuthenticated: !!ctx.state.user,
|
|
1329
|
+
|
|
1330
|
+
// App info
|
|
1331
|
+
appName: 'My Application',
|
|
1332
|
+
version: '1.0.0',
|
|
1333
|
+
env: process.env.NODE_ENV,
|
|
1334
|
+
|
|
1335
|
+
// Utility
|
|
1336
|
+
currentYear: new Date().getFullYear(),
|
|
1337
|
+
timestamp: new Date().toISOString()
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
// Uso nella funzione render
|
|
1342
|
+
render: async (ctx, next, filePath) => {
|
|
1343
|
+
const commonData = getCommonData(ctx);
|
|
1344
|
+
const specificData = { /* dati specifici */ };
|
|
1345
|
+
const data = { ...commonData, ...specificData };
|
|
1346
|
+
|
|
1347
|
+
const html = await ejs.renderFile(filePath, data);
|
|
1348
|
+
ctx.type = 'text/html';
|
|
1349
|
+
ctx.body = html;
|
|
1350
|
+
}
|
|
1351
|
+
```
|
|
1352
|
+
|
|
1353
|
+
---
|
|
1354
|
+
|
|
1355
|
+
### 4. Routing Basato su File
|
|
1356
|
+
|
|
1357
|
+
Mappa automaticamente file a dati:
|
|
1358
|
+
|
|
1359
|
+
```javascript
|
|
1360
|
+
const path = require('path');
|
|
1361
|
+
|
|
1362
|
+
const dataProviders = {
|
|
1363
|
+
'index': () => ({ title: 'Home', message: 'Welcome' }),
|
|
1364
|
+
'about': () => ({ title: 'About Us', team: [...] }),
|
|
1365
|
+
'products': async () => ({
|
|
1366
|
+
title: 'Products',
|
|
1367
|
+
products: await fetchProducts()
|
|
1368
|
+
})
|
|
1369
|
+
};
|
|
1370
|
+
|
|
1371
|
+
render: async (ctx, next, filePath) => {
|
|
1372
|
+
const templateName = path.basename(filePath, '.ejs');
|
|
1373
|
+
const dataProvider = dataProviders[templateName] || (() => ({}));
|
|
1374
|
+
const specificData = await dataProvider();
|
|
1375
|
+
|
|
1376
|
+
const data = {
|
|
1377
|
+
...getCommonData(ctx),
|
|
1378
|
+
...specificData
|
|
1379
|
+
};
|
|
1380
|
+
|
|
1381
|
+
const html = await ejs.renderFile(filePath, data);
|
|
1382
|
+
ctx.type = 'text/html';
|
|
1383
|
+
ctx.body = html;
|
|
1384
|
+
}
|
|
1385
|
+
```
|
|
1386
|
+
|
|
1387
|
+
---
|
|
1388
|
+
|
|
1389
|
+
### 5. Organizzazione con passData
|
|
1390
|
+
|
|
1391
|
+
Per applicazioni complesse, organizza tutti i dati in un oggetto `passData`:
|
|
1392
|
+
|
|
1393
|
+
```javascript
|
|
1394
|
+
// ✅ BUONO - tutto organizzato
|
|
1395
|
+
ctx.body = await ejs.renderFile(filePath, {
|
|
1396
|
+
passData: {
|
|
1397
|
+
config: { ... },
|
|
1398
|
+
plugin: { ... },
|
|
1399
|
+
request: { ... },
|
|
1400
|
+
user: { ... }
|
|
1401
|
+
}
|
|
1402
|
+
});
|
|
1403
|
+
|
|
1404
|
+
// ❌ CATTIVO - dati sparsi
|
|
1405
|
+
ctx.body = await ejs.renderFile(filePath, {
|
|
1406
|
+
apiPrefix: '...',
|
|
1407
|
+
pluginSys: { ... },
|
|
1408
|
+
href: '...',
|
|
1409
|
+
// ... tutto mescolato
|
|
1410
|
+
});
|
|
1411
|
+
```
|
|
1412
|
+
|
|
1413
|
+
---
|
|
1414
|
+
|
|
1415
|
+
### 6. Non Esporre Dati Sensibili
|
|
1416
|
+
|
|
1417
|
+
```javascript
|
|
1418
|
+
// ❌ PERICOLOSO
|
|
1419
|
+
passData: {
|
|
1420
|
+
adminPrefix: ital8Conf.adminPrefix, // Non esporre!
|
|
1421
|
+
databasePassword: '...', // Mai!
|
|
1422
|
+
secretKey: '...' // Mai!
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// ✅ SICURO
|
|
1426
|
+
passData: {
|
|
1427
|
+
apiPrefix: ital8Conf.apiPrefix, // OK esporre
|
|
1428
|
+
// adminPrefix NON incluso
|
|
1429
|
+
}
|
|
1430
|
+
```
|
|
1431
|
+
|
|
1432
|
+
---
|
|
1433
|
+
|
|
1434
|
+
### 7. Imposta Content-Type
|
|
1435
|
+
|
|
1436
|
+
```javascript
|
|
1437
|
+
// ✅ Imposta sempre il tipo
|
|
1438
|
+
ctx.type = 'text/html';
|
|
1439
|
+
```
|
|
1440
|
+
|
|
1441
|
+
---
|
|
1442
|
+
|
|
1443
|
+
## Troubleshooting
|
|
1444
|
+
|
|
1445
|
+
### Problema: Template non viene renderizzato
|
|
1446
|
+
|
|
1447
|
+
**Causa:** Estensione file non è in `template.ext`
|
|
1448
|
+
|
|
1449
|
+
**Soluzione:**
|
|
1450
|
+
```javascript
|
|
1451
|
+
// Verifica che l'estensione sia corretta (case-sensitive)
|
|
1452
|
+
ext: ['ejs', 'EJS'] // Riconosce sia .ejs che .EJS
|
|
1453
|
+
```
|
|
1454
|
+
|
|
1455
|
+
---
|
|
1456
|
+
|
|
1457
|
+
### Problema: Errore "Cannot find module 'ejs'"
|
|
1458
|
+
|
|
1459
|
+
**Causa:** Template engine non installato
|
|
1460
|
+
|
|
1461
|
+
**Soluzione:**
|
|
1462
|
+
```bash
|
|
1463
|
+
npm install ejs
|
|
1464
|
+
```
|
|
1465
|
+
|
|
1466
|
+
---
|
|
1467
|
+
|
|
1468
|
+
### Problema: Dati non disponibili nel template
|
|
1469
|
+
|
|
1470
|
+
**Causa:** Dati non passati alla funzione render
|
|
1471
|
+
|
|
1472
|
+
**Esempio errore:**
|
|
1473
|
+
```
|
|
1474
|
+
ReferenceError: nome is not defined
|
|
1475
|
+
at eval ("/path/to/template.ejs":10:20)
|
|
1476
|
+
```
|
|
1477
|
+
|
|
1478
|
+
**Soluzione:**
|
|
1479
|
+
```javascript
|
|
1480
|
+
render: async (ctx, next, filePath) => {
|
|
1481
|
+
// Assicurati di passare i dati
|
|
1482
|
+
const html = await ejs.renderFile(filePath, {
|
|
1483
|
+
nome: 'Mario', // ✓ Passa i dati richiesti dal template
|
|
1484
|
+
eta: 30,
|
|
1485
|
+
citta: 'Roma'
|
|
1486
|
+
});
|
|
1487
|
+
ctx.body = html;
|
|
1488
|
+
}
|
|
1489
|
+
```
|
|
1490
|
+
|
|
1491
|
+
**Come vedere quali variabili usa un template:**
|
|
1492
|
+
|
|
1493
|
+
Apri il file `.ejs` e cerca `<%= ... %>`:
|
|
1494
|
+
|
|
1495
|
+
```html
|
|
1496
|
+
<%= nome %> <!-- Usa: nome -->
|
|
1497
|
+
<%= eta %> <!-- Usa: eta -->
|
|
1498
|
+
<%= citta %> <!-- Usa: citta -->
|
|
1499
|
+
```
|
|
1500
|
+
|
|
1501
|
+
---
|
|
1502
|
+
|
|
1503
|
+
### Problema: Server crash su errore template
|
|
1504
|
+
|
|
1505
|
+
**Causa:** Errori non gestiti nella funzione render
|
|
1506
|
+
|
|
1507
|
+
**Soluzione:**
|
|
1508
|
+
```javascript
|
|
1509
|
+
render: async (ctx, next, filePath) => {
|
|
1510
|
+
try {
|
|
1511
|
+
const html = await ejs.renderFile(filePath, data);
|
|
1512
|
+
ctx.body = html;
|
|
1513
|
+
} catch (error) {
|
|
1514
|
+
console.error('Rendering error:', error);
|
|
1515
|
+
ctx.status = 500;
|
|
1516
|
+
ctx.body = 'Template Error';
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
```
|
|
1520
|
+
|
|
1521
|
+
---
|
|
1522
|
+
|
|
1523
|
+
### Problema: Internal Server Error 500 con .ejs
|
|
1524
|
+
|
|
1525
|
+
**Possibili cause:**
|
|
1526
|
+
|
|
1527
|
+
1. **Variabile non definita nel template**
|
|
1528
|
+
```html
|
|
1529
|
+
<!-- ❌ SBAGLIATO - 'user' potrebbe non esistere -->
|
|
1530
|
+
<p><%= user.name %></p>
|
|
1531
|
+
|
|
1532
|
+
<!-- ✅ CORRETTO - controlla prima -->
|
|
1533
|
+
<p><%= user ? user.name : 'Guest' %></p>
|
|
1534
|
+
```
|
|
1535
|
+
|
|
1536
|
+
2. **Dati non passati alla render function**
|
|
1537
|
+
```javascript
|
|
1538
|
+
// ❌ SBAGLIATO - 'user' non passato
|
|
1539
|
+
ejs.renderFile(filePath, { title: 'App' })
|
|
1540
|
+
|
|
1541
|
+
// ✅ CORRETTO - passa tutti i dati necessari
|
|
1542
|
+
ejs.renderFile(filePath, {
|
|
1543
|
+
title: 'App',
|
|
1544
|
+
user: { name: 'Guest' },
|
|
1545
|
+
path: ctx.path
|
|
1546
|
+
})
|
|
1547
|
+
```
|
|
1548
|
+
|
|
1549
|
+
3. **Sintassi EJS sbagliata**
|
|
1550
|
+
```html
|
|
1551
|
+
<!-- ❌ SBAGLIATO -->
|
|
1552
|
+
<%= <%= user.name %> %>
|
|
1553
|
+
|
|
1554
|
+
<!-- ✅ CORRETTO -->
|
|
1555
|
+
<%= user.name %>
|
|
1556
|
+
```
|
|
1557
|
+
|
|
1558
|
+
---
|
|
1559
|
+
|
|
1560
|
+
### Problema: Directory non trovata
|
|
1561
|
+
|
|
1562
|
+
**Causa:** La directory specificata non esiste
|
|
1563
|
+
|
|
1564
|
+
**Verifica:**
|
|
1565
|
+
```javascript
|
|
1566
|
+
const fs = require('fs');
|
|
1567
|
+
const publicDir = path.join(__dirname, 'public');
|
|
1568
|
+
|
|
1569
|
+
if (!fs.existsSync(publicDir)) {
|
|
1570
|
+
console.error('❌ Directory non esiste:', publicDir);
|
|
1571
|
+
process.exit(1);
|
|
1572
|
+
}
|
|
1573
|
+
```
|
|
1574
|
+
|
|
1575
|
+
---
|
|
1576
|
+
|
|
1577
|
+
### Problema: File statici processati come template
|
|
1578
|
+
|
|
1579
|
+
**Causa:** Estensione HTML nella lista `ext`
|
|
1580
|
+
|
|
1581
|
+
**Soluzione:**
|
|
1582
|
+
```javascript
|
|
1583
|
+
ext: ['ejs', 'EJS'] // ✅ Solo file .ejs vengono processati dal template engine
|
|
1584
|
+
// NON includere 'html' se vuoi servire file .html staticamente
|
|
1585
|
+
```
|
|
1586
|
+
|
|
1587
|
+
---
|
|
1588
|
+
|
|
1589
|
+
### Problema: Cache non funziona in produzione
|
|
1590
|
+
|
|
1591
|
+
**Causa:** Cache non abilitata nelle opzioni EJS
|
|
1592
|
+
|
|
1593
|
+
**Soluzione:**
|
|
1594
|
+
```javascript
|
|
1595
|
+
const html = await ejs.renderFile(filePath, data, {
|
|
1596
|
+
cache: true, // Abilita cache
|
|
1597
|
+
filename: filePath // Necessario per cache
|
|
1598
|
+
});
|
|
1599
|
+
```
|
|
1600
|
+
|
|
1601
|
+
---
|
|
1602
|
+
|
|
1603
|
+
## Performance Tips
|
|
1604
|
+
|
|
1605
|
+
### 1. Abilita cache in produzione
|
|
1606
|
+
|
|
1607
|
+
```javascript
|
|
1608
|
+
cache: process.env.NODE_ENV === 'production'
|
|
1609
|
+
```
|
|
1610
|
+
|
|
1611
|
+
---
|
|
1612
|
+
|
|
1613
|
+
### 2. Pre-compila template comuni
|
|
1614
|
+
|
|
1615
|
+
```javascript
|
|
1616
|
+
const compiledTemplates = new Map();
|
|
1617
|
+
|
|
1618
|
+
// Pre-compila all'avvio
|
|
1619
|
+
compiledTemplates.set('index', ejs.compile(indexTemplate));
|
|
1620
|
+
|
|
1621
|
+
// Usa template pre-compilato
|
|
1622
|
+
const html = compiledTemplates.get('index')(data);
|
|
1623
|
+
```
|
|
1624
|
+
|
|
1625
|
+
---
|
|
1626
|
+
|
|
1627
|
+
### 3. Usa async/await correttamente
|
|
1628
|
+
|
|
1629
|
+
```javascript
|
|
1630
|
+
// ✓ Buono - parallelo
|
|
1631
|
+
const [users, products] = await Promise.all([
|
|
1632
|
+
fetchUsers(),
|
|
1633
|
+
fetchProducts()
|
|
1634
|
+
]);
|
|
1635
|
+
|
|
1636
|
+
// ✗ Cattivo - sequenziale
|
|
1637
|
+
const users = await fetchUsers();
|
|
1638
|
+
const products = await fetchProducts();
|
|
1639
|
+
```
|
|
1640
|
+
|
|
1641
|
+
---
|
|
1642
|
+
|
|
1643
|
+
### 4. Minimizza accesso al filesystem
|
|
1644
|
+
|
|
1645
|
+
```javascript
|
|
1646
|
+
// Cache template content in memoria in produzione
|
|
1647
|
+
const templateCache = new Map();
|
|
1648
|
+
|
|
1649
|
+
if (process.env.NODE_ENV === 'production') {
|
|
1650
|
+
if (!templateCache.has(filePath)) {
|
|
1651
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
1652
|
+
templateCache.set(filePath, content);
|
|
1653
|
+
}
|
|
1654
|
+
const content = templateCache.get(filePath);
|
|
1655
|
+
const html = ejs.render(content, data);
|
|
1656
|
+
}
|
|
1657
|
+
```
|
|
1658
|
+
|
|
1659
|
+
---
|
|
1660
|
+
|
|
1661
|
+
## Checklist Pre-Produzione
|
|
1662
|
+
|
|
1663
|
+
Prima di mettere in produzione:
|
|
1664
|
+
|
|
1665
|
+
- [ ] Try/catch in tutte le render function
|
|
1666
|
+
- [ ] `ctx.type = 'text/html'` impostato
|
|
1667
|
+
- [ ] AdminPrefix NON esposto nelle pagine pubbliche
|
|
1668
|
+
- [ ] URL riservati configurati in `urlsReserved` (se necessario)
|
|
1669
|
+
- [ ] Gestione errori diversa per production/development
|
|
1670
|
+
- [ ] Dati organizzati (opzionale: usa `passData`)
|
|
1671
|
+
- [ ] Cache abilitata in produzione
|
|
1672
|
+
- [ ] Testato con e senza sessione
|
|
1673
|
+
- [ ] Testato con e senza autenticazione
|
|
1674
|
+
- [ ] Input utente sempre escaped (usa `<%= %>` non `<%- %>`)
|
|
1675
|
+
- [ ] Validazione input utente implementata
|
|
1676
|
+
|
|
1677
|
+
---
|
|
1678
|
+
|
|
1679
|
+
## Debug Tips
|
|
1680
|
+
|
|
1681
|
+
### Visualizza Dati Disponibili
|
|
1682
|
+
|
|
1683
|
+
Nel template, aggiungi temporaneamente:
|
|
1684
|
+
|
|
1685
|
+
```html
|
|
1686
|
+
<pre><%= JSON.stringify(passData, null, 2) %></pre>
|
|
1687
|
+
```
|
|
1688
|
+
|
|
1689
|
+
Questo mostra tutti i dati disponibili per il debug.
|
|
1690
|
+
|
|
1691
|
+
---
|
|
1692
|
+
|
|
1693
|
+
### Verifica Errori
|
|
1694
|
+
|
|
1695
|
+
Se vedi `ReferenceError: xxx is not defined`:
|
|
1696
|
+
|
|
1697
|
+
1. Verifica che passi la variabile nel server
|
|
1698
|
+
2. Controlla che sia dentro l'oggetto dati
|
|
1699
|
+
3. Nel template usa la variabile correttamente
|
|
1700
|
+
|
|
1701
|
+
---
|
|
1702
|
+
|
|
1703
|
+
## Link Utili
|
|
1704
|
+
|
|
1705
|
+
- [EJS Documentation](https://ejs.co/)
|
|
1706
|
+
- [Pug Documentation](https://pugjs.org/)
|
|
1707
|
+
- [Handlebars Documentation](https://handlebarsjs.com/)
|
|
1708
|
+
- [Nunjucks Documentation](https://mozilla.github.io/nunjucks/)
|
|
1709
|
+
- [koa-classic-server Documentation](../DOCUMENTATION.md)
|
|
1710
|
+
|
|
1711
|
+
---
|
|
1712
|
+
|
|
1713
|
+
## Esempi Pratici
|
|
1714
|
+
|
|
1715
|
+
Tutti gli esempi pratici sono disponibili nella cartella `examples/`:
|
|
1716
|
+
|
|
1717
|
+
- `examples/esempio1-nessun-dato.ejs` - Template statico
|
|
1718
|
+
- `examples/esempio2-una-variabile.ejs` - Una variabile
|
|
1719
|
+
- `examples/esempio3-piu-variabili.ejs` - Più variabili
|
|
1720
|
+
- `examples/esempio4-condizionale.ejs` - Logica condizionale
|
|
1721
|
+
- `examples/esempio5-loop.ejs` - Iterazione array
|
|
1722
|
+
- `examples/index-esempi.html` - Pagina indice interattiva
|
|
1723
|
+
|
|
1724
|
+
Per eseguire gli esempi:
|
|
1725
|
+
|
|
1726
|
+
```bash
|
|
1727
|
+
node esempi-incrementali.js
|
|
1728
|
+
```
|
|
1729
|
+
|
|
1730
|
+
Poi apri http://localhost:3000/index-esempi.html
|
|
1731
|
+
|
|
1732
|
+
---
|
|
1733
|
+
|
|
1734
|
+
**Buon lavoro con i template engine! 🚀**
|