@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 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