@jxsuite/compiler 0.28.5 → 0.29.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 +108 -103
- package/dist/compiler.js +97 -92
- package/package.json +5 -4
- package/src/compiler.ts +6 -14
- package/src/shared.ts +26 -30
- package/src/site/content-loader.ts +7 -5
- package/src/site/context-injection.ts +1 -1
- package/src/site/image-cache.ts +2 -2
- package/src/site/image-optimizer.ts +4 -4
- package/src/site/image-transform.ts +5 -5
- package/src/site/pages-discovery.ts +4 -5
- package/src/site/site-build.ts +89 -40
- package/src/targets/compile-class.ts +7 -11
- package/src/targets/compile-client.ts +102 -42
- package/src/targets/compile-element.ts +82 -70
|
@@ -128,16 +128,16 @@ export function compileClassJson(classDef: JxClassDef, _opts: Record<string, unk
|
|
|
128
128
|
if (m.role === "accessor") {
|
|
129
129
|
// Getter
|
|
130
130
|
if (m.getter) {
|
|
131
|
-
lines.push(` ${staticPrefix}get ${prefix}${name}() {`);
|
|
132
|
-
lines.push(` ${m.getter.body}`);
|
|
133
|
-
lines.push(" }");
|
|
131
|
+
lines.push(` ${staticPrefix}get ${prefix}${name}() {`, ` ${m.getter.body}`, " }");
|
|
134
132
|
}
|
|
135
133
|
// Setter
|
|
136
134
|
if (m.setter) {
|
|
137
135
|
const setterParams = resolveParams(m.setter.parameters ?? []);
|
|
138
|
-
lines.push(
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
lines.push(
|
|
137
|
+
` ${staticPrefix}set ${prefix}${name}(${setterParams}) {`,
|
|
138
|
+
` ${m.setter.body}`,
|
|
139
|
+
" }",
|
|
140
|
+
);
|
|
141
141
|
}
|
|
142
142
|
} else {
|
|
143
143
|
lines.push(` ${staticPrefix}${asyncPrefix}${prefix}${name}(${params}) {`);
|
|
@@ -148,11 +148,7 @@ export function compileClassJson(classDef: JxClassDef, _opts: Record<string, unk
|
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
lines.push("}");
|
|
152
|
-
lines.push("");
|
|
153
|
-
lines.push(`export { ${className} };`);
|
|
154
|
-
lines.push(`export default ${className};`);
|
|
155
|
-
lines.push("");
|
|
151
|
+
lines.push("}", "", `export { ${className} };`, `export default ${className};`, "");
|
|
156
152
|
|
|
157
153
|
return lines.join("\n");
|
|
158
154
|
}
|
|
@@ -39,6 +39,7 @@ import type {
|
|
|
39
39
|
JsonObject,
|
|
40
40
|
JsonValue,
|
|
41
41
|
JxDocument,
|
|
42
|
+
JxMappedArray,
|
|
42
43
|
JxMutableNode,
|
|
43
44
|
JxPrototypeDef,
|
|
44
45
|
JxStyle,
|
|
@@ -481,6 +482,22 @@ function buildClientNode(
|
|
|
481
482
|
return `<${tag}${staticAttrs}${dataBindAttr2}${bindAttrStr2}>`;
|
|
482
483
|
}
|
|
483
484
|
return `<${tag}${staticAttrs}${dataBindAttr2}${bindAttrStr2}></${tag}>`;
|
|
485
|
+
} else if (Array.isArray(source.children) && source.children.some((c) => isMappedArray(c))) {
|
|
486
|
+
// ─── Children mix static nodes with array pseudo-elements ───
|
|
487
|
+
// Render the whole children region via one lit binding on this element (wrapper-less, one
|
|
488
|
+
// Lit part per element so multiple arrays among siblings don't collide), with each array
|
|
489
|
+
// Member expanded inline as a `.map()` hole.
|
|
490
|
+
counter.needsLit = true;
|
|
491
|
+
const listKey = `_children${counter.l}`;
|
|
492
|
+
counter.l += 1;
|
|
493
|
+
const childrenTpl = source.children
|
|
494
|
+
.map((c) => emitChildLit(c as JxMutableNode))
|
|
495
|
+
.join("\n ");
|
|
496
|
+
bindings.set(listKey, `() => html\`\n ${childrenTpl}\n \``);
|
|
497
|
+
bindAttrs.push(`:render="${listKey}"`);
|
|
498
|
+
needsBind = true;
|
|
499
|
+
const bindAttrStr2 = ` ${bindAttrs.join(" ")}`;
|
|
500
|
+
return `<${tag}${staticAttrs} data-bind${bindAttrStr2}></${tag}>`;
|
|
484
501
|
} else if (Array.isArray(source.children)) {
|
|
485
502
|
const rawChildren = Array.isArray(raw?.children) ? raw.children : undefined;
|
|
486
503
|
inner = source.children
|
|
@@ -611,10 +628,11 @@ function emitLitMapTemplate(def: JxMutableNode | undefined) {
|
|
|
611
628
|
}
|
|
612
629
|
} else if (def.innerHTML) {
|
|
613
630
|
inner = mapRefsToLit(String(def.innerHTML));
|
|
631
|
+
} else if (isMappedArray(def.children)) {
|
|
632
|
+
// Legacy whole-children repeater nested inside a map template.
|
|
633
|
+
inner = `\n ${emitArrayHole(def.children)}\n `;
|
|
614
634
|
} else if (Array.isArray(def.children)) {
|
|
615
|
-
inner = `\n ${def.children
|
|
616
|
-
.map((c) => emitLitMapTemplate(c as JxMutableNode))
|
|
617
|
-
.join("\n ")}\n `;
|
|
635
|
+
inner = `\n ${def.children.map((c) => emitChildLit(c)).join("\n ")}\n `;
|
|
618
636
|
}
|
|
619
637
|
|
|
620
638
|
const voidTags = new Set(["input", "br", "hr", "img", "meta", "link"]);
|
|
@@ -624,6 +642,42 @@ function emitLitMapTemplate(def: JxMutableNode | undefined) {
|
|
|
624
642
|
return `<${tag}${attrs}>${inner}</${tag}>`;
|
|
625
643
|
}
|
|
626
644
|
|
|
645
|
+
/**
|
|
646
|
+
* Emit one child of a lit template: a string text node, an array pseudo-element (expanded inline
|
|
647
|
+
* via a `.map()` hole), or a nested element.
|
|
648
|
+
*
|
|
649
|
+
* @param {JxMutableNode | string} c
|
|
650
|
+
* @returns {string}
|
|
651
|
+
*/
|
|
652
|
+
function emitChildLit(c: JxMutableNode | string): string {
|
|
653
|
+
if (typeof c === "string") {
|
|
654
|
+
return isTemplateString(c) ? mapRefsToLit(c) : escapeHtml(c);
|
|
655
|
+
}
|
|
656
|
+
if (typeof c === "number" || typeof c === "boolean") {
|
|
657
|
+
return escapeHtml(String(c));
|
|
658
|
+
}
|
|
659
|
+
if (isMappedArray(c)) {
|
|
660
|
+
return emitArrayHole(c);
|
|
661
|
+
}
|
|
662
|
+
return emitLitMapTemplate(c);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Emit a lit-html `${(items ?? []).map(...)}` hole for a mapped array, expanding its template
|
|
667
|
+
* inline among sibling nodes (wrapper-less).
|
|
668
|
+
*
|
|
669
|
+
* @param {JxMappedArray} arrayDef
|
|
670
|
+
* @returns {string}
|
|
671
|
+
*/
|
|
672
|
+
function emitArrayHole(arrayDef: JxMappedArray): string {
|
|
673
|
+
const { items } = arrayDef;
|
|
674
|
+
const itemsExpr = isRefObject(items)
|
|
675
|
+
? `state.${refToBindingKey((items as { $ref: string }).$ref)}`
|
|
676
|
+
: JSON.stringify(items ?? []);
|
|
677
|
+
const tpl = emitLitMapTemplate(arrayDef.map);
|
|
678
|
+
return `\${(${itemsExpr} ?? []).map((item, index) => html\`${tpl}\`)}`;
|
|
679
|
+
}
|
|
680
|
+
|
|
627
681
|
/**
|
|
628
682
|
* Replace $map references: $map.item → item, $map.index → index
|
|
629
683
|
*
|
|
@@ -696,15 +750,12 @@ function emitClientModule(
|
|
|
696
750
|
lines.push(`import { ${[...names].join(", ")} } from '${src}';`);
|
|
697
751
|
}
|
|
698
752
|
|
|
699
|
-
lines.push("");
|
|
700
|
-
|
|
701
753
|
// State — reactive state
|
|
702
|
-
lines.push("const state = reactive({");
|
|
754
|
+
lines.push("", "const state = reactive({");
|
|
703
755
|
for (const [key, val] of stateEntries) {
|
|
704
756
|
lines.push(` ${key}: ${JSON.stringify(val)},`);
|
|
705
757
|
}
|
|
706
|
-
lines.push("});");
|
|
707
|
-
lines.push("");
|
|
758
|
+
lines.push("});", "");
|
|
708
759
|
|
|
709
760
|
// Prototype init blocks (Request fetch, LocalStorage read, etc.)
|
|
710
761
|
if (initBlocks.length > 0) {
|
|
@@ -750,38 +801,43 @@ function emitClientModule(
|
|
|
750
801
|
} else {
|
|
751
802
|
lines.push("const on = {};");
|
|
752
803
|
}
|
|
753
|
-
lines.push("");
|
|
754
|
-
|
|
755
804
|
// Hydration function
|
|
756
|
-
lines.push(
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
805
|
+
lines.push(
|
|
806
|
+
"",
|
|
807
|
+
"function hydrate(root) {",
|
|
808
|
+
" root.querySelectorAll('[data-bind]').forEach(el => {",
|
|
809
|
+
" [...el.attributes].forEach(a => {",
|
|
810
|
+
" if (a.name.startsWith(':')) {",
|
|
811
|
+
" const parts = a.name.slice(1).split('.');",
|
|
812
|
+
" const key = a.value;",
|
|
813
|
+
);
|
|
762
814
|
if (needsLit) {
|
|
763
|
-
lines.push(
|
|
764
|
-
|
|
765
|
-
|
|
815
|
+
lines.push(
|
|
816
|
+
" if (parts[0] === 'render') {",
|
|
817
|
+
" effect(() => { render(bind[key](), el); });",
|
|
818
|
+
" } else if (parts[0] === 'style' && parts.length > 1) {",
|
|
819
|
+
);
|
|
766
820
|
} else {
|
|
767
821
|
lines.push(" if (parts[0] === 'style' && parts.length > 1) {");
|
|
768
822
|
}
|
|
769
|
-
lines.push(
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
823
|
+
lines.push(
|
|
824
|
+
" effect(() => { el.style[parts[1]] = bind[key](); });",
|
|
825
|
+
" } else if (parts[0] === 'attr' && parts.length > 1) {",
|
|
826
|
+
" effect(() => { el.setAttribute(parts[1], bind[key]()); });",
|
|
827
|
+
" } else {",
|
|
828
|
+
" const prop = parts[0].replace(/-([a-z])/g, (_, c) => c.toUpperCase());",
|
|
829
|
+
" effect(() => { el[prop] = bind[key](); });",
|
|
830
|
+
" }",
|
|
831
|
+
" } else if (a.name.startsWith('@')) {",
|
|
832
|
+
" el.addEventListener(a.name.slice(1), on[a.value]);",
|
|
833
|
+
" }",
|
|
834
|
+
" });",
|
|
835
|
+
" });",
|
|
836
|
+
"}",
|
|
837
|
+
"",
|
|
838
|
+
"hydrate(document);",
|
|
839
|
+
"",
|
|
840
|
+
);
|
|
785
841
|
|
|
786
842
|
return lines.join("\n");
|
|
787
843
|
}
|
|
@@ -808,8 +864,10 @@ function emitRequestInit(key: string, def: JxPrototypeDef) {
|
|
|
808
864
|
];
|
|
809
865
|
|
|
810
866
|
if (isTemplateUrl) {
|
|
811
|
-
lines.push(
|
|
812
|
-
|
|
867
|
+
lines.push(
|
|
868
|
+
` const url = \`${url}\`;`,
|
|
869
|
+
' if (!url || url === "undefined" || url.includes("undefined")) return;',
|
|
870
|
+
);
|
|
813
871
|
} else {
|
|
814
872
|
lines.push(` const url = ${JSON.stringify(url)};`);
|
|
815
873
|
}
|
|
@@ -830,11 +888,13 @@ function emitRequestInit(key: string, def: JxPrototypeDef) {
|
|
|
830
888
|
}
|
|
831
889
|
|
|
832
890
|
const optsStr = fetchOpts.length > 0 ? `, { ${fetchOpts.join(", ")} }` : "";
|
|
833
|
-
lines.push(
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
891
|
+
lines.push(
|
|
892
|
+
` fetch(url${optsStr})`,
|
|
893
|
+
" .then(r => r.ok ? r.json() : Promise.reject(r.statusText))",
|
|
894
|
+
` .then(d => { state.${key} = d; })`,
|
|
895
|
+
` .catch(e => { state.${key} = { error: String(e) }; });`,
|
|
896
|
+
"});",
|
|
897
|
+
);
|
|
838
898
|
|
|
839
899
|
return lines.join("\n");
|
|
840
900
|
}
|
|
@@ -268,16 +268,17 @@ export function emitElementModule(doc: JxDocument, className: string, elementImp
|
|
|
268
268
|
lines.push(`import { ${names.join(", ")} } from '${srcPath}';`);
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
lines.push(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
271
|
+
lines.push(
|
|
272
|
+
`import { reactive, computed, effect } from '@vue/reactivity';`,
|
|
273
|
+
`import { render, html } from 'lit-html';`,
|
|
274
|
+
"",
|
|
275
|
+
`class ${className} extends HTMLElement {`,
|
|
276
|
+
" #dispose = null;",
|
|
277
|
+
"",
|
|
278
|
+
// Constructor: build reactive state
|
|
279
|
+
" constructor() {",
|
|
280
|
+
" super();",
|
|
281
|
+
);
|
|
281
282
|
|
|
282
283
|
const stateEntries: [string, string][] = [];
|
|
283
284
|
const computedEntries: [string, JxExpressionDef | JxFunctionDef][] = [];
|
|
@@ -330,9 +331,11 @@ export function emitElementModule(doc: JxDocument, className: string, elementImp
|
|
|
330
331
|
// $src function — wrap imported function so it receives state
|
|
331
332
|
lines.push(` this.state.${key} = (${paramList}) => ${key}(${paramList});`);
|
|
332
333
|
} else {
|
|
333
|
-
lines.push(
|
|
334
|
-
|
|
335
|
-
|
|
334
|
+
lines.push(
|
|
335
|
+
` this.state.${key} = (${paramList}) => {`,
|
|
336
|
+
` ${def.body ?? ""}`,
|
|
337
|
+
" };",
|
|
338
|
+
);
|
|
336
339
|
}
|
|
337
340
|
}
|
|
338
341
|
}
|
|
@@ -351,13 +354,11 @@ export function emitElementModule(doc: JxDocument, className: string, elementImp
|
|
|
351
354
|
} else {
|
|
352
355
|
lines.push(` this.state.${key} = computed(() => {`);
|
|
353
356
|
const body = (def.body ?? "").replaceAll("state.", "this.state.");
|
|
354
|
-
lines.push(` ${body}
|
|
355
|
-
lines.push(" });");
|
|
357
|
+
lines.push(` ${body}`, " });");
|
|
356
358
|
}
|
|
357
359
|
}
|
|
358
360
|
|
|
359
|
-
lines.push(" }"); // End constructor
|
|
360
|
-
lines.push("");
|
|
361
|
+
lines.push(" }", ""); // End constructor
|
|
361
362
|
|
|
362
363
|
// Collect CSS rules from children tree (assigns .jx-N classes to defs)
|
|
363
364
|
const cssRules: string[] = [];
|
|
@@ -368,35 +369,36 @@ export function emitElementModule(doc: JxDocument, className: string, elementImp
|
|
|
368
369
|
}
|
|
369
370
|
}
|
|
370
371
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
372
|
+
lines.push(
|
|
373
|
+
// Template method
|
|
374
|
+
" template() {",
|
|
375
|
+
" const s = this.state;",
|
|
376
|
+
" return html`",
|
|
377
|
+
emitLitChildren(doc.children, doc.style, " "),
|
|
378
|
+
" `;",
|
|
379
|
+
" }",
|
|
380
|
+
"",
|
|
381
|
+
// ConnectedCallback
|
|
382
|
+
" connectedCallback() {",
|
|
383
|
+
// Read $props from data-jx-props attribute (set by compiler for pre-rendered instances)
|
|
384
|
+
" const _pa = this.getAttribute('data-jx-props');",
|
|
385
|
+
" if (_pa) {",
|
|
386
|
+
" try {",
|
|
387
|
+
" const _p = JSON.parse(_pa);",
|
|
388
|
+
" for (const [k, v] of Object.entries(_p)) {",
|
|
389
|
+
" if (k in this.state) this.state[k] = v;",
|
|
390
|
+
" }",
|
|
391
|
+
" } catch {}",
|
|
392
|
+
" this.removeAttribute('data-jx-props');",
|
|
393
|
+
" }",
|
|
394
|
+
// Merge JS properties set before connection (by parent runtime).
|
|
395
|
+
// Only check own properties to avoid inherited DOM properties like `title`.
|
|
396
|
+
" for (const key of Object.keys(this.state)) {",
|
|
397
|
+
" if (this.hasOwnProperty(key) && this[key] !== undefined) {",
|
|
398
|
+
" this.state[key] = this[key];",
|
|
399
|
+
" }",
|
|
400
|
+
" }",
|
|
401
|
+
);
|
|
400
402
|
if (doc.style && typeof doc.style === "object") {
|
|
401
403
|
const dynamicStyles: [string, string][] = [];
|
|
402
404
|
for (const [prop, value] of Object.entries(doc.style)) {
|
|
@@ -436,31 +438,35 @@ export function emitElementModule(doc: JxDocument, className: string, elementImp
|
|
|
436
438
|
" const _slotted = Array.from(this.childNodes).filter(n => n.nodeType === 1 || (n.nodeType === 3 && n.textContent.trim()));",
|
|
437
439
|
);
|
|
438
440
|
}
|
|
439
|
-
lines.push(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
441
|
+
lines.push(
|
|
442
|
+
" if (this.hasAttribute('data-jx-prerendered')) {",
|
|
443
|
+
" this.removeAttribute('data-jx-prerendered');",
|
|
444
|
+
" }",
|
|
445
|
+
" this.innerHTML = '';",
|
|
446
|
+
" this.#dispose = effect(() => render(this.template(), this));",
|
|
447
|
+
);
|
|
444
448
|
if (hasSlot) {
|
|
445
449
|
// Replace <slot> placeholder with saved slotted content
|
|
446
|
-
lines.push(
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
450
|
+
lines.push(
|
|
451
|
+
" const _slot = this.querySelector('slot');",
|
|
452
|
+
" if (_slot && _slotted.length > 0) {",
|
|
453
|
+
" for (const n of _slotted) _slot.before(n);",
|
|
454
|
+
" _slot.remove();",
|
|
455
|
+
" }",
|
|
456
|
+
);
|
|
451
457
|
}
|
|
452
|
-
lines.push(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
458
|
+
lines.push(
|
|
459
|
+
" }",
|
|
460
|
+
"",
|
|
461
|
+
// DisconnectedCallback
|
|
462
|
+
" disconnectedCallback() {",
|
|
463
|
+
" if (this.#dispose) { this.#dispose(); this.#dispose = null; }",
|
|
464
|
+
" }",
|
|
465
|
+
"}",
|
|
466
|
+
"",
|
|
467
|
+
`customElements.define('${doc.tagName}', ${className});`,
|
|
468
|
+
"",
|
|
469
|
+
);
|
|
464
470
|
|
|
465
471
|
return lines.join("\n");
|
|
466
472
|
}
|
|
@@ -475,13 +481,14 @@ export function emitElementModule(doc: JxDocument, className: string, elementImp
|
|
|
475
481
|
*/
|
|
476
482
|
function emitLitChildren(
|
|
477
483
|
children: JxMutableNode["children"],
|
|
478
|
-
|
|
484
|
+
_parentStyle: JxStyle | null | undefined,
|
|
479
485
|
indent: string,
|
|
480
486
|
) {
|
|
481
487
|
if (!children) {
|
|
482
488
|
return "";
|
|
483
489
|
}
|
|
484
490
|
|
|
491
|
+
// Legacy whole-children repeater: the array IS the children slot.
|
|
485
492
|
if (isMappedArray(children)) {
|
|
486
493
|
return emitMappedArray(children, indent);
|
|
487
494
|
}
|
|
@@ -490,7 +497,12 @@ function emitLitChildren(
|
|
|
490
497
|
return "";
|
|
491
498
|
}
|
|
492
499
|
|
|
493
|
-
|
|
500
|
+
// Mixed children: elements/text plus array pseudo-elements expanded inline among siblings.
|
|
501
|
+
return children
|
|
502
|
+
.map((child: JxMutableNode | string) =>
|
|
503
|
+
isMappedArray(child) ? emitMappedArray(child, indent) : emitLitNode(child, indent),
|
|
504
|
+
)
|
|
505
|
+
.join("\n");
|
|
494
506
|
}
|
|
495
507
|
|
|
496
508
|
/**
|