@matthesketh/utopia-compiler 0.0.1 → 0.0.3

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/dist/index.cjs CHANGED
@@ -415,7 +415,7 @@ var CodeGenerator = class {
415
415
 
416
416
  ` : "";
417
417
  const fnBody = this.code.map((l) => ` ${l}`).join("\n");
418
- const moduleCode = `${importLine}export default function render(_ctx) {
418
+ const moduleCode = `${importLine}function __render() {
419
419
  ${fnBody}
420
420
  }
421
421
  `;
@@ -480,7 +480,8 @@ ${fnBody}
480
480
  if (!node.content) return null;
481
481
  this.helpers.add("createTextNode");
482
482
  const v = this.freshVar();
483
- this.emit(`const ${v} = createTextNode(${JSON.stringify(node.content)})`);
483
+ const decoded = decodeEntities(node.content);
484
+ this.emit(`const ${v} = createTextNode(${JSON.stringify(decoded)})`);
484
485
  return v;
485
486
  }
486
487
  genInterpolation(node, scope) {
@@ -562,12 +563,13 @@ ${fnBody}
562
563
  const anchorVar = this.freshVar();
563
564
  this.helpers.add("createComment");
564
565
  this.emit(`const ${anchorVar} = createComment('u-for')`);
565
- const forMatch = dir.expression.match(/^\s*(\w+)\s+in\s+(.+)$/);
566
+ const forMatch = dir.expression.match(/^\s*(?:\(\s*(\w+)\s*(?:,\s*(\w+)\s*)?\)|(\w+))\s+in\s+(.+)$/);
566
567
  if (!forMatch) {
567
568
  throw new Error(`Invalid u-for expression: "${dir.expression}"`);
568
569
  }
569
- const itemName = forMatch[1];
570
- const listExpr = this.resolveExpression(forMatch[2].trim(), scope);
570
+ const itemName = forMatch[1] ?? forMatch[3];
571
+ const indexName = forMatch[2] ?? "_index";
572
+ const listExpr = this.resolveExpression(forMatch[4].trim(), scope);
571
573
  const innerScope = new Set(scope);
572
574
  innerScope.add(itemName);
573
575
  const strippedNode = {
@@ -580,7 +582,7 @@ ${fnBody}
580
582
  const innerLines = [...this.code];
581
583
  this.code = savedCode;
582
584
  const renderFnVar = this.freshVar();
583
- this.emit(`const ${renderFnVar} = (${itemName}, _index) => {`);
585
+ this.emit(`const ${renderFnVar} = (${itemName}, ${indexName}) => {`);
584
586
  for (const line of innerLines) {
585
587
  this.emit(` ${line}`);
586
588
  }
@@ -606,32 +608,21 @@ ${fnBody}
606
608
  }
607
609
  }
608
610
  const propsStr = propEntries.length > 0 ? `{ ${propEntries.join(", ")} }` : "{}";
609
- this.emit(`const ${compVar} = _ctx.${node.tag}(${propsStr})`);
611
+ this.helpers.add("createComponent");
612
+ this.emit(`const ${compVar} = createComponent(${node.tag}, ${propsStr})`);
610
613
  return compVar;
611
614
  }
612
615
  // ---- Expression resolution ----------------------------------------------
613
616
  /**
614
617
  * Resolve a template expression to a JS expression.
615
618
  *
616
- * If the leading identifier is in `scope` (a u-for item variable), it is
617
- * emitted as a bare variable reference. Otherwise it is prefixed with
618
- * `_ctx.` to access the component context.
619
+ * All identifiers are emitted as bare references user script variables
620
+ * live at module scope and are accessible via closure.
619
621
  */
