@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,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Site-build
|
|
2
|
+
* Site-build — Multi-page build orchestrator
|
|
3
3
|
*
|
|
4
4
|
* Coordinates the full site build pipeline: 1. Load project.json 2. Discover pages/ routes 3.
|
|
5
5
|
* Expand dynamic routes ($paths) 4. For each route: resolve layout, merge $head, inject context,
|
|
@@ -20,13 +20,13 @@ import {
|
|
|
20
20
|
readdirSync,
|
|
21
21
|
} from "node:fs";
|
|
22
22
|
import { resolve, dirname, join } from "node:path";
|
|
23
|
-
import { loadProjectConfig } from "./site-loader.
|
|
24
|
-
import { discoverPages, expandDynamicRoutes } from "./pages-discovery.
|
|
25
|
-
import { resolveLayout } from "./layout-resolver.
|
|
26
|
-
import { mergeHead, renderHead } from "./head-merger.
|
|
27
|
-
import { injectContext } from "./context-injection.
|
|
28
|
-
import { compile, compileServer, compileSiteServer, compilePagesFunctions } from "../compiler.
|
|
29
|
-
import { compileElement } from "../targets/compile-element.
|
|
23
|
+
import { loadProjectConfig } from "./site-loader.ts";
|
|
24
|
+
import { discoverPages, expandDynamicRoutes } from "./pages-discovery.ts";
|
|
25
|
+
import { resolveLayout } from "./layout-resolver.ts";
|
|
26
|
+
import { mergeHead, renderHead } from "./head-merger.ts";
|
|
27
|
+
import { injectContext } from "./context-injection.ts";
|
|
28
|
+
import { compile, compileServer, compileSiteServer, compilePagesFunctions } from "../compiler.ts";
|
|
29
|
+
import { compileElement } from "../targets/compile-element.ts";
|
|
30
30
|
import {
|
|
31
31
|
buildInitialScope,
|
|
32
32
|
isTemplateString,
|
|
@@ -40,12 +40,22 @@ import {
|
|
|
40
40
|
resolveRefValue,
|
|
41
41
|
DEFAULT_REACTIVITY_SRC,
|
|
42
42
|
DEFAULT_LIT_HTML_SRC,
|
|
43
|
-
} from "../shared.
|
|
44
|
-
import { loadContentTypes, loadContentConfig, resolveContentTypeRefs } from "./content-loader.
|
|
45
|
-
import { resolvePrototypes } from "./prototype-resolver.
|
|
46
|
-
import { compileMarkdown } from "../targets/compile-markdown.
|
|
47
|
-
import { transformImageNodes } from "./image-transform.
|
|
48
|
-
import { loadCache, saveCache } from "./image-cache.
|
|
43
|
+
} from "../shared.ts";
|
|
44
|
+
import { loadContentTypes, loadContentConfig, resolveContentTypeRefs } from "./content-loader.ts";
|
|
45
|
+
import { resolvePrototypes } from "./prototype-resolver.ts";
|
|
46
|
+
import { compileMarkdown } from "../targets/compile-markdown.ts";
|
|
47
|
+
import { transformImageNodes } from "./image-transform.ts";
|
|
48
|
+
import { loadCache, saveCache } from "./image-cache.ts";
|
|
49
|
+
import type {
|
|
50
|
+
JxElement,
|
|
51
|
+
JxMutableNode,
|
|
52
|
+
JxStyle,
|
|
53
|
+
JxHeadEntry,
|
|
54
|
+
JxStateDefinition,
|
|
55
|
+
ProjectConfig,
|
|
56
|
+
} from "@jxsuite/schema/types";
|
|
57
|
+
import type { ContentLoaderEntry } from "@jxsuite/parser/types";
|
|
58
|
+
import type { SiteRoute } from "../types.ts";
|
|
49
59
|
|
|
50
60
|
/**
|
|
51
61
|
* Build an entire Jx site from a project directory.
|
|
@@ -56,10 +66,15 @@ import { loadCache, saveCache } from "./image-cache.js";
|
|
|
56
66
|
* @param {boolean} [options.verbose] - Log progress
|
|
57
67
|
* @returns {Promise<{ routes: number; files: number; errors: string[] }>}
|
|
58
68
|
*/
|
|
59
|
-
export async function buildSite(
|
|
69
|
+
export async function buildSite(
|
|
70
|
+
projectRoot: string,
|
|
71
|
+
options: {
|
|
72
|
+
clean?: boolean;
|
|
73
|
+
verbose?: boolean;
|
|
74
|
+
} = {},
|
|
75
|
+
) {
|
|
60
76
|
const { clean = true, verbose = false } = options;
|
|
61
|
-
|
|
62
|
-
const errors = [];
|
|
77
|
+
const errors: string[] = [];
|
|
63
78
|
const log = verbose ? console.log.bind(console) : () => {};
|
|
64
79
|
|
|
65
80
|
// ── 1. Load project configuration ──────────────────────────────────────────
|
|
@@ -118,16 +133,13 @@ export async function buildSite(projectRoot, options = {}) {
|
|
|
118
133
|
|
|
119
134
|
// ── 5. Compile site components ──────────────────────────────────────────
|
|
120
135
|
const componentsDir = resolve(projectRoot, "components");
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
const componentCSS = new Map(); // tagName → CSS text
|
|
125
|
-
/** @type {Map<string, JxElement>} */
|
|
126
|
-
const componentDefs = new Map(); // tagName → parsed component definition
|
|
136
|
+
const compiledComponentTags: string[] = [];
|
|
137
|
+
const componentCSS: Map<string, string> = new Map(); // tagName → CSS text
|
|
138
|
+
const componentDefs: Map<string, JxElement> = new Map(); // tagName → parsed component definition
|
|
127
139
|
if (existsSync(componentsDir)) {
|
|
128
140
|
log("Compiling components...");
|
|
129
141
|
const componentFiles = readdirSync(componentsDir).filter(
|
|
130
|
-
(
|
|
142
|
+
(f: string) => f.endsWith(".json") || f.endsWith(".md"),
|
|
131
143
|
);
|
|
132
144
|
const componentOutDir = resolve(outDir, "components");
|
|
133
145
|
mkdirSync(componentOutDir, { recursive: true });
|
|
@@ -137,9 +149,7 @@ export async function buildSite(projectRoot, options = {}) {
|
|
|
137
149
|
const componentPath = resolve(componentsDir, file);
|
|
138
150
|
const result = await compileElement(componentPath, { $media: projectConfig.$media });
|
|
139
151
|
for (const f of result.files) {
|
|
140
|
-
const outName = f.path.includes("/")
|
|
141
|
-
? /** @type {string} */ (f.path.split("/").pop())
|
|
142
|
-
: f.path;
|
|
152
|
+
const outName = f.path.includes("/") ? (f.path.split("/").pop() as string) : f.path;
|
|
143
153
|
writeFileSync(resolve(componentOutDir, outName), f.content, "utf8");
|
|
144
154
|
if (f.tagName) compiledComponentTags.push(f.tagName);
|
|
145
155
|
fileCount++;
|
|
@@ -161,7 +171,7 @@ export async function buildSite(projectRoot, options = {}) {
|
|
|
161
171
|
}
|
|
162
172
|
}
|
|
163
173
|
} catch (e) {
|
|
164
|
-
const err =
|
|
174
|
+
const err = e as Error;
|
|
165
175
|
errors.push(`Error compiling component ${file}: ${err.message}`);
|
|
166
176
|
console.error(`Error compiling component ${file}: ${err.message}`);
|
|
167
177
|
}
|
|
@@ -192,7 +202,7 @@ export async function buildSite(projectRoot, options = {}) {
|
|
|
192
202
|
try {
|
|
193
203
|
log(` Compiling ${route.urlPattern} ...`);
|
|
194
204
|
const result = await compilePage(
|
|
195
|
-
route,
|
|
205
|
+
route as unknown as SiteRoute,
|
|
196
206
|
projectConfig,
|
|
197
207
|
projectRoot,
|
|
198
208
|
contentTypes,
|
|
@@ -202,8 +212,7 @@ export async function buildSite(projectRoot, options = {}) {
|
|
|
202
212
|
);
|
|
203
213
|
|
|
204
214
|
// Determine which component tags are fully static (for script omission)
|
|
205
|
-
|
|
206
|
-
const staticTags = new Set();
|
|
215
|
+
const staticTags: Set<string> = new Set();
|
|
207
216
|
for (const [tag, def] of componentDefs) {
|
|
208
217
|
if (isComponentFullyStatic(def)) staticTags.add(tag);
|
|
209
218
|
}
|
|
@@ -233,7 +242,7 @@ export async function buildSite(projectRoot, options = {}) {
|
|
|
233
242
|
fileCount++;
|
|
234
243
|
}
|
|
235
244
|
} catch (e) {
|
|
236
|
-
const err =
|
|
245
|
+
const err = e as Error;
|
|
237
246
|
errors.push(`Error exporting markdown for ${route.urlPattern}: ${err.message}`);
|
|
238
247
|
}
|
|
239
248
|
|
|
@@ -252,7 +261,7 @@ export async function buildSite(projectRoot, options = {}) {
|
|
|
252
261
|
fileCount++;
|
|
253
262
|
}
|
|
254
263
|
} catch (e) {
|
|
255
|
-
const err =
|
|
264
|
+
const err = e as Error;
|
|
256
265
|
const msg = `Error compiling ${route.urlPattern}: ${err.message}`;
|
|
257
266
|
errors.push(msg);
|
|
258
267
|
console.error(msg);
|
|
@@ -341,8 +350,8 @@ export async function buildSite(projectRoot, options = {}) {
|
|
|
341
350
|
if (projectConfig.copy) {
|
|
342
351
|
log("Copying mapped files...");
|
|
343
352
|
for (const [src, dest] of Object.entries(projectConfig.copy)) {
|
|
344
|
-
const srcPath = resolve(projectRoot,
|
|
345
|
-
const destPath = resolve(outDir,
|
|
353
|
+
const srcPath = resolve(projectRoot, src as string);
|
|
354
|
+
const destPath = resolve(outDir, dest as string);
|
|
346
355
|
mkdirSync(dirname(destPath), { recursive: true });
|
|
347
356
|
cpSync(srcPath, destPath);
|
|
348
357
|
}
|
|
@@ -377,30 +386,30 @@ export async function buildSite(projectRoot, options = {}) {
|
|
|
377
386
|
* }>}
|
|
378
387
|
*/
|
|
379
388
|
async function compilePage(
|
|
380
|
-
route,
|
|
381
|
-
projectConfig,
|
|
382
|
-
projectRoot,
|
|
383
|
-
contentTypes = new Map(),
|
|
384
|
-
imageCache = null,
|
|
385
|
-
outDir = "",
|
|
386
|
-
componentDefs = new Map(),
|
|
389
|
+
route: SiteRoute,
|
|
390
|
+
projectConfig: ProjectConfig,
|
|
391
|
+
projectRoot: string,
|
|
392
|
+
contentTypes: Map<string, ContentLoaderEntry[]> = new Map(),
|
|
393
|
+
imageCache: import("./image-cache.js").CacheManifest | null = null,
|
|
394
|
+
outDir: string = "",
|
|
395
|
+
componentDefs: Map<string, JxElement> = new Map(),
|
|
387
396
|
) {
|
|
388
397
|
// Load the raw page document
|
|
389
398
|
let pageDoc;
|
|
390
|
-
if (
|
|
399
|
+
if ((route.sourcePath as string).endsWith(".md")) {
|
|
391
400
|
const { transpileJxMarkdown } = await import("@jxsuite/parser/transpile");
|
|
392
|
-
pageDoc = transpileJxMarkdown(readFileSync(
|
|
401
|
+
pageDoc = transpileJxMarkdown(readFileSync(route.sourcePath as string, "utf8"));
|
|
393
402
|
} else {
|
|
394
|
-
pageDoc = JSON.parse(readFileSync(
|
|
403
|
+
pageDoc = JSON.parse(readFileSync(route.sourcePath as string, "utf8"));
|
|
395
404
|
}
|
|
396
405
|
|
|
397
406
|
// Resolve layout (wraps page in layout with slot distribution)
|
|
398
407
|
const layoutDoc = resolveLayout(pageDoc, projectConfig, projectRoot);
|
|
399
408
|
|
|
400
409
|
// Extract head arrays before they get lost in the merge
|
|
401
|
-
const pageHead =
|
|
410
|
+
const pageHead = pageDoc.$head ?? layoutDoc._pageHead ?? ([] as JxHeadEntry[]);
|
|
402
411
|
const layoutHead = layoutDoc.$head ?? [];
|
|
403
|
-
const pageTitle =
|
|
412
|
+
const pageTitle = pageDoc.title ?? layoutDoc._pageTitle ?? (null as string | null);
|
|
404
413
|
|
|
405
414
|
// Clean up internal properties
|
|
406
415
|
delete layoutDoc._pageHead;
|
|
@@ -421,7 +430,7 @@ async function compilePage(
|
|
|
421
430
|
// Determine the page title — resolve template strings against the scope
|
|
422
431
|
let title = pageTitle ?? projectConfig.name ?? "Jx Site";
|
|
423
432
|
if (typeof title === "string" && isTemplateString(title)) {
|
|
424
|
-
title =
|
|
433
|
+
title = evaluateStaticTemplate(title, scope) ?? (title as string);
|
|
425
434
|
}
|
|
426
435
|
|
|
427
436
|
// Resolve template strings in $head entries
|
|
@@ -446,7 +455,7 @@ async function compilePage(
|
|
|
446
455
|
def &&
|
|
447
456
|
typeof def === "object" &&
|
|
448
457
|
!Array.isArray(def) &&
|
|
449
|
-
|
|
458
|
+
(def as JxMutableNode).timing === "compiler"
|
|
450
459
|
) {
|
|
451
460
|
delete layoutDoc.state[key];
|
|
452
461
|
} else if (Array.isArray(def)) {
|
|
@@ -462,8 +471,8 @@ async function compilePage(
|
|
|
462
471
|
const mergedHead = mergeHead(resolvedSiteHead, resolvedLayoutHead, resolvedPageHead, {
|
|
463
472
|
title,
|
|
464
473
|
charset: projectConfig.defaults?.charset ?? "utf-8",
|
|
465
|
-
siteName: projectConfig.name,
|
|
466
|
-
siteUrl: projectConfig.url,
|
|
474
|
+
...(projectConfig.name != null && { siteName: projectConfig.name }),
|
|
475
|
+
...(projectConfig.url != null && { siteUrl: projectConfig.url }),
|
|
467
476
|
pageUrl: route.urlPattern,
|
|
468
477
|
});
|
|
469
478
|
|
|
@@ -474,7 +483,13 @@ async function compilePage(
|
|
|
474
483
|
|
|
475
484
|
// Transform <img> nodes for responsive image optimization
|
|
476
485
|
if (imageCache && projectConfig.images?.optimize && outDir) {
|
|
477
|
-
await transformImageNodes(
|
|
486
|
+
await transformImageNodes(
|
|
487
|
+
layoutDoc,
|
|
488
|
+
projectConfig.images as import("./image-optimizer").ImageConfig,
|
|
489
|
+
projectRoot,
|
|
490
|
+
outDir,
|
|
491
|
+
imageCache,
|
|
492
|
+
);
|
|
478
493
|
}
|
|
479
494
|
|
|
480
495
|
// Compile the document using the existing compiler
|
|
@@ -489,19 +504,17 @@ async function compilePage(
|
|
|
489
504
|
|
|
490
505
|
// Inject <script type="module"> for npm $elements (cherry-picked component imports)
|
|
491
506
|
const npmElements = (layoutDoc.$elements ?? []).filter(
|
|
492
|
-
(
|
|
493
|
-
typeof e === "string" && !e.startsWith("./") && !e.startsWith("../"),
|
|
507
|
+
(e: JxElement | string) => typeof e === "string" && !e.startsWith("./") && !e.startsWith("../"),
|
|
494
508
|
);
|
|
495
509
|
if (npmElements.length > 0) {
|
|
496
510
|
result.html = injectNpmElementScripts(result.html, /** @type {string[]} */ (npmElements));
|
|
497
511
|
}
|
|
498
512
|
|
|
499
513
|
// Compile server handler if applicable (skip when provider bundles site-wide)
|
|
500
|
-
|
|
501
|
-
let serverHandler = null;
|
|
514
|
+
let serverHandler: string | null = null;
|
|
502
515
|
if (!projectConfig.build?.adapter) {
|
|
503
516
|
try {
|
|
504
|
-
const serverResult = await compileServer(
|
|
517
|
+
const serverResult = await compileServer(route.sourcePath as string);
|
|
505
518
|
if (serverResult) {
|
|
506
519
|
serverHandler = serverResult;
|
|
507
520
|
}
|
|
@@ -520,24 +533,22 @@ async function compilePage(
|
|
|
520
533
|
* @param {Record<string, unknown>} scope
|
|
521
534
|
* @returns {JxHeadEntry[]}
|
|
522
535
|
*/
|
|
523
|
-
function resolveHeadTemplates(headEntries, scope) {
|
|
524
|
-
return headEntries.map((
|
|
536
|
+
function resolveHeadTemplates(headEntries: JxHeadEntry[], scope: Record<string, unknown>) {
|
|
537
|
+
return headEntries.map((entry: JxHeadEntry) => {
|
|
525
538
|
if (!entry || typeof entry !== "object") return entry;
|
|
526
539
|
const resolved = { ...entry };
|
|
527
540
|
if (resolved.attributes) {
|
|
528
541
|
resolved.attributes = { ...resolved.attributes };
|
|
529
542
|
for (const [k, v] of Object.entries(resolved.attributes)) {
|
|
530
543
|
if (typeof v === "string" && isTemplateString(v)) {
|
|
531
|
-
resolved.attributes[k] =
|
|
532
|
-
evaluateStaticTemplate(v, scope) ?? v
|
|
533
|
-
);
|
|
544
|
+
resolved.attributes[k] = evaluateStaticTemplate(v, scope) ?? (v as string | boolean);
|
|
534
545
|
}
|
|
535
546
|
}
|
|
536
547
|
}
|
|
537
548
|
if (typeof resolved.textContent === "string" && isTemplateString(resolved.textContent)) {
|
|
538
|
-
resolved.textContent =
|
|
539
|
-
evaluateStaticTemplate(resolved.textContent, scope) ??
|
|
540
|
-
|
|
549
|
+
resolved.textContent =
|
|
550
|
+
evaluateStaticTemplate(resolved.textContent, scope) ??
|
|
551
|
+
(resolved.textContent as string | undefined);
|
|
541
552
|
}
|
|
542
553
|
return resolved;
|
|
543
554
|
});
|
|
@@ -551,8 +562,8 @@ function resolveHeadTemplates(headEntries, scope) {
|
|
|
551
562
|
* @param {JxHeadEntry[]} headEntries
|
|
552
563
|
* @returns {JxHeadEntry[]}
|
|
553
564
|
*/
|
|
554
|
-
function resolveHeadBareSpecifiers(headEntries) {
|
|
555
|
-
return headEntries.map((
|
|
565
|
+
function resolveHeadBareSpecifiers(headEntries: JxHeadEntry[]) {
|
|
566
|
+
return headEntries.map((entry: JxHeadEntry) => {
|
|
556
567
|
if (!entry || typeof entry !== "object" || !entry.attributes) return entry;
|
|
557
568
|
const resolved = { ...entry, attributes: { ...entry.attributes } };
|
|
558
569
|
for (const key of ["href", "src"]) {
|
|
@@ -571,7 +582,7 @@ function resolveHeadBareSpecifiers(headEntries) {
|
|
|
571
582
|
* @param {string} s
|
|
572
583
|
* @returns {boolean}
|
|
573
584
|
*/
|
|
574
|
-
function isBareSpecifier(s) {
|
|
585
|
+
function isBareSpecifier(s: string) {
|
|
575
586
|
return (
|
|
576
587
|
!s.startsWith("/") &&
|
|
577
588
|
!s.startsWith("./") &&
|
|
@@ -588,9 +599,9 @@ function isBareSpecifier(s) {
|
|
|
588
599
|
* @param {Record<string, unknown>} scope
|
|
589
600
|
* @returns {JxMutableNode}
|
|
590
601
|
*/
|
|
591
|
-
function expandMapTemplate(template, scope) {
|
|
602
|
+
function expandMapTemplate(template: JxMutableNode, scope: Record<string, unknown>) {
|
|
592
603
|
if (!template || typeof template !== "object") return template;
|
|
593
|
-
const node =
|
|
604
|
+
const node = {} as JxMutableNode;
|
|
594
605
|
for (const [k, v] of Object.entries(template)) {
|
|
595
606
|
if (k === "children" && Array.isArray(v)) {
|
|
596
607
|
node.children = v.map((child) => {
|
|
@@ -598,7 +609,7 @@ function expandMapTemplate(template, scope) {
|
|
|
598
609
|
return expandMapTemplate(/** @type {JxMutableNode} */ (child), scope);
|
|
599
610
|
});
|
|
600
611
|
} else if (k === "style" && v && typeof v === "object") {
|
|
601
|
-
const style =
|
|
612
|
+
const style = { ...v } as Record<string, unknown>;
|
|
602
613
|
for (const [sk, sv] of Object.entries(style)) {
|
|
603
614
|
if (typeof sv === "string" && isTemplateString(sv)) {
|
|
604
615
|
style[sk] = evaluateMapTemplate(sv, scope) ?? sv;
|
|
@@ -606,7 +617,7 @@ function expandMapTemplate(template, scope) {
|
|
|
606
617
|
}
|
|
607
618
|
node.style = style;
|
|
608
619
|
} else if (k === "attributes" && v && typeof v === "object") {
|
|
609
|
-
const attrs =
|
|
620
|
+
const attrs = { ...v } as Record<string, unknown>;
|
|
610
621
|
for (const [ak, av] of Object.entries(attrs)) {
|
|
611
622
|
if (typeof av === "string" && isTemplateString(av)) {
|
|
612
623
|
attrs[ak] = evaluateMapTemplate(av, scope) ?? av;
|
|
@@ -630,10 +641,10 @@ function expandMapTemplate(template, scope) {
|
|
|
630
641
|
* @param {Record<string, unknown>} scope
|
|
631
642
|
* @returns {unknown}
|
|
632
643
|
*/
|
|
633
|
-
function evaluateMapTemplate(str, scope) {
|
|
644
|
+
function evaluateMapTemplate(str: string, scope: Record<string, unknown>) {
|
|
634
645
|
try {
|
|
635
|
-
const item =
|
|
636
|
-
const index =
|
|
646
|
+
const item = (scope.$map as Record<string, unknown>)?.item;
|
|
647
|
+
const index = (scope.$map as Record<string, unknown>)?.index;
|
|
637
648
|
const singleExprMatch = str.match(/^\$\{(.+)\}$/s);
|
|
638
649
|
if (singleExprMatch) {
|
|
639
650
|
const fn = new Function("state", "$map", "item", "index", `return (${singleExprMatch[1]})`);
|
|
@@ -653,7 +664,7 @@ function evaluateMapTemplate(str, scope) {
|
|
|
653
664
|
* @param {JxElement | string} node
|
|
654
665
|
* @param {Record<string, unknown>} scope
|
|
655
666
|
*/
|
|
656
|
-
function resolveDocTemplates(node, scope) {
|
|
667
|
+
function resolveDocTemplates(node: JxElement | string, scope: Record<string, unknown>) {
|
|
657
668
|
if (!node || typeof node !== "object") return;
|
|
658
669
|
|
|
659
670
|
if (typeof node.innerHTML === "string" && isTemplateString(node.innerHTML)) {
|
|
@@ -666,16 +677,13 @@ function resolveDocTemplates(node, scope) {
|
|
|
666
677
|
}
|
|
667
678
|
}
|
|
668
679
|
if (typeof node.textContent === "string" && isTemplateString(node.textContent)) {
|
|
669
|
-
node.textContent =
|
|
670
|
-
evaluateStaticTemplate(node.textContent, scope) ?? node.textContent
|
|
671
|
-
);
|
|
680
|
+
node.textContent =
|
|
681
|
+
evaluateStaticTemplate(node.textContent, scope) ?? (node.textContent as string | null);
|
|
672
682
|
}
|
|
673
683
|
if (node.style && typeof node.style === "object") {
|
|
674
684
|
for (const [k, v] of Object.entries(node.style)) {
|
|
675
685
|
if (typeof v === "string" && isTemplateString(v)) {
|
|
676
|
-
node.style[k] =
|
|
677
|
-
evaluateStaticTemplate(v, scope) ?? v
|
|
678
|
-
);
|
|
686
|
+
node.style[k] = evaluateStaticTemplate(v, scope) ?? (v as string | number | JxStyle);
|
|
679
687
|
}
|
|
680
688
|
}
|
|
681
689
|
}
|
|
@@ -686,41 +694,39 @@ function resolveDocTemplates(node, scope) {
|
|
|
686
694
|
}
|
|
687
695
|
}
|
|
688
696
|
}
|
|
689
|
-
const rawChildren =
|
|
697
|
+
const rawChildren = node.children as unknown;
|
|
690
698
|
if (
|
|
691
699
|
rawChildren &&
|
|
692
700
|
typeof rawChildren === "object" &&
|
|
693
701
|
!Array.isArray(rawChildren) &&
|
|
694
|
-
|
|
702
|
+
(rawChildren as JxMutableNode).$prototype === "Array"
|
|
695
703
|
) {
|
|
696
|
-
const arrayDef =
|
|
704
|
+
const arrayDef = rawChildren as JxMutableNode;
|
|
697
705
|
const itemsSrc = arrayDef.items;
|
|
698
706
|
let items = null;
|
|
699
|
-
if (itemsSrc && typeof itemsSrc === "object" &&
|
|
700
|
-
const ref =
|
|
707
|
+
if (itemsSrc && typeof itemsSrc === "object" && (itemsSrc as JxMutableNode).$ref) {
|
|
708
|
+
const ref = (itemsSrc as JxMutableNode).$ref as string;
|
|
701
709
|
items = resolveRefValue(ref, scope);
|
|
702
710
|
} else if (Array.isArray(itemsSrc)) {
|
|
703
711
|
items = itemsSrc;
|
|
704
712
|
}
|
|
705
713
|
if (Array.isArray(items) && arrayDef.map) {
|
|
706
|
-
node.children =
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
})
|
|
716
|
-
);
|
|
714
|
+
node.children = items.map((item, index) => {
|
|
715
|
+
const childScope = Object.create(scope);
|
|
716
|
+
childScope.$map = { item, index };
|
|
717
|
+
childScope["$map/item"] = item;
|
|
718
|
+
childScope["$map/index"] = index;
|
|
719
|
+
const expanded = expandMapTemplate(arrayDef.map, childScope);
|
|
720
|
+
resolveDocTemplates(expanded as JxElement, childScope);
|
|
721
|
+
return expanded;
|
|
722
|
+
}) as (string | JxElement)[];
|
|
717
723
|
return;
|
|
718
724
|
}
|
|
719
725
|
}
|
|
720
726
|
if (typeof rawChildren === "string" && isTemplateString(rawChildren)) {
|
|
721
727
|
const resolved = evaluateStaticTemplate(rawChildren, scope);
|
|
722
728
|
if (Array.isArray(resolved)) {
|
|
723
|
-
const resolvedNodes =
|
|
729
|
+
const resolvedNodes = resolved as (JxElement | string)[];
|
|
724
730
|
node.children = resolvedNodes;
|
|
725
731
|
for (const child of resolvedNodes) {
|
|
726
732
|
resolveDocTemplates(child, scope);
|
|
@@ -733,7 +739,7 @@ function resolveDocTemplates(node, scope) {
|
|
|
733
739
|
if (typeof child === "string" && isTemplateString(child)) {
|
|
734
740
|
const resolved = evaluateStaticTemplate(child, scope);
|
|
735
741
|
if (Array.isArray(resolved)) {
|
|
736
|
-
const resolvedNodes =
|
|
742
|
+
const resolvedNodes = resolved as (JxElement | string)[];
|
|
737
743
|
node.children.splice(i, 1, ...resolvedNodes);
|
|
738
744
|
for (const spliced of resolvedNodes) {
|
|
739
745
|
resolveDocTemplates(spliced, scope);
|
|
@@ -755,7 +761,7 @@ function resolveDocTemplates(node, scope) {
|
|
|
755
761
|
* @param {JxElement | string} node
|
|
756
762
|
* @param {Map<string, JxElement>} componentDefs
|
|
757
763
|
*/
|
|
758
|
-
function expandComponents(node, componentDefs) {
|
|
764
|
+
function expandComponents(node: JxElement | string, componentDefs: Map<string, JxElement>) {
|
|
759
765
|
if (!node || typeof node !== "object") return;
|
|
760
766
|
if (Array.isArray(node)) {
|
|
761
767
|
node.forEach((n) => expandComponents(n, componentDefs));
|
|
@@ -769,13 +775,11 @@ function expandComponents(node, componentDefs) {
|
|
|
769
775
|
}
|
|
770
776
|
}
|
|
771
777
|
|
|
772
|
-
const def = componentDefs.get(
|
|
778
|
+
const def = componentDefs.get(node.tagName as string);
|
|
773
779
|
if (def) {
|
|
774
780
|
const slotContent =
|
|
775
781
|
Array.isArray(node.children) && node.children.length > 0
|
|
776
|
-
? node.children
|
|
777
|
-
.map((/** @type {JxElement | string} */ c) => renderStaticNode(c, {}, null))
|
|
778
|
-
.join("\n")
|
|
782
|
+
? node.children.map((c: JxElement | string) => renderStaticNode(c, {}, null)).join("\n")
|
|
779
783
|
: null;
|
|
780
784
|
|
|
781
785
|
const innerHTML = preRenderComponentHtml(def, node.$props || null, slotContent);
|
|
@@ -786,15 +790,13 @@ function expandComponents(node, componentDefs) {
|
|
|
786
790
|
|
|
787
791
|
// Resolve template-string host styles with props (per-instance values like background-image)
|
|
788
792
|
if (def.style && node.$props) {
|
|
789
|
-
|
|
790
|
-
let stateDefs = { ...def.state };
|
|
793
|
+
let stateDefs: Record<string, JxStateDefinition> = { ...def.state };
|
|
791
794
|
for (const [key, value] of Object.entries(node.$props)) {
|
|
792
|
-
if (key in stateDefs) stateDefs[key] =
|
|
793
|
-
else stateDefs[key] =
|
|
795
|
+
if (key in stateDefs) stateDefs[key] = value as JxStateDefinition;
|
|
796
|
+
else stateDefs[key] = value as JxStateDefinition;
|
|
794
797
|
}
|
|
795
798
|
const scope = buildInitialScope(stateDefs, null);
|
|
796
|
-
|
|
797
|
-
const resolvedStyle = {};
|
|
799
|
+
const resolvedStyle: Record<string, unknown> = {};
|
|
798
800
|
for (const [prop, value] of Object.entries(def.style)) {
|
|
799
801
|
if (typeof value === "string" && isTemplateString(value)) {
|
|
800
802
|
const resolved = resolveStaticValue(value, scope);
|
|
@@ -802,7 +804,7 @@ function expandComponents(node, componentDefs) {
|
|
|
802
804
|
}
|
|
803
805
|
}
|
|
804
806
|
if (Object.keys(resolvedStyle).length > 0) {
|
|
805
|
-
node.style =
|
|
807
|
+
node.style = { ...node.style, ...resolvedStyle } as JxStyle;
|
|
806
808
|
}
|
|
807
809
|
}
|
|
808
810
|
|
|
@@ -824,28 +826,28 @@ function expandComponents(node, componentDefs) {
|
|
|
824
826
|
* @returns {string}
|
|
825
827
|
*/
|
|
826
828
|
function injectComponentScripts(
|
|
827
|
-
html,
|
|
828
|
-
allComponentTags,
|
|
829
|
-
cssMap = new Map(),
|
|
830
|
-
staticTags = new Set(),
|
|
829
|
+
html: string,
|
|
830
|
+
allComponentTags: string[],
|
|
831
|
+
cssMap: Map<string, string> = new Map(),
|
|
832
|
+
staticTags: Set<string> = new Set(),
|
|
831
833
|
) {
|
|
832
834
|
// Find which components are actually referenced in this page
|
|
833
835
|
const usedTags = allComponentTags.filter(
|
|
834
|
-
(
|
|
836
|
+
(tag: string) => html.includes(`<${tag}`), // matches <tag> and <tag ...>
|
|
835
837
|
);
|
|
836
838
|
if (usedTags.length === 0) return html;
|
|
837
839
|
|
|
838
840
|
// Inject CSS links in <head> for ALL components that have CSS sidecars
|
|
839
841
|
const cssLinks = usedTags
|
|
840
|
-
.filter((
|
|
841
|
-
.map((
|
|
842
|
+
.filter((tag: string) => cssMap.has(tag))
|
|
843
|
+
.map((tag: string) => `<link rel="stylesheet" href="/components/${tag}.css">`)
|
|
842
844
|
.join("\n ");
|
|
843
845
|
if (cssLinks) {
|
|
844
846
|
html = html.replace("</head>", ` ${cssLinks}\n</head>`);
|
|
845
847
|
}
|
|
846
848
|
|
|
847
849
|
// Only inject JS for components that have non-static instances
|
|
848
|
-
const jsTags = usedTags.filter((
|
|
850
|
+
const jsTags = usedTags.filter((tag: string) => !staticTags.has(tag));
|
|
849
851
|
if (jsTags.length === 0) return html;
|
|
850
852
|
|
|
851
853
|
// Build import map (needed for @vue/reactivity and lit-html)
|
|
@@ -859,9 +861,7 @@ function injectComponentScripts(
|
|
|
859
861
|
</script>`;
|
|
860
862
|
|
|
861
863
|
const moduleScripts = jsTags
|
|
862
|
-
.map(
|
|
863
|
-
(/** @type {string} */ tag) => `<script type="module" src="/components/${tag}.js"></script>`,
|
|
864
|
-
)
|
|
864
|
+
.map((tag: string) => `<script type="module" src="/components/${tag}.js"></script>`)
|
|
865
865
|
.join("\n ");
|
|
866
866
|
|
|
867
867
|
// Check if an import map already exists (from islands etc.)
|
|
@@ -880,11 +880,9 @@ function injectComponentScripts(
|
|
|
880
880
|
* "@shoelace-style/shoelace/components/button/button.js"
|
|
881
881
|
* @returns {string}
|
|
882
882
|
*/
|
|
883
|
-
function injectNpmElementScripts(html, npmElements) {
|
|
883
|
+
function injectNpmElementScripts(html: string, npmElements: string[]) {
|
|
884
884
|
const scripts = npmElements
|
|
885
|
-
.map(
|
|
886
|
-
(/** @type {string} */ spec) => `<script type="module" src="/node_modules/${spec}"></script>`,
|
|
887
|
-
)
|
|
885
|
+
.map((spec: string) => `<script type="module" src="/node_modules/${spec}"></script>`)
|
|
888
886
|
.join("\n ");
|
|
889
887
|
|
|
890
888
|
return html.replace("</body>", ` ${scripts}\n</body>`);
|
|
@@ -898,7 +896,7 @@ function injectNpmElementScripts(html, npmElements) {
|
|
|
898
896
|
* @param {string} lang
|
|
899
897
|
* @returns {string}
|
|
900
898
|
*/
|
|
901
|
-
function injectHead(html, headEntries, lang) {
|
|
899
|
+
function injectHead(html: string, headEntries: JxHeadEntry[], lang: string) {
|
|
902
900
|
const headHtml = renderHead(headEntries);
|
|
903
901
|
|
|
904
902
|
// Replace the existing <head>...</head> block, preserving compiler-generated <style> and <script> blocks
|
|
@@ -916,7 +914,7 @@ function injectHead(html, headEntries, lang) {
|
|
|
916
914
|
}
|
|
917
915
|
|
|
918
916
|
// Set the lang attribute on <html>
|
|
919
|
-
html = html.replace(/<html\s[^>]*>/i, (
|
|
917
|
+
html = html.replace(/<html\s[^>]*>/i, (match: string) => {
|
|
920
918
|
if (/lang=/.test(match)) {
|
|
921
919
|
return match.replace(/lang="[^"]*"/, `lang="${lang}"`);
|
|
922
920
|
}
|
|
@@ -937,7 +935,7 @@ function injectHead(html, headEntries, lang) {
|
|
|
937
935
|
* @param {string} trailingSlash
|
|
938
936
|
* @returns {string}
|
|
939
937
|
*/
|
|
940
|
-
function routeToOutputPath(urlPattern, outDir, trailingSlash) {
|
|
938
|
+
function routeToOutputPath(urlPattern: string, outDir: string, trailingSlash: string) {
|
|
941
939
|
if (urlPattern === "/") {
|
|
942
940
|
return join(outDir, "index.html");
|
|
943
941
|
}
|
|
@@ -960,10 +958,12 @@ function routeToOutputPath(urlPattern, outDir, trailingSlash) {
|
|
|
960
958
|
* @param {string} outDir
|
|
961
959
|
* @returns {number} Number of files written
|
|
962
960
|
*/
|
|
963
|
-
function generateRedirects(
|
|
961
|
+
function generateRedirects(
|
|
962
|
+
redirects: Record<string, string | { destination: string; status?: number }>,
|
|
963
|
+
outDir: string,
|
|
964
|
+
) {
|
|
964
965
|
let count = 0;
|
|
965
|
-
|
|
966
|
-
const redirectLines = [];
|
|
966
|
+
const redirectLines: string[] = [];
|
|
967
967
|
|
|
968
968
|
for (const [source, target] of Object.entries(redirects)) {
|
|
969
969
|
const dest = typeof target === "object" ? target.destination : target;
|
|
@@ -1008,7 +1008,7 @@ function generateRedirects(redirects, outDir) {
|
|
|
1008
1008
|
* @param {string} str
|
|
1009
1009
|
* @returns {string}
|
|
1010
1010
|
*/
|
|
1011
|
-
function escapeHtml(str) {
|
|
1011
|
+
function escapeHtml(str: string) {
|
|
1012
1012
|
return String(str).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1013
1013
|
}
|
|
1014
1014
|
|
|
@@ -1016,6 +1016,6 @@ function escapeHtml(str) {
|
|
|
1016
1016
|
* @param {string} str
|
|
1017
1017
|
* @returns {string}
|
|
1018
1018
|
*/
|
|
1019
|
-
function escapeAttr(str) {
|
|
1019
|
+
function escapeAttr(str: string) {
|
|
1020
1020
|
return String(str).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
1021
1021
|
}
|
|
@@ -50,7 +50,7 @@ const DEFAULTS = {
|
|
|
50
50
|
* @returns {{ config: Record<string, any>; configPath: string; projectRoot: string }}
|
|
51
51
|
* @throws {Error} If project.json is missing or invalid JSON
|
|
52
52
|
*/
|
|
53
|
-
export function loadProjectConfig(projectRoot) {
|
|
53
|
+
export function loadProjectConfig(projectRoot: string) {
|
|
54
54
|
const configPath = resolve(projectRoot, "project.json");
|
|
55
55
|
|
|
56
56
|
if (!existsSync(configPath)) {
|
|
@@ -61,7 +61,7 @@ export function loadProjectConfig(projectRoot) {
|
|
|
61
61
|
try {
|
|
62
62
|
raw = JSON.parse(readFileSync(configPath, "utf8"));
|
|
63
63
|
} catch (e) {
|
|
64
|
-
const err =
|
|
64
|
+
const err = e as Error;
|
|
65
65
|
throw new Error(`Invalid JSON in ${configPath}: ${err.message}`);
|
|
66
66
|
}
|
|
67
67
|
|