|
4 | 4 | * |
5 | 5 | * This compiler transforms DSL-based page and blog definitions |
6 | 6 | * into the standard format used by ProcPage.js and ProcPageManager.js. |
| 7 | + * |
| 8 | + * Since the compiler makes an assumption for possible human error, |
| 9 | + * Please don't run your ProcPages site in real time mode, |
| 10 | + * Pre-compile your pages before publishing changes to the web for the best security and performance. |
| 11 | + * |
| 12 | + * I'd like to allow the most flexibility possible for users to define their pages in a way that makes the most sense to them, |
| 13 | + * So the compiler is designed to be forgiving and adaptable to different styles of page definition. |
7 | 14 | */ |
8 | 15 |
|
9 | | -import { ProcPagesDSL } from './parser.js'; |
| 16 | +import { ProcPagesDSL, Signal } from './parser.js'; |
10 | 17 | import { validate } from './schema.js'; |
11 | 18 |
|
12 | 19 | export class ProcPagesCompiler { |
13 | | - constructor() { |
14 | | - this.dsl = new ProcPagesDSL(); |
15 | | - } |
| 20 | + constructor() { |
| 21 | + this.dsl = new ProcPagesDSL(); |
| 22 | + this.globalSignals = {}; |
| 23 | + this.globalFuncs = {}; |
| 24 | + } |
16 | 25 |
|
17 | | - /** |
18 | | - * Compile DSL string or object into standard format |
19 | | - * @param {Object} input - DSL object to compile |
20 | | - * @returns {Object} - Compiled results |
21 | | - */ |
22 | | - compile(input) { |
23 | | - // Validate input against DSL schema |
24 | | - for (const [id, config] of Object.entries(input)) { |
25 | | - const errors = validate('page', config); |
26 | | - if (errors.length > 0) { |
27 | | - console.error(`Validation error in page ${id}:`, errors); |
28 | | - continue; |
29 | | - } |
30 | | - this.dsl.page(id, config); |
31 | | - } |
| 26 | + /** |
| 27 | + * Compile DSL functional node or object into standard format |
| 28 | + * @param {Object} input - DSL component or object to compile |
| 29 | + * @returns {Object} - Compiled results |
| 30 | + */ |
| 31 | + compile(input) { |
| 32 | + // If it's a Node structure (functional DSL) |
| 33 | + if (input.type && input.props) { |
| 34 | + return this.compileNode(input); |
| 35 | + } |
32 | 36 |
|
33 | | - const compiledPages = this.dsl.compile(); |
34 | | - const results = {}; |
| 37 | + // Validate input against DSL schema for backward compatibility with pure JSON |
| 38 | + const results = {}; |
| 39 | + for (const [id, config] of Object.entries(input)) { |
| 40 | + const errors = validate('page', config); |
| 41 | + if (errors.length > 0) { |
| 42 | + console.error(`Validation error in page ${id}:`, errors); |
| 43 | + continue; |
| 44 | + } |
| 45 | + results[id] = this.finalizePageData(config); |
| 46 | + } |
35 | 47 |
|
36 | | - for (const [id, pageData] of Object.entries(compiledPages)) { |
37 | | - // Further transformations to match exact ProcPage constructor needs |
38 | | - results[id] = this.finalizePageData(pageData); |
39 | | - } |
| 48 | + return results; |
| 49 | + } |
40 | 50 |
|
41 | | - return results; |
| 51 | + /** |
| 52 | + * Compile a functional Node tree |
| 53 | + * @param {Object} node |
| 54 | + */ |
| 55 | + compileNode(node) { |
| 56 | + switch(node.type) { |
| 57 | + case "Header": |
| 58 | + this.processHeaderNode(node); |
| 59 | + return null; |
| 60 | + case "Page": |
| 61 | + return this.finalizePageData(this.dsl.parseNode(node)); |
| 62 | + default: |
| 63 | + return this.dsl.parseNode(node); |
42 | 64 | } |
| 65 | + } |
43 | 66 |
|
44 | | - /** |
45 | | - * Finalize the page data into the exact format ProcPage.js expects |
46 | | - * @param {Object} data - DSL-compiled page data |
47 | | - */ |
48 | | - finalizePageData(data) { |
49 | | - const final = { |
50 | | - 'page': data.page, |
51 | | - 'header': data.header || data.page, |
52 | | - 'theme': data.theme || '#184d76', |
53 | | - 'layout': data.layout || 'triple', |
54 | | - 'activeNavButton': data.activeNavButton || [], |
55 | | - 'subHeader': data.subHeader || '', |
56 | | - 'footer': data.footer || '', |
57 | | - 'pageStyles': data.pageStyles || {}, |
58 | | - 'styleOverrides': data.styleOverrides || {}, |
59 | | - 'initialSection': data.initialSection || 0, |
60 | | - 'sections': data.sections || [], |
61 | | - 'metaData': data.metaData || {} |
62 | | - }; |
| 67 | + /** |
| 68 | + * Process Header node for global state and engine metadata |
| 69 | + */ |
| 70 | + processHeaderNode(node) { |
| 71 | + node.children.forEach(child => { |
| 72 | + if (child.type === 'signals') { |
| 73 | + Object.entries(child.value).forEach(([k, v]) => { |
| 74 | + this.globalSignals[k] = Signal(v); |
| 75 | + }); |
| 76 | + } |
| 77 | + if (child.type === 'funcs') { |
| 78 | + Object.entries(child.value).forEach(([k, v]) => { |
| 79 | + this.globalFuncs[k] = v; |
| 80 | + }); |
| 81 | + } |
| 82 | + if (child.type === 'global') { |
| 83 | + // Future global processing |
| 84 | + } |
| 85 | + }); |
| 86 | + } |
63 | 87 |
|
64 | | - // Handle the optional pxlNav callbacks and triggers |
65 | | - // pxlNav: { room: '...', view: '...', settings: { ... } } |
66 | | - if (data.pxlNav) { |
67 | | - final.pxlNav = { |
68 | | - room: data.pxlNav.room, |
69 | | - view: data.pxlNav.view, |
70 | | - settings: data.pxlNav.settings || {}, |
71 | | - callbacks: data.pxlNav.callbacks || [] |
72 | | - }; |
73 | | - } |
| 88 | + /** |
| 89 | + * Finalize the page data into the format ProcPage.js expects |
| 90 | + * @param {Object} data - DSL-compiled page data |
| 91 | + */ |
| 92 | + finalizePageData(data) { |
| 93 | + const final = { |
| 94 | + 'page': data.id, |
| 95 | + 'header': data.title || data.id, |
| 96 | + 'theme': data.theme || '#184d76', |
| 97 | + 'layout': data.layout || 'triple', |
| 98 | + 'activeNavButton': data.activeNavButton || [], |
| 99 | + 'subHeader': data.subHeader || '', |
| 100 | + 'footer': data.footer || '', |
| 101 | + 'pageStyles': data.pageStyles || {}, |
| 102 | + 'styleOverrides': data.styleOverrides || {}, |
| 103 | + 'initialSection': data.initialSection || 0, |
| 104 | + 'sections': data.sections || [], |
| 105 | + 'metaData': data.metaData || {}, |
| 106 | + // Extended logic properties |
| 107 | + 'signals': data.signals || {}, |
| 108 | + 'funcs': data.funcs || {}, |
| 109 | + 'onLoad': data.onLoad || null |
| 110 | + }; |
74 | 111 |
|
75 | | - return final; |
| 112 | + // Handle the optional pxlNav callbacks and triggers |
| 113 | + if (data.pxlNav) { |
| 114 | + final.pxlNav = { |
| 115 | + room: data.pxlNav.room, |
| 116 | + view: data.pxlNav.view, |
| 117 | + settings: data.pxlNav.settings || {}, |
| 118 | + callbacks: data.pxlNav.callbacks || [], |
| 119 | + signals: data.pxlNav.signals || {} |
| 120 | + }; |
76 | 121 | } |
77 | 122 |
|
78 | | - /** |
79 | | - * Generate actual JavaScript code for a page from compiled data |
80 | | - * This is used in the pre-compile pipeline for SEO/Runtime. |
81 | | - * @param {string} id - Page ID |
82 | | - * @param {Object} compiledData - The finalized page data |
83 | | - */ |
84 | | - generateJS(id, compiledData) { |
85 | | - const jsonContent = JSON.stringify(compiledData, null, 2); |
86 | | - return ` |
| 123 | + return final; |
| 124 | + } |
| 125 | + |
| 126 | + /** |
| 127 | + * Generate actual JavaScript code for a page from compiled data |
| 128 | + * This is used in the pre-compile pipeline for SEO/Runtime. |
| 129 | + * @param {string} id - Page ID |
| 130 | + * @param {Object} compiledData - The finalized page data |
| 131 | + */ |
| 132 | + generateJS(id, compiledData) { |
| 133 | + const jsonContent = JSON.stringify(compiledData, (key, value) => { |
| 134 | + // Special handling to stringify functions or skip them for raw JSON |
| 135 | + if (typeof value === 'function') { |
| 136 | + return value.toString(); |
| 137 | + } |
| 138 | + return value; |
| 139 | + }, 2); |
| 140 | + |
| 141 | + return ` |
87 | 142 | import { PageMetaData } from '../../js/procPages/PageMetaData.js'; |
88 | 143 | import { ProcPage } from '../../js/procPages/ProcPage.js'; |
89 | 144 |
|
| 145 | +// Global Engine Logic for Page: ${id} |
90 | 146 | const metaDataInput = ${JSON.stringify(compiledData.metaData, null, 2)}; |
91 | 147 | const pageContentObject = ${jsonContent}; |
92 | 148 |
|
| 149 | +// Revive functions if stored as strings |
| 150 | +Object.entries(pageContentObject).forEach(([k, v]) => { |
| 151 | + if (typeof v === 'string' && v.startsWith('(')) { |
| 152 | + pageContentObject[k] = eval(v); |
| 153 | + } |
| 154 | +}); |
| 155 | +
|
93 | 156 | const metaData = new PageMetaData(metaDataInput); |
94 | 157 | pageContentObject['metaData'] = metaData; |
95 | 158 | export const pageData = new ProcPage(pageContentObject); |
96 | | - `.trim(); |
97 | | - } |
| 159 | + `.trim(); |
| 160 | + } |
98 | 161 | } |
0 commit comments