@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Jx static HTML compiler, island detector, and site builder",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"scripts": {
|
|
36
36
|
"prepare": "bun build src/compiler.ts src/cli.ts --outdir=dist --format=esm --minify --target=node",
|
|
37
37
|
"build": "bun run prepare",
|
|
38
|
+
"build:metafile": "bun build src/compiler.ts src/cli.ts --outdir=dist --format=esm --minify --target=node --metafile=metafile.json",
|
|
38
39
|
"build:site": "bun src/cli.ts build",
|
|
39
40
|
"test": "bun test --isolate",
|
|
40
41
|
"test:coverage": "bun test --isolate --coverage",
|
|
@@ -42,9 +43,9 @@
|
|
|
42
43
|
},
|
|
43
44
|
"dependencies": {
|
|
44
45
|
"@apidevtools/json-schema-ref-parser": "^15.3.6",
|
|
45
|
-
"@jxsuite/parser": "^0.
|
|
46
|
-
"@jxsuite/runtime": "^0.
|
|
47
|
-
"@jxsuite/schema": "^0.
|
|
46
|
+
"@jxsuite/parser": "^0.29.0",
|
|
47
|
+
"@jxsuite/runtime": "^0.29.0",
|
|
48
|
+
"@jxsuite/schema": "^0.29.0",
|
|
48
49
|
"sharp": "^0.35.1"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
package/src/compiler.ts
CHANGED
|
@@ -22,13 +22,9 @@ import {
|
|
|
22
22
|
isDynamic,
|
|
23
23
|
tagNameToClassName,
|
|
24
24
|
} from "./shared.ts";
|
|
25
|
-
import { compileServer
|
|
25
|
+
import { compileServer } from "./targets/compile-server.ts";
|
|
26
26
|
|
|
27
|
-
import {
|
|
28
|
-
compileElement,
|
|
29
|
-
compileElementPage,
|
|
30
|
-
emitElementModule,
|
|
31
|
-
} from "./targets/compile-element.ts";
|
|
27
|
+
import { emitElementModule } from "./targets/compile-element.ts";
|
|
32
28
|
import { compileStaticPage } from "./targets/compile-static.ts";
|
|
33
29
|
import { compileClient } from "./targets/compile-client.ts";
|
|
34
30
|
import { isClassDef } from "@jxsuite/schema/guards";
|
|
@@ -37,14 +33,10 @@ import type { JxDocument, JxStyle } from "@jxsuite/schema/types";
|
|
|
37
33
|
import type { FormatRegistry } from "@jxsuite/schema/format-registry";
|
|
38
34
|
|
|
39
35
|
// Re-exports for consumers
|
|
40
|
-
export {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
compileElement,
|
|
45
|
-
compileElementPage,
|
|
46
|
-
compileClient,
|
|
47
|
-
};
|
|
36
|
+
export { isDynamic } from "./shared.ts";
|
|
37
|
+
export { compileServer, compileSiteServer } from "./targets/compile-server.ts";
|
|
38
|
+
export { compileElement, compileElementPage } from "./targets/compile-element.ts";
|
|
39
|
+
export { compileClient } from "./targets/compile-client.ts";
|
|
48
40
|
|
|
49
41
|
// ─── Entry ────────────────────────────────────────────────────────────────────
|
|
50
42
|
|
package/src/shared.ts
CHANGED
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { RESERVED_KEYS, camelToKebab, toCSSText } from "@jxsuite/runtime";
|
|
10
|
-
import {
|
|
10
|
+
import { evaluateExpression, isMutating } from "@jxsuite/runtime/expression";
|
|
11
11
|
import {
|
|
12
|
+
childrenContainArray,
|
|
12
13
|
isExpressionDef,
|
|
13
14
|
isFunctionDef,
|
|
15
|
+
isMappedArray,
|
|
14
16
|
isPrototypeDef,
|
|
15
17
|
isRef,
|
|
16
18
|
isSchemaOnlyDef as isSchemaOnly,
|
|
@@ -22,7 +24,6 @@ import type { ExpressionNode } from "@jxsuite/runtime/expression";
|
|
|
22
24
|
import type {
|
|
23
25
|
JsonValue,
|
|
24
26
|
JxElement,
|
|
25
|
-
JxMappedArray,
|
|
26
27
|
JxMutableNode,
|
|
27
28
|
JxPrototypeDef,
|
|
28
29
|
JxRef,
|
|
@@ -32,15 +33,9 @@ import type {
|
|
|
32
33
|
} from "@jxsuite/schema/types";
|
|
33
34
|
|
|
34
35
|
// Re-export runtime utilities used by submodules
|
|
35
|
-
export {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
RESERVED_KEYS,
|
|
39
|
-
compileExpression,
|
|
40
|
-
isMutating,
|
|
41
|
-
evaluateExpression,
|
|
42
|
-
};
|
|
43
|
-
export type { ExpressionNode };
|
|
36
|
+
export { RESERVED_KEYS, camelToKebab, toCSSText } from "@jxsuite/runtime";
|
|
37
|
+
export { compileExpression, evaluateExpression, isMutating } from "@jxsuite/runtime/expression";
|
|
38
|
+
export type { ExpressionNode } from "@jxsuite/runtime/expression";
|
|
44
39
|
|
|
45
40
|
// CDN defaults
|
|
46
41
|
export const DEFAULT_REACTIVITY_SRC = "https://esm.sh/@vue/reactivity@3.5.32";
|
|
@@ -110,10 +105,7 @@ export function isDynamic(def: JxElement | JxMutableNode | string) {
|
|
|
110
105
|
if (def.$switch) {
|
|
111
106
|
return true;
|
|
112
107
|
}
|
|
113
|
-
if (
|
|
114
|
-
!Array.isArray(def.children) &&
|
|
115
|
-
(def.children as JxMappedArray | undefined)?.$prototype === "Array"
|
|
116
|
-
) {
|
|
108
|
+
if (isMappedArray(def) || childrenContainArray(def.children)) {
|
|
117
109
|
return true;
|
|
118
110
|
}
|
|
119
111
|
|
|
@@ -170,10 +162,7 @@ export function isNodeDynamic(def: JxElement | JxMutableNode | string) {
|
|
|
170
162
|
if (def.$switch) {
|
|
171
163
|
return true;
|
|
172
164
|
}
|
|
173
|
-
if (
|
|
174
|
-
!Array.isArray(def.children) &&
|
|
175
|
-
(def.children as JxMappedArray | undefined)?.$prototype === "Array"
|
|
176
|
-
) {
|
|
165
|
+
if (isMappedArray(def) || childrenContainArray(def.children)) {
|
|
177
166
|
return true;
|
|
178
167
|
}
|
|
179
168
|
|
|
@@ -252,9 +241,9 @@ export function createCompileContext(
|
|
|
252
241
|
scopeDefs: Record<string, unknown> = {},
|
|
253
242
|
media: Record<string, string> = {},
|
|
254
243
|
): CompileContext {
|
|
255
|
-
const scope = raw?.state
|
|
244
|
+
const scope: Record<string, unknown> = raw?.state
|
|
256
245
|
? buildInitialScope(raw.state, parentScope)
|
|
257
|
-
: (parentScope ?? Object.create(null));
|
|
246
|
+
: (parentScope ?? (Object.create(null) as Record<string, unknown>));
|
|
258
247
|
return { media, scope, scopeDefs };
|
|
259
248
|
}
|
|
260
249
|
|
|
@@ -267,7 +256,7 @@ export function buildInitialScope(
|
|
|
267
256
|
defs: Record<string, JxStateDefinition> = {},
|
|
268
257
|
parentScope: Record<string, unknown> | null = null,
|
|
269
258
|
) {
|
|
270
|
-
const scope = Object.create(parentScope ?? null)
|
|
259
|
+
const scope = Object.create(parentScope ?? null) as Record<string, unknown>;
|
|
271
260
|
|
|
272
261
|
for (const [key, def] of Object.entries(defs)) {
|
|
273
262
|
if (typeof def !== "object" || def === null || Array.isArray(def)) {
|
|
@@ -306,7 +295,9 @@ export function buildInitialScope(
|
|
|
306
295
|
if (isFunctionDef(def)) {
|
|
307
296
|
if (def.body) {
|
|
308
297
|
const names = def.parameters ? paramNames(def.parameters) : (def.arguments ?? []);
|
|
309
|
-
const fn = new Function("state", ...names, def.body)
|
|
298
|
+
const fn = new Function("state", ...names, def.body) as (
|
|
299
|
+
state: Record<string, unknown>,
|
|
300
|
+
) => unknown;
|
|
310
301
|
if (def.body.includes("return")) {
|
|
311
302
|
defineLazyScopeValue(scope, key, () => fn(scope));
|
|
312
303
|
} else {
|
|
@@ -393,7 +384,7 @@ export function resolveRefValue(refValue: unknown, scope: Record<string, unknown
|
|
|
393
384
|
}
|
|
394
385
|
if (refValue.startsWith("$map/")) {
|
|
395
386
|
const parts = refValue.split("/");
|
|
396
|
-
const
|
|
387
|
+
const key = parts[1]!;
|
|
397
388
|
const base = (scope.$map as Record<string, unknown> | undefined)?.[key] ?? scope[`$map/${key}`];
|
|
398
389
|
return parts.length > 2 ? getPathValue(base, parts.slice(2).join("/")) : base;
|
|
399
390
|
}
|
|
@@ -417,10 +408,16 @@ export function evaluateStaticTemplate(str: string, scope: Record<string, unknow
|
|
|
417
408
|
try {
|
|
418
409
|
const singleExprMatch = str.match(/^\$\{(.+)\}$/s);
|
|
419
410
|
if (singleExprMatch) {
|
|
420
|
-
const fn = new Function("state", "$map", `return (${singleExprMatch[1]})`)
|
|
411
|
+
const fn = new Function("state", "$map", `return (${singleExprMatch[1]})`) as (
|
|
412
|
+
state: Record<string, unknown>,
|
|
413
|
+
$map: unknown,
|
|
414
|
+
) => unknown;
|
|
421
415
|
return fn(scope, scope?.$map);
|
|
422
416
|
}
|
|
423
|
-
const fn = new Function("state", "$map", `return \`${str}\``)
|
|
417
|
+
const fn = new Function("state", "$map", `return \`${str}\``) as (
|
|
418
|
+
state: Record<string, unknown>,
|
|
419
|
+
$map: unknown,
|
|
420
|
+
) => unknown;
|
|
424
421
|
return fn(scope, scope?.$map);
|
|
425
422
|
} catch {
|
|
426
423
|
return null;
|
|
@@ -632,7 +629,7 @@ export function compileStyles(
|
|
|
632
629
|
if (mProps) {
|
|
633
630
|
rules.push(`${atRule} { ${selector} { ${mProps} } }`);
|
|
634
631
|
}
|
|
635
|
-
for (const [sel, sub] of Object.entries(
|
|
632
|
+
for (const [sel, sub] of Object.entries(val as Record<string, unknown>)) {
|
|
636
633
|
if (sub === null || typeof sub !== "object" || Array.isArray(sub)) {
|
|
637
634
|
continue;
|
|
638
635
|
}
|
|
@@ -753,7 +750,7 @@ function emitNestedElement(
|
|
|
753
750
|
if (mProps) {
|
|
754
751
|
rules.push(`${atRule} { ${selector} { ${mProps} } }`);
|
|
755
752
|
}
|
|
756
|
-
for (const [sel, sub] of Object.entries(
|
|
753
|
+
for (const [sel, sub] of Object.entries(val as Record<string, unknown>)) {
|
|
757
754
|
if (sub === null || typeof sub !== "object" || Array.isArray(sub)) {
|
|
758
755
|
continue;
|
|
759
756
|
}
|
|
@@ -995,8 +992,7 @@ function _walkSrc(def: JxElement | JxMutableNode | string, srcs: Set<string>) {
|
|
|
995
992
|
* @returns {{ key: string; exportName: string; src: string }[]}
|
|
996
993
|
*/
|
|
997
994
|
export function collectServerEntries(doc: JxElement) {
|
|
998
|
-
|
|
999
|
-
const entries = new Map();
|
|
995
|
+
const entries = new Map<string, { key: string; exportName: string; src: string }>();
|
|
1000
996
|
_walkServerEntries(doc, entries);
|
|
1001
997
|
return [...entries.values()];
|
|
1002
998
|
}
|
|
@@ -21,6 +21,7 @@ import type { FormatEntry, FormatRegistry } from "@jxsuite/schema/format-registr
|
|
|
21
21
|
import type {
|
|
22
22
|
ContentTypeDef,
|
|
23
23
|
ContentTypeSchema,
|
|
24
|
+
ContentTypeSchemaField,
|
|
24
25
|
JxMutableNode,
|
|
25
26
|
ProjectConfig,
|
|
26
27
|
} from "@jxsuite/schema/types";
|
|
@@ -36,16 +37,16 @@ import type { ContentLoaderEntry } from "@jxsuite/parser/types";
|
|
|
36
37
|
* @returns {ContentLoaderEntry[]} Array of ContentEntry shapes
|
|
37
38
|
*/
|
|
38
39
|
function loadJSONEntries(filePath: string) {
|
|
39
|
-
const raw = JSON.parse(readFileSync(filePath, "utf8"));
|
|
40
|
+
const raw = JSON.parse(readFileSync(filePath, "utf8")) as unknown;
|
|
40
41
|
if (Array.isArray(raw)) {
|
|
41
|
-
return raw.map((item: Record<string, unknown>, i: number) => ({
|
|
42
|
+
return (raw as Record<string, unknown>[]).map((item: Record<string, unknown>, i: number) => ({
|
|
42
43
|
body: null,
|
|
43
44
|
data: item,
|
|
44
45
|
id: (item.id as string) ?? `${basename(filePath, ".json")}-${i}`,
|
|
45
46
|
}));
|
|
46
47
|
}
|
|
47
48
|
// Single object file — filename is the id
|
|
48
|
-
const rawObj
|
|
49
|
+
const rawObj = raw as Record<string, unknown>;
|
|
49
50
|
return [
|
|
50
51
|
{
|
|
51
52
|
body: null,
|
|
@@ -108,7 +109,7 @@ export async function loadContentTypes(
|
|
|
108
109
|
) {
|
|
109
110
|
const result = loadContentConfig(projectRoot, projectConfig);
|
|
110
111
|
if (!result) {
|
|
111
|
-
return new Map();
|
|
112
|
+
return new Map<string, ContentLoaderEntry[]>();
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
const { config } = result;
|
|
@@ -359,7 +360,8 @@ export function resolveContentTypeRefs(
|
|
|
359
360
|
continue;
|
|
360
361
|
}
|
|
361
362
|
|
|
362
|
-
for (const [field,
|
|
363
|
+
for (const [field, rawDef] of Object.entries(schema.properties)) {
|
|
364
|
+
const def = rawDef as ContentTypeSchemaField;
|
|
363
365
|
if (!def.$ref?.startsWith("#/contentTypes/")) {
|
|
364
366
|
continue;
|
|
365
367
|
}
|
|
@@ -36,7 +36,7 @@ export function injectContext(
|
|
|
36
36
|
doc: JxDocument,
|
|
37
37
|
projectConfig: ProjectConfig,
|
|
38
38
|
route: SiteRoute,
|
|
39
|
-
|
|
39
|
+
_contentTypes = new Map<string, ContentLoaderEntry[]>(),
|
|
40
40
|
projectRoot: string | null = null,
|
|
41
41
|
) {
|
|
42
42
|
if (!doc.state) {
|
package/src/site/image-cache.ts
CHANGED
|
@@ -94,13 +94,13 @@ export function cacheKey(srcPath: string, config: ImageConfig) {
|
|
|
94
94
|
* @param {string} projectRoot
|
|
95
95
|
* @returns {CacheManifest}
|
|
96
96
|
*/
|
|
97
|
-
export function loadCache(projectRoot: string) {
|
|
97
|
+
export function loadCache(projectRoot: string): CacheManifest {
|
|
98
98
|
const manifestPath = resolve(getImageCacheDir(projectRoot), "manifest.json");
|
|
99
99
|
if (!existsSync(manifestPath)) {
|
|
100
100
|
return { entries: {}, version: 1 };
|
|
101
101
|
}
|
|
102
102
|
try {
|
|
103
|
-
return JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
103
|
+
return JSON.parse(readFileSync(manifestPath, "utf8")) as CacheManifest;
|
|
104
104
|
} catch {
|
|
105
105
|
return { entries: {}, version: 1 };
|
|
106
106
|
}
|
|
@@ -10,7 +10,8 @@ import { errorMessage } from "@jxsuite/schema/parse";
|
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
11
11
|
import { basename, extname, resolve } from "node:path";
|
|
12
12
|
import { createRequire } from "node:module";
|
|
13
|
-
|
|
13
|
+
// Sharp 0.35 only has a default export (function merged with a types namespace).
|
|
14
|
+
import type SharpNS from "sharp";
|
|
14
15
|
|
|
15
16
|
export interface ImageVariant {
|
|
16
17
|
width: number; // Pixel width of the variant
|
|
@@ -41,8 +42,7 @@ export interface ImageConfig {
|
|
|
41
42
|
remoteDomains?: string[];
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
type SharpModule = typeof import("sharp");
|
|
45
|
+
type SharpModule = typeof SharpNS;
|
|
46
46
|
|
|
47
47
|
let _sharp: SharpModule | null = null;
|
|
48
48
|
|
|
@@ -175,7 +175,7 @@ export async function processImage(srcPath: string, cacheImgDir: string, config:
|
|
|
175
175
|
const quality = config.quality[format as keyof ImageConfig["quality"]] ?? 80;
|
|
176
176
|
const task = sharp(srcPath)
|
|
177
177
|
.resize(width)
|
|
178
|
-
.toFormat(format as keyof FormatEnum, { quality })
|
|
178
|
+
.toFormat(format as keyof SharpNS.FormatEnum, { quality })
|
|
179
179
|
.toFile(absolutePath)
|
|
180
180
|
.then(() => {});
|
|
181
181
|
|
|
@@ -340,7 +340,7 @@ async function transformImgNode(
|
|
|
340
340
|
imageRefs.set(absoluteSrc, manifest);
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
-
const preferredFormat = config.formats.includes("avif") ? "avif" : config.formats[0]
|
|
343
|
+
const preferredFormat = config.formats.includes("avif") ? "avif" : config.formats[0]!;
|
|
344
344
|
srcset = buildSrcset(manifest.variants, preferredFormat);
|
|
345
345
|
}
|
|
346
346
|
}
|
|
@@ -384,8 +384,8 @@ async function transformInnerHtmlImages(
|
|
|
384
384
|
const replacements = [];
|
|
385
385
|
|
|
386
386
|
for (const m of html.matchAll(IMG_TAG_RE)) {
|
|
387
|
-
const
|
|
388
|
-
const
|
|
387
|
+
const tag = m[0]!;
|
|
388
|
+
const attrs = m[1]!;
|
|
389
389
|
|
|
390
390
|
if (SRCSET_ATTR_RE.test(attrs)) {
|
|
391
391
|
continue;
|
|
@@ -400,7 +400,7 @@ async function transformInnerHtmlImages(
|
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
// Attribute values in pre-rendered innerHTML are entity-escaped — decode for processing
|
|
403
|
-
const src = srcMatch[1]
|
|
403
|
+
const src = srcMatch[1]!.replaceAll("&", "&");
|
|
404
404
|
const remote = isAllowedRemote(src, config);
|
|
405
405
|
if (!remote && shouldSkip(src)) {
|
|
406
406
|
continue;
|
|
@@ -425,7 +425,7 @@ async function transformInnerHtmlImages(
|
|
|
425
425
|
imageRefs.set(absoluteSrc as string, manifest);
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
-
const preferredFormat = config.formats.includes("avif") ? "avif" : config.formats[0]
|
|
428
|
+
const preferredFormat = config.formats.includes("avif") ? "avif" : config.formats[0]!;
|
|
429
429
|
srcset = buildSrcset(manifest.variants, preferredFormat);
|
|
430
430
|
}
|
|
431
431
|
if (!srcset) {
|
|
@@ -169,7 +169,7 @@ async function fileToRoute(relativePath: string, absolutePath: string, registry?
|
|
|
169
169
|
// Convert [param] → :param and [...param] → *
|
|
170
170
|
const urlPattern = urlPath.replaceAll(
|
|
171
171
|
/\[\.\.\.(\w+)\]|\[(\w+)\]/g,
|
|
172
|
-
(
|
|
172
|
+
(_match: string, spread: string, named: string) => {
|
|
173
173
|
if (spread) {
|
|
174
174
|
isCatchAll = true;
|
|
175
175
|
isDynamic = true;
|
|
@@ -319,10 +319,9 @@ function resolvePathEntries(
|
|
|
319
319
|
// Data file ref: { "$ref": "./data/products.json", param: "id", field: "sku" }
|
|
320
320
|
if ("$ref" in $paths && $paths.$ref) {
|
|
321
321
|
const filePath = resolve(projectRoot, $paths.$ref);
|
|
322
|
-
|
|
323
|
-
let data;
|
|
322
|
+
let data: unknown;
|
|
324
323
|
try {
|
|
325
|
-
data = JSON.parse(readFileSync(filePath, "utf8"));
|
|
324
|
+
data = JSON.parse(readFileSync(filePath, "utf8")) as unknown;
|
|
326
325
|
} catch (error) {
|
|
327
326
|
const err = error as Error;
|
|
328
327
|
console.warn(`Warning: $paths.$ref could not load "${$paths.$ref}": ${err.message}`);
|
|
@@ -334,7 +333,7 @@ function resolvePathEntries(
|
|
|
334
333
|
}
|
|
335
334
|
const param = $paths.param ?? "id";
|
|
336
335
|
const field = $paths.field ?? "id";
|
|
337
|
-
return data.map((item: Record<string, unknown>) => ({
|
|
336
|
+
return (data as Record<string, unknown>[]).map((item: Record<string, unknown>) => ({
|
|
338
337
|
[param]: item[field] ?? item.id ?? String(item),
|
|
339
338
|
}));
|
|
340
339
|
}
|
package/src/site/site-build.ts
CHANGED
|
@@ -54,6 +54,7 @@ import type {
|
|
|
54
54
|
JxAttributeValue,
|
|
55
55
|
JxElement,
|
|
56
56
|
JxHeadEntry,
|
|
57
|
+
JxMappedArray,
|
|
57
58
|
JxMutableNode,
|
|
58
59
|
JxStateDefinition,
|
|
59
60
|
JxStyle,
|
|
@@ -188,8 +189,7 @@ export async function buildSite(
|
|
|
188
189
|
}
|
|
189
190
|
|
|
190
191
|
// ── 5b. Collect server entries from components (for site-wide bundling) ──
|
|
191
|
-
|
|
192
|
-
const siteServerEntries = [];
|
|
192
|
+
const siteServerEntries: { exportName: string; src: string }[] = [];
|
|
193
193
|
if (projectConfig.build.adapter) {
|
|
194
194
|
for (const [, doc] of componentDefs) {
|
|
195
195
|
const entries = collectServerEntries(doc);
|
|
@@ -207,8 +207,7 @@ export async function buildSite(
|
|
|
207
207
|
|
|
208
208
|
const cfImages = projectConfig.images.optimize && projectConfig.images.service === "cloudflare";
|
|
209
209
|
const imageCache = projectConfig.images.optimize && !cfImages ? loadCache(projectRoot) : null;
|
|
210
|
-
|
|
211
|
-
const imageMetaCache = cfImages ? new Map() : null;
|
|
210
|
+
const imageMetaCache: ImageMetaCache | null = cfImages ? new Map() : null;
|
|
212
211
|
if (cfImages) {
|
|
213
212
|
console.log(
|
|
214
213
|
`images.service is "cloudflare" — srcsets use /cdn-cgi/image transform URLs. ` +
|
|
@@ -274,7 +273,7 @@ export async function buildSite(
|
|
|
274
273
|
mode: "export",
|
|
275
274
|
})) as string;
|
|
276
275
|
if (content) {
|
|
277
|
-
const sidecarPath = outPath.replace(/\.html$/, fmt.extensions[0]);
|
|
276
|
+
const sidecarPath = outPath.replace(/\.html$/, fmt.extensions[0]!);
|
|
278
277
|
writeFileSync(sidecarPath, content, "utf8");
|
|
279
278
|
fileCount += 1;
|
|
280
279
|
}
|
|
@@ -326,7 +325,7 @@ export async function buildSite(
|
|
|
326
325
|
const { adapter } = projectConfig.build;
|
|
327
326
|
log("Generating site-wide server worker...");
|
|
328
327
|
|
|
329
|
-
const deduped = new Map();
|
|
328
|
+
const deduped = new Map<string, { exportName: string; src: string }>();
|
|
330
329
|
for (const entry of siteServerEntries) {
|
|
331
330
|
if (!deduped.has(entry.exportName)) {
|
|
332
331
|
deduped.set(entry.exportName, entry);
|
|
@@ -460,7 +459,7 @@ async function compilePage(
|
|
|
460
459
|
// Determine the page title — resolve template strings against the scope
|
|
461
460
|
let title = pageTitle ?? projectConfig.name ?? "Jx Site";
|
|
462
461
|
if (typeof title === "string" && isTemplateString(title)) {
|
|
463
|
-
title = evaluateStaticTemplate(title, scope) ?? (title as string);
|
|
462
|
+
title = (evaluateStaticTemplate(title, scope) as string | null) ?? (title as string);
|
|
464
463
|
}
|
|
465
464
|
|
|
466
465
|
// Resolve template strings in $head entries
|
|
@@ -595,14 +594,16 @@ function resolveHeadTemplates(headEntries: JxHeadEntry[], scope: Record<string,
|
|
|
595
594
|
resolved.attributes = { ...resolved.attributes };
|
|
596
595
|
for (const [k, v] of Object.entries(resolved.attributes)) {
|
|
597
596
|
if (typeof v === "string" && isTemplateString(v)) {
|
|
598
|
-
resolved.attributes[k] =
|
|
597
|
+
resolved.attributes[k] =
|
|
598
|
+
(evaluateStaticTemplate(v, scope) as string | boolean | null) ??
|
|
599
|
+
(v as string | boolean);
|
|
599
600
|
}
|
|
600
601
|
}
|
|
601
602
|
}
|
|
602
603
|
if (typeof resolved.textContent === "string" && isTemplateString(resolved.textContent)) {
|
|
603
604
|
resolved.textContent =
|
|
604
|
-
evaluateStaticTemplate(resolved.textContent, scope) ??
|
|
605
|
-
|
|
605
|
+
(evaluateStaticTemplate(resolved.textContent, scope) as string | null) ??
|
|
606
|
+
resolved.textContent;
|
|
606
607
|
}
|
|
607
608
|
return resolved;
|
|
608
609
|
});
|
|
@@ -662,11 +663,11 @@ function expandMapTemplate(template: JxElement, scope: Record<string, unknown>):
|
|
|
662
663
|
const node = {} as JxElement;
|
|
663
664
|
for (const [k, v] of Object.entries(template)) {
|
|
664
665
|
if (k === "children" && Array.isArray(v)) {
|
|
665
|
-
node.children = v.map((child) => {
|
|
666
|
+
node.children = (v as (string | JxElement)[]).map((child) => {
|
|
666
667
|
if (typeof child === "string") {
|
|
667
668
|
return child;
|
|
668
669
|
}
|
|
669
|
-
return expandMapTemplate(
|
|
670
|
+
return expandMapTemplate(child, scope);
|
|
670
671
|
});
|
|
671
672
|
} else if (k === "style" && v && typeof v === "object") {
|
|
672
673
|
const style: JxStyle = { ...(v as JxStyle) };
|
|
@@ -718,10 +719,26 @@ function evaluateMapTemplate(str: string, scope: Record<string, unknown>) {
|
|
|
718
719
|
const index = (scope.$map as Record<string, unknown>)?.index;
|
|
719
720
|
const singleExprMatch = str.match(/^\$\{(.+)\}$/s);
|
|
720
721
|
if (singleExprMatch) {
|
|
721
|
-
const fn = new Function(
|
|
722
|
+
const fn = new Function(
|
|
723
|
+
"state",
|
|
724
|
+
"$map",
|
|
725
|
+
"item",
|
|
726
|
+
"index",
|
|
727
|
+
`return (${singleExprMatch[1]})`,
|
|
728
|
+
) as (
|
|
729
|
+
state: Record<string, unknown>,
|
|
730
|
+
$map: unknown,
|
|
731
|
+
item: unknown,
|
|
732
|
+
index: unknown,
|
|
733
|
+
) => unknown;
|
|
722
734
|
return fn(scope, scope.$map, item, index);
|
|
723
735
|
}
|
|
724
|
-
const fn = new Function("state", "$map", "item", "index", `return \`${str}\``)
|
|
736
|
+
const fn = new Function("state", "$map", "item", "index", `return \`${str}\``) as (
|
|
737
|
+
state: Record<string, unknown>,
|
|
738
|
+
$map: unknown,
|
|
739
|
+
item: unknown,
|
|
740
|
+
index: unknown,
|
|
741
|
+
) => unknown;
|
|
725
742
|
return fn(scope, scope.$map, item, index);
|
|
726
743
|
} catch {
|
|
727
744
|
return null;
|
|
@@ -751,49 +768,38 @@ function resolveDocTemplates(node: JxElement | string, scope: Record<string, unk
|
|
|
751
768
|
}
|
|
752
769
|
if (typeof node.textContent === "string" && isTemplateString(node.textContent)) {
|
|
753
770
|
node.textContent =
|
|
754
|
-
evaluateStaticTemplate(node.textContent, scope)
|
|
771
|
+
(evaluateStaticTemplate(node.textContent, scope) as string | null) ??
|
|
772
|
+
(node.textContent as string | null);
|
|
755
773
|
}
|
|
756
774
|
if (node.style && typeof node.style === "object") {
|
|
757
775
|
for (const [k, v] of Object.entries(node.style)) {
|
|
758
776
|
if (typeof v === "string" && isTemplateString(v)) {
|
|
759
|
-
node.style[k] =
|
|
777
|
+
node.style[k] =
|
|
778
|
+
(evaluateStaticTemplate(v, scope) as string | number | JxStyle | undefined) ??
|
|
779
|
+
(v as string | number | JxStyle);
|
|
760
780
|
}
|
|
761
781
|
}
|
|
762
782
|
}
|
|
763
783
|
if (node.attributes && typeof node.attributes === "object") {
|
|
764
784
|
for (const [k, v] of Object.entries(node.attributes)) {
|
|
765
785
|
if (typeof v === "string" && isTemplateString(v)) {
|
|
766
|
-
node.attributes[k] = evaluateStaticTemplate(v, scope) ?? v;
|
|
786
|
+
node.attributes[k] = (evaluateStaticTemplate(v, scope) as JxAttributeValue | null) ?? v;
|
|
767
787
|
}
|
|
768
788
|
}
|
|
769
789
|
}
|
|
770
790
|
if (node.$props && typeof node.$props === "object") {
|
|
771
791
|
for (const [k, v] of Object.entries(node.$props)) {
|
|
772
792
|
if (typeof v === "string" && isTemplateString(v)) {
|
|
773
|
-
node.$props[k] = evaluateStaticTemplate(v, scope) ?? v;
|
|
793
|
+
node.$props[k] = (evaluateStaticTemplate(v, scope) as JsonValue | null) ?? v;
|
|
774
794
|
}
|
|
775
795
|
}
|
|
776
796
|
}
|
|
777
797
|
const rawChildren = node.children;
|
|
798
|
+
// Legacy whole-children repeater: expand the items into the node's static children.
|
|
778
799
|
if (isMappedArray(rawChildren)) {
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
items = resolveRefValue(itemsSrc.$ref, scope);
|
|
783
|
-
} else if (Array.isArray(itemsSrc)) {
|
|
784
|
-
items = itemsSrc;
|
|
785
|
-
}
|
|
786
|
-
const mapTemplate = rawChildren.map;
|
|
787
|
-
if (Array.isArray(items) && mapTemplate) {
|
|
788
|
-
node.children = items.map((item, index) => {
|
|
789
|
-
const childScope = Object.create(scope);
|
|
790
|
-
childScope.$map = { index, item };
|
|
791
|
-
childScope["$map/item"] = item;
|
|
792
|
-
childScope["$map/index"] = index;
|
|
793
|
-
const expanded = expandMapTemplate(mapTemplate, childScope);
|
|
794
|
-
resolveDocTemplates(expanded, childScope);
|
|
795
|
-
return expanded;
|
|
796
|
-
});
|
|
800
|
+
const expanded = expandMappedArrayStatic(rawChildren, scope);
|
|
801
|
+
if (expanded) {
|
|
802
|
+
node.children = expanded;
|
|
797
803
|
return;
|
|
798
804
|
}
|
|
799
805
|
}
|
|
@@ -809,7 +815,7 @@ function resolveDocTemplates(node: JxElement | string, scope: Record<string, unk
|
|
|
809
815
|
} else if (Array.isArray(node.children)) {
|
|
810
816
|
let i = 0;
|
|
811
817
|
while (i < node.children.length) {
|
|
812
|
-
const child = node.children[i]
|
|
818
|
+
const child = node.children[i]!;
|
|
813
819
|
if (typeof child === "string" && isTemplateString(child)) {
|
|
814
820
|
const resolved = evaluateStaticTemplate(child, scope);
|
|
815
821
|
if (Array.isArray(resolved)) {
|
|
@@ -822,12 +828,55 @@ function resolveDocTemplates(node: JxElement | string, scope: Record<string, unk
|
|
|
822
828
|
continue;
|
|
823
829
|
}
|
|
824
830
|
}
|
|
831
|
+
// Array pseudo-element among siblings: expand its items in place.
|
|
832
|
+
if (isMappedArray(child)) {
|
|
833
|
+
const expanded = expandMappedArrayStatic(child, scope);
|
|
834
|
+
if (expanded) {
|
|
835
|
+
node.children.splice(i, 1, ...expanded);
|
|
836
|
+
i += expanded.length;
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
825
840
|
resolveDocTemplates(child, scope);
|
|
826
841
|
i += 1;
|
|
827
842
|
}
|
|
828
843
|
}
|
|
829
844
|
}
|
|
830
845
|
|
|
846
|
+
/**
|
|
847
|
+
* Statically expand a mapped array to its resolved item nodes when `items` resolves to an array at
|
|
848
|
+
* build time, or null otherwise (leave the array node for client-side rendering).
|
|
849
|
+
*
|
|
850
|
+
* @param {JxMappedArray} arrayDef
|
|
851
|
+
* @param {Record<string, unknown>} scope
|
|
852
|
+
* @returns {(JxElement | string)[] | null}
|
|
853
|
+
*/
|
|
854
|
+
function expandMappedArrayStatic(
|
|
855
|
+
arrayDef: JxMappedArray,
|
|
856
|
+
scope: Record<string, unknown>,
|
|
857
|
+
): (JxElement | string)[] | null {
|
|
858
|
+
const itemsSrc = arrayDef.items;
|
|
859
|
+
let items: unknown = null;
|
|
860
|
+
if (isRef(itemsSrc)) {
|
|
861
|
+
items = resolveRefValue(itemsSrc.$ref, scope);
|
|
862
|
+
} else if (Array.isArray(itemsSrc)) {
|
|
863
|
+
items = itemsSrc;
|
|
864
|
+
}
|
|
865
|
+
const mapTemplate = arrayDef.map;
|
|
866
|
+
if (!Array.isArray(items) || !mapTemplate) {
|
|
867
|
+
return null;
|
|
868
|
+
}
|
|
869
|
+
return (items as unknown[]).map((item: unknown, index) => {
|
|
870
|
+
const childScope = Object.create(scope) as Record<string, unknown>;
|
|
871
|
+
childScope.$map = { index, item };
|
|
872
|
+
childScope["$map/item"] = item;
|
|
873
|
+
childScope["$map/index"] = index;
|
|
874
|
+
const expanded = expandMapTemplate(mapTemplate, childScope);
|
|
875
|
+
resolveDocTemplates(expanded, childScope);
|
|
876
|
+
return expanded;
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
|
|
831
880
|
/** Collector for CSS rules extracted from component slot content during expansion. */
|
|
832
881
|
interface SlotCssCollector {
|
|
833
882
|
rules: string[];
|
|
@@ -853,7 +902,7 @@ function expandComponents(
|
|
|
853
902
|
return;
|
|
854
903
|
}
|
|
855
904
|
if (Array.isArray(node)) {
|
|
856
|
-
for (const n of node) {
|
|
905
|
+
for (const n of node as (JxElement | string)[]) {
|
|
857
906
|
expandComponents(n, componentDefs, slotCss);
|
|
858
907
|
}
|
|
859
908
|
return;
|
|
@@ -1012,11 +1061,11 @@ function injectHead(html: string, headEntries: JxHeadEntry[], lang: string) {
|
|
|
1012
1061
|
const existingMatch = html.match(headPattern);
|
|
1013
1062
|
let preservedBlocks = "";
|
|
1014
1063
|
if (existingMatch) {
|
|
1015
|
-
const styles = existingMatch[1]
|
|
1064
|
+
const styles = existingMatch[1]!.match(/<style>[\s\S]*?<\/style>/gi);
|
|
1016
1065
|
if (styles) {
|
|
1017
1066
|
preservedBlocks += `\n ${styles.join("\n ")}`;
|
|
1018
1067
|
}
|
|
1019
|
-
const scripts = existingMatch[1]
|
|
1068
|
+
const scripts = existingMatch[1]!.match(/<script[\s\S]*?<\/script>/gi);
|
|
1020
1069
|
if (scripts) {
|
|
1021
1070
|
preservedBlocks += `\n ${scripts.join("\n ")}`;
|
|
1022
1071
|
}
|