@matthesketh/utopia-compiler 0.2.0 → 0.3.1
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 +98 -3
- package/dist/index.js +98 -3
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -245,7 +245,7 @@ var TemplateParser = class {
|
|
|
245
245
|
if (this.lookingAt("{{")) {
|
|
246
246
|
flush();
|
|
247
247
|
this.pos += 2;
|
|
248
|
-
const endIdx = this.
|
|
248
|
+
const endIdx = this.findInterpolationEnd(this.pos);
|
|
249
249
|
if (endIdx === -1) throw this.error("Unterminated interpolation {{ }}");
|
|
250
250
|
const expression = this.source.slice(this.pos, endIdx).trim();
|
|
251
251
|
nodes.push({ type: 3 /* Interpolation */, expression });
|
|
@@ -257,6 +257,39 @@ var TemplateParser = class {
|
|
|
257
257
|
flush();
|
|
258
258
|
return nodes;
|
|
259
259
|
}
|
|
260
|
+
// ---- Interpolation end finder -------------------------------------------
|
|
261
|
+
/**
|
|
262
|
+
* Find the position of the closing `}}` for an interpolation expression,
|
|
263
|
+
* respecting JavaScript string literals so that `}}` inside quotes is not
|
|
264
|
+
* treated as the end delimiter.
|
|
265
|
+
*
|
|
266
|
+
* Handles single-quoted, double-quoted, and template literal strings,
|
|
267
|
+
* including escaped characters within them.
|
|
268
|
+
*/
|
|
269
|
+
findInterpolationEnd(start) {
|
|
270
|
+
let pos = start;
|
|
271
|
+
let inSingle = false;
|
|
272
|
+
let inDouble = false;
|
|
273
|
+
let inBacktick = false;
|
|
274
|
+
while (pos < this.source.length - 1) {
|
|
275
|
+
const ch = this.source[pos];
|
|
276
|
+
if ((inSingle || inDouble || inBacktick) && ch === "\\") {
|
|
277
|
+
pos += 2;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (!inDouble && !inBacktick && ch === "'") {
|
|
281
|
+
inSingle = !inSingle;
|
|
282
|
+
} else if (!inSingle && !inBacktick && ch === '"') {
|
|
283
|
+
inDouble = !inDouble;
|
|
284
|
+
} else if (!inSingle && !inDouble && ch === "`") {
|
|
285
|
+
inBacktick = !inBacktick;
|
|
286
|
+
} else if (!inSingle && !inDouble && !inBacktick && ch === "}" && this.source[pos + 1] === "}") {
|
|
287
|
+
return pos;
|
|
288
|
+
}
|
|
289
|
+
pos++;
|
|
290
|
+
}
|
|
291
|
+
return -1;
|
|
292
|
+
}
|
|
260
293
|
// ---- Low-level helpers --------------------------------------------------
|
|
261
294
|
readTagName() {
|
|
262
295
|
const start = this.pos;
|
|
@@ -412,7 +445,7 @@ var CodeGenerator = class {
|
|
|
412
445
|
|
|
413
446
|
` : "";
|
|
414
447
|
const fnBody = this.code.map((l) => ` ${l}`).join("\n");
|
|
415
|
-
const moduleCode = `${importLine}function __render() {
|
|
448
|
+
const moduleCode = `${importLine}function __render(_ctx) {
|
|
416
449
|
${fnBody}
|
|
417
450
|
}
|
|
418
451
|
`;
|
|
@@ -441,6 +474,9 @@ ${fnBody}
|
|
|
441
474
|
if (forDir) {
|
|
442
475
|
return this.genFor(node, forDir, scope);
|
|
443
476
|
}
|
|
477
|
+
if (node.tag === "slot") {
|
|
478
|
+
return this.genSlot(node);
|
|
479
|
+
}
|
|
444
480
|
if (isComponentTag(node.tag)) {
|
|
445
481
|
return this.genComponent(node, scope);
|
|
446
482
|
}
|
|
@@ -678,6 +714,30 @@ ${fnBody}
|
|
|
678
714
|
this.emit(line);
|
|
679
715
|
}
|
|
680
716
|
}
|
|
717
|
+
// ---- Slot rendering -----------------------------------------------------
|
|
718
|
+
/**
|
|
719
|
+
* Generate code for a `<slot />` element.
|
|
720
|
+
*
|
|
721
|
+
* Named slots use `<slot name="foo" />`, defaulting to "default".
|
|
722
|
+
* The generated code reads from `_ctx.$slots[name]()` if available,
|
|
723
|
+
* otherwise renders a comment placeholder.
|
|
724
|
+
*/
|
|
725
|
+
genSlot(node) {
|
|
726
|
+
const nameAttr = node.attrs.find((a) => a.name === "name");
|
|
727
|
+
const slotName = nameAttr?.value ?? "default";
|
|
728
|
+
const slotVar = this.freshVar();
|
|
729
|
+
this.helpers.add("createComment");
|
|
730
|
+
if (slotName === "default") {
|
|
731
|
+
this.emit(
|
|
732
|
+
`const ${slotVar} = _ctx && _ctx.$slots && _ctx.$slots['default'] ? _ctx.$slots['default']() : (_ctx && _ctx.children instanceof Node ? _ctx.children : createComment('slot'))`
|
|
733
|
+
);
|
|
734
|
+
} else {
|
|
735
|
+
this.emit(
|
|
736
|
+
`const ${slotVar} = _ctx && _ctx.$slots && _ctx.$slots['${escapeStr(slotName)}'] ? _ctx.$slots['${escapeStr(slotName)}']() : createComment('slot')`
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
return slotVar;
|
|
740
|
+
}
|
|
681
741
|
// ---- Component generation -----------------------------------------------
|
|
682
742
|
genComponent(node, scope) {
|
|
683
743
|
const compVar = this.freshVar();
|
|
@@ -696,7 +756,42 @@ ${fnBody}
|
|
|
696
756
|
}
|
|
697
757
|
const propsStr = propEntries.length > 0 ? `{ ${propEntries.join(", ")} }` : "{}";
|
|
698
758
|
this.helpers.add("createComponent");
|
|
699
|
-
|
|
759
|
+
const substantiveChildren = node.children.filter(
|
|
760
|
+
(c) => c.type === 1 /* Element */ || c.type === 3 /* Interpolation */ || c.type === 2 /* Text */ && c.content.trim() !== ""
|
|
761
|
+
);
|
|
762
|
+
if (substantiveChildren.length > 0) {
|
|
763
|
+
const slotFnVar = this.freshVar();
|
|
764
|
+
const savedCode = this.code;
|
|
765
|
+
this.code = [];
|
|
766
|
+
if (substantiveChildren.length === 1 && substantiveChildren[0].type === 1 /* Element */) {
|
|
767
|
+
const innerVar = this.genNode(substantiveChildren[0], scope);
|
|
768
|
+
this.emit(`return ${innerVar}`);
|
|
769
|
+
} else {
|
|
770
|
+
this.helpers.add("createElement");
|
|
771
|
+
this.helpers.add("appendChild");
|
|
772
|
+
const fragVar = this.freshVar();
|
|
773
|
+
this.emit(`const ${fragVar} = createElement('div')`);
|
|
774
|
+
for (const child of node.children) {
|
|
775
|
+
const childVar = this.genNode(child, scope);
|
|
776
|
+
if (childVar) {
|
|
777
|
+
this.emit(`appendChild(${fragVar}, ${childVar})`);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
this.emit(`return ${fragVar}`);
|
|
781
|
+
}
|
|
782
|
+
const slotLines = [...this.code];
|
|
783
|
+
this.code = savedCode;
|
|
784
|
+
this.emit(`const ${slotFnVar} = () => {`);
|
|
785
|
+
for (const line of slotLines) {
|
|
786
|
+
this.emit(` ${line}`);
|
|
787
|
+
}
|
|
788
|
+
this.emit(`}`);
|
|
789
|
+
this.emit(
|
|
790
|
+
`const ${compVar} = createComponent(${node.tag}, ${propsStr}, { default: ${slotFnVar} })`
|
|
791
|
+
);
|
|
792
|
+
} else {
|
|
793
|
+
this.emit(`const ${compVar} = createComponent(${node.tag}, ${propsStr})`);
|
|
794
|
+
}
|
|
700
795
|
return compVar;
|
|
701
796
|
}
|
|
702
797
|
// ---- Expression resolution ----------------------------------------------
|
package/dist/index.js
CHANGED
|
@@ -213,7 +213,7 @@ var TemplateParser = class {
|
|
|
213
213
|
if (this.lookingAt("{{")) {
|
|
214
214
|
flush();
|
|
215
215
|
this.pos += 2;
|
|
216
|
-
const endIdx = this.
|
|
216
|
+
const endIdx = this.findInterpolationEnd(this.pos);
|
|
217
217
|
if (endIdx === -1) throw this.error("Unterminated interpolation {{ }}");
|
|
218
218
|
const expression = this.source.slice(this.pos, endIdx).trim();
|
|
219
219
|
nodes.push({ type: 3 /* Interpolation */, expression });
|
|
@@ -225,6 +225,39 @@ var TemplateParser = class {
|
|
|
225
225
|
flush();
|
|
226
226
|
return nodes;
|
|
227
227
|
}
|
|
228
|
+
// ---- Interpolation end finder -------------------------------------------
|
|
229
|
+
/**
|
|
230
|
+
* Find the position of the closing `}}` for an interpolation expression,
|
|
231
|
+
* respecting JavaScript string literals so that `}}` inside quotes is not
|
|
232
|
+
* treated as the end delimiter.
|
|
233
|
+
*
|
|
234
|
+
* Handles single-quoted, double-quoted, and template literal strings,
|
|
235
|
+
* including escaped characters within them.
|
|
236
|
+
*/
|
|
237
|
+
findInterpolationEnd(start) {
|
|
238
|
+
let pos = start;
|
|
239
|
+
let inSingle = false;
|
|
240
|
+
let inDouble = false;
|
|
241
|
+
let inBacktick = false;
|
|
242
|
+
while (pos < this.source.length - 1) {
|
|
243
|
+
const ch = this.source[pos];
|
|
244
|
+
if ((inSingle || inDouble || inBacktick) && ch === "\\") {
|
|
245
|
+
pos += 2;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (!inDouble && !inBacktick && ch === "'") {
|
|
249
|
+
inSingle = !inSingle;
|
|
250
|
+
} else if (!inSingle && !inBacktick && ch === '"') {
|
|
251
|
+
inDouble = !inDouble;
|
|
252
|
+
} else if (!inSingle && !inDouble && ch === "`") {
|
|
253
|
+
inBacktick = !inBacktick;
|
|
254
|
+
} else if (!inSingle && !inDouble && !inBacktick && ch === "}" && this.source[pos + 1] === "}") {
|
|
255
|
+
return pos;
|
|
256
|
+
}
|
|
257
|
+
pos++;
|
|
258
|
+
}
|
|
259
|
+
return -1;
|
|
260
|
+
}
|
|
228
261
|
// ---- Low-level helpers --------------------------------------------------
|
|
229
262
|
readTagName() {
|
|
230
263
|
const start = this.pos;
|
|
@@ -380,7 +413,7 @@ var CodeGenerator = class {
|
|
|
380
413
|
|
|
381
414
|
` : "";
|
|
382
415
|
const fnBody = this.code.map((l) => ` ${l}`).join("\n");
|
|
383
|
-
const moduleCode = `${importLine}function __render() {
|
|
416
|
+
const moduleCode = `${importLine}function __render(_ctx) {
|
|
384
417
|
${fnBody}
|
|
385
418
|
}
|
|
386
419
|
`;
|
|
@@ -409,6 +442,9 @@ ${fnBody}
|
|
|
409
442
|
if (forDir) {
|
|
410
443
|
return this.genFor(node, forDir, scope);
|
|
411
444
|
}
|
|
445
|
+
if (node.tag === "slot") {
|
|
446
|
+
return this.genSlot(node);
|
|
447
|
+
}
|
|
412
448
|
if (isComponentTag(node.tag)) {
|
|
413
449
|
return this.genComponent(node, scope);
|
|
414
450
|
}
|
|
@@ -646,6 +682,30 @@ ${fnBody}
|
|
|
646
682
|
this.emit(line);
|
|
647
683
|
}
|
|
648
684
|
}
|
|
685
|
+
// ---- Slot rendering -----------------------------------------------------
|
|
686
|
+
/**
|
|
687
|
+
* Generate code for a `<slot />` element.
|
|
688
|
+
*
|
|
689
|
+
* Named slots use `<slot name="foo" />`, defaulting to "default".
|
|
690
|
+
* The generated code reads from `_ctx.$slots[name]()` if available,
|
|
691
|
+
* otherwise renders a comment placeholder.
|
|
692
|
+
*/
|
|
693
|
+
genSlot(node) {
|
|
694
|
+
const nameAttr = node.attrs.find((a) => a.name === "name");
|
|
695
|
+
const slotName = nameAttr?.value ?? "default";
|
|
696
|
+
const slotVar = this.freshVar();
|
|
697
|
+
this.helpers.add("createComment");
|
|
698
|
+
if (slotName === "default") {
|
|
699
|
+
this.emit(
|
|
700
|
+
`const ${slotVar} = _ctx && _ctx.$slots && _ctx.$slots['default'] ? _ctx.$slots['default']() : (_ctx && _ctx.children instanceof Node ? _ctx.children : createComment('slot'))`
|
|
701
|
+
);
|
|
702
|
+
} else {
|
|
703
|
+
this.emit(
|
|
704
|
+
`const ${slotVar} = _ctx && _ctx.$slots && _ctx.$slots['${escapeStr(slotName)}'] ? _ctx.$slots['${escapeStr(slotName)}']() : createComment('slot')`
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
return slotVar;
|
|
708
|
+
}
|
|
649
709
|
// ---- Component generation -----------------------------------------------
|
|
650
710
|
genComponent(node, scope) {
|
|
651
711
|
const compVar = this.freshVar();
|
|
@@ -664,7 +724,42 @@ ${fnBody}
|
|
|
664
724
|
}
|
|
665
725
|
const propsStr = propEntries.length > 0 ? `{ ${propEntries.join(", ")} }` : "{}";
|
|
666
726
|
this.helpers.add("createComponent");
|
|
667
|
-
|
|
727
|
+
const substantiveChildren = node.children.filter(
|
|
728
|
+
(c) => c.type === 1 /* Element */ || c.type === 3 /* Interpolation */ || c.type === 2 /* Text */ && c.content.trim() !== ""
|
|
729
|
+
);
|
|
730
|
+
if (substantiveChildren.length > 0) {
|
|
731
|
+
const slotFnVar = this.freshVar();
|
|
732
|
+
const savedCode = this.code;
|
|
733
|
+
this.code = [];
|
|
734
|
+
if (substantiveChildren.length === 1 && substantiveChildren[0].type === 1 /* Element */) {
|
|
735
|
+
const innerVar = this.genNode(substantiveChildren[0], scope);
|
|
736
|
+
this.emit(`return ${innerVar}`);
|
|
737
|
+
} else {
|
|
738
|
+
this.helpers.add("createElement");
|
|
739
|
+
this.helpers.add("appendChild");
|
|
740
|
+
const fragVar = this.freshVar();
|
|
741
|
+
this.emit(`const ${fragVar} = createElement('div')`);
|
|
742
|
+
for (const child of node.children) {
|
|
743
|
+
const childVar = this.genNode(child, scope);
|
|
744
|
+
if (childVar) {
|
|
745
|
+
this.emit(`appendChild(${fragVar}, ${childVar})`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
this.emit(`return ${fragVar}`);
|
|
749
|
+
}
|
|
750
|
+
const slotLines = [...this.code];
|
|
751
|
+
this.code = savedCode;
|
|
752
|
+
this.emit(`const ${slotFnVar} = () => {`);
|
|
753
|
+
for (const line of slotLines) {
|
|
754
|
+
this.emit(` ${line}`);
|
|
755
|
+
}
|
|
756
|
+
this.emit(`}`);
|
|
757
|
+
this.emit(
|
|
758
|
+
`const ${compVar} = createComponent(${node.tag}, ${propsStr}, { default: ${slotFnVar} })`
|
|
759
|
+
);
|
|
760
|
+
} else {
|
|
761
|
+
this.emit(`const ${compVar} = createComponent(${node.tag}, ${propsStr})`);
|
|
762
|
+
}
|
|
668
763
|
return compVar;
|
|
669
764
|
}
|
|
670
765
|
// ---- Expression resolution ----------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matthesketh/utopia-compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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.
|
|
42
|
+
"@matthesketh/utopia-core": "0.3.1"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "tsup src/index.ts --format esm,cjs --dts",
|