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.
Files changed (2) hide show
  1. package/compiler/generator.js +110 -113
  2. package/package.json +1 -1
@@ -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
- < html lang = "en" >
205
- <head>
206
- <meta charset="UTF-8">
207
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
208
- <title>Free Ultra | ${viewName}</title>
209
- <meta name="csrf-token" content="${res.context.csrfToken || ''}">
210
- <link rel="preconnect" href="https://fonts.googleapis.com">
211
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
212
- <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap" rel="stylesheet">
213
- <link rel="stylesheet" href="/css/app.css">
214
- <style>
215
- :root {--primary:#00ff88; --secondary:#00bdff; --dark:#050505; }
216
- body {margin:0; font-family:'Outfit', system-ui, sans-serif; background:var(--dark); color:white; overflow-x:hidden; scroll-behavior:smooth; }
217
- ${scopedCSS}
218
- </style>
219
- <script src="/free-runtime.js" defer></script>
220
- </head>
221
- <body>
222
- ${headerHtml}
223
- <main id="free-app-root">
224
- ${pageHtml}
225
- </main>
226
- </body>
227
- </html>`;
228
- res.header('Content-Type', 'text/html').send(fullHTML);
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
- " // API route - return JSON\n" +
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
- output += `\nif (require.main === module) {
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
- return output;
243
+ return output;
247
244
  }
248
245
 
249
- function generateUltraComponent(node) {
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
- let code = `\nfunction render${node.name}(props = { }, helpers = { }) {\n`;
252
+ let code = `\nfunction render${node.name}(props = { }, helpers = { }) {\n`;
256
253
  code += ` const e = (str) => String(str).replace(/[&<>'"]/g, m => ({'&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#39;', '"': '&quot;' }[m]));\n`;
257
- code += ` const state = {\n`;
254
+ code += ` const state = {\n`;
258
255
  node.states.forEach(s => {
259
- let val = s.default;
260
- const isExpression = typeof val === 'string' && (val.includes('props.') || val.includes('||') || val.includes('&&') || val === 'true' || val === 'false' || val === 'null' || val.trim().startsWith('{') || val.trim().startsWith('['));
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
- code += ` "${s.name}": ${finalVal},\n`;
259
+ code += ` "${s.name}": ${finalVal},\n`;
263
260
  });
264
261
  code += ` };\n`;
265
262
  if (stateNames.length > 0) {
266
- code += ` const { ${stateNames.join(', ')} } = state;\n`;
263
+ code += ` const { ${stateNames.join(', ')} } = state;\n`;
267
264
  }
268
265
 
269
- code += ` const _events = [];\n`;
270
- code += ` const islandAttr = ${isInteractive} ? ' data-component="${node.name}"' : '';\n`;
271
- code += ` let html = "<div class='free-component'" + islandAttr + " data-state='" + JSON.stringify(state).replace(/'/g, "&#39;") + "'";\n`;
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, "&#39;") + "'";\n`;
272
269
 
273
- if (onMount) {
274
- code += ` _events.push({ id: 'onMount', fn: function(state) { ${onMount} } });\n`;
275
- code += ` html += " data-free-on-mount='onMount'";\n`;
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
- if (onDestroy) {
278
- code += ` _events.push({ id: 'onDestroy', fn: function(state) { ${onDestroy} } });\n`;
279
- code += ` html += " data-free-on-destroy='onDestroy'";\n`;
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
- code += generateComponentBody(node.body, stateNames);
280
+ code += generateComponentBody(node.body, stateNames);
284
281
 
285
- if (isInteractive) {
286
- code += ` html += "<script>window.__free_actions = window.__free_actions || {}; window.__free_actions['${node.name}'] = {};";\n`;
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
- code += ` html += "window.__free_actions['${node.name}'][ev.id] = " + ev.fn.toString() + ";";\n`;
285
+ code += ` html += "window.__free_actions['${node.name}'][ev.id] = " + ev.fn.toString() + ";";\n`;
289
286
  code += ` });\n`;
290
- code += ` html += "</script>";\n`;
287
+ code += ` html += "</script>";\n`;
291
288
  }
292
289
 
293
- code += ` html += "</div>";\n return html;\n}\n`;
294
- return code;
290
+ code += ` html += "</div>";\n return html;\n}\n`;
291
+ return code;
295
292
  }
296
293
 
297
- function generateComponentBody(body, stateNames) {
298
- let code = "";
294
+ function generateComponentBody(body, stateNames) {
295
+ let code = "";
299
296
  body.forEach(node => {
300
297
  if (node.type === 'tag') {
301
- code += generateTagCode(node, stateNames);
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
- code += ` html += \`<script>${scriptCode}</script>\`;\n`;
303
+ code += ` html += \`<script>${scriptCode}</script>\`;\n`;
307
304
  } else if (node.type === 'loop') {
308
- code += " if (Array.isArray(" + node.list + ")) {\n";
305
+ code += " if (Array.isArray(" + node.list + ")) {\n";
309
306
  code += " " + node.list + ".forEach(" + node.item + " => {\n";
310
- code += generateComponentBody(node.body, stateNames).replace(/^ /gm, ' ');
307
+ code += generateComponentBody(node.body, stateNames).replace(/^ /gm, ' ');
311
308
  code += " });\n";
312
309
  code += " }\n";
313
310
  } else if (node.type === 'condition') {
314
- code += " if (" + node.condition + ") {\n";
315
- code += generateComponentBody(node.body, stateNames).replace(/^ /gm, ' ');
311
+ code += " if (" + node.condition + ") {\n";
312
+ code += generateComponentBody(node.body, stateNames).replace(/^ /gm, ' ');
316
313
  code += " }\n";
317
314
  }
