@jxsuite/compiler 1.3.0 → 1.5.0
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/cli.js +167 -168
- package/dist/compiler.js +105 -107
- package/package.json +8 -7
- package/src/shared.ts +236 -8
- package/src/site/asset-mounts.ts +140 -0
- package/src/site/image-transform.ts +25 -7
- package/src/site/schema-command.ts +37 -77
- package/src/site/site-build.ts +96 -15
- package/src/site/validate-command.ts +133 -37
- package/src/targets/compile-client.ts +63 -67
- package/src/targets/compile-element.ts +246 -31
- package/src/targets/compile-static.ts +8 -2
|
@@ -16,21 +16,26 @@ import {
|
|
|
16
16
|
DEFAULT_LIT_HTML_SRC,
|
|
17
17
|
DEFAULT_REACTIVITY_SRC,
|
|
18
18
|
buildAttrs,
|
|
19
|
+
childSeparator,
|
|
19
20
|
colorSchemePrePaintScript,
|
|
20
21
|
compileExpression,
|
|
21
22
|
compileStatements,
|
|
22
23
|
compileStyles,
|
|
23
24
|
createCompileContext,
|
|
24
25
|
emitFormulaFn,
|
|
26
|
+
emitRequestFetch,
|
|
25
27
|
escapeHtml,
|
|
26
28
|
isMutating,
|
|
27
29
|
isRefObject,
|
|
28
30
|
isSchemaOnly,
|
|
29
31
|
isTemplateString,
|
|
32
|
+
PREFORMATTED_TAGS,
|
|
30
33
|
pureSchemeOf,
|
|
31
34
|
resolveStaticValue,
|
|
35
|
+
srcImportBinding,
|
|
32
36
|
} from "../shared.ts";
|
|
33
37
|
import {
|
|
38
|
+
bodyReturnsValue,
|
|
34
39
|
hasStructuredBody,
|
|
35
40
|
isExpandedSignal,
|
|
36
41
|
isExpressionDef,
|
|
@@ -47,7 +52,6 @@ import type {
|
|
|
47
52
|
JxDocument,
|
|
48
53
|
JxMappedArray,
|
|
49
54
|
JxMutableNode,
|
|
50
|
-
JxPrototypeDef,
|
|
51
55
|
JxStyle,
|
|
52
56
|
} from "@jxsuite/schema/types";
|
|
53
57
|
|
|
@@ -161,11 +165,11 @@ export function compileClient(
|
|
|
161
165
|
if (!srcImportMap.has(def.$src)) {
|
|
162
166
|
srcImportMap.set(def.$src, new Set());
|
|
163
167
|
}
|
|
164
|
-
(srcImportMap.get(def.$src) as Set<string>).add(key);
|
|
168
|
+
(srcImportMap.get(def.$src) as Set<string>).add(srcImportBinding(key, def));
|
|
165
169
|
|
|
166
170
|
// $src functions always produce computed entries (they return values)
|
|
167
171
|
computedEntries.push([key, `() => { return ${key}(state); }`]);
|
|
168
|
-
} else if (typeof def.body === "string" && def.body
|
|
172
|
+
} else if (typeof def.body === "string" && bodyReturnsValue(def.body)) {
|
|
169
173
|
// Body contains return → computed
|
|
170
174
|
computedEntries.push([key, `() => { ${def.body} }`]);
|
|
171
175
|
} else {
|
|
@@ -200,7 +204,7 @@ export function compileClient(
|
|
|
200
204
|
// $prototype: "Request"
|
|
201
205
|
if (def.$prototype === "Request") {
|
|
202
206
|
stateEntries.push([key, null]);
|
|
203
|
-
initBlocks.push(
|
|
207
|
+
initBlocks.push(emitRequestFetch(key, def));
|
|
204
208
|
continue;
|
|
205
209
|
}
|
|
206
210
|
|
|
@@ -335,6 +339,8 @@ function buildClientNode(
|
|
|
335
339
|
);
|
|
336
340
|
|
|
337
341
|
const tag = def.tagName ?? "div";
|
|
342
|
+
// `white-space` inherits, so once inside a <pre> the whole subtree stays whitespace-significant.
|
|
343
|
+
nextContext.preformatted = context.preformatted === true || PREFORMATTED_TAGS.has(tag);
|
|
338
344
|
const bindAttrs = [];
|
|
339
345
|
let needsBind = false;
|
|
340
346
|
|
|
@@ -530,9 +536,14 @@ function buildClientNode(
|
|
|
530
536
|
const listKey = `_children${counter.l}`;
|
|
531
537
|
counter.l += 1;
|
|
532
538
|
const childrenTpl = source.children
|
|
533
|
-
.map((c) => emitChildLit(c as JxMutableNode))
|
|
534
|
-
.join("
|
|
535
|
-
bindings.set(
|
|
539
|
+
.map((c) => emitChildLit(c as JxMutableNode, nextContext.preformatted))
|
|
540
|
+
.join(childSeparator(nextContext.preformatted, " "));
|
|
541
|
+
bindings.set(
|
|
542
|
+
listKey,
|
|
543
|
+
nextContext.preformatted
|
|
544
|
+
? `() => html\`${childrenTpl}\``
|
|
545
|
+
: `() => html\`\n ${childrenTpl}\n \``,
|
|
546
|
+
);
|
|
536
547
|
bindAttrs.push(`:render="${listKey}"`);
|
|
537
548
|
needsBind = true;
|
|
538
549
|
const bindAttrStr2 = ` ${bindAttrs.join(" ")}`;
|
|
@@ -551,7 +562,7 @@ function buildClientNode(
|
|
|
551
562
|
counter,
|
|
552
563
|
);
|
|
553
564
|
})
|
|
554
|
-
.join(
|
|
565
|
+
.join(childSeparator(nextContext.preformatted));
|
|
555
566
|
}
|
|
556
567
|
|
|
557
568
|
// Self-closing tags
|
|
@@ -570,13 +581,16 @@ function buildClientNode(
|
|
|
570
581
|
* index.
|
|
571
582
|
*
|
|
572
583
|
* @param {JxMutableNode} def
|
|
584
|
+
* @param {boolean} [preformatted]
|
|
573
585
|
* @returns {string}
|
|
574
586
|
*/
|
|
575
|
-
function emitLitMapTemplate(def: JxMutableNode | undefined) {
|
|
587
|
+
function emitLitMapTemplate(def: JxMutableNode | undefined, preformatted = false) {
|
|
576
588
|
if (!def) {
|
|
577
589
|
return "";
|
|
578
590
|
}
|
|
579
591
|
const tag = def.tagName ?? "div";
|
|
592
|
+
// `white-space` inherits, so once inside a <pre> the whole subtree stays whitespace-significant.
|
|
593
|
+
const inPre = preformatted || PREFORMATTED_TAGS.has(tag);
|
|
580
594
|
let attrs = "";
|
|
581
595
|
|
|
582
596
|
if (def.id) {
|
|
@@ -586,6 +600,20 @@ function emitLitMapTemplate(def: JxMutableNode | undefined) {
|
|
|
586
600
|
attrs += ` class="${mapRefsToLit(def.className)}"`;
|
|
587
601
|
}
|
|
588
602
|
|
|
603
|
+
// $props → lit property bindings. The element target emits these; the client target emitted
|
|
604
|
+
// Nothing, so a component in a list silently received none of its per-item props.
|
|
605
|
+
if (def.$props && typeof def.$props === "object") {
|
|
606
|
+
for (const [k, v] of Object.entries(def.$props)) {
|
|
607
|
+
if (isRefObject(v)) {
|
|
608
|
+
attrs += ` .${k}="\${${mapRefToClientExpr((v as { $ref: string }).$ref)}}"`;
|
|
609
|
+
} else if (typeof v === "string" && isTemplateString(v)) {
|
|
610
|
+
attrs += ` .${k}="${mapRefsToLit(v)}"`;
|
|
611
|
+
} else {
|
|
612
|
+
attrs += ` .${k}="\${${JSON.stringify(v)}}"`;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
589
617
|
// Attributes object
|
|
590
618
|
if (def.attributes && typeof def.attributes === "object") {
|
|
591
619
|
for (const [k, v] of Object.entries(def.attributes)) {
|
|
@@ -674,7 +702,8 @@ function emitLitMapTemplate(def: JxMutableNode | undefined) {
|
|
|
674
702
|
// Legacy whole-children repeater nested inside a map template.
|
|
675
703
|
inner = `\n ${emitArrayHole(def.children)}\n `;
|
|
676
704
|
} else if (Array.isArray(def.children)) {
|
|
677
|
-
|
|
705
|
+
const rendered = def.children.map((c) => emitChildLit(c, inPre));
|
|
706
|
+
inner = inPre ? rendered.join("") : `\n ${rendered.join("\n ")}\n `;
|
|
678
707
|
}
|
|
679
708
|
|
|
680
709
|
const voidTags = new Set(["input", "br", "hr", "img", "meta", "link"]);
|
|
@@ -689,9 +718,10 @@ function emitLitMapTemplate(def: JxMutableNode | undefined) {
|
|
|
689
718
|
* via a `.map()` hole), or a nested element.
|
|
690
719
|
*
|
|
691
720
|
* @param {JxMutableNode | string} c
|
|
721
|
+
* @param {boolean} [preformatted]
|
|
692
722
|
* @returns {string}
|
|
693
723
|
*/
|
|
694
|
-
function emitChildLit(c: JxMutableNode | string): string {
|
|
724
|
+
function emitChildLit(c: JxMutableNode | string, preformatted = false): string {
|
|
695
725
|
if (typeof c === "string") {
|
|
696
726
|
return isTemplateString(c) ? mapRefsToLit(c) : escapeHtml(c);
|
|
697
727
|
}
|
|
@@ -701,7 +731,7 @@ function emitChildLit(c: JxMutableNode | string): string {
|
|
|
701
731
|
if (isMappedArray(c)) {
|
|
702
732
|
return emitArrayHole(c);
|
|
703
733
|
}
|
|
704
|
-
return emitLitMapTemplate(c);
|
|
734
|
+
return emitLitMapTemplate(c, preformatted);
|
|
705
735
|
}
|
|
706
736
|
|
|
707
737
|
/**
|
|
@@ -888,61 +918,8 @@ function emitClientModule(
|
|
|
888
918
|
}
|
|
889
919
|
|
|
890
920
|
// ─── Prototype init emitters ─────────────────────────────────────────────────
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
* @param {string} key
|
|
894
|
-
* @param {JxMutableNode} def
|
|
895
|
-
* @returns {string}
|
|
896
|
-
*/
|
|
897
|
-
function emitRequestInit(key: string, def: JxPrototypeDef) {
|
|
898
|
-
const { url } = def;
|
|
899
|
-
const method = def.method ?? "GET";
|
|
900
|
-
const isTemplateUrl = isTemplateString(url);
|
|
901
|
-
|
|
902
|
-
if (def.manual) {
|
|
903
|
-
return `// ${key}: manual Request — fetch triggered by user action`;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
const lines: string[] = [
|
|
907
|
-
`// ${key}: auto-fetch from ${isTemplateUrl ? "(dynamic URL)" : url}`,
|
|
908
|
-
"effect(() => {",
|
|
909
|
-
];
|
|
910
|
-
|
|
911
|
-
if (isTemplateUrl) {
|
|
912
|
-
lines.push(
|
|
913
|
-
` const url = \`${url}\`;`,
|
|
914
|
-
' if (!url || url === "undefined" || url.includes("undefined")) return;',
|
|
915
|
-
);
|
|
916
|
-
} else {
|
|
917
|
-
lines.push(` const url = ${JSON.stringify(url)};`);
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
const fetchOpts: string[] = [];
|
|
921
|
-
if (method !== "GET") {
|
|
922
|
-
fetchOpts.push(`method: ${JSON.stringify(method)}`);
|
|
923
|
-
}
|
|
924
|
-
if (def.headers) {
|
|
925
|
-
fetchOpts.push(`headers: ${JSON.stringify(def.headers)}`);
|
|
926
|
-
}
|
|
927
|
-
if (def.body) {
|
|
928
|
-
const bodyStr =
|
|
929
|
-
typeof def.body === "object"
|
|
930
|
-
? JSON.stringify(JSON.stringify(def.body))
|
|
931
|
-
: JSON.stringify(def.body);
|
|
932
|
-
fetchOpts.push(`body: ${bodyStr}`);
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
const optsStr = fetchOpts.length > 0 ? `, { ${fetchOpts.join(", ")} }` : "";
|
|
936
|
-
lines.push(
|
|
937
|
-
` fetch(url${optsStr})`,
|
|
938
|
-
" .then(r => r.ok ? r.json() : Promise.reject(r.statusText))",
|
|
939
|
-
` .then(d => { state.${key} = d; })`,
|
|
940
|
-
` .catch(e => { state.${key} = { error: String(e) }; });`,
|
|
941
|
-
"});",
|
|
942
|
-
);
|
|
943
|
-
|
|
944
|
-
return lines.join("\n");
|
|
945
|
-
}
|
|
921
|
+
// Request auto-fetch lives in shared.ts (`emitRequestFetch`) so the element target emits the same
|
|
922
|
+
// Effect against `this.state`.
|
|
946
923
|
|
|
947
924
|
/**
|
|
948
925
|
* @param {string} key
|
|
@@ -995,6 +972,25 @@ function emitCookieInit(key: string, cookieName: string, defaultVal: unknown) {
|
|
|
995
972
|
* @param {string} ref
|
|
996
973
|
* @returns {string}
|
|
997
974
|
*/
|
|
975
|
+
/**
|
|
976
|
+
* Lower a `$ref` to the JS expression that reads it inside a map callback — the mirror of the
|
|
977
|
+
* element target's `refToExpr`/`mapRefToExpr`. Distinct from `refToBindingKey` below, which
|
|
978
|
+
* flattens `/` to `_` to name a binding and would produce nonsense (`state.$map_item`) as an
|
|
979
|
+
* expression.
|
|
980
|
+
*
|
|
981
|
+
* @param {string} ref
|
|
982
|
+
* @returns {string}
|
|
983
|
+
*/
|
|
984
|
+
function mapRefToClientExpr(ref: string) {
|
|
985
|
+
if (ref.startsWith("$map/")) {
|
|
986
|
+
return ref.slice("$map/".length).replaceAll("/", ".");
|
|
987
|
+
}
|
|
988
|
+
if (ref.startsWith("#/state/")) {
|
|
989
|
+
return `state.${ref.slice("#/state/".length).replaceAll("/", ".")}`;
|
|
990
|
+
}
|
|
991
|
+
return `state.${ref.replaceAll("/", ".")}`;
|
|
992
|
+
}
|
|
993
|
+
|
|
998
994
|
function refToBindingKey(ref: string) {
|
|
999
995
|
if (ref.startsWith("#/state/")) {
|
|
1000
996
|
return ref.slice("#/state/".length).replaceAll("/", "_");
|