@aetherframework/template-engine 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +430 -0
- package/index.js +17 -1
- package/package.json +1 -1
- package/src/engines/AetherEngine.js +162 -56
- package/src/engines/CompressionEngine.js +642 -0
- package/src/engines/SSRModeEngine.js +29 -4
- package/src/engines/TemplateModeEngine.js +29 -3
- package/examples/dist/basic-usage-result.html +0 -31
- package/examples/dist/layout-example-result.html +0 -210
- package/examples/dist/templates/layouts/main.aether +0 -58
- package/examples/dist/templates/pages/home.aether +0 -116
|
@@ -13,40 +13,95 @@
|
|
|
13
13
|
import fs from 'fs-extra';
|
|
14
14
|
import path from 'path';
|
|
15
15
|
import crypto from 'crypto';
|
|
16
|
+
import CompressionEngine from './CompressionEngine.js';
|
|
16
17
|
|
|
17
18
|
class AetherEngine {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
...options
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
this.filters = new Map();
|
|
35
|
-
this.functions = new Map();
|
|
36
|
-
this.layouts = new Map();
|
|
37
|
-
this.routes = {};
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.name = 'aether';
|
|
21
|
+
this.version = '1.3.0';
|
|
22
|
+
this.initialized = false;
|
|
23
|
+
|
|
24
|
+
this.options = {
|
|
25
|
+
templateDir: options.templateDir || './templates',
|
|
26
|
+
cacheEnabled: options.cacheEnabled !== false,
|
|
27
|
+
cacheTTL: options.cacheTTL || 3600,
|
|
28
|
+
compileCache: new Map(),
|
|
29
|
+
templateCache: new Map(),
|
|
30
|
+
debug: options.debug || false,
|
|
31
|
+
csrfToken: options.csrfToken || '',
|
|
38
32
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
// Compression options
|
|
34
|
+
compressionEnabled: options.compressionEnabled !== false,
|
|
35
|
+
minifyHTML: options.minifyHTML !== false,
|
|
36
|
+
minifyCSS: options.minifyCSS !== false,
|
|
37
|
+
minifyJS: options.minifyJS !== false,
|
|
38
|
+
mangleJS: options.mangleJS || false,
|
|
39
|
+
removeComments: options.removeComments || false,
|
|
40
|
+
collapseWhitespace: options.collapseWhitespace || true,
|
|
41
|
+
cacheCompressed: options.cacheCompressed !== false,
|
|
42
|
+
...options
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Initialize compression engine
|
|
46
|
+
this.compressionEngine = new CompressionEngine(this.options);
|
|
47
|
+
|
|
48
|
+
this.filters = new Map();
|
|
49
|
+
this.functions = new Map();
|
|
50
|
+
this.layouts = new Map();
|
|
51
|
+
this.routes = {};
|
|
52
|
+
|
|
53
|
+
this.ensureTemplateDir();
|
|
54
|
+
this.registerDefaultFilters();
|
|
55
|
+
this.registerDefaultFunctions();
|
|
56
|
+
}
|
|
43
57
|
|
|
44
58
|
initialize() {
|
|
45
59
|
if (this.initialized) return;
|
|
46
60
|
console.log(`Aether Engine initialized (v${this.version})`);
|
|
47
61
|
this.initialized = true;
|
|
48
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Process content with compression based on options
|
|
65
|
+
* @param {string} content - Content to process
|
|
66
|
+
* @param {Object} options - Processing options
|
|
67
|
+
* @returns {string} Processed content
|
|
68
|
+
*/
|
|
69
|
+
processWithCompression(content, options = {}) {
|
|
70
|
+
if (!this.options.compressionEnabled) {
|
|
71
|
+
return content;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const processOptions = {
|
|
75
|
+
minifyHTML: this.options.minifyHTML,
|
|
76
|
+
minifyCSS: this.options.minifyCSS,
|
|
77
|
+
minifyJS: this.options.minifyJS,
|
|
78
|
+
mangleJS: this.options.mangleJS,
|
|
79
|
+
removeComments: this.options.removeComments,
|
|
80
|
+
collapseWhitespace: this.options.collapseWhitespace,
|
|
81
|
+
cacheCompressed: this.options.cacheCompressed,
|
|
82
|
+
...options
|
|
83
|
+
};
|
|
49
84
|
|
|
85
|
+
return this.compressionEngine.processHTML(content, processOptions);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Clear compression cache
|
|
90
|
+
*/
|
|
91
|
+
clearCompressionCache() {
|
|
92
|
+
if (this.compressionEngine) {
|
|
93
|
+
this.compressionEngine.clearCompressionCache();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get compression statistics
|
|
99
|
+
* @returns {Object} Compression statistics
|
|
100
|
+
*/
|
|
101
|
+
getCompressionStats() {
|
|
102
|
+
return this.compressionEngine ? this.compressionEngine.getStats() : null;
|
|
103
|
+
}
|
|
104
|
+
|
|
50
105
|
ensureTemplateDir() {
|
|
51
106
|
const dirs = [
|
|
52
107
|
this.options.templateDir,
|
|
@@ -512,41 +567,92 @@ class AetherEngine {
|
|
|
512
567
|
}
|
|
513
568
|
}
|
|
514
569
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
570
|
+
async render(templateName, data = {}, options = {}) {
|
|
571
|
+
if (!this.initialized) this.initialize();
|
|
572
|
+
let templateContent = templateName;
|
|
573
|
+
|
|
574
|
+
const isFilePath = typeof templateName === 'string' && !templateName.includes('\n') && !templateName.includes('<') && (templateName.endsWith('.aether') || templateName.endsWith('.html') || templateName.includes('/') || templateName.includes('\\'));
|
|
575
|
+
if (isFilePath) templateContent = await this.loadTemplate(templateName);
|
|
576
|
+
|
|
577
|
+
// 【修复1】:恢复正确的正则表达式 \(\)
|
|
578
|
+
const extendsMatch = templateContent.match(/@extends\s*\(\s*'([^']+)'\s*\)/);
|
|
579
|
+
if (extendsMatch) {
|
|
580
|
+
// 【修复2】:恢复正确的匹配组索引 [1]
|
|
581
|
+
const layoutName = extendsMatch[1];
|
|
582
|
+
try {
|
|
583
|
+
const layoutContent = await this.loadTemplate(layoutName);
|
|
584
|
+
const sections = {};
|
|
585
|
+
|
|
586
|
+
// 【修复1】:恢复正确的正则表达式
|
|
587
|
+
const sectionRegex = /@section\s*\(\s*'([^']+)'\s*\)([\s\S]*?)@endsection/g;
|
|
588
|
+
let sectionMatch;
|
|
589
|
+
while ((sectionMatch = sectionRegex.exec(templateContent)) !== null) {
|
|
590
|
+
// 【修复2】:恢复正确的匹配组索引 [1] 和 [2]
|
|
591
|
+
sections[sectionMatch[1]] = sectionMatch[2].trim();
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// 【修复1】:恢复正确的正则表达式
|
|
595
|
+
const inlineSectionRegex = /@section\s*\(\s*'([^']+)'\s*,\s*'([^']*)'\s*\)/g;
|
|
596
|
+
let inlineMatch;
|
|
597
|
+
while ((inlineMatch = inlineSectionRegex.exec(templateContent)) !== null) {
|
|
598
|
+
// 【修复2】:恢复正确的匹配组索引 [1] 和 [2]
|
|
599
|
+
sections[inlineMatch[1]] = inlineMatch[2];
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// 【修复1】:恢复正确的正则表达式
|
|
603
|
+
const yieldRegex = /@yield\s*\(\s*'([^']+)'(?:\s*,\s*'([^']*)')?\s*\)/g;
|
|
604
|
+
templateContent = layoutContent.replace(yieldRegex, (match, name, defaultValue) =>
|
|
605
|
+
sections[name] !== undefined ? sections[name] : (defaultValue || '')
|
|
606
|
+
);
|
|
607
|
+
} catch (error) {
|
|
608
|
+
console.warn(`Warning: Could not load layout '${layoutName}':`, error.message);
|
|
539
609
|
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const renderFunc = this.compile(templateContent);
|
|
613
|
+
|
|
614
|
+
// 【关键修复】: 保存渲染结果到变量,而不是直接 return,以便后续进行压缩处理
|
|
615
|
+
const renderedContent = renderFunc(data, {
|
|
616
|
+
filters: this.filters,
|
|
617
|
+
functions: this.functions,
|
|
618
|
+
includes: options.includes || {},
|
|
619
|
+
engine: this
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// Apply compression if enabled
|
|
623
|
+
if (this.options.compressionEnabled) {
|
|
624
|
+
const compressionOptions = {
|
|
625
|
+
minifyHTML: this.options.minifyHTML,
|
|
626
|
+
minifyCSS: this.options.minifyCSS,
|
|
627
|
+
minifyJS: this.options.minifyJS,
|
|
628
|
+
mangleJS: this.options.mangleJS,
|
|
629
|
+
removeComments: this.options.removeComments,
|
|
630
|
+
collapseWhitespace: this.options.collapseWhitespace,
|
|
631
|
+
removeAttributeQuotes: this.options.removeAttributeQuotes,
|
|
632
|
+
removeEmptyAttributes: this.options.removeEmptyAttributes,
|
|
633
|
+
cacheCompressed: this.options.cacheCompressed,
|
|
634
|
+
...options.compression // Allow per-render override
|
|
635
|
+
};
|
|
540
636
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
637
|
+
// Use the compression engine to process the rendered content
|
|
638
|
+
if (this.compressionEngine) {
|
|
639
|
+
const compressedContent = this.compressionEngine.processHTML(renderedContent, compressionOptions);
|
|
640
|
+
|
|
641
|
+
// Log compression statistics in debug mode
|
|
642
|
+
if (this.options.debug) {
|
|
643
|
+
const originalSize = renderedContent.length;
|
|
644
|
+
const compressedSize = compressedContent.length;
|
|
645
|
+
const reduction = ((originalSize - compressedSize) / originalSize * 100).toFixed(2);
|
|
646
|
+
console.log(`[AetherEngine] Compression applied: ${originalSize} → ${compressedSize} bytes (${reduction}% reduction)`);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
return compressedContent;
|
|
650
|
+
}
|
|
549
651
|
}
|
|
652
|
+
|
|
653
|
+
return renderedContent;
|
|
654
|
+
}
|
|
655
|
+
|
|
550
656
|
|
|
551
657
|
// FIX: Extract unified path resolution logic, supporting dot notation (e.g., 'partials.header')
|
|
552
658
|
_getTemplatePaths(templateName) {
|