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.
Files changed (45) hide show
  1. package/README.md +31 -17
  2. package/__tests__/directory-sorting-links.test.js +135 -0
  3. package/__tests__/ejs.test.js +299 -0
  4. package/__tests__/performance.test.js +75 -6
  5. package/__tests__/publicWwwTest/ejs-templates/complex.ejs +56 -0
  6. package/__tests__/publicWwwTest/ejs-templates/index.ejs +30 -0
  7. package/__tests__/publicWwwTest/ejs-templates/simple.ejs +13 -0
  8. package/__tests__/publicWwwTest/ejs-templates/with-conditional.ejs +28 -0
  9. package/__tests__/publicWwwTest/ejs-templates/with-escaping.ejs +26 -0
  10. package/__tests__/publicWwwTest/ejs-templates/with-loop.ejs +16 -0
  11. package/{scripts → __tests__}/setup-benchmark.js +1 -1
  12. package/docs/CODE_REVIEW.md +298 -0
  13. package/docs/FLOW_DIAGRAM.md +952 -0
  14. package/docs/template-engine/TEMPLATE_ENGINE_GUIDE.md +1734 -0
  15. package/docs/template-engine/esempi-incrementali.js +192 -0
  16. package/docs/template-engine/examples/esempio1-nessun-dato.ejs +12 -0
  17. package/docs/template-engine/examples/esempio2-una-variabile.ejs +11 -0
  18. package/docs/template-engine/examples/esempio3-piu-variabili.ejs +15 -0
  19. package/docs/template-engine/examples/esempio4-condizionale.ejs +18 -0
  20. package/docs/template-engine/examples/esempio5-loop.ejs +18 -0
  21. package/docs/template-engine/examples/index-esempi.html +181 -0
  22. package/docs/template-engine/examples/index.html +40 -0
  23. package/docs/template-engine/examples/test.ejs +64 -0
  24. package/index.cjs +186 -35
  25. package/package.json +9 -6
  26. package/CREATE_RELEASE.sh +0 -53
  27. package/publish-to-npm.sh +0 -65
  28. /package/{benchmark-results-baseline-v1.2.0.txt → __tests__/benchmark-results-baseline-v1.2.0.txt} +0 -0
  29. /package/{benchmark-results-optimized-v2.0.0.txt → __tests__/benchmark-results-optimized-v2.0.0.txt} +0 -0
  30. /package/{benchmark.js → __tests__/benchmark.js} +0 -0
  31. /package/{customTest → __tests__/customTest}/README.md +0 -0
  32. /package/{customTest → __tests__/customTest}/loadConfig.util.js +0 -0
  33. /package/{customTest → __tests__/customTest}/serversToLoad.util.js +0 -0
  34. /package/{demo-regex-index.js → __tests__/demo-regex-index.js} +0 -0
  35. /package/{test-regex-quick.js → __tests__/test-regex-quick.js} +0 -0
  36. /package/{BENCHMARKS.md → docs/BENCHMARKS.md} +0 -0
  37. /package/{CHANGELOG.md → docs/CHANGELOG.md} +0 -0
  38. /package/{DEBUG_REPORT.md → docs/DEBUG_REPORT.md} +0 -0
  39. /package/{DOCUMENTATION.md → docs/DOCUMENTATION.md} +0 -0
  40. /package/{EXAMPLES_INDEX_OPTION.md → docs/EXAMPLES_INDEX_OPTION.md} +0 -0
  41. /package/{INDEX_OPTION_PRIORITY.md → docs/INDEX_OPTION_PRIORITY.md} +0 -0
  42. /package/{OPTIMIZATION_HTTP_CACHING.md → docs/OPTIMIZATION_HTTP_CACHING.md} +0 -0
  43. /package/{PERFORMANCE_ANALYSIS.md → docs/PERFORMANCE_ANALYSIS.md} +0 -0
  44. /package/{PERFORMANCE_COMPARISON.md → docs/PERFORMANCE_COMPARISON.md} +0 -0
  45. /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: &lt;script&gt;alert('XSS')&lt;/script&gt;</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>&copy; <%= 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! 🚀**