318
315
  });
319
- return code;
316
+ return code;
320
317
  }
321
318
 
322
- function hasInteractivity(nodes) {
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
- if (n.children && hasInteractivity(n.children)) return true;
323
+ if (n.children && hasInteractivity(n.children)) return true;
327
324
  }
328
- return false;
325
+ return false;
329
326
  });
330
327
  }
331
328
 
332
- function generateTagCode(tag, stateNames) {
329
+ function generateTagCode(tag, stateNames) {
333
330
  const isComponent = tag.name[0] === tag.name[0].toUpperCase();
334
- if (isComponent) {
335
- let propsStr = "{";
331
+ if (isComponent) {
332
+ let propsStr = "{";
336
333
  if (tag.attributes) {
337
- Object.keys(tag.attributes).forEach((a, i) => {
338
- let val = tag.attributes[a].replace(/\{([^}]+)\}/g, (_, p) => "${" + p + "}");
339
- propsStr += (i === 0 ? "" : ", ") + '"' + a + '": `' + val + '`';
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
- return ` html += helpers.renderComponent('${tag.name}', ${propsStr}, helpers);\n`;
340
+ return ` html += helpers.renderComponent('${tag.name}', ${propsStr}, helpers);\n`;
344
341
  }
345
342
 
346
- let code = ` html += "<${tag.name}";\n`;
347
-
348
- if (tag.attributes) {
349
- Object.keys(tag.attributes).forEach(a => {
350
- if (a.startsWith('on')) {
351
- const eventName = a.substring(2).toLowerCase();
352
- const uniqueId = `${eventName}_${tag.name}_${Math.random().toString(36).substring(7)}`;
353
- if (tag.attributes[a].includes('++')) {
354
- const key = tag.attributes[a].split('++')[0].trim();
355
- code += ` _events.push({ id: '${uniqueId}', fn: function(state) { state['${key}']++; } });\n`;
356
- } else {
357
- code += ` _events.push({ id: '${uniqueId}', fn: function(state, event) { ${tag.attributes[a]} } });\n`;
358
- }
359
- code += ` html += " data-on-${eventName}='${uniqueId}'";\n`;
360
- } else {
361
- let val = tag.attributes[a].replace(/\{([^}]+)\}/g, (_, p) => "${e(" + p + ")}");
362
- code += " html += \" \" + " + JSON.stringify(a) + " + \"='\" + `" + val + "` + \"'\";\n";
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
- if (tag.children) {
368
- tag.children.forEach(child => {
369
- if (child.type === 'event') {
370
- const eventName = child.event.toLowerCase();
371
- const uniqueId = `${eventName}_child_${Math.random().toString(36).substring(7)}`;
372
- code += ` _events.push({ id: '${uniqueId}', fn: function(state, event) { ${child.code} } });\n`;
373
- code += ` html += " data-on-${eventName}='${uniqueId}'";\n`;
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
- if (tag.content) {
381
- let cont = tag.content.replace(/`/g, '\\`').replace(/\$/g, '\\$').replace(/\{([^}]+)\}/g, (_, p) => {
377
+ if (tag.content) {
378
+ let cont = tag.content.replace(/`/g, '\\`').replace(/\$/g, '\\$').replace(/\{([^}]+)\}/g, (_, p) => {
382
379
  const parts = p.split('.');
383
- if (stateNames.includes(parts[0])) return `\${e(state["${parts[0]}"]${ parts.slice(1).map(part => `?.["${part}"]`).join('') })}`;
384
- return `\${e(${ p })}`;
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
- code += ` html += \`${cont}\`;\n`;
383
+ code += ` html += \`${cont}\`;\n`;
387
384
  }
388
385
 
389
- if (tag.children) code += generateComponentBody(tag.children, stateNames);
390
- code += ` html += "</${tag.name}>";\n`;
391
- return code;
386
+ if (tag.children) code += generateComponentBody(tag.children, stateNames);
387
+ code += ` html += "</${tag.name}>";\n`;
388
+ return code;
392
389
  }
393
390
 
394
- module.exports = {generate};
391
+ module.exports = { generate };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-framework",
3
- "version": "4.7.0",
3
+ "version": "4.7.2",
4
4
  "description": "Professional Node.js engine for the .free language. Blazing-fast SSR, Islands Architecture, and built-in ORM.",
5
5
  "main": "index.js",
6
6
  "bin": {