@aetherframework/template-engine 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +662 -0
- package/examples/basic-usage.js +217 -0
- package/examples/dist/basic-usage-result.html +31 -0
- package/examples/dist/layout-example-result.html +210 -0
- package/examples/dist/templates/layouts/main.aether +58 -0
- package/examples/dist/templates/pages/home.aether +116 -0
- package/examples/layout-example.js +404 -0
- package/examples/ssr-example.js +180 -0
- package/index.js +179 -0
- package/package.json +42 -0
- package/src/core/CacheManager.js +245 -0
- package/src/core/EngineRegistry.js +148 -0
- package/src/core/ModeManager.js +231 -0
- package/src/core/TemplateEngineFactory.js +373 -0
- package/src/engines/AetherEngine.js +582 -0
- package/src/engines/BaseEngine.js +101 -0
- package/src/engines/SSRModeEngine.js +139 -0
- package/src/engines/TemplateModeEngine.js +320 -0
- package/src/utils/ConfigLoader.js +279 -0
- package/src/utils/ErrorHandler.js +276 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-present, AetherFramework Contributors.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
Aether Framework Template Engine
|
|
2
|
+
|
|
3
|
+
A modern, lightweight template engine for Node.js with Blade-like syntax, supporting SSR (Server-Side Rendering) and template modes. Features include template inheritance, includes, conditionals, loops, filters, and custom functions.
|
|
4
|
+
|
|
5
|
+
Features
|
|
6
|
+
|
|
7
|
+
- Blade-like Syntax: Familiar syntax similar to Laravel Blade
|
|
8
|
+
- Template Inheritance: Support for `@extends`, `@section`, `@yield`
|
|
9
|
+
- Conditionals & Loops: `@if`, `@else`, `@endif`, `@foreach`, `@endforeach`
|
|
10
|
+
- Custom Functions: `{{ route('home') }}`, `{{ asset('images/logo.png') }}`
|
|
11
|
+
- Chained Properties: `{{ auth().user.name }}`
|
|
12
|
+
- Filters: `{{ variable|upper }}`, `{{ variable|date:'YYYY-MM-DD' }}`
|
|
13
|
+
- SSR Support: Server-side rendering mode
|
|
14
|
+
- Caching: Built-in template compilation caching
|
|
15
|
+
- ES Module: Native ES Module support
|
|
16
|
+
|
|
17
|
+
Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @aetherframework/template-engine
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Quick Start
|
|
24
|
+
|
|
25
|
+
Basic Usage
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
import AetherEngine from '@aetherframework/template-engine';
|
|
29
|
+
|
|
30
|
+
// Initialize the engine
|
|
31
|
+
const engine = new AetherEngine({
|
|
32
|
+
templateDir: './templates', // Template directory
|
|
33
|
+
cacheEnabled: true, // Enable caching
|
|
34
|
+
debug: true // Debug mode
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await engine.initialize();
|
|
38
|
+
|
|
39
|
+
// Define your template
|
|
40
|
+
const template = `
|
|
41
|
+
<header class="header">
|
|
42
|
+
<nav class="navbar">
|
|
43
|
+
<div class="container">
|
|
44
|
+
<a class="navbar-brand" href="{{ route('home') }}">
|
|
45
|
+
<img src="{{ asset('images/logo.png') }}" alt="Logo" height="40">
|
|
46
|
+
</a>
|
|
47
|
+
|
|
48
|
+
<ul class="navbar-nav">
|
|
49
|
+
<li class="nav-item">
|
|
50
|
+
<a class="nav-link" href="{{ route('home') }}">Home</a>
|
|
51
|
+
</li>
|
|
52
|
+
<li class="nav-item">
|
|
53
|
+
<a class="nav-link" href="{{ route('about') }}">About</a>
|
|
54
|
+
</li>
|
|
55
|
+
|
|
56
|
+
@if(auth().check())
|
|
57
|
+
<li class="nav-item dropdown">
|
|
58
|
+
<a class="nav-link dropdown-toggle" href="" role="button">
|
|
59
|
+
{{ auth().user.name }}
|
|
60
|
+
</a>
|
|
61
|
+
<div class="dropdown-menu">
|
|
62
|
+
<a class="dropdown-item" href="{{ route('profile') }}">Profile</a>
|
|
63
|
+
<a class="dropdown-item" href="{{ route('logout') }}">Logout</a>
|
|
64
|
+
</div>
|
|
65
|
+
</li>
|
|
66
|
+
@else
|
|
67
|
+
<li class="nav-item">
|
|
68
|
+
<a class="nav-link" href="{{ route('login') }}">Login</a>
|
|
69
|
+
</li>
|
|
70
|
+
<li class="nav-item">
|
|
71
|
+
<a class="nav-link" href="{{ route('register') }}">Register</a>
|
|
72
|
+
</li>
|
|
73
|
+
@endif
|
|
74
|
+
</ul>
|
|
75
|
+
</div>
|
|
76
|
+
</nav>
|
|
77
|
+
</header>`;
|
|
78
|
+
|
|
79
|
+
// Prepare data
|
|
80
|
+
const data = {
|
|
81
|
+
auth: () => ({
|
|
82
|
+
check: () => true,
|
|
83
|
+
user: { name: 'John Doe' }
|
|
84
|
+
}),
|
|
85
|
+
route: (name) => {
|
|
86
|
+
const routes = {
|
|
87
|
+
'home': '/',
|
|
88
|
+
'about': '/about',
|
|
89
|
+
'login': '/login',
|
|
90
|
+
'register': '/register',
|
|
91
|
+
'profile': '/profile',
|
|
92
|
+
'logout': '/logout'
|
|
93
|
+
};
|
|
94
|
+
return routes[name] || '';
|
|
95
|
+
},
|
|
96
|
+
asset: (path) => `/assets/${path}`
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Render the template
|
|
100
|
+
const html = await engine.render(template, data);
|
|
101
|
+
console.log(html);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Using Factory Pattern
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
import { createEngine } from '@aetherframework/template-engine';
|
|
108
|
+
|
|
109
|
+
// Create engine factory
|
|
110
|
+
const factory = await createEngine({
|
|
111
|
+
mode: 'template', // 'template' or 'ssr'
|
|
112
|
+
templateDir: './views',
|
|
113
|
+
cacheEnabled: true,
|
|
114
|
+
debug: process.env.NODE_ENV === 'development'
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Create renderer
|
|
118
|
+
const renderer = factory.createRenderer('aether');
|
|
119
|
+
|
|
120
|
+
// Render template
|
|
121
|
+
const html = await renderer.render(template, data);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Template Syntax
|
|
125
|
+
|
|
126
|
+
Variables
|
|
127
|
+
|
|
128
|
+
```html
|
|
129
|
+
<!-- Simple variable -->
|
|
130
|
+
<p>Hello, {{ name }}!</p>
|
|
131
|
+
|
|
132
|
+
<!-- Object property -->
|
|
133
|
+
<p>Email: {{ user.email }}</p>
|
|
134
|
+
|
|
135
|
+
<!-- Array access -->
|
|
136
|
+
<p>First item: {{ items[0] }}</p>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Functions
|
|
140
|
+
|
|
141
|
+
```html
|
|
142
|
+
<!-- Function call -->
|
|
143
|
+
<a href="{{ route('home') }}">Home</a>
|
|
144
|
+
<img src="{{ asset('images/logo.png') }}">
|
|
145
|
+
|
|
146
|
+
<!-- Chained method calls -->
|
|
147
|
+
@if(auth().check())
|
|
148
|
+
<p>Welcome, {{ auth().user.name }}!</p>
|
|
149
|
+
@endif
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Conditionals
|
|
153
|
+
|
|
154
|
+
```html
|
|
155
|
+
@if(user.isAdmin)
|
|
156
|
+
<p>Administrator Access</p>
|
|
157
|
+
@elseif(user.isModerator)
|
|
158
|
+
<p>Moderator Access</p>
|
|
159
|
+
@else
|
|
160
|
+
<p>User Access</p>
|
|
161
|
+
@endif
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Loops
|
|
165
|
+
|
|
166
|
+
```html
|
|
167
|
+
<ul>
|
|
168
|
+
@foreach(users as user)
|
|
169
|
+
<li>{{ user.name }} - {{ user.email }}</li>
|
|
170
|
+
@endforeach
|
|
171
|
+
</ul>
|
|
172
|
+
|
|
173
|
+
<!-- Alternative syntax -->
|
|
174
|
+
@foreach(items as item)
|
|
175
|
+
<div>{{ item.name }}</div>
|
|
176
|
+
@endforeach
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Filters
|
|
180
|
+
|
|
181
|
+
```html
|
|
182
|
+
<!-- Single filter -->
|
|
183
|
+
<p>{{ content|upper }}</p>
|
|
184
|
+
|
|
185
|
+
<!-- Multiple filters -->
|
|
186
|
+
<p>{{ content|escape|lower }}</p>
|
|
187
|
+
|
|
188
|
+
<!-- Filter with arguments -->
|
|
189
|
+
<p>{{ date|date:'YYYY-MM-DD' }}</p>
|
|
190
|
+
<p>{{ price|formatCurrency:'USD' }}</p>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Layouts and Inheritance
|
|
194
|
+
|
|
195
|
+
layouts/base.aether:
|
|
196
|
+
```html
|
|
197
|
+
<!DOCTYPE html>
|
|
198
|
+
<html lang="en">
|
|
199
|
+
<head>
|
|
200
|
+
<meta charset="UTF-8">
|
|
201
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
202
|
+
<title>@yield('title', 'Default Title')</title>
|
|
203
|
+
@yield('head')
|
|
204
|
+
</head>
|
|
205
|
+
<body>
|
|
206
|
+
@include('partials/header')
|
|
207
|
+
|
|
208
|
+
<main>
|
|
209
|
+
@yield('content')
|
|
210
|
+
</main>
|
|
211
|
+
|
|
212
|
+
@include('partials/footer')
|
|
213
|
+
|
|
214
|
+
@yield('scripts')
|
|
215
|
+
</body>
|
|
216
|
+
</html>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
pages/home.aether:
|
|
220
|
+
```html
|
|
221
|
+
@extends('layouts/base')
|
|
222
|
+
|
|
223
|
+
@section('title', 'Home Page')
|
|
224
|
+
|
|
225
|
+
@section('head')
|
|
226
|
+
<link rel="stylesheet" href="/css/home.css">
|
|
227
|
+
@endsection
|
|
228
|
+
|
|
229
|
+
@section('content')
|
|
230
|
+
<h1>Welcome to {{ site.name }}</h1>
|
|
231
|
+
<p>{{ welcomeMessage }}</p>
|
|
232
|
+
|
|
233
|
+
@foreach(features as feature)
|
|
234
|
+
<div class="feature">
|
|
235
|
+
<h3>{{ feature.title }}</h3>
|
|
236
|
+
<p>{{ feature.description }}</p>
|
|
237
|
+
</div>
|
|
238
|
+
@endforeach
|
|
239
|
+
@endsection
|
|
240
|
+
|
|
241
|
+
@section('scripts')
|
|
242
|
+
<script src="/js/home.js"></script>
|
|
243
|
+
@endsection
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
API Reference
|
|
247
|
+
|
|
248
|
+
AetherEngine Class
|
|
249
|
+
|
|
250
|
+
Constructor
|
|
251
|
+
|
|
252
|
+
```javascript
|
|
253
|
+
const engine = new AetherEngine(options);
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Options:
|
|
257
|
+
- `templateDir` (string): Template directory path (default: './templates')
|
|
258
|
+
- `cacheEnabled` (boolean): Enable template caching (default: true)
|
|
259
|
+
- `cacheTTL` (number): Cache time-to-live in seconds (default: 3600)
|
|
260
|
+
- `debug` (boolean): Enable debug mode (default: false)
|
|
261
|
+
|
|
262
|
+
Methods
|
|
263
|
+
|
|
264
|
+
initialize()
|
|
265
|
+
Initialize the engine (called automatically on first render).
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
await engine.initialize();
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
render(template, data, options)
|
|
272
|
+
Render a template with data.
|
|
273
|
+
|
|
274
|
+
```javascript
|
|
275
|
+
const html = await engine.render(templateString, data);
|
|
276
|
+
// or
|
|
277
|
+
const html = await engine.render('template-file.aether', data);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Parameters:
|
|
281
|
+
- `template` (string): Template content or file path
|
|
282
|
+
- `data` (object): Template data
|
|
283
|
+
- `options` (object): Render options
|
|
284
|
+
|
|
285
|
+
filter(name, handler)
|
|
286
|
+
Register a custom filter.
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
engine.filter('uppercase', (value) => value.toUpperCase());
|
|
290
|
+
engine.filter('truncate', (value, length = 100) => {
|
|
291
|
+
return value.length > length ? value.substring(0, length) + '...' : value;
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Usage in template: {{ content|uppercase|truncate:50 }}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
function(name, handler)
|
|
298
|
+
Register a custom function.
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
engine.function('config', (key, defaultValue = null) => {
|
|
302
|
+
return process.env[key] || defaultValue;
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
engine.function('csrf_token', () => {
|
|
306
|
+
return generateCSRFToken();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Usage in template: {{ config('APP_NAME') }}, {{ csrf_token() }}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
registerLayout(name, content)
|
|
313
|
+
Register a layout template.
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
engine.registerLayout('default', `
|
|
317
|
+
<!DOCTYPE html>
|
|
318
|
+
<html>
|
|
319
|
+
<head>
|
|
320
|
+
<title>@yield('title')</title>
|
|
321
|
+
</head>
|
|
322
|
+
<body>
|
|
323
|
+
@yield('content')
|
|
324
|
+
</body>
|
|
325
|
+
</html>
|
|
326
|
+
`);
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
clearCache()
|
|
330
|
+
Clear the template cache.
|
|
331
|
+
|
|
332
|
+
```javascript
|
|
333
|
+
engine.clearCache();
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
getMetadata()
|
|
337
|
+
Get engine metadata.
|
|
338
|
+
|
|
339
|
+
```javascript
|
|
340
|
+
const metadata = engine.getMetadata();
|
|
341
|
+
// Returns: { name, version, initialized, filters, functions, layouts, cacheEnabled, templateDir }
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Factory Functions
|
|
345
|
+
|
|
346
|
+
createEngine(options)
|
|
347
|
+
Create a template engine factory.
|
|
348
|
+
|
|
349
|
+
```javascript
|
|
350
|
+
import { createEngine } from '@aetherframework/template-engine';
|
|
351
|
+
|
|
352
|
+
const factory = await createEngine({
|
|
353
|
+
mode: 'template', // 'template' or 'ssr'
|
|
354
|
+
templateDir: './views',
|
|
355
|
+
cacheEnabled: true
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
createEngineFromEnv()
|
|
360
|
+
Create engine from environment configuration.
|
|
361
|
+
|
|
362
|
+
```javascript
|
|
363
|
+
const factory = await createEngineFromEnv();
|
|
364
|
+
// Reads from .env file or environment variables
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
quickStart(options)
|
|
368
|
+
Quick start with default configuration.
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
const { factory, renderer, config } = await quickStart({
|
|
372
|
+
templateDir: './templates',
|
|
373
|
+
debug: true
|
|
374
|
+
});
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Configuration
|
|
378
|
+
|
|
379
|
+
Environment Variables
|
|
380
|
+
|
|
381
|
+
```env
|
|
382
|
+
TEMPLATE_ENGINE_MODE=template 'template' or 'ssr'
|
|
383
|
+
TEMPLATE_ENGINE=aether Default engine
|
|
384
|
+
TEMPLATE_DIR=./templates Template directory
|
|
385
|
+
CACHE_ENABLED=true Enable caching
|
|
386
|
+
CACHE_TTL=300000 Cache TTL in milliseconds (5 minutes)
|
|
387
|
+
DEBUG=true Debug mode
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Configuration File (.env)
|
|
391
|
+
|
|
392
|
+
Create a `.env` file in your project root:
|
|
393
|
+
|
|
394
|
+
```env
|
|
395
|
+
Template Engine Configuration
|
|
396
|
+
TEMPLATE_ENGINE_MODE=template
|
|
397
|
+
TEMPLATE_DIR=./resources/views
|
|
398
|
+
CACHE_ENABLED=true
|
|
399
|
+
CACHE_TTL=300000
|
|
400
|
+
DEBUG=false
|
|
401
|
+
|
|
402
|
+
Custom Functions Configuration
|
|
403
|
+
ASSET_URL=/assets
|
|
404
|
+
BASE_URL=http://localhost:3000
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Integration Examples
|
|
408
|
+
|
|
409
|
+
Express.js Integration
|
|
410
|
+
|
|
411
|
+
```javascript
|
|
412
|
+
import express from 'express';
|
|
413
|
+
import { createEngine } from '@aetherframework/template-engine';
|
|
414
|
+
|
|
415
|
+
const app = express();
|
|
416
|
+
|
|
417
|
+
// Initialize template engine
|
|
418
|
+
const factory = await createEngine({
|
|
419
|
+
templateDir: './views',
|
|
420
|
+
cacheEnabled: process.env.NODE_ENV === 'production'
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
const renderer = factory.createRenderer('aether');
|
|
424
|
+
|
|
425
|
+
// Middleware to add render method
|
|
426
|
+
app.use((req, res, next) => {
|
|
427
|
+
res.render = async (template, data = {}) => {
|
|
428
|
+
try {
|
|
429
|
+
const html = await renderer.render(template, {
|
|
430
|
+
...data,
|
|
431
|
+
req,
|
|
432
|
+
res,
|
|
433
|
+
csrfToken: req.csrfToken ? req.csrfToken() : null
|
|
434
|
+
});
|
|
435
|
+
res.send(html);
|
|
436
|
+
} catch (error) {
|
|
437
|
+
console.error('Render error:', error);
|
|
438
|
+
res.status(500).send('Internal Server Error');
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
next();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Route example
|
|
445
|
+
app.get('/', async (req, res) => {
|
|
446
|
+
const data = {
|
|
447
|
+
title: 'Home Page',
|
|
448
|
+
user: req.user || null,
|
|
449
|
+
products: await Product.find(),
|
|
450
|
+
csrfToken: req.csrfToken ? req.csrfToken() : null
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
await res.render('home', data);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
app.listen(3000, () => {
|
|
457
|
+
console.log('Server running on http://localhost:3000');
|
|
458
|
+
});
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Koa.js Integration
|
|
462
|
+
|
|
463
|
+
```javascript
|
|
464
|
+
import Koa from 'koa';
|
|
465
|
+
import { createEngine } from '@aetherframework/template-engine';
|
|
466
|
+
|
|
467
|
+
const app = new Koa();
|
|
468
|
+
|
|
469
|
+
const factory = await createEngine();
|
|
470
|
+
const renderer = factory.createRenderer('aether');
|
|
471
|
+
|
|
472
|
+
// Middleware
|
|
473
|
+
app.use(async (ctx, next) => {
|
|
474
|
+
ctx.render = async (template, data = {}) => {
|
|
475
|
+
const html = await renderer.render(template, {
|
|
476
|
+
...data,
|
|
477
|
+
ctx,
|
|
478
|
+
state: ctx.state
|
|
479
|
+
});
|
|
480
|
+
ctx.type = 'html';
|
|
481
|
+
ctx.body = html;
|
|
482
|
+
};
|
|
483
|
+
await next();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// Route
|
|
487
|
+
app.use(async (ctx) => {
|
|
488
|
+
if (ctx.path === '/') {
|
|
489
|
+
await ctx.render('home', {
|
|
490
|
+
title: 'Welcome',
|
|
491
|
+
user: ctx.state.user
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
app.listen(3000);
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Advanced Usage
|
|
500
|
+
|
|
501
|
+
Custom Filters
|
|
502
|
+
|
|
503
|
+
```javascript
|
|
504
|
+
// Register custom filters
|
|
505
|
+
engine.filter('formatDate', (date, format = 'YYYY-MM-DD') => {
|
|
506
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
507
|
+
year: 'numeric',
|
|
508
|
+
month: '2-digit',
|
|
509
|
+
day: '2-digit'
|
|
510
|
+
}).format(new Date(date));
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
engine.filter('pluralize', (count, singular, plural) => {
|
|
514
|
+
return count === 1 ? singular : plural;
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
// Usage in template
|
|
518
|
+
// {{ created_at|formatDate:'MM/DD/YYYY' }}
|
|
519
|
+
// {{ count|pluralize:'item':'items' }}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
Custom Functions
|
|
523
|
+
|
|
524
|
+
```javascript
|
|
525
|
+
// Register custom functions
|
|
526
|
+
engine.function('url', (path) => {
|
|
527
|
+
const baseUrl = process.env.BASE_URL || 'http://localhost:3000';
|
|
528
|
+
return `${baseUrl}${path.startsWith('/') ? path : '/' + path}`;
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
engine.function('old', (field, defaultValue = '') => {
|
|
532
|
+
// Simulate Laravel's old() function for form data
|
|
533
|
+
return session?.old?.[field] || defaultValue;
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
engine.function('can', (permission) => {
|
|
537
|
+
// Check user permissions
|
|
538
|
+
return currentUser?.permissions?.includes(permission) || false;
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// Usage in template
|
|
542
|
+
// <a href="{{ url('/dashboard') }}">Dashboard</a>
|
|
543
|
+
// <input value="{{ old('username') }}">
|
|
544
|
+
// @if(can('edit-post')) ... @endif
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
Template Caching
|
|
548
|
+
|
|
549
|
+
```javascript
|
|
550
|
+
// Enable/disable caching
|
|
551
|
+
const engine = new AetherEngine({
|
|
552
|
+
cacheEnabled: true,
|
|
553
|
+
cacheTTL: 3600000 // 1 hour
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// Clear cache manually
|
|
557
|
+
engine.clearCache();
|
|
558
|
+
|
|
559
|
+
// Or clear cache on specific events
|
|
560
|
+
app.post('/clear-cache', (req, res) => {
|
|
561
|
+
engine.clearCache();
|
|
562
|
+
res.json({ message: 'Cache cleared' });
|
|
563
|
+
});
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Error Handling
|
|
567
|
+
|
|
568
|
+
```javascript
|
|
569
|
+
try {
|
|
570
|
+
const html = await engine.render(template, data);
|
|
571
|
+
// Success
|
|
572
|
+
} catch (error) {
|
|
573
|
+
if (error.message.includes('Template not found')) {
|
|
574
|
+
console.error('Template file not found');
|
|
575
|
+
} else if (error.message.includes('Template compilation failed')) {
|
|
576
|
+
console.error('Template syntax error:', error.message);
|
|
577
|
+
} else if (error.message.includes('Runtime error')) {
|
|
578
|
+
console.error('Template runtime error:', error.message);
|
|
579
|
+
} else {
|
|
580
|
+
console.error('Unknown error:', error);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Fallback to error template
|
|
584
|
+
const errorHtml = await engine.render('errors/500', { error: error.message });
|
|
585
|
+
}
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
File Structure
|
|
589
|
+
|
|
590
|
+
```
|
|
591
|
+
project/
|
|
592
|
+
├── templates/
|
|
593
|
+
│ ├── layouts/
|
|
594
|
+
│ │ ├── default.aether
|
|
595
|
+
│ │ └── admin.aether
|
|
596
|
+
│ ├── pages/
|
|
597
|
+
│ │ ├── home.aether
|
|
598
|
+
│ │ ├── about.aether
|
|
599
|
+
│ │ └── contact.aether
|
|
600
|
+
│ ├── components/
|
|
601
|
+
│ │ ├── header.aether
|
|
602
|
+
│ │ ├── footer.aether
|
|
603
|
+
│ │ └── sidebar.aether
|
|
604
|
+
│ └── partials/
|
|
605
|
+
│ ├── nav.aether
|
|
606
|
+
│ └── alerts.aether
|
|
607
|
+
├── .env
|
|
608
|
+
├── package.json
|
|
609
|
+
└── app.js
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
Performance Tips
|
|
613
|
+
|
|
614
|
+
1. Enable Caching in Production: Always enable caching in production environments
|
|
615
|
+
2. Precompile Templates: For frequently used templates, precompile them
|
|
616
|
+
3. Use Template Inheritance: Reduces duplication and improves maintainability
|
|
617
|
+
4. Minimize Complex Logic in Templates: Move complex logic to controllers or services
|
|
618
|
+
5. Use Includes for Reusable Components: Create reusable partials for common UI elements
|
|
619
|
+
|
|
620
|
+
Troubleshooting
|
|
621
|
+
|
|
622
|
+
Common Issues
|
|
623
|
+
|
|
624
|
+
1. Template not found: Ensure template directory is correctly configured
|
|
625
|
+
2. Syntax errors: Check for missing `@endif` or `@endforeach`
|
|
626
|
+
3. Function not defined: Register custom functions before rendering
|
|
627
|
+
4. Cache issues: Clear cache with `engine.clearCache()`
|
|
628
|
+
|
|
629
|
+
Debug Mode
|
|
630
|
+
|
|
631
|
+
Enable debug mode for detailed error messages:
|
|
632
|
+
|
|
633
|
+
```javascript
|
|
634
|
+
const engine = new AetherEngine({
|
|
635
|
+
debug: process.env.NODE_ENV === 'development'
|
|
636
|
+
});
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
License
|
|
640
|
+
|
|
641
|
+
MIT License - see LICENSE file for details.
|
|
642
|
+
|
|
643
|
+
Support
|
|
644
|
+
|
|
645
|
+
For issues and feature requests, please visit the [GitHub repository](https://github.com/yourusername/@aetherframework/template-engine).
|
|
646
|
+
|
|
647
|
+
Contributing
|
|
648
|
+
|
|
649
|
+
1. Fork the repository
|
|
650
|
+
2. Create a feature branch
|
|
651
|
+
3. Make your changes
|
|
652
|
+
4. Add tests
|
|
653
|
+
5. Submit a pull request
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
📄 License
|
|
657
|
+
|
|
658
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
659
|
+
|
|
660
|
+
---
|
|
661
|
+
|
|
662
|
+
Made with ❤️ by the Aether Team
|