@jxsuite/compiler 0.22.0 → 0.23.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/compiler.js +171 -171
- package/package.json +3 -3
- package/src/shared.js +53 -25
- package/src/site/site-build.js +1 -1
- package/src/targets/compile-client.js +30 -0
- package/src/targets/compile-element.js +43 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Jx static HTML compiler, island detector, and site builder",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@apidevtools/json-schema-ref-parser": "^15.3.5",
|
|
41
|
-
"@jxsuite/parser": "^0.
|
|
42
|
-
"@jxsuite/runtime": "^0.
|
|
41
|
+
"@jxsuite/parser": "^0.23.0",
|
|
42
|
+
"@jxsuite/runtime": "^0.23.0",
|
|
43
43
|
"remark-gfm": "^4.0.1",
|
|
44
44
|
"remark-stringify": "^11.0.0",
|
|
45
45
|
"sharp": "^0.34.5",
|
package/src/shared.js
CHANGED
|
@@ -6,9 +6,17 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { camelToKebab, toCSSText, RESERVED_KEYS } from "@jxsuite/runtime";
|
|
9
|
+
import { compileExpression, isMutating, evaluateExpression } from "@jxsuite/runtime/expression";
|
|
9
10
|
|
|
10
11
|
// Re-export runtime utilities used by submodules
|
|
11
|
-
export {
|
|
12
|
+
export {
|
|
13
|
+
camelToKebab,
|
|
14
|
+
toCSSText,
|
|
15
|
+
RESERVED_KEYS,
|
|
16
|
+
compileExpression,
|
|
17
|
+
isMutating,
|
|
18
|
+
evaluateExpression,
|
|
19
|
+
};
|
|
12
20
|
|
|
13
21
|
// CDN defaults
|
|
14
22
|
export const DEFAULT_REACTIVITY_SRC = "https://esm.sh/@vue/reactivity@3.5.32";
|
|
@@ -231,7 +239,7 @@ export function buildInitialScope(defs = {}, parentScope = null) {
|
|
|
231
239
|
setOwnScopeValue(scope, key, cloneValue(d.default));
|
|
232
240
|
continue;
|
|
233
241
|
}
|
|
234
|
-
if (!d.$prototype && !isSchemaOnly(d)) {
|
|
242
|
+
if (!d.$prototype && !("$expression" in d) && !isSchemaOnly(d)) {
|
|
235
243
|
setOwnScopeValue(scope, key, cloneValue(d));
|
|
236
244
|
}
|
|
237
245
|
}
|
|
@@ -243,6 +251,17 @@ export function buildInitialScope(defs = {}, parentScope = null) {
|
|
|
243
251
|
}
|
|
244
252
|
if (!def || typeof def !== "object") continue;
|
|
245
253
|
const d = /** @type {JxStateObject & JxPrototypeDef} */ (def);
|
|
254
|
+
if ("$expression" in d) {
|
|
255
|
+
const node = /** @type {any} */ (d).$expression;
|
|
256
|
+
if (isMutating(node.operator)) {
|
|
257
|
+
setOwnScopeValue(scope, key, (/** @type {any} */ s, /** @type {any} */ event) =>
|
|
258
|
+
evaluateExpression(node, s, event),
|
|
259
|
+
);
|
|
260
|
+
} else {
|
|
261
|
+
defineLazyScopeValue(scope, key, () => evaluateExpression(node, scope, null));
|
|
262
|
+
}
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
246
265
|
if (d.$prototype === "Function") {
|
|
247
266
|
if (d.body) {
|
|
248
267
|
const fn = new Function("state", ...(d.parameters ?? d.arguments ?? []), d.body);
|
|
@@ -518,11 +537,11 @@ export function compileStyles(doc, mediaQueries = {}, projectStyle = null) {
|
|
|
518
537
|
for (const [key, val] of Object.entries(obj)) {
|
|
519
538
|
if (val === null || typeof val !== "object" || Array.isArray(val)) continue;
|
|
520
539
|
if (key.startsWith("@")) {
|
|
521
|
-
const
|
|
522
|
-
?
|
|
523
|
-
: key
|
|
540
|
+
const atRule = key.startsWith("@--")
|
|
541
|
+
? `@media ${mediaQueries[key.slice(1)] ?? key.slice(1)}`
|
|
542
|
+
: key;
|
|
524
543
|
const mProps = toCSSText(val);
|
|
525
|
-
if (mProps) rules.push(
|
|
544
|
+
if (mProps) rules.push(`${atRule} { ${selector} { ${mProps} } }`);
|
|
526
545
|
for (const [sel, sub] of Object.entries(/** @type {Record<string, unknown>} */ (val))) {
|
|
527
546
|
if (sub === null || typeof sub !== "object" || Array.isArray(sub)) continue;
|
|
528
547
|
if (sel.startsWith("@")) continue;
|
|
@@ -532,7 +551,7 @@ export function compileStyles(doc, mediaQueries = {}, projectStyle = null) {
|
|
|
532
551
|
? `${selector}${sel}`
|
|
533
552
|
: `${selector} ${sel}`;
|
|
534
553
|
const subProps = toCSSText(sub);
|
|
535
|
-
if (subProps) rules.push(
|
|
554
|
+
if (subProps) rules.push(`${atRule} { ${resolved} { ${subProps} } }`);
|
|
536
555
|
}
|
|
537
556
|
continue;
|
|
538
557
|
}
|
|
@@ -558,10 +577,12 @@ export function compileStyles(doc, mediaQueries = {}, projectStyle = null) {
|
|
|
558
577
|
emitProjectRules(key, val);
|
|
559
578
|
} else if (key.startsWith("@")) {
|
|
560
579
|
// @media block at top level
|
|
561
|
-
const
|
|
562
|
-
?
|
|
563
|
-
: key.
|
|
564
|
-
|
|
580
|
+
const atRule = key.startsWith("@--")
|
|
581
|
+
? `@media ${mediaQueries[key.slice(1)] ?? key.slice(1)}`
|
|
582
|
+
: key.startsWith("@(")
|
|
583
|
+
? `@media ${key.slice(1)}`
|
|
584
|
+
: key;
|
|
585
|
+
rules.push(`${atRule} { body { ${toCSSText(/** @type {object} */ (val))} } }`);
|
|
565
586
|
}
|
|
566
587
|
}
|
|
567
588
|
// Collect CSS custom properties into :root {}
|
|
@@ -610,11 +631,13 @@ function emitNestedElement(selector, obj, rules, mediaQueries) {
|
|
|
610
631
|
for (const [key, val] of Object.entries(obj)) {
|
|
611
632
|
if (val === null || typeof val !== "object" || Array.isArray(val)) continue;
|
|
612
633
|
if (key.startsWith("@")) {
|
|
613
|
-
const
|
|
614
|
-
?
|
|
615
|
-
: key.
|
|
634
|
+
const atRule = key.startsWith("@--")
|
|
635
|
+
? `@media ${mediaQueries[key.slice(1)] ?? key.slice(1)}`
|
|
636
|
+
: key.startsWith("@(")
|
|
637
|
+
? `@media ${key.slice(1)}`
|
|
638
|
+
: key;
|
|
616
639
|
const mProps = toCSSText(val);
|
|
617
|
-
if (mProps) rules.push(
|
|
640
|
+
if (mProps) rules.push(`${atRule} { ${selector} { ${mProps} } }`);
|
|
618
641
|
for (const [sel, sub] of Object.entries(/** @type {Record<string, unknown>} */ (val))) {
|
|
619
642
|
if (sub === null || typeof sub !== "object" || Array.isArray(sub)) continue;
|
|
620
643
|
if (sel.startsWith("@")) continue;
|
|
@@ -624,7 +647,7 @@ function emitNestedElement(selector, obj, rules, mediaQueries) {
|
|
|
624
647
|
? `${selector}${sel}`
|
|
625
648
|
: `${selector} ${sel}`;
|
|
626
649
|
const subProps = toCSSText(sub);
|
|
627
|
-
if (subProps) rules.push(
|
|
650
|
+
if (subProps) rules.push(`${atRule} { ${resolved} { ${subProps} } }`);
|
|
628
651
|
}
|
|
629
652
|
continue;
|
|
630
653
|
}
|
|
@@ -688,10 +711,13 @@ export function collectStyles(
|
|
|
688
711
|
for (const [prop, val] of Object.entries(def.style)) {
|
|
689
712
|
if (val === null || typeof val !== "object" || Array.isArray(val)) continue;
|
|
690
713
|
if (prop.startsWith("@")) {
|
|
691
|
-
const
|
|
692
|
-
?
|
|
693
|
-
: prop.
|
|
694
|
-
|
|
714
|
+
const atRule = prop.startsWith("@--")
|
|
715
|
+
? `@media ${mediaQueries[prop.slice(1)] ?? prop.slice(1)}`
|
|
716
|
+
: prop.startsWith("@(")
|
|
717
|
+
? `@media ${prop.slice(1)}`
|
|
718
|
+
: prop;
|
|
719
|
+
const atBaseProps = toCSSText(val);
|
|
720
|
+
if (atBaseProps) rules.push(`${atRule} { ${selector} { ${atBaseProps} } }`);
|
|
695
721
|
for (const [sel, nestedRules] of Object.entries(
|
|
696
722
|
/** @type {Record<string, unknown>} */ (val),
|
|
697
723
|
)) {
|
|
@@ -703,7 +729,7 @@ export function collectStyles(
|
|
|
703
729
|
: sel.startsWith(":") || sel.startsWith(".") || sel.startsWith("[")
|
|
704
730
|
? `${selector}${sel}`
|
|
705
731
|
: `${selector} ${sel}`;
|
|
706
|
-
rules.push(
|
|
732
|
+
rules.push(`${atRule} { ${resolved} { ${toCSSText(nestedRules)} } }`);
|
|
707
733
|
}
|
|
708
734
|
} else {
|
|
709
735
|
const resolved = prop.startsWith("&")
|
|
@@ -1038,10 +1064,12 @@ export function buildComponentCSS(tagName, styleDef, doc = null, mediaQueries =
|
|
|
1038
1064
|
|
|
1039
1065
|
for (const [prop, val] of Object.entries(styleDef)) {
|
|
1040
1066
|
if (prop.startsWith("@")) {
|
|
1041
|
-
const
|
|
1042
|
-
?
|
|
1043
|
-
: prop.
|
|
1044
|
-
|
|
1067
|
+
const atRule = prop.startsWith("@--")
|
|
1068
|
+
? `@media ${mediaQueries[prop.slice(1)] ?? prop.slice(1)}`
|
|
1069
|
+
: prop.startsWith("@(")
|
|
1070
|
+
? `@media ${prop.slice(1)}`
|
|
1071
|
+
: prop;
|
|
1072
|
+
rules.push(`${atRule} { ${tagName} { ${toCSSText(/** @type {object} */ (val))} } }`);
|
|
1045
1073
|
} else if (
|
|
1046
1074
|
prop.startsWith(":") ||
|
|
1047
1075
|
prop.startsWith(".") ||
|
package/src/site/site-build.js
CHANGED
|
@@ -134,7 +134,7 @@ export async function buildSite(projectRoot, options = {}) {
|
|
|
134
134
|
for (const file of componentFiles) {
|
|
135
135
|
try {
|
|
136
136
|
const componentPath = resolve(componentsDir, file);
|
|
137
|
-
const result = await compileElement(componentPath);
|
|
137
|
+
const result = await compileElement(componentPath, { $media: projectConfig.$media });
|
|
138
138
|
for (const f of result.files) {
|
|
139
139
|
const outName = f.path.includes("/")
|
|
140
140
|
? /** @type {string} */ (f.path.split("/").pop())
|
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
escapeHtml,
|
|
24
24
|
DEFAULT_REACTIVITY_SRC,
|
|
25
25
|
DEFAULT_LIT_HTML_SRC,
|
|
26
|
+
compileExpression,
|
|
27
|
+
isMutating,
|
|
26
28
|
} from "../shared.js";
|
|
27
29
|
|
|
28
30
|
/**
|
|
@@ -85,6 +87,19 @@ export function compileClient(raw, opts) {
|
|
|
85
87
|
|
|
86
88
|
const d = /** @type {JxMutableNode} */ (def);
|
|
87
89
|
|
|
90
|
+
// $expression: Shape 5
|
|
91
|
+
if ("$expression" in d) {
|
|
92
|
+
const node = /** @type {any} */ (d).$expression;
|
|
93
|
+
if (isMutating(node.operator)) {
|
|
94
|
+
const compiled = compileExpression(node, { statePrefix: "state", eventParam: "e" });
|
|
95
|
+
onEntries.push([key, { args: ["state", "e"], body: compiled }]);
|
|
96
|
+
} else {
|
|
97
|
+
const compiled = compileExpression(node, { statePrefix: "state", eventParam: "e" });
|
|
98
|
+
computedEntries.push([key, `() => ${compiled}`]);
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
88
103
|
// $prototype: "Function"
|
|
89
104
|
if (d.$prototype === "Function") {
|
|
90
105
|
const args = d.parameters ?? d.arguments;
|
|
@@ -274,6 +289,15 @@ function buildClientNode(def, raw, context, bindings, handlers, counter) {
|
|
|
274
289
|
bindAttrs.push(`@${eventName}="${key}"`);
|
|
275
290
|
handlers.set(key, { args: v.parameters ?? v.arguments ?? ["state", "event"], body: v.body });
|
|
276
291
|
needsBind = true;
|
|
292
|
+
} else if (val && typeof val === "object" && "$expression" in /** @type {any} */ (val)) {
|
|
293
|
+
const key = `_h${counter.h++}`;
|
|
294
|
+
bindAttrs.push(`@${eventName}="${key}"`);
|
|
295
|
+
const compiled = compileExpression(/** @type {any} */ (val).$expression, {
|
|
296
|
+
statePrefix: "state",
|
|
297
|
+
eventParam: "e",
|
|
298
|
+
});
|
|
299
|
+
handlers.set(key, { args: ["state", "e"], body: compiled });
|
|
300
|
+
needsBind = true;
|
|
277
301
|
}
|
|
278
302
|
}
|
|
279
303
|
|
|
@@ -500,6 +524,12 @@ function emitLitMapTemplate(def) {
|
|
|
500
524
|
) {
|
|
501
525
|
const body = mapRefsToLit(/** @type {string} */ (/** @type {JxMutableNode} */ (val).body));
|
|
502
526
|
attrs += " @" + eventName + "=${(e) => { " + body + " }}";
|
|
527
|
+
} else if (val && typeof val === "object" && "$expression" in /** @type {any} */ (val)) {
|
|
528
|
+
const compiled = compileExpression(/** @type {any} */ (val).$expression, {
|
|
529
|
+
statePrefix: "state",
|
|
530
|
+
eventParam: "e",
|
|
531
|
+
});
|
|
532
|
+
attrs += " @" + eventName + "=${(e) => { state.$map = { item, index }; " + compiled + "; }}";
|
|
503
533
|
}
|
|
504
534
|
}
|
|
505
535
|
|
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { camelToKebab, RESERVED_KEYS } from "@jxsuite/runtime";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
escapeHtml,
|
|
11
|
+
tagNameToClassName,
|
|
12
|
+
isSchemaOnly,
|
|
13
|
+
collectStyles,
|
|
14
|
+
compileExpression,
|
|
15
|
+
isMutating,
|
|
16
|
+
} from "../shared.js";
|
|
10
17
|
|
|
11
18
|
/**
|
|
12
19
|
* Compile a Jx custom element document to a JS module string.
|
|
@@ -16,7 +23,7 @@ import { escapeHtml, tagNameToClassName, isSchemaOnly, collectStyles } from "../
|
|
|
16
23
|
* @returns {Promise<{ files: { path: string; content: string; tagName: string }[] }>}
|
|
17
24
|
*/
|
|
18
25
|
export async function compileElement(sourcePath, opts = {}) {
|
|
19
|
-
const { resolveElementPath } = /** @type {JxMutableNode} */ (opts);
|
|
26
|
+
const { resolveElementPath, $media: optsMedia } = /** @type {JxMutableNode} */ (opts);
|
|
20
27
|
/** @type {{ path: string; content: string; tagName: string }[]} */
|
|
21
28
|
const files = [];
|
|
22
29
|
/** @type {Set<string>} */
|
|
@@ -82,6 +89,9 @@ export async function compileElement(sourcePath, opts = {}) {
|
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
const className = tagNameToClassName(tagName);
|
|
92
|
+
if (optsMedia) {
|
|
93
|
+
doc.$media = { ...optsMedia, ...doc.$media };
|
|
94
|
+
}
|
|
85
95
|
const jsContent = emitElementModule(doc, className, elementImports);
|
|
86
96
|
const outputPath = filePath ? filePath.replace(/\.json$/, ".js") : `${tagName}.js`;
|
|
87
97
|
files.push({ path: outputPath, content: jsContent, tagName });
|
|
@@ -227,7 +237,14 @@ export function emitElementModule(doc, className, elementImports) {
|
|
|
227
237
|
|
|
228
238
|
for (const [key, def] of Object.entries(defs)) {
|
|
229
239
|
const d = /** @type {JxMutableNode} */ (def);
|
|
230
|
-
if (d && typeof d === "object" && !Array.isArray(d) &&
|
|
240
|
+
if (d && typeof d === "object" && !Array.isArray(d) && "$expression" in d) {
|
|
241
|
+
const node = /** @type {any} */ (d).$expression;
|
|
242
|
+
if (isMutating(node.operator)) {
|
|
243
|
+
functionEntries.push([key, d]);
|
|
244
|
+
} else {
|
|
245
|
+
computedEntries.push([key, d]);
|
|
246
|
+
}
|
|
247
|
+
} else if (d && typeof d === "object" && !Array.isArray(d) && d.$prototype === "Function") {
|
|
231
248
|
if (typeof d.body === "string" && d.body.includes("return")) {
|
|
232
249
|
computedEntries.push([key, d]);
|
|
233
250
|
} else {
|
|
@@ -252,7 +269,10 @@ export function emitElementModule(doc, className, elementImports) {
|
|
|
252
269
|
// Emit functions: this.state.fnName = (state) => { body } or imported $src function
|
|
253
270
|
for (const [key, def] of functionEntries) {
|
|
254
271
|
lines.push("");
|
|
255
|
-
if (def
|
|
272
|
+
if ("$expression" in def) {
|
|
273
|
+
const compiled = compileExpression(def.$expression, { statePrefix: "s", eventParam: "e" });
|
|
274
|
+
lines.push(` this.state.${key} = (s, e) => { ${compiled}; };`);
|
|
275
|
+
} else if (def.$src) {
|
|
256
276
|
// $src function — wrap imported function so it receives state
|
|
257
277
|
const args = def.parameters ?? def.arguments ?? ["state"];
|
|
258
278
|
const paramList = args.join(", ");
|
|
@@ -269,7 +289,13 @@ export function emitElementModule(doc, className, elementImports) {
|
|
|
269
289
|
// Emit computed signals — $src or inline body
|
|
270
290
|
for (const [key, def] of computedEntries) {
|
|
271
291
|
lines.push("");
|
|
272
|
-
if (def
|
|
292
|
+
if ("$expression" in def) {
|
|
293
|
+
const compiled = compileExpression(def.$expression, {
|
|
294
|
+
statePrefix: "this.state",
|
|
295
|
+
eventParam: "e",
|
|
296
|
+
});
|
|
297
|
+
lines.push(` this.state.${key} = computed(() => ${compiled});`);
|
|
298
|
+
} else if (def.$src) {
|
|
273
299
|
lines.push(` this.state.${key} = computed(() => ${key}(this.state));`);
|
|
274
300
|
} else {
|
|
275
301
|
lines.push(` this.state.${key} = computed(() => {`);
|
|
@@ -488,6 +514,12 @@ function emitLitNode(def, indent) {
|
|
|
488
514
|
parts.push(
|
|
489
515
|
`@${eventName}="\${(e) => ${refToExpr(/** @type {string} */ (/** @type {JxMutableNode} */ (val).$ref))}(s, e)}"`,
|
|
490
516
|
);
|
|
517
|
+
} else if (val && typeof val === "object" && "$expression" in /** @type {any} */ (val)) {
|
|
518
|
+
const compiled = compileExpression(/** @type {any} */ (val).$expression, {
|
|
519
|
+
statePrefix: "s",
|
|
520
|
+
eventParam: "e",
|
|
521
|
+
});
|
|
522
|
+
parts.push(`@${eventName}="\${(e) => { ${compiled}; }}"`);
|
|
491
523
|
} else if (
|
|
492
524
|
val &&
|
|
493
525
|
typeof val === "object" &&
|
|
@@ -558,6 +590,12 @@ function emitMappedArray(arrayDef, indent) {
|
|
|
558
590
|
parts.push(
|
|
559
591
|
`@${eventName}="\${(e) => ${refToExpr(/** @type {string} */ (/** @type {JxMutableNode} */ (val).$ref))}(s, e)}"`,
|
|
560
592
|
);
|
|
593
|
+
} else if (val && typeof val === "object" && "$expression" in /** @type {any} */ (val)) {
|
|
594
|
+
const compiled = compileExpression(/** @type {any} */ (val).$expression, {
|
|
595
|
+
statePrefix: "s",
|
|
596
|
+
eventParam: "e",
|
|
597
|
+
});
|
|
598
|
+
parts.push(`@${eventName}="\${(e) => { ${compiled}; }}"`);
|
|
561
599
|
}
|
|
562
600
|
}
|
|
563
601
|
|