@jxsuite/compiler 0.23.2 → 0.24.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 +155 -155
- package/package.json +18 -16
- package/src/{cli.js → cli.ts} +2 -2
- package/src/{compile-cli.js → compile-cli.ts} +1 -1
- package/src/{compiler.js → compiler.ts} +10 -8
- package/src/{shared.js → shared.ts} +161 -123
- package/src/site/{content-loader.js → content-loader.ts} +57 -48
- package/src/site/{context-injection.js → context-injection.ts} +18 -12
- package/src/site/{head-merger.js → head-merger.ts} +17 -11
- package/src/site/{image-cache.js → image-cache.ts} +22 -22
- package/src/site/{image-optimizer.js → image-optimizer.ts} +35 -44
- package/src/site/{image-transform.js → image-transform.ts} +52 -22
- package/src/site/{layout-resolver.js → layout-resolver.ts} +31 -26
- package/src/site/{pages-discovery.js → pages-discovery.ts} +31 -30
- package/src/site/{prototype-resolver.js → prototype-resolver.ts} +16 -5
- package/src/site/{site-build.js → site-build.ts} +135 -135
- package/src/site/{site-loader.js → site-loader.ts} +2 -2
- package/src/targets/{compile-class.js → compile-class.ts} +20 -19
- package/src/targets/{compile-client.js → compile-client.ts} +84 -105
- package/src/targets/{compile-element.js → compile-element.ts} +82 -88
- package/src/targets/{compile-markdown.js → compile-markdown.ts} +95 -81
- package/src/targets/{compile-server.js → compile-server.ts} +23 -6
- package/src/targets/{compile-static.js → compile-static.ts} +35 -18
- package/src/types.ts +16 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
/**
|
|
2
3
|
* Shared.js — Shared compiler utilities
|
|
3
4
|
*
|
|
@@ -7,6 +8,16 @@
|
|
|
7
8
|
|
|
8
9
|
import { camelToKebab, toCSSText, RESERVED_KEYS } from "@jxsuite/runtime";
|
|
9
10
|
import { compileExpression, isMutating, evaluateExpression } from "@jxsuite/runtime/expression";
|
|
11
|
+
import type { ExpressionNode } from "@jxsuite/runtime/expression";
|
|
12
|
+
import type {
|
|
13
|
+
JxElement,
|
|
14
|
+
JxMutableNode,
|
|
15
|
+
JxStyle,
|
|
16
|
+
JxStateDefinition,
|
|
17
|
+
JxPrototypeDef,
|
|
18
|
+
JxStateObject,
|
|
19
|
+
JxMappedArray,
|
|
20
|
+
} from "@jxsuite/schema/types";
|
|
10
21
|
|
|
11
22
|
// Re-export runtime utilities used by submodules
|
|
12
23
|
export {
|
|
@@ -17,6 +28,7 @@ export {
|
|
|
17
28
|
isMutating,
|
|
18
29
|
evaluateExpression,
|
|
19
30
|
};
|
|
31
|
+
export type { ExpressionNode };
|
|
20
32
|
|
|
21
33
|
// CDN defaults
|
|
22
34
|
export const DEFAULT_REACTIVITY_SRC = "https://esm.sh/@vue/reactivity@3.5.32";
|
|
@@ -53,7 +65,7 @@ export const SCHEMA_KEYWORDS = new Set([
|
|
|
53
65
|
* @param {unknown} src
|
|
54
66
|
* @returns {boolean}
|
|
55
67
|
*/
|
|
56
|
-
export function isClassJsonSrc(src) {
|
|
68
|
+
export function isClassJsonSrc(src: unknown) {
|
|
57
69
|
return typeof src === "string" && src.endsWith(".class.json");
|
|
58
70
|
}
|
|
59
71
|
|
|
@@ -63,7 +75,7 @@ export function isClassJsonSrc(src) {
|
|
|
63
75
|
* @param {Record<string, unknown>} obj
|
|
64
76
|
* @returns {boolean}
|
|
65
77
|
*/
|
|
66
|
-
export function isSchemaOnly(obj) {
|
|
78
|
+
export function isSchemaOnly(obj: Record<string, unknown>) {
|
|
67
79
|
for (const k of Object.keys(obj)) {
|
|
68
80
|
if (!SCHEMA_KEYWORDS.has(k)) return false;
|
|
69
81
|
}
|
|
@@ -76,7 +88,7 @@ export function isSchemaOnly(obj) {
|
|
|
76
88
|
* @param {unknown} val
|
|
77
89
|
* @returns {boolean}
|
|
78
90
|
*/
|
|
79
|
-
export function isTemplateString(val) {
|
|
91
|
+
export function isTemplateString(val: unknown) {
|
|
80
92
|
return typeof val === "string" && val.includes("${");
|
|
81
93
|
}
|
|
82
94
|
|
|
@@ -86,7 +98,7 @@ export function isTemplateString(val) {
|
|
|
86
98
|
* @param {JxElement | JxMutableNode | string} def
|
|
87
99
|
* @returns {boolean}
|
|
88
100
|
*/
|
|
89
|
-
export function isDynamic(def) {
|
|
101
|
+
export function isDynamic(def: JxElement | JxMutableNode | string) {
|
|
90
102
|
if (!def || typeof def !== "object") return false;
|
|
91
103
|
|
|
92
104
|
if (def.state) {
|
|
@@ -98,11 +110,11 @@ export function isDynamic(def) {
|
|
|
98
110
|
d &&
|
|
99
111
|
typeof d === "object" &&
|
|
100
112
|
!Array.isArray(d) &&
|
|
101
|
-
|
|
113
|
+
(d as JxPrototypeDef).timing === "compiler"
|
|
102
114
|
)
|
|
103
115
|
continue;
|
|
104
116
|
if (typeof d !== "object" || d === null || Array.isArray(d)) return true;
|
|
105
|
-
if (
|
|
117
|
+
if ((d as JxPrototypeDef).$prototype) return true;
|
|
106
118
|
if ("default" in /** @type {object} */ (d)) return true;
|
|
107
119
|
if (isSchemaOnly(d)) continue;
|
|
108
120
|
return true;
|
|
@@ -112,7 +124,7 @@ export function isDynamic(def) {
|
|
|
112
124
|
if (def.$switch) return true;
|
|
113
125
|
if (
|
|
114
126
|
!Array.isArray(def.children) &&
|
|
115
|
-
|
|
127
|
+
(def.children as JxMappedArray | undefined)?.$prototype === "Array"
|
|
116
128
|
)
|
|
117
129
|
return true;
|
|
118
130
|
|
|
@@ -122,11 +134,7 @@ export function isDynamic(def) {
|
|
|
122
134
|
|
|
123
135
|
for (const [key, val] of Object.entries(def)) {
|
|
124
136
|
if (RESERVED_KEYS.has(key)) continue;
|
|
125
|
-
if (
|
|
126
|
-
val !== null &&
|
|
127
|
-
typeof val === "object" &&
|
|
128
|
-
typeof (/** @type {JxMutableNode} */ (val).$ref) === "string"
|
|
129
|
-
)
|
|
137
|
+
if (val !== null && typeof val === "object" && typeof (val as JxMutableNode).$ref === "string")
|
|
130
138
|
return true;
|
|
131
139
|
if (isTemplateString(val)) return true;
|
|
132
140
|
}
|
|
@@ -152,23 +160,19 @@ export function isDynamic(def) {
|
|
|
152
160
|
* @param {JxElement | JxMutableNode | string} def
|
|
153
161
|
* @returns {boolean}
|
|
154
162
|
*/
|
|
155
|
-
export function isNodeDynamic(def) {
|
|
163
|
+
export function isNodeDynamic(def: JxElement | JxMutableNode | string) {
|
|
156
164
|
if (!def || typeof def !== "object") return false;
|
|
157
165
|
|
|
158
166
|
if (def.$switch) return true;
|
|
159
167
|
if (
|
|
160
168
|
!Array.isArray(def.children) &&
|
|
161
|
-
|
|
169
|
+
(def.children as JxMappedArray | undefined)?.$prototype === "Array"
|
|
162
170
|
)
|
|
163
171
|
return true;
|
|
164
172
|
|
|
165
173
|
for (const [key, val] of Object.entries(def)) {
|
|
166
174
|
if (RESERVED_KEYS.has(key)) continue;
|
|
167
|
-
if (
|
|
168
|
-
val !== null &&
|
|
169
|
-
typeof val === "object" &&
|
|
170
|
-
typeof (/** @type {JxMutableNode} */ (val).$ref) === "string"
|
|
171
|
-
)
|
|
175
|
+
if (val !== null && typeof val === "object" && typeof (val as JxMutableNode).$ref === "string")
|
|
172
176
|
return true;
|
|
173
177
|
if (isTemplateString(val)) return true;
|
|
174
178
|
}
|
|
@@ -194,10 +198,10 @@ export function isNodeDynamic(def) {
|
|
|
194
198
|
* @param {JxElement | JxMutableNode | string} def
|
|
195
199
|
* @returns {boolean}
|
|
196
200
|
*/
|
|
197
|
-
export function hasAnyIsland(def) {
|
|
201
|
+
export function hasAnyIsland(def: JxElement | JxMutableNode | string): boolean {
|
|
198
202
|
if (!def || typeof def !== "object") return false;
|
|
199
203
|
if (isDynamic(def)) return true;
|
|
200
|
-
if (Array.isArray(def.children)) return def.children.some((c) => hasAnyIsland(c));
|
|
204
|
+
if (Array.isArray(def.children)) return def.children.some((c): boolean => hasAnyIsland(c));
|
|
201
205
|
return false;
|
|
202
206
|
}
|
|
203
207
|
|
|
@@ -214,7 +218,12 @@ export function hasAnyIsland(def) {
|
|
|
214
218
|
* media: Record<string, string>;
|
|
215
219
|
* }}
|
|
216
220
|
*/
|
|
217
|
-
export function createCompileContext(
|
|
221
|
+
export function createCompileContext(
|
|
222
|
+
raw: JxElement | JxMutableNode | null,
|
|
223
|
+
parentScope: Record<string, unknown> | null = null,
|
|
224
|
+
scopeDefs: Record<string, unknown> = {},
|
|
225
|
+
media: Record<string, string> = {},
|
|
226
|
+
) {
|
|
218
227
|
const scope = raw?.state
|
|
219
228
|
? buildInitialScope(raw.state, parentScope)
|
|
220
229
|
: (parentScope ?? Object.create(null));
|
|
@@ -226,7 +235,10 @@ export function createCompileContext(raw, parentScope = null, scopeDefs = {}, me
|
|
|
226
235
|
* @param {Record<string, unknown> | null} [parentScope]
|
|
227
236
|
* @returns {Record<string, unknown>}
|
|
228
237
|
*/
|
|
229
|
-
export function buildInitialScope(
|
|
238
|
+
export function buildInitialScope(
|
|
239
|
+
defs: Record<string, JxStateDefinition> = {},
|
|
240
|
+
parentScope: Record<string, unknown> | null = null,
|
|
241
|
+
) {
|
|
230
242
|
const scope = Object.create(parentScope ?? null);
|
|
231
243
|
|
|
232
244
|
for (const [key, def] of Object.entries(defs)) {
|
|
@@ -234,7 +246,7 @@ export function buildInitialScope(defs = {}, parentScope = null) {
|
|
|
234
246
|
setOwnScopeValue(scope, key, cloneValue(def));
|
|
235
247
|
continue;
|
|
236
248
|
}
|
|
237
|
-
const d =
|
|
249
|
+
const d = def as JxStateObject & JxPrototypeDef;
|
|
238
250
|
if ("default" in d) {
|
|
239
251
|
setOwnScopeValue(scope, key, cloneValue(d.default));
|
|
240
252
|
continue;
|
|
@@ -250,11 +262,11 @@ export function buildInitialScope(defs = {}, parentScope = null) {
|
|
|
250
262
|
continue;
|
|
251
263
|
}
|
|
252
264
|
if (!def || typeof def !== "object") continue;
|
|
253
|
-
const d =
|
|
265
|
+
const d = def as JxStateObject & JxPrototypeDef;
|
|
254
266
|
if ("$expression" in d) {
|
|
255
|
-
const node =
|
|
267
|
+
const node = (d as Record<string, unknown>).$expression as ExpressionNode;
|
|
256
268
|
if (isMutating(node.operator)) {
|
|
257
|
-
setOwnScopeValue(scope, key, (
|
|
269
|
+
setOwnScopeValue(scope, key, (s: Record<string, any>, event: Event) =>
|
|
258
270
|
evaluateExpression(node, s, event),
|
|
259
271
|
);
|
|
260
272
|
} else {
|
|
@@ -288,7 +300,7 @@ export function buildInitialScope(defs = {}, parentScope = null) {
|
|
|
288
300
|
* @param {string} key
|
|
289
301
|
* @param {unknown} value
|
|
290
302
|
*/
|
|
291
|
-
export function setOwnScopeValue(scope, key, value) {
|
|
303
|
+
export function setOwnScopeValue(scope: Record<string, unknown>, key: string, value: unknown) {
|
|
292
304
|
Object.defineProperty(scope, key, {
|
|
293
305
|
value,
|
|
294
306
|
enumerable: true,
|
|
@@ -302,7 +314,11 @@ export function setOwnScopeValue(scope, key, value) {
|
|
|
302
314
|
* @param {string} key
|
|
303
315
|
* @param {() => unknown} getter
|
|
304
316
|
*/
|
|
305
|
-
export function defineLazyScopeValue(
|
|
317
|
+
export function defineLazyScopeValue(
|
|
318
|
+
scope: Record<string, unknown>,
|
|
319
|
+
key: string,
|
|
320
|
+
getter: () => unknown,
|
|
321
|
+
) {
|
|
306
322
|
Object.defineProperty(scope, key, {
|
|
307
323
|
enumerable: true,
|
|
308
324
|
configurable: true,
|
|
@@ -315,9 +331,9 @@ export function defineLazyScopeValue(scope, key, getter) {
|
|
|
315
331
|
* @param {Record<string, unknown>} scope
|
|
316
332
|
* @returns {unknown}
|
|
317
333
|
*/
|
|
318
|
-
export function resolveStaticValue(value, scope) {
|
|
319
|
-
if (isRefObject(value)) return resolveRefValue(
|
|
320
|
-
if (isTemplateString(value)) return evaluateStaticTemplate(
|
|
334
|
+
export function resolveStaticValue(value: unknown, scope: Record<string, unknown> | null) {
|
|
335
|
+
if (isRefObject(value)) return resolveRefValue((value as JxMutableNode).$ref, scope!);
|
|
336
|
+
if (isTemplateString(value)) return evaluateStaticTemplate(value as string, scope!);
|
|
321
337
|
return value;
|
|
322
338
|
}
|
|
323
339
|
|
|
@@ -325,11 +341,9 @@ export function resolveStaticValue(value, scope) {
|
|
|
325
341
|
* @param {unknown} value
|
|
326
342
|
* @returns {boolean}
|
|
327
343
|
*/
|
|
328
|
-
export function isRefObject(value) {
|
|
344
|
+
export function isRefObject(value: unknown) {
|
|
329
345
|
return (
|
|
330
|
-
value !== null &&
|
|
331
|
-
typeof value === "object" &&
|
|
332
|
-
typeof (/** @type {JxMutableNode} */ (value).$ref) === "string"
|
|
346
|
+
value !== null && typeof value === "object" && typeof (value as JxMutableNode).$ref === "string"
|
|
333
347
|
);
|
|
334
348
|
}
|
|
335
349
|
|
|
@@ -338,13 +352,12 @@ export function isRefObject(value) {
|
|
|
338
352
|
* @param {Record<string, unknown>} scope
|
|
339
353
|
* @returns {unknown}
|
|
340
354
|
*/
|
|
341
|
-
export function resolveRefValue(refValue, scope) {
|
|
355
|
+
export function resolveRefValue(refValue: unknown, scope: Record<string, unknown>) {
|
|
342
356
|
if (typeof refValue !== "string") return refValue;
|
|
343
357
|
if (refValue.startsWith("$map/")) {
|
|
344
358
|
const parts = refValue.split("/");
|
|
345
359
|
const key = parts[1];
|
|
346
|
-
const base =
|
|
347
|
-
scope["$map/" + key];
|
|
360
|
+
const base = (scope.$map as Record<string, unknown> | undefined)?.[key] ?? scope["$map/" + key];
|
|
348
361
|
return parts.length > 2 ? getPathValue(base, parts.slice(2).join("/")) : base;
|
|
349
362
|
}
|
|
350
363
|
if (refValue.startsWith("#/state/")) {
|
|
@@ -361,7 +374,7 @@ export function resolveRefValue(refValue, scope) {
|
|
|
361
374
|
* @param {Record<string, unknown>} scope
|
|
362
375
|
* @returns {unknown}
|
|
363
376
|
*/
|
|
364
|
-
export function evaluateStaticTemplate(str, scope) {
|
|
377
|
+
export function evaluateStaticTemplate(str: string, scope: Record<string, unknown>) {
|
|
365
378
|
try {
|
|
366
379
|
const singleExprMatch = str.match(/^\$\{(.+)\}$/s);
|
|
367
380
|
if (singleExprMatch) {
|
|
@@ -380,24 +393,21 @@ export function evaluateStaticTemplate(str, scope) {
|
|
|
380
393
|
* @param {string} path
|
|
381
394
|
* @returns {unknown}
|
|
382
395
|
*/
|
|
383
|
-
export function getPathValue(base, path) {
|
|
396
|
+
export function getPathValue(base: unknown, path: string) {
|
|
384
397
|
if (!path) return base;
|
|
385
|
-
return path
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
(acc, key) => (/** @type {Record<string, unknown>} */ (acc == null ? undefined : acc[key])),
|
|
392
|
-
/** @type {Record<string, unknown>} */ (base),
|
|
393
|
-
);
|
|
398
|
+
return path
|
|
399
|
+
.split("/")
|
|
400
|
+
.reduce<unknown>(
|
|
401
|
+
(acc, key: string) => (acc == null ? undefined : (acc as Record<string, unknown>)[key]),
|
|
402
|
+
base,
|
|
403
|
+
);
|
|
394
404
|
}
|
|
395
405
|
|
|
396
406
|
/**
|
|
397
407
|
* @param {unknown} value
|
|
398
408
|
* @returns {unknown}
|
|
399
409
|
*/
|
|
400
|
-
export function cloneValue(value) {
|
|
410
|
+
export function cloneValue(value: unknown) {
|
|
401
411
|
if (value === null || typeof value !== "object") return value;
|
|
402
412
|
return JSON.parse(JSON.stringify(value));
|
|
403
413
|
}
|
|
@@ -410,7 +420,7 @@ export function cloneValue(value) {
|
|
|
410
420
|
* @param {JxElement | JxMutableNode} def
|
|
411
421
|
* @param {Record<string, unknown>} scope @returns {string}
|
|
412
422
|
*/
|
|
413
|
-
export function buildAttrs(def, scope) {
|
|
423
|
+
export function buildAttrs(def: JxElement | JxMutableNode, scope: Record<string, unknown> | null) {
|
|
414
424
|
let out = "";
|
|
415
425
|
|
|
416
426
|
const id = resolveStaticValue(def.id, scope);
|
|
@@ -492,7 +502,16 @@ export function buildAttrs(def, scope) {
|
|
|
492
502
|
* @param {(def: any, raw: any, context: any) => string} childCompiler
|
|
493
503
|
* @returns {string}
|
|
494
504
|
*/
|
|
495
|
-
export function buildInner(
|
|
505
|
+
export function buildInner(
|
|
506
|
+
def: JxElement,
|
|
507
|
+
raw: JxElement | null,
|
|
508
|
+
context: {
|
|
509
|
+
scope: Record<string, unknown> | null;
|
|
510
|
+
scopeDefs: Record<string, unknown>;
|
|
511
|
+
media: Record<string, string>;
|
|
512
|
+
},
|
|
513
|
+
childCompiler: (def: unknown, raw: unknown, context: unknown) => string,
|
|
514
|
+
) {
|
|
496
515
|
const source = raw ?? def;
|
|
497
516
|
|
|
498
517
|
if (source.textContent !== undefined) {
|
|
@@ -500,12 +519,12 @@ export function buildInner(def, raw, context, childCompiler) {
|
|
|
500
519
|
return value == null ? "" : escapeHtml(String(value));
|
|
501
520
|
}
|
|
502
521
|
if (source.innerHTML)
|
|
503
|
-
return
|
|
522
|
+
return (resolveStaticValue(source.innerHTML, context.scope) as string) ?? "";
|
|
504
523
|
if (Array.isArray(source.children)) {
|
|
505
524
|
const rawChildren = raw?.children;
|
|
506
525
|
return source.children
|
|
507
|
-
.map((
|
|
508
|
-
const childRaw =
|
|
526
|
+
.map((c: JxElement | JxMutableNode | string, i: number) => {
|
|
527
|
+
const childRaw = (rawChildren as (JxElement | string)[] | undefined)?.[i] ?? c;
|
|
509
528
|
return childCompiler(c, childRaw, context);
|
|
510
529
|
})
|
|
511
530
|
.join("\n ");
|
|
@@ -523,15 +542,18 @@ export function buildInner(def, raw, context, childCompiler) {
|
|
|
523
542
|
* @param {JxStyle | null} [projectStyle]
|
|
524
543
|
* @returns {string}
|
|
525
544
|
*/
|
|
526
|
-
export function compileStyles(
|
|
527
|
-
|
|
528
|
-
|
|
545
|
+
export function compileStyles(
|
|
546
|
+
doc: JxElement | JxMutableNode,
|
|
547
|
+
mediaQueries: Record<string, string> = {},
|
|
548
|
+
projectStyle: JxStyle | null = null,
|
|
549
|
+
) {
|
|
550
|
+
const rules: string[] = [];
|
|
529
551
|
|
|
530
552
|
// Emit project-level (site-wide) styles — CSS custom properties go on :root,
|
|
531
553
|
// everything else on body. Project-level style is implicitly :root, so a
|
|
532
554
|
// flat object like { "--bg": "#000", "margin": "0" } is the expected format.
|
|
533
555
|
if (projectStyle && typeof projectStyle === "object") {
|
|
534
|
-
function emitProjectRules(
|
|
556
|
+
function emitProjectRules(selector: string, obj: Record<string, unknown>) {
|
|
535
557
|
const props = toCSSText(obj);
|
|
536
558
|
if (props) rules.push(`${selector} { ${props} }`);
|
|
537
559
|
for (const [key, val] of Object.entries(obj)) {
|
|
@@ -560,13 +582,13 @@ export function compileStyles(doc, mediaQueries = {}, projectStyle = null) {
|
|
|
560
582
|
: key.startsWith(":") || key.startsWith(".") || key.startsWith("[")
|
|
561
583
|
? `${selector}${key}`
|
|
562
584
|
: `${selector} ${key}`;
|
|
563
|
-
emitProjectRules(resolved,
|
|
585
|
+
emitProjectRules(resolved, val as Record<string, unknown>);
|
|
564
586
|
}
|
|
565
587
|
}
|
|
566
588
|
|
|
567
589
|
for (const [key, val] of Object.entries(projectStyle)) {
|
|
568
590
|
if (key.startsWith(":") || key.startsWith(".") || key.startsWith("[")) {
|
|
569
|
-
emitProjectRules(key,
|
|
591
|
+
emitProjectRules(key, val as Record<string, unknown>);
|
|
570
592
|
} else if (
|
|
571
593
|
val !== null &&
|
|
572
594
|
typeof val === "object" &&
|
|
@@ -574,7 +596,7 @@ export function compileStyles(doc, mediaQueries = {}, projectStyle = null) {
|
|
|
574
596
|
!key.startsWith("@") &&
|
|
575
597
|
!key.startsWith("--")
|
|
576
598
|
) {
|
|
577
|
-
emitProjectRules(key, val);
|
|
599
|
+
emitProjectRules(key, val as Record<string, unknown>);
|
|
578
600
|
} else if (key.startsWith("@")) {
|
|
579
601
|
// @media block at top level
|
|
580
602
|
const atRule = key.startsWith("@--")
|
|
@@ -582,15 +604,13 @@ export function compileStyles(doc, mediaQueries = {}, projectStyle = null) {
|
|
|
582
604
|
: key.startsWith("@(")
|
|
583
605
|
? `@media ${key.slice(1)}`
|
|
584
606
|
: key;
|
|
585
|
-
rules.push(`${atRule} { body { ${toCSSText(
|
|
607
|
+
rules.push(`${atRule} { body { ${toCSSText(val as Record<string, unknown>)} } }`);
|
|
586
608
|
}
|
|
587
609
|
}
|
|
588
610
|
// Collect CSS custom properties into :root {}
|
|
589
|
-
|
|
590
|
-
const rootProps = {};
|
|
611
|
+
const rootProps: Record<string, unknown> = {};
|
|
591
612
|
// Collect direct CSS properties into body {}
|
|
592
|
-
|
|
593
|
-
const bodyProps = {};
|
|
613
|
+
const bodyProps: Record<string, unknown> = {};
|
|
594
614
|
for (const [key, val] of Object.entries(projectStyle)) {
|
|
595
615
|
if (key.startsWith(":") || key.startsWith(".") || key.startsWith("[") || key.startsWith("@"))
|
|
596
616
|
continue;
|
|
@@ -625,7 +645,12 @@ export function compileStyles(doc, mediaQueries = {}, projectStyle = null) {
|
|
|
625
645
|
* @param {string[]} rules
|
|
626
646
|
* @param {Record<string, string>} mediaQueries
|
|
627
647
|
*/
|
|
628
|
-
function emitNestedElement(
|
|
648
|
+
function emitNestedElement(
|
|
649
|
+
selector: string,
|
|
650
|
+
obj: Record<string, unknown>,
|
|
651
|
+
rules: string[],
|
|
652
|
+
mediaQueries: Record<string, string>,
|
|
653
|
+
) {
|
|
629
654
|
const props = toCSSText(obj);
|
|
630
655
|
if (props) rules.push(`${selector} { ${props} }`);
|
|
631
656
|
for (const [key, val] of Object.entries(obj)) {
|
|
@@ -656,7 +681,7 @@ function emitNestedElement(selector, obj, rules, mediaQueries) {
|
|
|
656
681
|
: key.startsWith(":") || key.startsWith(".") || key.startsWith("[")
|
|
657
682
|
? `${selector}${key}`
|
|
658
683
|
: `${selector} ${key}`;
|
|
659
|
-
emitNestedElement(resolved,
|
|
684
|
+
emitNestedElement(resolved, val as Record<string, unknown>, rules, mediaQueries);
|
|
660
685
|
}
|
|
661
686
|
}
|
|
662
687
|
|
|
@@ -668,11 +693,11 @@ function emitNestedElement(selector, obj, rules, mediaQueries) {
|
|
|
668
693
|
* @param {{ n: number }} [counter]
|
|
669
694
|
*/
|
|
670
695
|
export function collectStyles(
|
|
671
|
-
def,
|
|
672
|
-
rules,
|
|
673
|
-
mediaQueries,
|
|
674
|
-
_parentSel = "",
|
|
675
|
-
counter = { n: 0 },
|
|
696
|
+
def: JxElement | JxMutableNode | string,
|
|
697
|
+
rules: string[],
|
|
698
|
+
mediaQueries: Record<string, string>,
|
|
699
|
+
_parentSel: string = "",
|
|
700
|
+
counter: { n: number } = { n: 0 },
|
|
676
701
|
prefix = "jx",
|
|
677
702
|
) {
|
|
678
703
|
if (!def || typeof def !== "object") return;
|
|
@@ -748,7 +773,7 @@ export function collectStyles(
|
|
|
748
773
|
}
|
|
749
774
|
|
|
750
775
|
if (Array.isArray(def.children)) {
|
|
751
|
-
def.children.forEach((
|
|
776
|
+
def.children.forEach((c: JxElement | JxMutableNode | string) => {
|
|
752
777
|
collectStyles(c, rules, mediaQueries, selector, counter, prefix);
|
|
753
778
|
});
|
|
754
779
|
}
|
|
@@ -762,7 +787,7 @@ export function collectStyles(
|
|
|
762
787
|
* @param {string} str
|
|
763
788
|
* @returns {string}
|
|
764
789
|
*/
|
|
765
|
-
export function escapeHtml(str) {
|
|
790
|
+
export function escapeHtml(str: string) {
|
|
766
791
|
return String(str)
|
|
767
792
|
.replace(/&/g, "&")
|
|
768
793
|
.replace(/</g, "<")
|
|
@@ -777,7 +802,7 @@ export function escapeHtml(str) {
|
|
|
777
802
|
* @param {string} title
|
|
778
803
|
* @returns {string}
|
|
779
804
|
*/
|
|
780
|
-
export function titleToTagName(title) {
|
|
805
|
+
export function titleToTagName(title: string) {
|
|
781
806
|
const slug = title
|
|
782
807
|
.toLowerCase()
|
|
783
808
|
.replace(/[^a-z0-9]+/g, "-")
|
|
@@ -789,10 +814,10 @@ export function titleToTagName(title) {
|
|
|
789
814
|
* @param {string} tagName
|
|
790
815
|
* @returns {string}
|
|
791
816
|
*/
|
|
792
|
-
export function tagNameToClassName(tagName) {
|
|
817
|
+
export function tagNameToClassName(tagName: string) {
|
|
793
818
|
return tagName
|
|
794
819
|
.split("-")
|
|
795
|
-
.map((
|
|
820
|
+
.map((s: string) => s.charAt(0).toUpperCase() + s.slice(1))
|
|
796
821
|
.join("");
|
|
797
822
|
}
|
|
798
823
|
|
|
@@ -802,9 +827,8 @@ export function tagNameToClassName(tagName) {
|
|
|
802
827
|
* @param {JxElement} doc
|
|
803
828
|
* @returns {string[]}
|
|
804
829
|
*/
|
|
805
|
-
export function collectSrcImports(doc) {
|
|
806
|
-
|
|
807
|
-
const srcs = new Set();
|
|
830
|
+
export function collectSrcImports(doc: JxElement) {
|
|
831
|
+
const srcs: Set<string> = new Set();
|
|
808
832
|
_walkSrc(doc, srcs);
|
|
809
833
|
return [...srcs];
|
|
810
834
|
}
|
|
@@ -813,24 +837,22 @@ export function collectSrcImports(doc) {
|
|
|
813
837
|
* @param {JxElement | JxMutableNode | string} def
|
|
814
838
|
* @param {Set<string>} srcs
|
|
815
839
|
*/
|
|
816
|
-
function _walkSrc(def, srcs) {
|
|
840
|
+
function _walkSrc(def: JxElement | JxMutableNode | string, srcs: Set<string>) {
|
|
817
841
|
if (!def || typeof def !== "object") return;
|
|
818
842
|
if (def.state) {
|
|
819
843
|
for (const d of Object.values(def.state)) {
|
|
820
844
|
if (
|
|
821
845
|
d &&
|
|
822
846
|
typeof d === "object" &&
|
|
823
|
-
|
|
824
|
-
|
|
847
|
+
(d as JxMutableNode).$prototype === "Function" &&
|
|
848
|
+
(d as JxMutableNode).$src
|
|
825
849
|
) {
|
|
826
|
-
srcs.add(
|
|
850
|
+
srcs.add((d as JxMutableNode).$src as string);
|
|
827
851
|
}
|
|
828
852
|
}
|
|
829
853
|
}
|
|
830
854
|
if (Array.isArray(def.children)) {
|
|
831
|
-
def.children.forEach((
|
|
832
|
-
_walkSrc(c, srcs),
|
|
833
|
-
);
|
|
855
|
+
def.children.forEach((c: JxElement | JxMutableNode | string) => _walkSrc(c, srcs));
|
|
834
856
|
}
|
|
835
857
|
}
|
|
836
858
|
|
|
@@ -840,7 +862,7 @@ function _walkSrc(def, srcs) {
|
|
|
840
862
|
* @param {JxElement} doc
|
|
841
863
|
* @returns {{ key: string; exportName: string; src: string }[]}
|
|
842
864
|
*/
|
|
843
|
-
export function collectServerEntries(doc) {
|
|
865
|
+
export function collectServerEntries(doc: JxElement) {
|
|
844
866
|
/** @type {Map<string, { key: string; exportName: string; src: string }>} */
|
|
845
867
|
const entries = new Map();
|
|
846
868
|
_walkServerEntries(doc, entries);
|
|
@@ -851,11 +873,14 @@ export function collectServerEntries(doc) {
|
|
|
851
873
|
* @param {JxElement | JxMutableNode | string} def
|
|
852
874
|
* @param {Map<string, { key: string; exportName: string; src: string }>} entries
|
|
853
875
|
*/
|
|
854
|
-
function _walkServerEntries(
|
|
876
|
+
function _walkServerEntries(
|
|
877
|
+
def: JxElement | JxMutableNode | string,
|
|
878
|
+
entries: Map<string, { key: string; exportName: string; src: string }>,
|
|
879
|
+
) {
|
|
855
880
|
if (!def || typeof def !== "object") return;
|
|
856
881
|
if (def.state) {
|
|
857
882
|
for (const [key, d] of Object.entries(def.state)) {
|
|
858
|
-
const entry =
|
|
883
|
+
const entry = d as JxMutableNode;
|
|
859
884
|
if (
|
|
860
885
|
entry &&
|
|
861
886
|
typeof entry === "object" &&
|
|
@@ -869,16 +894,23 @@ function _walkServerEntries(def, entries) {
|
|
|
869
894
|
}
|
|
870
895
|
}
|
|
871
896
|
if (Array.isArray(def.children)) {
|
|
872
|
-
def.children.forEach((
|
|
873
|
-
_walkServerEntries(c, entries),
|
|
874
|
-
);
|
|
897
|
+
def.children.forEach((c: JxElement | JxMutableNode | string) => _walkServerEntries(c, entries));
|
|
875
898
|
}
|
|
876
899
|
}
|
|
877
900
|
|
|
878
901
|
// ─── Component pre-rendering ─────────────────────────────────────────────────
|
|
879
902
|
|
|
880
|
-
|
|
881
|
-
|
|
903
|
+
const SELF_CLOSING: Set<string> = new Set([
|
|
904
|
+
"input",
|
|
905
|
+
"br",
|
|
906
|
+
"hr",
|
|
907
|
+
"img",
|
|
908
|
+
"meta",
|
|
909
|
+
"link",
|
|
910
|
+
"area",
|
|
911
|
+
"col",
|
|
912
|
+
"source",
|
|
913
|
+
]);
|
|
882
914
|
|
|
883
915
|
/**
|
|
884
916
|
* Recursively render a Jx node tree to static HTML for pre-rendering.
|
|
@@ -888,7 +920,11 @@ const SELF_CLOSING = new Set(["input", "br", "hr", "img", "meta", "link", "area"
|
|
|
888
920
|
* @param {string | null} [slotContent] - HTML to substitute for `<slot>` elements
|
|
889
921
|
* @returns {string}
|
|
890
922
|
*/
|
|
891
|
-
export function renderStaticNode(
|
|
923
|
+
export function renderStaticNode(
|
|
924
|
+
node: JxElement | JxMutableNode | string,
|
|
925
|
+
scope: Record<string, unknown> | null,
|
|
926
|
+
slotContent: string | null = null,
|
|
927
|
+
): string {
|
|
892
928
|
if (typeof node === "string") {
|
|
893
929
|
if (isTemplateString(node) && scope) {
|
|
894
930
|
const val = evaluateStaticTemplate(node, scope);
|
|
@@ -898,8 +934,8 @@ export function renderStaticNode(node, scope, slotContent = null) {
|
|
|
898
934
|
}
|
|
899
935
|
if (typeof node === "number" || typeof node === "boolean") return escapeHtml(String(node));
|
|
900
936
|
if (Array.isArray(node))
|
|
901
|
-
return node
|
|
902
|
-
.map((
|
|
937
|
+
return (node as (JxElement | JxMutableNode | string)[])
|
|
938
|
+
.map((c: JxElement | JxMutableNode | string): string =>
|
|
903
939
|
renderStaticNode(c, scope, slotContent),
|
|
904
940
|
)
|
|
905
941
|
.join("\n");
|
|
@@ -923,12 +959,10 @@ export function renderStaticNode(node, scope, slotContent = null) {
|
|
|
923
959
|
inner = val != null ? escapeHtml(String(val)) : "";
|
|
924
960
|
} else if (node.innerHTML) {
|
|
925
961
|
const val = resolveStaticValue(node.innerHTML, scope);
|
|
926
|
-
inner = val != null ? String(val) :
|
|
962
|
+
inner = val != null ? String(val) : (node.innerHTML as string);
|
|
927
963
|
} else if (Array.isArray(node.children)) {
|
|
928
964
|
inner = node.children
|
|
929
|
-
.map((
|
|
930
|
-
renderStaticNode(c, scope, slotContent),
|
|
931
|
-
)
|
|
965
|
+
.map((c: JxElement | JxMutableNode | string) => renderStaticNode(c, scope, slotContent))
|
|
932
966
|
.join("\n");
|
|
933
967
|
}
|
|
934
968
|
|
|
@@ -944,9 +978,12 @@ export function renderStaticNode(node, scope, slotContent = null) {
|
|
|
944
978
|
* @param {string | null} [slotContent] - HTML to substitute for `<slot>` elements
|
|
945
979
|
* @returns {string} The pre-rendered innerHTML
|
|
946
980
|
*/
|
|
947
|
-
export function preRenderComponentHtml(
|
|
948
|
-
|
|
949
|
-
|
|
981
|
+
export function preRenderComponentHtml(
|
|
982
|
+
doc: JxElement,
|
|
983
|
+
propsOverride: Record<string, unknown> | null = null,
|
|
984
|
+
slotContent: string | null = null,
|
|
985
|
+
) {
|
|
986
|
+
let stateDefs: Record<string, JxStateDefinition> = doc.state ?? {};
|
|
950
987
|
if (propsOverride) {
|
|
951
988
|
stateDefs = { ...stateDefs };
|
|
952
989
|
for (const [key, value] of Object.entries(propsOverride)) {
|
|
@@ -960,19 +997,17 @@ export function preRenderComponentHtml(doc, propsOverride = null, slotContent =
|
|
|
960
997
|
) {
|
|
961
998
|
stateDefs[key] = { .../** @type {JxStateObject} */ (existing), default: value };
|
|
962
999
|
} else {
|
|
963
|
-
stateDefs[key] =
|
|
1000
|
+
stateDefs[key] = value as JxStateDefinition;
|
|
964
1001
|
}
|
|
965
1002
|
} else {
|
|
966
|
-
stateDefs[key] =
|
|
1003
|
+
stateDefs[key] = value as JxStateDefinition;
|
|
967
1004
|
}
|
|
968
1005
|
}
|
|
969
1006
|
}
|
|
970
1007
|
const scope = buildInitialScope(stateDefs, null);
|
|
971
1008
|
if (!Array.isArray(doc.children)) return "";
|
|
972
1009
|
return doc.children
|
|
973
|
-
.map((
|
|
974
|
-
renderStaticNode(c, scope, slotContent),
|
|
975
|
-
)
|
|
1010
|
+
.map((c: JxElement | JxMutableNode | string) => renderStaticNode(c, scope, slotContent))
|
|
976
1011
|
.join("\n");
|
|
977
1012
|
}
|
|
978
1013
|
|
|
@@ -985,7 +1020,7 @@ export function preRenderComponentHtml(doc, propsOverride = null, slotContent =
|
|
|
985
1020
|
* @param {JxElement} doc - Component JSON definition
|
|
986
1021
|
* @returns {boolean}
|
|
987
1022
|
*/
|
|
988
|
-
export function isComponentFullyStatic(doc) {
|
|
1023
|
+
export function isComponentFullyStatic(doc: JxElement) {
|
|
989
1024
|
return _isStaticNode(doc);
|
|
990
1025
|
}
|
|
991
1026
|
|
|
@@ -993,7 +1028,7 @@ export function isComponentFullyStatic(doc) {
|
|
|
993
1028
|
* @param {JxElement | string | (JxElement | string)[]} node
|
|
994
1029
|
* @returns {boolean}
|
|
995
1030
|
*/
|
|
996
|
-
function _isStaticNode(node) {
|
|
1031
|
+
function _isStaticNode(node: JxElement | string | (JxElement | string)[]) {
|
|
997
1032
|
if (!node || typeof node !== "object") return true;
|
|
998
1033
|
if (Array.isArray(node)) return node.every(_isStaticNode);
|
|
999
1034
|
|
|
@@ -1006,7 +1041,7 @@ function _isStaticNode(node) {
|
|
|
1006
1041
|
if (node.state) {
|
|
1007
1042
|
for (const def of Object.values(node.state)) {
|
|
1008
1043
|
if (!def || typeof def !== "object") continue;
|
|
1009
|
-
const d =
|
|
1044
|
+
const d = def as JxMutableNode;
|
|
1010
1045
|
if (d.$prototype) return false;
|
|
1011
1046
|
if (d.$ref) return false;
|
|
1012
1047
|
}
|
|
@@ -1038,13 +1073,16 @@ function _isStaticNode(node) {
|
|
|
1038
1073
|
* @param {Record<string, string>} [mediaQueries] - Project media query definitions
|
|
1039
1074
|
* @returns {string} CSS text, or empty string if no styles
|
|
1040
1075
|
*/
|
|
1041
|
-
export function buildComponentCSS(
|
|
1042
|
-
|
|
1043
|
-
|
|
1076
|
+
export function buildComponentCSS(
|
|
1077
|
+
tagName: string,
|
|
1078
|
+
styleDef: JxStyle | null | undefined,
|
|
1079
|
+
doc: JxElement | null = null,
|
|
1080
|
+
mediaQueries: Record<string, string> = {},
|
|
1081
|
+
) {
|
|
1082
|
+
const rules: string[] = [];
|
|
1044
1083
|
|
|
1045
1084
|
if (styleDef && typeof styleDef === "object") {
|
|
1046
|
-
|
|
1047
|
-
const decls = [];
|
|
1085
|
+
const decls: string[] = [];
|
|
1048
1086
|
for (const [prop, value] of Object.entries(styleDef)) {
|
|
1049
1087
|
if (
|
|
1050
1088
|
prop.startsWith(":") ||
|
|
@@ -1069,7 +1107,7 @@ export function buildComponentCSS(tagName, styleDef, doc = null, mediaQueries =
|
|
|
1069
1107
|
: prop.startsWith("@(")
|
|
1070
1108
|
? `@media ${prop.slice(1)}`
|
|
1071
1109
|
: prop;
|
|
1072
|
-
rules.push(`${atRule} { ${tagName} { ${toCSSText(
|
|
1110
|
+
rules.push(`${atRule} { ${tagName} { ${toCSSText(val as Record<string, unknown>)} } }`);
|
|
1073
1111
|
} else if (
|
|
1074
1112
|
prop.startsWith(":") ||
|
|
1075
1113
|
prop.startsWith(".") ||
|
|
@@ -1077,7 +1115,7 @@ export function buildComponentCSS(tagName, styleDef, doc = null, mediaQueries =
|
|
|
1077
1115
|
prop.startsWith("[")
|
|
1078
1116
|
) {
|
|
1079
1117
|
const resolved = prop.startsWith("&") ? prop.replace("&", tagName) : `${tagName}${prop}`;
|
|
1080
|
-
rules.push(`${resolved} { ${toCSSText(
|
|
1118
|
+
rules.push(`${resolved} { ${toCSSText(val as Record<string, unknown>)} }`);
|
|
1081
1119
|
}
|
|
1082
1120
|
}
|
|
1083
1121
|
}
|