@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.
@@ -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
- constructor(options = {}) {
19
- this.name = 'aether';
20
- this.version = '1.3.0'; // Version upgraded
21
- this.initialized = false;
22
-
23
- this.options = {
24
- templateDir: options.templateDir || './templates',
25
- cacheEnabled: options.cacheEnabled !== false,
26
- cacheTTL: options.cacheTTL || 3600,
27
- compileCache: new Map(),
28
- templateCache: new Map(),
29
- debug: options.debug || false,
30
- csrfToken: options.csrfToken || '',
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
- this.ensureTemplateDir();
40
- this.registerDefaultFilters();
41
- this.registerDefaultFunctions();
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
- async render(templateName, data = {}, options = {}) {
516
- if (!this.initialized) this.initialize();
517
- let templateContent = templateName;
518
-
519
- const isFilePath = typeof templateName === 'string' && !templateName.includes('\n') && !templateName.includes('<') && (templateName.endsWith('.aether') || templateName.endsWith('.html') || templateName.includes('/') || templateName.includes('\\'));
520
- if (isFilePath) templateContent = await this.loadTemplate(templateName);
521
-
522
- const extendsMatch = templateContent.match(/@extends\s*\(\s*'([^']+)'\s*\)/);
523
- if (extendsMatch) {
524
- const layoutName = extendsMatch[1];
525
- try {
526
- const layoutContent = await this.loadTemplate(layoutName);
527
- const sections = {};
528
- const sectionRegex = /@section\s*\(\s*'([^']+)'\s*\)([\s\S]*?)@endsection/g;
529
- let sectionMatch;
530
- while ((sectionMatch = sectionRegex.exec(templateContent)) !== null) sections[sectionMatch[1]] = sectionMatch[2].trim();
531
-
532
- const inlineSectionRegex = /@section\s*\(\s*'([^']+)'\s*,\s*'([^']*)'\s*\)/g;
533
- let inlineMatch;
534
- while ((inlineMatch = inlineSectionRegex.exec(templateContent)) !== null) sections[inlineMatch[1]] = inlineMatch[2];
535
-
536
- const yieldRegex = /@yield\s*\(\s*'([^']+)'(?:\s*,\s*'([^']*)')?\s*\)/g;
537
- templateContent = layoutContent.replace(yieldRegex, (match, name, defaultValue) => sections[name] !== undefined ? sections[name] : (defaultValue || ''));
538
- } catch (error) { console.warn(`Warning: Could not load layout '${layoutName}':`, error.message); }
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
- const renderFunc = this.compile(templateContent);
542
- // FIX: Pass engine instance to helpers so __include can synchronously call loadTemplateSync
543
- return renderFunc(data, {
544
- filters: this.filters,
545
- functions: this.functions,
546
- includes: options.includes || {},
547
- engine: this
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) {