@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/index.js ADDED
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Aether Template Engine - Main Entry Point
3
+ * Provides a unified interface to access the Template Engine Factory
4
+ */
5
+
6
+ import TemplateEngineFactory from './src/core/TemplateEngineFactory.js';
7
+ import ConfigLoader from './src/utils/ConfigLoader.js';
8
+
9
+ // Export main factory class
10
+ export { TemplateEngineFactory };
11
+
12
+ // Export utility classes
13
+ export { ConfigLoader };
14
+
15
+ // Export engine classes
16
+ export { default as AetherEngine } from './src/engines/AetherEngine.js';
17
+ export { default as SSRModeEngine } from './src/engines/SSRModeEngine.js';
18
+ export { default as TemplateModeEngine } from './src/engines/TemplateModeEngine.js';
19
+ export { default as BaseEngine } from './src/engines/BaseEngine.js';
20
+
21
+ // Export core components
22
+ export { default as EngineRegistry } from './src/core/EngineRegistry.js';
23
+ export { default as ModeManager } from './src/core/ModeManager.js';
24
+ export { default as CacheManager } from './src/core/CacheManager.js';
25
+
26
+ /**
27
+ * Create a template engine factory with default configuration
28
+ * @param {Object} options - Configuration options
29
+ * @returns {Promise<TemplateEngineFactory>} Template engine factory instance
30
+ */
31
+ export async function createEngine(options = {}) {
32
+ // Create factory instance
33
+ const factory = new TemplateEngineFactory(options);
34
+
35
+ // Initialize the factory (this will register engines)
36
+ await factory.initialize();
37
+
38
+ return factory;
39
+ }
40
+
41
+ /**
42
+ * Create a template engine factory from environment configuration
43
+ * @returns {Promise<TemplateEngineFactory>} Template engine factory instance
44
+ */
45
+ export async function createEngineFromEnv() {
46
+ const configLoader = new ConfigLoader();
47
+ const config = await configLoader.load();
48
+
49
+ // Validate configuration
50
+ const validation = configLoader.validate();
51
+ if (!validation.valid) {
52
+ console.warn('⚠️ Configuration validation warnings:', validation.warnings);
53
+ if (validation.errors.length > 0) {
54
+ throw new Error(`Configuration errors: ${validation.errors.join(', ')}`);
55
+ }
56
+ }
57
+
58
+ const factory = new TemplateEngineFactory(config);
59
+ await factory.initialize();
60
+
61
+ return factory;
62
+ }
63
+
64
+ /**
65
+ * Quick start function for common use cases
66
+ * @param {Object} options - Quick start options
67
+ * @returns {Promise<Object>} Engine instance and utilities
68
+ */
69
+ export async function quickStart(options = {}) {
70
+ const defaultOptions = {
71
+ mode: 'template',
72
+ templateDir: './templates',
73
+ cacheEnabled: true,
74
+ debug: process.env.NODE_ENV === 'development'
75
+ };
76
+
77
+ const config = { ...defaultOptions, ...options };
78
+ const factory = new TemplateEngineFactory(config);
79
+ await factory.initialize();
80
+
81
+ // Create default directory structure
82
+ const fs = await import('fs-extra');
83
+ const dirs = [
84
+ config.templateDir,
85
+ `${config.templateDir}/layouts`,
86
+ `${config.templateDir}/pages`,
87
+ `${config.templateDir}/components`,
88
+ `${config.templateDir}/partials`
89
+ ];
90
+
91
+ for (const dir of dirs) {
92
+ await fs.ensureDir(dir);
93
+ }
94
+
95
+ // Create default layout
96
+ const defaultLayout = `<!DOCTYPE html>
97
+ <html lang="zh-CN">
98
+ <head>
99
+ <meta charset="UTF-8">
100
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
101
+ <title>@yield('title', 'Aether Framework')</title>
102
+ <meta name="description" content="@yield('description', '下一代轻量级全栈解决方案')">
103
+ <meta name="keywords" content="@yield('keywords', 'Aether,Framework,Web')">
104
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
105
+ <script src="https://cdn.tailwindcss.com"></script>
106
+ @yield('head')
107
+ </head>
108
+ <body class="antialiased bg-[#020617] text-white">
109
+ @yield('content')
110
+ @yield('scripts')
111
+ </body>
112
+ </html>`;
113
+
114
+ const layoutPath = `${config.templateDir}/layouts/default.aether`;
115
+ await fs.writeFile(layoutPath, defaultLayout, 'utf-8');
116
+
117
+ // Create example page
118
+ const examplePage = `@extends('layouts/default')
119
+
120
+ @section('title', 'Welcome to Aether Framework')
121
+
122
+ @section('description', 'A modern template engine for Node.js applications')
123
+
124
+ @section('keywords', 'template, engine, nodejs, javascript')
125
+
126
+ @section('content')
127
+ <div class="min-h-screen flex items-center justify-center">
128
+ <div class="text-center">
129
+ <h1 class="text-4xl font-bold mb-6">Welcome to Aether Framework</h1>
130
+ <p class="text-xl text-gray-300 mb-8">Your template engine is ready to use!</p>
131
+
132
+ <div class="bg-gray-800 rounded-lg p-6 max-w-md mx-auto">
133
+ <h2 class="text-2xl font-semibold mb-4">Quick Start</h2>
134
+ <p class="mb-4">This is an example page created with Aether Template Engine.</p>
135
+
136
+ <div class="space-y-3">
137
+ <div class="flex items-center justify-between">
138
+ <span>Mode:</span>
139
+ <code class="bg-gray-900 px-2 py-1 rounded">{{mode}}</code>
140
+ </div>
141
+ <div class="flex items-center justify-between">
142
+ <span>Cache:</span>
143
+ <span class="{{cacheEnabled ? 'text-green-500' : 'text-red-500'}}">{{cacheEnabled ? 'Enabled' : 'Disabled'}}</span>
144
+ </div>
145
+ <div class="flex items-center justify-between">
146
+ <span>Template Dir:</span>
147
+ <code class="bg-gray-900 px-2 py-1 rounded">{{templateDir}}</code>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ </div>
152
+ </div>
153
+ @endsection
154
+
155
+ @section('scripts')
156
+ <script>
157
+ console.log('Aether Template Engine loaded successfully!');
158
+ </script>
159
+ @endsection`;
160
+
161
+ const pagePath = `${config.templateDir}/pages/welcome.aether`;
162
+ await fs.writeFile(pagePath, examplePage, 'utf-8');
163
+
164
+ // Create renderer
165
+ const renderer = factory.createRenderer('aether');
166
+
167
+ return {
168
+ factory,
169
+ renderer,
170
+ config
171
+ };
172
+ }
173
+
174
+ export default {
175
+ createEngine,
176
+ createEngineFromEnv,
177
+ quickStart,
178
+ TemplateEngineFactory
179
+ };
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@aetherframework/template-engine",
3
+ "version": "1.0.0",
4
+ "description": "A lightweight, high-performance template engine with SSR support and custom syntax",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "start": "node examples/basic-usage.js",
9
+ "test:basic": "node examples/basic-usage.js",
10
+ "test:layout": "node examples/layout-example.js",
11
+ "test:ssr": "node examples/ssr-example.js",
12
+ "clean": "rm -rf templates/cache"
13
+ },
14
+ "author": "Aether Framework Team",
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/aetherjs/aetherframework-template-engine.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/aetherjs/aetherframework-template-engine/issues",
22
+ "email": "support@aetherjs.org"
23
+ },
24
+ "homepage": "https://www.aetherjs.org",
25
+ "files": [
26
+ "index.js",
27
+ "README.md",
28
+ "src",
29
+ "examples",
30
+ "LICENSE"
31
+ ],
32
+ "keywords": [
33
+ "template",
34
+ "engine",
35
+ "ssr",
36
+ "aether",
37
+ "html"
38
+ ],
39
+ "dependencies": {
40
+ "fs-extra": "^11.2.0"
41
+ }
42
+ }
@@ -0,0 +1,245 @@
1
+ /**
2
+ * @license MIT
3
+ * Copyright (c) 2026-present AetherFramework Contributors.
4
+ * SPDX-License-Identifier: MIT
5
+ * @module @aetherframework/template-engine/src/core/CacheManager
6
+ */
7
+ /**
8
+ * Cache Manager - Manages template and compilation cache
9
+ */
10
+ class CacheManager {
11
+ constructor(options = {}) {
12
+ this.options = {
13
+ enabled: options.enabled !== false,
14
+ ttl: options.ttl || 300000, // 5 minutes default
15
+ maxSize: options.maxSize || 1000,
16
+ ...options
17
+ };
18
+
19
+ this.cache = new Map();
20
+ this.stats = {
21
+ hits: 0,
22
+ misses: 0,
23
+ sets: 0,
24
+ deletes: 0,
25
+ size: 0
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Set cache entry
31
+ * @param {string} key - Cache key
32
+ * @param {any} value - Cache value
33
+ * @param {number} ttl - Time to live in milliseconds
34
+ * @returns {CacheManager} This instance for chaining
35
+ */
36
+ set(key, value, ttl = this.options.ttl) {
37
+ if (!this.options.enabled) return this;
38
+
39
+ // Check cache size limit
40
+ if (this.cache.size >= this.options.maxSize) {
41
+ this.evictOldest();
42
+ }
43
+
44
+ const entry = {
45
+ value,
46
+ timestamp: Date.now(),
47
+ ttl,
48
+ expiresAt: Date.now() + ttl
49
+ };
50
+
51
+ this.cache.set(key, entry);
52
+ this.stats.sets++;
53
+ this.stats.size = this.cache.size;
54
+
55
+ return this;
56
+ }
57
+
58
+ /**
59
+ * Get cache entry
60
+ * @param {string} key - Cache key
61
+ * @returns {any} Cached value or undefined
62
+ */
63
+ get(key) {
64
+ if (!this.options.enabled) return undefined;
65
+
66
+ const entry = this.cache.get(key);
67
+
68
+ if (!entry) {
69
+ this.stats.misses++;
70
+ return undefined;
71
+ }
72
+
73
+ // Check if entry has expired
74
+ if (Date.now() > entry.expiresAt) {
75
+ this.cache.delete(key);
76
+ this.stats.misses++;
77
+ this.stats.size = this.cache.size;
78
+ return undefined;
79
+ }
80
+
81
+ this.stats.hits++;
82
+ return entry.value;
83
+ }
84
+
85
+ /**
86
+ * Check if cache has key
87
+ * @param {string} key - Cache key
88
+ * @returns {boolean} True if key exists and is not expired
89
+ */
90
+ has(key) {
91
+ if (!this.options.enabled) return false;
92
+
93
+ const entry = this.cache.get(key);
94
+ if (!entry) return false;
95
+
96
+ if (Date.now() > entry.expiresAt) {
97
+ this.cache.delete(key);
98
+ this.stats.size = this.cache.size;
99
+ return false;
100
+ }
101
+
102
+ return true;
103
+ }
104
+
105
+ /**
106
+ * Delete cache entry
107
+ * @param {string} key - Cache key
108
+ * @returns {boolean} True if entry was deleted
109
+ */
110
+ delete(key) {
111
+ const deleted = this.cache.delete(key);
112
+ if (deleted) {
113
+ this.stats.deletes++;
114
+ this.stats.size = this.cache.size;
115
+ }
116
+ return deleted;
117
+ }
118
+
119
+ /**
120
+ * Clear all cache entries
121
+ * @returns {CacheManager} This instance for chaining
122
+ */
123
+ clear() {
124
+ this.cache.clear();
125
+ this.stats.size = 0;
126
+ this.stats.deletes++;
127
+ return this;
128
+ }
129
+
130
+ /**
131
+ * Get cache size
132
+ * @returns {number} Number of cache entries
133
+ */
134
+ size() {
135
+ return this.cache.size;
136
+ }
137
+
138
+ /**
139
+ * Get cache statistics
140
+ * @returns {Object} Cache statistics
141
+ */
142
+ getStats() {
143
+ return {
144
+ ...this.stats,
145
+ enabled: this.options.enabled,
146
+ ttl: this.options.ttl,
147
+ maxSize: this.options.maxSize,
148
+ currentSize: this.cache.size,
149
+ hitRate: this.stats.hits + this.stats.misses > 0
150
+ ? (this.stats.hits / (this.stats.hits + this.stats.misses) * 100).toFixed(2) + '%'
151
+ : '0%'
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Evict oldest cache entries
157
+ * @private
158
+ */
159
+ evictOldest() {
160
+ if (this.cache.size === 0) return;
161
+
162
+ // Find oldest entry
163
+ let oldestKey = null;
164
+ let oldestTime = Infinity;
165
+
166
+ for (const [key, entry] of this.cache) {
167
+ if (entry.timestamp < oldestTime) {
168
+ oldestTime = entry.timestamp;
169
+ oldestKey = key;
170
+ }
171
+ }
172
+
173
+ if (oldestKey) {
174
+ this.cache.delete(oldestKey);
175
+ this.stats.deletes++;
176
+ this.stats.size = this.cache.size;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Clean expired cache entries
182
+ * @returns {number} Number of entries cleaned
183
+ */
184
+ cleanExpired() {
185
+ let cleaned = 0;
186
+ const now = Date.now();
187
+
188
+ for (const [key, entry] of this.cache) {
189
+ if (now > entry.expiresAt) {
190
+ this.cache.delete(key);
191
+ cleaned++;
192
+ }
193
+ }
194
+
195
+ this.stats.size = this.cache.size;
196
+ return cleaned;
197
+ }
198
+
199
+ /**
200
+ * Enable cache
201
+ * @returns {CacheManager} This instance for chaining
202
+ */
203
+ enable() {
204
+ this.options.enabled = true;
205
+ return this;
206
+ }
207
+
208
+ /**
209
+ * Disable cache
210
+ * @returns {CacheManager} This instance for chaining
211
+ */
212
+ disable() {
213
+ this.options.enabled = false;
214
+ this.clear();
215
+ return this;
216
+ }
217
+
218
+ /**
219
+ * Set cache TTL
220
+ * @param {number} ttl - Time to live in milliseconds
221
+ * @returns {CacheManager} This instance for chaining
222
+ */
223
+ setTTL(ttl) {
224
+ this.options.ttl = ttl;
225
+ return this;
226
+ }
227
+
228
+ /**
229
+ * Set cache max size
230
+ * @param {number} maxSize - Maximum cache size
231
+ * @returns {CacheManager} This instance for chaining
232
+ */
233
+ setMaxSize(maxSize) {
234
+ this.options.maxSize = maxSize;
235
+
236
+ // Evict entries if current size exceeds new max
237
+ while (this.cache.size > maxSize) {
238
+ this.evictOldest();
239
+ }
240
+
241
+ return this;
242
+ }
243
+ }
244
+
245
+ export default CacheManager;
@@ -0,0 +1,148 @@
1
+ /**
2
+ * @license MIT
3
+ * Copyright (c) 2026-present AetherFramework Contributors.
4
+ * SPDX-License-Identifier: MIT
5
+ * @module @aetherframework/template-engine/src/core/EngineRegistry
6
+ */
7
+
8
+ /**
9
+ * Engine Registry - Manages template engine registration and retrieval
10
+ */
11
+ class EngineRegistry {
12
+ constructor() {
13
+ this.engines = new Map();
14
+ this.defaultEngine = null;
15
+ }
16
+
17
+ /**
18
+ * Register a template engine
19
+ * @param {string} name - Engine name
20
+ * @param {BaseEngine} engine - Engine instance
21
+ * @param {boolean} isDefault - Whether to set as default engine
22
+ */
23
+ register(name, engine, isDefault = false) {
24
+ if (this.engines.has(name)) {
25
+ console.warn(`⚠️ Engine "${name}" already registered, overwriting...`);
26
+ }
27
+
28
+ this.engines.set(name, engine);
29
+
30
+ if (isDefault || this.engines.size === 1) {
31
+ this.defaultEngine = name;
32
+ }
33
+
34
+ return this;
35
+ }
36
+
37
+ /**
38
+ * Get engine by name
39
+ * @param {string} name - Engine name
40
+ * @returns {BaseEngine} Engine instance
41
+ */
42
+ get(name) {
43
+ if (!this.engines.has(name)) {
44
+ throw new Error(`Engine "${name}" not found. Available engines: ${Array.from(this.engines.keys()).join(', ')}`);
45
+ }
46
+ return this.engines.get(name);
47
+ }
48
+
49
+ /**
50
+ * Check if engine exists
51
+ * @param {string} name - Engine name
52
+ * @returns {boolean} True if engine exists
53
+ */
54
+ has(name) {
55
+ return this.engines.has(name);
56
+ }
57
+
58
+ /**
59
+ * Remove engine by name
60
+ * @param {string} name - Engine name
61
+ * @returns {boolean} True if engine was removed
62
+ */
63
+ unregister(name) {
64
+ const removed = this.engines.delete(name);
65
+
66
+ if (removed && this.defaultEngine === name) {
67
+ this.defaultEngine = this.engines.keys().next().value || null;
68
+ }
69
+
70
+ return removed;
71
+ }
72
+
73
+ /**
74
+ * Get default engine
75
+ * @returns {BaseEngine} Default engine instance
76
+ */
77
+ getDefault() {
78
+ if (!this.defaultEngine) {
79
+ throw new Error('No default engine set');
80
+ }
81
+ return this.get(this.defaultEngine);
82
+ }
83
+
84
+ /**
85
+ * Set default engine
86
+ * @param {string} name - Engine name
87
+ */
88
+ setDefault(name) {
89
+ if (!this.engines.has(name)) {
90
+ throw new Error(`Cannot set default: Engine "${name}" not found`);
91
+ }
92
+ this.defaultEngine = name;
93
+ return this;
94
+ }
95
+
96
+ /**
97
+ * List all registered engines
98
+ * @returns {Array} List of engine names
99
+ */
100
+ listEngines() {
101
+ return Array.from(this.engines.keys());
102
+ }
103
+
104
+ /**
105
+ * Get engine metadata
106
+ * @param {string} name - Engine name
107
+ * @returns {Object} Engine metadata
108
+ */
109
+ getEngineInfo(name) {
110
+ const engine = this.get(name);
111
+ return engine ? engine.getMetadata() : null;
112
+ }
113
+
114
+ /**
115
+ * Clear all engine caches
116
+ */
117
+ clearCaches() {
118
+ for (const [name, engine] of this.engines) {
119
+ if (typeof engine.clearCache === 'function') {
120
+ engine.clearCache();
121
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Get engine statistics
127
+ * @returns {Object} Statistics
128
+ */
129
+ getStats() {
130
+ const stats = {
131
+ totalEngines: this.engines.size,
132
+ defaultEngine: this.defaultEngine,
133
+ engines: []
134
+ };
135
+
136
+ for (const [name, engine] of this.engines) {
137
+ stats.engines.push({
138
+ name,
139
+ initialized: engine.initialized || false,
140
+ metadata: engine.getMetadata ? engine.getMetadata() : {}
141
+ });
142
+ }
143
+
144
+ return stats;
145
+ }
146
+ }
147
+
148
+ export default EngineRegistry;