620
- resolveExpression(expr, scope) {
622
+ resolveExpression(expr, _scope) {
621
623
  const trimmed = expr.trim();
622
624
  if (!trimmed) return "''";
623
- const leadIdMatch = trimmed.match(/^([a-zA-Z_$][\w$]*)/);
624
- if (!leadIdMatch) {
625
- return trimmed;
626
- }
627
- const leadId = leadIdMatch[1];
628
- if (scope.has(leadId)) {
629
- return trimmed;
630
- }
631
- if (trimmed.includes("=>")) {
632
- return trimmed;
633
- }
634
- return `_ctx.${trimmed}`;
625
+ return trimmed;
635
626
  }
636
627
  // ---- Utilities ----------------------------------------------------------
637
628
  freshVar() {
@@ -647,6 +638,40 @@ function isComponentTag(tag) {
647
638
  function escapeStr(s) {
648
639
  return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
649
640
  }
641
+ var ENTITY_MAP = {
642
+ "&": "&",
643
+ "&lt;": "<",
644
+ "&gt;": ">",
645
+ "&quot;": '"',
646
+ "&#39;": "'",
647
+ "&apos;": "'",
648
+ "&nbsp;": "\xA0",
649
+ "&mdash;": "\u2014",
650
+ "&ndash;": "\u2013",
651
+ "&lsquo;": "\u2018",
652
+ "&rsquo;": "\u2019",
653
+ "&ldquo;": "\u201C",
654
+ "&rdquo;": "\u201D",
655
+ "&bull;": "\u2022",
656
+ "&hellip;": "\u2026",
657
+ "&copy;": "\xA9",
658
+ "&reg;": "\xAE",
659
+ "&trade;": "\u2122",
660
+ "&rarr;": "\u2192",
661
+ "&larr;": "\u2190",
662
+ "&uarr;": "\u2191",
663
+ "&darr;": "\u2193",
664
+ "&times;": "\xD7",
665
+ "&divide;": "\xF7"
666
+ };
667
+ function decodeEntities(text) {
668
+ return text.replace(/&(?:#(\d+)|#x([0-9a-fA-F]+)|(\w+));/g, (match, dec, hex, named) => {
669
+ if (dec) return String.fromCodePoint(parseInt(dec, 10));
670
+ if (hex) return String.fromCodePoint(parseInt(hex, 16));
671
+ if (named) return ENTITY_MAP[`&${named};`] ?? match;
672
+ return match;
673
+ });
674
+ }
650
675
  function generate(ast, options) {
651
676
  const gen = new CodeGenerator(options);
652
677
  return gen.generate(ast);
@@ -810,6 +835,7 @@ function compile(source, options = {}) {
810
835
  if (body) {
811
836
  parts.push(body);
812
837
  }
838
+ parts.push("export default { render: __render }");
813
839
  const code = parts.join("\n\n") + "\n";
814
840
  return { code, css };
815
841
  }
package/dist/index.js CHANGED
@@ -383,7 +383,7 @@ var CodeGenerator = class {
383
383
 
384
384
  ` : "";
385
385
  const fnBody = this.code.map((l) => ` ${l}`).join("\n");
386
- const moduleCode = `${importLine}export default function render(_ctx) {
386
+ const moduleCode = `${importLine}function __render() {
387
387
  ${fnBody}
388
388
  }
389
389
  `;
@@ -448,7 +448,8 @@ ${fnBody}
448
448
  if (!node.content) return null;
449
449
  this.helpers.add("createTextNode");
450
450
  const v = this.freshVar();
451
- this.emit(`const ${v} = createTextNode(${JSON.stringify(node.content)})`);
451
+ const decoded = decodeEntities(node.content);
452
+ this.emit(`const ${v} = createTextNode(${JSON.stringify(decoded)})`);
452
453
  return v;
453
454
  }
454
455
  genInterpolation(node, scope) {
@@ -530,12 +531,13 @@ ${fnBody}
530
531
  const anchorVar = this.freshVar();
531
532
  this.helpers.add("createComment");
532
533
  this.emit(`const ${anchorVar} = createComment('u-for')`);
533
- const forMatch = dir.expression.match(/^\s*(\w+)\s+in\s+(.+)$/);
534
+ const forMatch = dir.expression.match(/^\s*(?:\(\s*(\w+)\s*(?:,\s*(\w+)\s*)?\)|(\w+))\s+in\s+(.+)$/);
534
535
  if (!forMatch) {
535
536
  throw new Error(`Invalid u-for expression: "${dir.expression}"`);
536
537
  }
537
- const itemName = forMatch[1];
538
- const listExpr = this.resolveExpression(forMatch[2].trim(), scope);
538
+ const itemName = forMatch[1] ?? forMatch[3];
539
+ const indexName = forMatch[2] ?? "_index";
540
+ const listExpr = this.resolveExpression(forMatch[4].trim(), scope);
539
541
  const innerScope = new Set(scope);
540
542
  innerScope.add(itemName);
541
543
  const strippedNode = {
@@ -548,7 +550,7 @@ ${fnBody}
548
550
  const innerLines = [...this.code];
549
551
  this.code = savedCode;
550
552
  const renderFnVar = this.freshVar();
551
- this.emit(`const ${renderFnVar} = (${itemName}, _index) => {`);
553
+ this.emit(`const ${renderFnVar} = (${itemName}, ${indexName}) => {`);
552
554
  for (const line of innerLines) {
553
555
  this.emit(` ${line}`);
554
556
  }
@@ -574,32 +576,21 @@ ${fnBody}
574
576
  }
575
577
  }
576
578
  const propsStr = propEntries.length > 0 ? `{ ${propEntries.join(", ")} }` : "{}";
577
- this.emit(`const ${compVar} = _ctx.${node.tag}(${propsStr})`);
579
+ this.helpers.add("createComponent");
580
+ this.emit(`const ${compVar} = createComponent(${node.tag}, ${propsStr})`);
578
581
  return compVar;
579
582
  }
580
583
  // ---- Expression resolution ----------------------------------------------
581
584
  /**
582
585
  * Resolve a template expression to a JS expression.
583
586
  *
584
- * If the leading identifier is in `scope` (a u-for item variable), it is
585
- * emitted as a bare variable reference. Otherwise it is prefixed with
586
- * `_ctx.` to access the component context.
587
+ * All identifiers are emitted as bare references user script variables
588
+ * live at module scope and are accessible via closure.
587
589
  */
588
- resolveExpression(expr, scope) {
590
+ resolveExpression(expr, _scope) {
589
591
  const trimmed = expr.trim();
590
592
  if (!trimmed) return "''";
591
- const leadIdMatch = trimmed.match(/^([a-zA-Z_$][\w$]*)/);
592
- if (!leadIdMatch) {
593
- return trimmed;
594
- }
595
- const leadId = leadIdMatch[1];
596
- if (scope.has(leadId)) {
597
- return trimmed;
598
- }
599
- if (trimmed.includes("=>")) {
600
- return trimmed;
601
- }
602
- return `_ctx.${trimmed}`;
593
+ return trimmed;
603
594
  }
604
595
  // ---- Utilities ----------------------------------------------------------
605
596
  freshVar() {
@@ -615,6 +606,40 @@ function isComponentTag(tag) {
615
606
  function escapeStr(s) {
616
607
  return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
617
608
  }
609
+ var ENTITY_MAP = {
610
+ "&amp;": "&",
611
+ "&lt;": "<",
612
+ "&gt;": ">",
613
+ "&quot;": '"',
614
+ "&#39;": "'",
615
+ "&apos;": "'",
616
+ "&nbsp;": "\xA0",
617
+ "&mdash;": "\u2014",
618
+ "&ndash;": "\u2013",
619
+ "&lsquo;": "\u2018",
620
+ "&rsquo;": "\u2019",
621
+ "&ldquo;": "\u201C",
622
+ "&rdquo;": "\u201D",
623
+ "&bull;": "\u2022",
624
+ "&hellip;": "\u2026",
625
+ "&copy;": "\xA9",
626
+ "&reg;": "\xAE",
627
+ "&trade;": "\u2122",
628
+ "&rarr;": "\u2192",
629
+ "&larr;": "\u2190",
630
+ "&uarr;": "\u2191",
631
+ "&darr;": "\u2193",
632
+ "&times;": "\xD7",
633
+ "&divide;": "\xF7"
634
+ };
635
+ function decodeEntities(text) {
636
+ return text.replace(/&(?:#(\d+)|#x([0-9a-fA-F]+)|(\w+));/g, (match, dec, hex, named) => {
637
+ if (dec) return String.fromCodePoint(parseInt(dec, 10));
638
+ if (hex) return String.fromCodePoint(parseInt(hex, 16));
639
+ if (named) return ENTITY_MAP[`&${named};`] ?? match;
640
+ return match;
641
+ });
642
+ }
618
643
  function generate(ast, options) {
619
644
  const gen = new CodeGenerator(options);
620
645
  return gen.generate(ast);
@@ -778,6 +803,7 @@ function compile(source, options = {}) {
778
803
  if (body) {
779
804
  parts.push(body);
780
805
  }
806
+ parts.push("export default { render: __render }");
781
807
  const code = parts.join("\n\n") + "\n";
782
808
  return { code, css };
783
809
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matthesketh/utopia-compiler",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Compiler for .utopia single-file components",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -39,7 +39,7 @@
39
39
  "dist"
40
40
  ],
41
41
  "dependencies": {
42
- "@matthesketh/utopia-core": "0.0.1"
42
+ "@matthesketh/utopia-core": "0.0.3"
43
43
  },
44
44
  "scripts": {
45
45
  "build": "tsup src/index.ts --format esm,cjs --dts",