free-framework 4.7.0 → 4.7.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/compiler/generator.js +110 -113
- package/package.json +1 -1
package/compiler/generator.js
CHANGED
|
@@ -194,47 +194,44 @@ if (server.namedMiddlewares['auth']) server.namedMiddlewares['AuthGuard'] = serv
|
|
|
194
194
|
" };\n" +
|
|
195
195
|
" const props = result || (res.context && res.context.props ? res.context.props : {});\n" +
|
|
196
196
|
" const pageHtml = helpers.renderComponent('" + viewName + "', props);\n" +
|
|
197
|
-
"\n" +
|
|
198
197
|
" if (req.headers['x-free-partial']) {\n" +
|
|
199
198
|
" return res.json({ title: 'Free Ultra | " + viewName + "', content: pageHtml, url: req.url });\n" +
|
|
200
199
|
" }\n" +
|
|
201
|
-
"\n" +
|
|
202
200
|
" const headerHtml = helpers.renderComponent('Header', props);\n" +
|
|
203
|
-
" const fullHTML = `<!DOCTYPE html
|
|
204
|
-
<
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
201
|
+
" const fullHTML = `<!DOCTYPE html>\n" +
|
|
202
|
+
"<html lang=\"en\">\n" +
|
|
203
|
+
"<head>\n" +
|
|
204
|
+
" <meta charset=\"UTF-8\">\n" +
|
|
205
|
+
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" +
|
|
206
|
+
" <title>Free Ultra | " + viewName + "</title>\n" +
|
|
207
|
+
" <meta name=\"csrf-token\" content=\"${res.context.csrfToken || ''}\">\n" +
|
|
208
|
+
" <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n" +
|
|
209
|
+
" <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n" +
|
|
210
|
+
" <link href=\"https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap\" rel=\"stylesheet\">\n" +
|
|
211
|
+
" <link rel=\"stylesheet\" href=\"/css/app.css\">\n" +
|
|
212
|
+
" <style>\n" +
|
|
213
|
+
" :root { --primary:#00ff88; --secondary:#00bdff; --dark:#050505; }\n" +
|
|
214
|
+
" body { margin:0; font-family:'Outfit', system-ui, sans-serif; background:var(--dark); color:white; overflow-x:hidden; scroll-behavior:smooth; }\n" +
|
|
215
|
+
" ${scopedCSS}\n" +
|
|
216
|
+
" </style>\n" +
|
|
217
|
+
" <script src=\"/free-runtime.js\" defer></script>\n" +
|
|
218
|
+
"</head>\n" +
|
|
219
|
+
"<body>\n" +
|
|
220
|
+
" ${headerHtml}\n" +
|
|
221
|
+
" <main id=\"free-app-root\">\n" +
|
|
222
|
+
" ${pageHtml}\n" +
|
|
223
|
+
" </main>\n" +
|
|
224
|
+
"</body>\n" +
|
|
225
|
+
"</html>`;\n" +
|
|
226
|
+
" res.header('Content-Type', 'text/html').send(fullHTML);\n" +
|
|
229
227
|
" } else {\n" +
|
|
230
|
-
|
|
231
|
-
" res.json({success: true, result });\n" +
|
|
228
|
+
" res.json({ success: true, result });\n" +
|
|
232
229
|
" }\n" +
|
|
233
|
-
"});\n";
|
|
230
|
+
" });\n";
|
|
234
231
|
}
|
|
235
232
|
});
|
|
236
233
|
|
|
237
|
-
|
|
234
|
+
output += `\nif (require.main === module) {
|
|
238
235
|
ORM.migrate(modelsRegistry).then(() => {
|
|
239
236
|
server.start(process.env.PORT || 4000);
|
|
240
237
|
}).catch(err => console.error(err));
|
|
@@ -243,152 +240,152 @@ if (server.namedMiddlewares['auth']) server.namedMiddlewares['AuthGuard'] = serv
|
|
|
243
240
|
module.exports = {...modelsRegistry, server, modelsRegistry, ORM};
|
|
244
241
|
`;
|
|
245
242
|
|
|
246
|
-
|
|
243
|
+
return output;
|
|
247
244
|
}
|
|
248
245
|
|
|
249
|
-
|
|
246
|
+
function generateUltraComponent(node) {
|
|
250
247
|
const onMount = node.body.find(n => n.type === 'onMount')?.code || '';
|
|
251
248
|
const onDestroy = node.body.find(n => n.type === 'onDestroy')?.code || '';
|
|
252
249
|
const isInteractive = node.states.length > 0 || onMount !== '' || onDestroy !== '' || hasInteractivity(node.body);
|
|
253
250
|
const stateNames = node.states.map(s => s.name);
|
|
254
251
|
|
|
255
|
-
|
|
252
|
+
let code = `\nfunction render${node.name}(props = { }, helpers = { }) {\n`;
|
|
256
253
|
code += ` const e = (str) => String(str).replace(/[&<>'"]/g, m => ({'&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[m]));\n`;
|
|
257
|
-
|
|
254
|
+
code += ` const state = {\n`;
|
|
258
255
|
node.states.forEach(s => {
|
|
259
|
-
|
|
260
|
-
|
|
256
|
+
let val = s.default;
|
|
257
|
+
const isExpression = typeof val === 'string' && (val.includes('props.') || val.includes('||') || val.includes('&&') || val === 'true' || val === 'false' || val === 'null' || val.trim().startsWith('{') || val.trim().startsWith('['));
|
|
261
258
|
const finalVal = (isExpression || typeof val === 'number') ? val : `"${val}"`;
|
|
262
|
-
|
|
259
|
+
code += ` "${s.name}": ${finalVal},\n`;
|
|
263
260
|
});
|
|
264
261
|
code += ` };\n`;
|
|
265
262
|
if (stateNames.length > 0) {
|
|
266
|
-
|
|
263
|
+
code += ` const { ${stateNames.join(', ')} } = state;\n`;
|
|
267
264
|
}
|
|
268
265
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
266
|
+
code += ` const _events = [];\n`;
|
|
267
|
+
code += ` const islandAttr = ${isInteractive} ? ' data-component="${node.name}"' : '';\n`;
|
|
268
|
+
code += ` let html = "<div class='free-component'" + islandAttr + " data-state='" + JSON.stringify(state).replace(/'/g, "'") + "'";\n`;
|
|
272
269
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
270
|
+
if (onMount) {
|
|
271
|
+
code += ` _events.push({ id: 'onMount', fn: function(state) { ${onMount} } });\n`;
|
|
272
|
+
code += ` html += " data-free-on-mount='onMount'";\n`;
|
|
276
273
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
274
|
+
if (onDestroy) {
|
|
275
|
+
code += ` _events.push({ id: 'onDestroy', fn: function(state) { ${onDestroy} } });\n`;
|
|
276
|
+
code += ` html += " data-free-on-destroy='onDestroy'";\n`;
|
|
280
277
|
}
|
|
281
278
|
|
|
282
279
|
code += ` html += ">";\n`;
|
|
283
|
-
|
|
280
|
+
code += generateComponentBody(node.body, stateNames);
|
|
284
281
|
|
|
285
|
-
|
|
286
|
-
|
|
282
|
+
if (isInteractive) {
|
|
283
|
+
code += ` html += "<script>window.__free_actions = window.__free_actions || {}; window.__free_actions['${node.name}'] = {};";\n`;
|
|
287
284
|
code += ` _events.forEach(ev => {\n`;
|
|
288
|
-
|
|
285
|
+
code += ` html += "window.__free_actions['${node.name}'][ev.id] = " + ev.fn.toString() + ";";\n`;
|
|
289
286
|
code += ` });\n`;
|
|
290
|
-
|
|
287
|
+
code += ` html += "</script>";\n`;
|
|
291
288
|
}
|
|
292
289
|
|
|
293
|
-
|
|
294
|
-
|
|
290
|
+
code += ` html += "</div>";\n return html;\n}\n`;
|
|
291
|
+
return code;
|
|
295
292
|
}
|
|
296
293
|
|
|
297
|
-
|
|
298
|
-
|
|
294
|
+
function generateComponentBody(body, stateNames) {
|
|
295
|
+
let code = "";
|
|
299
296
|
body.forEach(node => {
|
|
300
297
|
if (node.type === 'tag') {
|
|
301
|
-
|
|
298
|
+
code += generateTagCode(node, stateNames);
|
|
302
299
|
} else if (node.type === 'event') {
|
|
303
300
|
return;
|
|
304
301
|
} else if (node.type === 'script') {
|
|
305
302
|
const scriptCode = node.code.replace(/`/g, '\\`').replace(/\$/g, '\\$');
|
|
306
|
-
|
|
303
|
+
code += ` html += \`<script>${scriptCode}</script>\`;\n`;
|
|
307
304
|
} else if (node.type === 'loop') {
|
|
308
|
-
|
|
305
|
+
code += " if (Array.isArray(" + node.list + ")) {\n";
|
|
309
306
|
code += " " + node.list + ".forEach(" + node.item + " => {\n";
|
|
310
|
-
|
|
307
|
+
code += generateComponentBody(node.body, stateNames).replace(/^ /gm, ' ');
|
|
311
308
|
code += " });\n";
|
|
312
309
|
code += " }\n";
|
|
313
310
|
} else if (node.type === 'condition') {
|
|
314
|
-
|
|
315
|
-
|
|
311
|
+
code += " if (" + node.condition + ") {\n";
|
|
312
|
+
code += generateComponentBody(node.body, stateNames).replace(/^ /gm, ' ');
|
|
316
313
|
code += " }\n";
|
|
317
314
|
}
|
|
318
315
|
});
|
|
319
|
-
|
|
316
|
+
return code;
|
|
320
317
|
}
|
|
321
318
|
|
|
322
|
-
|
|
319
|
+
function hasInteractivity(nodes) {
|
|
323
320
|
return nodes.some(n => {
|
|
324
321
|
if (n.type === 'tag') {
|
|
325
322
|
if (n.attributes && Object.keys(n.attributes).some(a => a.startsWith('on'))) return true;
|
|
326
|
-
|
|
323
|
+
if (n.children && hasInteractivity(n.children)) return true;
|
|
327
324
|
}
|
|
328
|
-
|
|
325
|
+
return false;
|
|
329
326
|
});
|
|
330
327
|
}
|
|
331
328
|
|
|
332
|
-
|
|
329
|
+
function generateTagCode(tag, stateNames) {
|
|
333
330
|
const isComponent = tag.name[0] === tag.name[0].toUpperCase();
|
|
334
|
-
|
|
335
|
-
|
|
331
|
+
if (isComponent) {
|
|
332
|
+
let propsStr = "{";
|
|
336
333
|
if (tag.attributes) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
334
|
+
Object.keys(tag.attributes).forEach((a, i) => {
|
|
335
|
+
let val = tag.attributes[a].replace(/\{([^}]+)\}/g, (_, p) => "${" + p + "}");
|
|
336
|
+
propsStr += (i === 0 ? "" : ", ") + '"' + a + '": `' + val + '`';
|
|
337
|
+
});
|
|
341
338
|
}
|
|
342
339
|
propsStr += "}";
|
|
343
|
-
|
|
340
|
+
return ` html += helpers.renderComponent('${tag.name}', ${propsStr}, helpers);\n`;
|
|
344
341
|
}
|
|
345
342
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
343
|
+
let code = ` html += "<${tag.name}";\n`;
|
|
344
|
+
|
|
345
|
+
if (tag.attributes) {
|
|
346
|
+
Object.keys(tag.attributes).forEach(a => {
|
|
347
|
+
if (a.startsWith('on')) {
|
|
348
|
+
const eventName = a.substring(2).toLowerCase();
|
|
349
|
+
const uniqueId = `${eventName}_${tag.name}_${Math.random().toString(36).substring(7)}`;
|
|
350
|
+
if (tag.attributes[a].includes('++')) {
|
|
351
|
+
const key = tag.attributes[a].split('++')[0].trim();
|
|
352
|
+
code += ` _events.push({ id: '${uniqueId}', fn: function(state) { state['${key}']++; } });\n`;
|
|
353
|
+
} else {
|
|
354
|
+
code += ` _events.push({ id: '${uniqueId}', fn: function(state, event) { ${tag.attributes[a]} } });\n`;
|
|
355
|
+
}
|
|
356
|
+
code += ` html += " data-on-${eventName}='${uniqueId}'";\n`;
|
|
357
|
+
} else {
|
|
358
|
+
let val = tag.attributes[a].replace(/\{([^}]+)\}/g, (_, p) => "${e(" + p + ")}");
|
|
359
|
+
code += " html += \" \" + " + JSON.stringify(a) + " + \"='\" + `" + val + "` + \"'\";\n";
|
|
360
|
+
}
|
|
361
|
+
});
|
|
365
362
|
}
|
|
366
363
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
364
|
+
if (tag.children) {
|
|
365
|
+
tag.children.forEach(child => {
|
|
366
|
+
if (child.type === 'event') {
|
|
367
|
+
const eventName = child.event.toLowerCase();
|
|
368
|
+
const uniqueId = `${eventName}_child_${Math.random().toString(36).substring(7)}`;
|
|
369
|
+
code += ` _events.push({ id: '${uniqueId}', fn: function(state, event) { ${child.code} } });\n`;
|
|
370
|
+
code += ` html += " data-on-${eventName}='${uniqueId}'";\n`;
|
|
371
|
+
}
|
|
372
|
+
});
|
|
376
373
|
}
|
|
377
374
|
|
|
378
375
|
code += ` html += ">";\n`;
|
|
379
376
|
|
|
380
|
-
|
|
381
|
-
|
|
377
|
+
if (tag.content) {
|
|
378
|
+
let cont = tag.content.replace(/`/g, '\\`').replace(/\$/g, '\\$').replace(/\{([^}]+)\}/g, (_, p) => {
|
|
382
379
|
const parts = p.split('.');
|
|
383
|
-
|
|
384
|
-
|
|
380
|
+
if (stateNames.includes(parts[0])) return `\${e(state["${parts[0]}"]${parts.slice(1).map(part => `?.["${part}"]`).join('')})}`;
|
|
381
|
+
return `\${e(${p})}`;
|
|
385
382
|
});
|
|
386
|
-
|
|
383
|
+
code += ` html += \`${cont}\`;\n`;
|
|
387
384
|
}
|
|
388
385
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
386
|
+
if (tag.children) code += generateComponentBody(tag.children, stateNames);
|
|
387
|
+
code += ` html += "</${tag.name}>";\n`;
|
|
388
|
+
return code;
|
|
392
389
|
}
|
|
393
390
|
|
|
394
|
-
|
|
391
|
+
module.exports = { generate };
|