@jxsuite/compiler 0.23.1 → 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} +59 -52
- 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} +134 -130
- 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,19 +609,21 @@ 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
|
-
|
|
602
|
-
for (const [sk, sv] of Object.entries(
|
|
612
|
+
const style = { ...v } as Record<string, unknown>;
|
|
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;
|
|
605
616
|
}
|
|
606
617
|
}
|
|
618
|
+
node.style = style;
|
|
607
619
|
} else if (k === "attributes" && v && typeof v === "object") {
|
|
608
|
-
|
|
609
|
-
for (const [ak, av] of Object.entries(
|
|
620
|
+
const attrs = { ...v } as Record<string, unknown>;
|
|
621
|
+
for (const [ak, av] of Object.entries(attrs)) {
|
|
610
622
|
if (typeof av === "string" && isTemplateString(av)) {
|
|
611
|
-
|
|
623
|
+
attrs[ak] = evaluateMapTemplate(av, scope) ?? av;
|
|
612
624
|
}
|
|
613
625
|
}
|
|
626
|
+
node.attributes = attrs;
|
|
614
627
|
} else if (typeof v === "string" && isTemplateString(v)) {
|
|
615
628
|
node[k] = evaluateMapTemplate(v, scope) ?? v;
|
|
616
629
|
} else {
|
|
@@ -628,10 +641,10 @@ function expandMapTemplate(template, scope) {
|
|
|
628
641
|
* @param {Record<string, unknown>} scope
|
|
629
642
|
* @returns {unknown}
|
|
630
643
|
*/
|
|
631
|
-
function evaluateMapTemplate(str, scope) {
|
|
644
|
+
function evaluateMapTemplate(str: string, scope: Record<string, unknown>) {
|
|
632
645
|
try {
|
|
633
|
-
const item =
|
|
634
|
-
const index =
|
|
646
|
+
const item = (scope.$map as Record<string, unknown>)?.item;
|
|
647
|
+
const index = (scope.$map as Record<string, unknown>)?.index;
|
|
635
648
|
const singleExprMatch = str.match(/^\$\{(.+)\}$/s);
|
|
636
649
|
if (singleExprMatch) {
|
|
637
650
|
const fn = new Function("state", "$map", "item", "index", `return (${singleExprMatch[1]})`);
|
|
@@ -651,7 +664,7 @@ function evaluateMapTemplate(str, scope) {
|
|
|
651
664
|
* @param {JxElement | string} node
|
|
652
665
|
* @param {Record<string, unknown>} scope
|
|
653
666
|
*/
|
|
654
|
-
function resolveDocTemplates(node, scope) {
|
|
667
|
+
function resolveDocTemplates(node: JxElement | string, scope: Record<string, unknown>) {
|
|
655
668
|
if (!node || typeof node !== "object") return;
|
|
656
669
|
|
|
657
670
|
if (typeof node.innerHTML === "string" && isTemplateString(node.innerHTML)) {
|
|
@@ -664,16 +677,13 @@ function resolveDocTemplates(node, scope) {
|
|
|
664
677
|
}
|
|
665
678
|
}
|
|
666
679
|
if (typeof node.textContent === "string" && isTemplateString(node.textContent)) {
|
|
667
|
-
node.textContent =
|
|
668
|
-
evaluateStaticTemplate(node.textContent, scope) ?? node.textContent
|
|
669
|
-
);
|
|
680
|
+
node.textContent =
|
|
681
|
+
evaluateStaticTemplate(node.textContent, scope) ?? (node.textContent as string | null);
|
|
670
682
|
}
|
|
671
683
|
if (node.style && typeof node.style === "object") {
|
|
672
684
|
for (const [k, v] of Object.entries(node.style)) {
|
|
673
685
|
if (typeof v === "string" && isTemplateString(v)) {
|
|
674
|
-
node.style[k] =
|
|
675
|
-
evaluateStaticTemplate(v, scope) ?? v
|
|
676
|
-
);
|
|
686
|
+
node.style[k] = evaluateStaticTemplate(v, scope) ?? (v as string | number | JxStyle);
|
|
677
687
|
}
|
|
678
688
|
}
|
|
679
689
|
}
|
|
@@ -684,18 +694,18 @@ function resolveDocTemplates(node, scope) {
|
|
|
684
694
|
}
|
|
685
695
|
}
|
|
686
696
|
}
|
|
687
|
-
const rawChildren =
|
|
697
|
+
const rawChildren = node.children as unknown;
|
|
688
698
|
if (
|
|
689
699
|
rawChildren &&
|
|
690
700
|
typeof rawChildren === "object" &&
|
|
691
701
|
!Array.isArray(rawChildren) &&
|
|
692
|
-
|
|
702
|
+
(rawChildren as JxMutableNode).$prototype === "Array"
|
|
693
703
|
) {
|
|
694
|
-
const arrayDef =
|
|
704
|
+
const arrayDef = rawChildren as JxMutableNode;
|
|
695
705
|
const itemsSrc = arrayDef.items;
|
|
696
706
|
let items = null;
|
|
697
|
-
if (itemsSrc && typeof itemsSrc === "object" &&
|
|
698
|
-
const ref =
|
|
707
|
+
if (itemsSrc && typeof itemsSrc === "object" && (itemsSrc as JxMutableNode).$ref) {
|
|
708
|
+
const ref = (itemsSrc as JxMutableNode).$ref as string;
|
|
699
709
|
items = resolveRefValue(ref, scope);
|
|
700
710
|
} else if (Array.isArray(itemsSrc)) {
|
|
701
711
|
items = itemsSrc;
|
|
@@ -707,16 +717,16 @@ function resolveDocTemplates(node, scope) {
|
|
|
707
717
|
childScope["$map/item"] = item;
|
|
708
718
|
childScope["$map/index"] = index;
|
|
709
719
|
const expanded = expandMapTemplate(arrayDef.map, childScope);
|
|
710
|
-
resolveDocTemplates(expanded, childScope);
|
|
720
|
+
resolveDocTemplates(expanded as JxElement, childScope);
|
|
711
721
|
return expanded;
|
|
712
|
-
});
|
|
722
|
+
}) as (string | JxElement)[];
|
|
713
723
|
return;
|
|
714
724
|
}
|
|
715
725
|
}
|
|
716
726
|
if (typeof rawChildren === "string" && isTemplateString(rawChildren)) {
|
|
717
727
|
const resolved = evaluateStaticTemplate(rawChildren, scope);
|
|
718
728
|
if (Array.isArray(resolved)) {
|
|
719
|
-
const resolvedNodes =
|
|
729
|
+
const resolvedNodes = resolved as (JxElement | string)[];
|
|
720
730
|
node.children = resolvedNodes;
|
|
721
731
|
for (const child of resolvedNodes) {
|
|
722
732
|
resolveDocTemplates(child, scope);
|
|
@@ -729,7 +739,7 @@ function resolveDocTemplates(node, scope) {
|
|
|
729
739
|
if (typeof child === "string" && isTemplateString(child)) {
|
|
730
740
|
const resolved = evaluateStaticTemplate(child, scope);
|
|
731
741
|
if (Array.isArray(resolved)) {
|
|
732
|
-
const resolvedNodes =
|
|
742
|
+
const resolvedNodes = resolved as (JxElement | string)[];
|
|
733
743
|
node.children.splice(i, 1, ...resolvedNodes);
|
|
734
744
|
for (const spliced of resolvedNodes) {
|
|
735
745
|
resolveDocTemplates(spliced, scope);
|
|
@@ -751,7 +761,7 @@ function resolveDocTemplates(node, scope) {
|
|
|
751
761
|
* @param {JxElement | string} node
|
|
752
762
|
* @param {Map<string, JxElement>} componentDefs
|
|
753
763
|
*/
|
|
754
|
-
function expandComponents(node, componentDefs) {
|
|
764
|
+
function expandComponents(node: JxElement | string, componentDefs: Map<string, JxElement>) {
|
|
755
765
|
if (!node || typeof node !== "object") return;
|
|
756
766
|
if (Array.isArray(node)) {
|
|
757
767
|
node.forEach((n) => expandComponents(n, componentDefs));
|
|
@@ -765,13 +775,11 @@ function expandComponents(node, componentDefs) {
|
|
|
765
775
|
}
|
|
766
776
|
}
|
|
767
777
|
|
|
768
|
-
const def = componentDefs.get(
|
|
778
|
+
const def = componentDefs.get(node.tagName as string);
|
|
769
779
|
if (def) {
|
|
770
780
|
const slotContent =
|
|
771
781
|
Array.isArray(node.children) && node.children.length > 0
|
|
772
|
-
? node.children
|
|
773
|
-
.map((/** @type {JxElement | string} */ c) => renderStaticNode(c, {}, null))
|
|
774
|
-
.join("\n")
|
|
782
|
+
? node.children.map((c: JxElement | string) => renderStaticNode(c, {}, null)).join("\n")
|
|
775
783
|
: null;
|
|
776
784
|
|
|
777
785
|
const innerHTML = preRenderComponentHtml(def, node.$props || null, slotContent);
|
|
@@ -782,15 +790,13 @@ function expandComponents(node, componentDefs) {
|
|
|
782
790
|
|
|
783
791
|
// Resolve template-string host styles with props (per-instance values like background-image)
|
|
784
792
|
if (def.style && node.$props) {
|
|
785
|
-
|
|
786
|
-
let stateDefs = { ...def.state };
|
|
793
|
+
let stateDefs: Record<string, JxStateDefinition> = { ...def.state };
|
|
787
794
|
for (const [key, value] of Object.entries(node.$props)) {
|
|
788
|
-
if (key in stateDefs) stateDefs[key] =
|
|
789
|
-
else stateDefs[key] =
|
|
795
|
+
if (key in stateDefs) stateDefs[key] = value as JxStateDefinition;
|
|
796
|
+
else stateDefs[key] = value as JxStateDefinition;
|
|
790
797
|
}
|
|
791
798
|
const scope = buildInitialScope(stateDefs, null);
|
|
792
|
-
|
|
793
|
-
const resolvedStyle = {};
|
|
799
|
+
const resolvedStyle: Record<string, unknown> = {};
|
|
794
800
|
for (const [prop, value] of Object.entries(def.style)) {
|
|
795
801
|
if (typeof value === "string" && isTemplateString(value)) {
|
|
796
802
|
const resolved = resolveStaticValue(value, scope);
|
|
@@ -798,7 +804,7 @@ function expandComponents(node, componentDefs) {
|
|
|
798
804
|
}
|
|
799
805
|
}
|
|
800
806
|
if (Object.keys(resolvedStyle).length > 0) {
|
|
801
|
-
node.style =
|
|
807
|
+
node.style = { ...node.style, ...resolvedStyle } as JxStyle;
|
|
802
808
|
}
|
|
803
809
|
}
|
|
804
810
|
|
|
@@ -820,28 +826,28 @@ function expandComponents(node, componentDefs) {
|
|
|
820
826
|
* @returns {string}
|
|
821
827
|
*/
|
|
822
828
|
function injectComponentScripts(
|
|
823
|
-
html,
|
|
824
|
-
allComponentTags,
|
|
825
|
-
cssMap = new Map(),
|
|
826
|
-
staticTags = new Set(),
|
|
829
|
+
html: string,
|
|
830
|
+
allComponentTags: string[],
|
|
831
|
+
cssMap: Map<string, string> = new Map(),
|
|
832
|
+
staticTags: Set<string> = new Set(),
|
|
827
833
|
) {
|
|
828
834
|
// Find which components are actually referenced in this page
|
|
829
835
|
const usedTags = allComponentTags.filter(
|
|
830
|
-
(
|
|
836
|
+
(tag: string) => html.includes(`<${tag}`), // matches <tag> and <tag ...>
|
|
831
837
|
);
|
|
832
838
|
if (usedTags.length === 0) return html;
|
|
833
839
|
|
|
834
840
|
// Inject CSS links in <head> for ALL components that have CSS sidecars
|
|
835
841
|
const cssLinks = usedTags
|
|
836
|
-
.filter((
|
|
837
|
-
.map((
|
|
842
|
+
.filter((tag: string) => cssMap.has(tag))
|
|
843
|
+
.map((tag: string) => `<link rel="stylesheet" href="/components/${tag}.css">`)
|
|
838
844
|
.join("\n ");
|
|
839
845
|
if (cssLinks) {
|
|
840
846
|
html = html.replace("</head>", ` ${cssLinks}\n</head>`);
|
|
841
847
|
}
|
|
842
848
|
|
|
843
849
|
// Only inject JS for components that have non-static instances
|
|
844
|
-
const jsTags = usedTags.filter((
|
|
850
|
+
const jsTags = usedTags.filter((tag: string) => !staticTags.has(tag));
|
|
845
851
|
if (jsTags.length === 0) return html;
|
|
846
852
|
|
|
847
853
|
// Build import map (needed for @vue/reactivity and lit-html)
|
|
@@ -855,9 +861,7 @@ function injectComponentScripts(
|
|
|
855
861
|
</script>`;
|
|
856
862
|
|
|
857
863
|
const moduleScripts = jsTags
|
|
858
|
-
.map(
|
|
859
|
-
(/** @type {string} */ tag) => `<script type="module" src="/components/${tag}.js"></script>`,
|
|
860
|
-
)
|
|
864
|
+
.map((tag: string) => `<script type="module" src="/components/${tag}.js"></script>`)
|
|
861
865
|
.join("\n ");
|
|
862
866
|
|
|
863
867
|
// Check if an import map already exists (from islands etc.)
|
|
@@ -876,11 +880,9 @@ function injectComponentScripts(
|
|
|
876
880
|
* "@shoelace-style/shoelace/components/button/button.js"
|
|
877
881
|
* @returns {string}
|
|
878
882
|
*/
|
|
879
|
-
function injectNpmElementScripts(html, npmElements) {
|
|
883
|
+
function injectNpmElementScripts(html: string, npmElements: string[]) {
|
|
880
884
|
const scripts = npmElements
|
|
881
|
-
.map(
|
|
882
|
-
(/** @type {string} */ spec) => `<script type="module" src="/node_modules/${spec}"></script>`,
|
|
883
|
-
)
|
|
885
|
+
.map((spec: string) => `<script type="module" src="/node_modules/${spec}"></script>`)
|
|
884
886
|
.join("\n ");
|
|
885
887
|
|
|
886
888
|
return html.replace("</body>", ` ${scripts}\n</body>`);
|
|
@@ -894,7 +896,7 @@ function injectNpmElementScripts(html, npmElements) {
|
|
|
894
896
|
* @param {string} lang
|
|
895
897
|
* @returns {string}
|
|
896
898
|
*/
|
|
897
|
-
function injectHead(html, headEntries, lang) {
|
|
899
|
+
function injectHead(html: string, headEntries: JxHeadEntry[], lang: string) {
|
|
898
900
|
const headHtml = renderHead(headEntries);
|
|
899
901
|
|
|
900
902
|
// Replace the existing <head>...</head> block, preserving compiler-generated <style> and <script> blocks
|
|
@@ -912,7 +914,7 @@ function injectHead(html, headEntries, lang) {
|
|
|
912
914
|
}
|
|
913
915
|
|
|
914
916
|
// Set the lang attribute on <html>
|
|
915
|
-
html = html.replace(/<html\s[^>]*>/i, (
|
|
917
|
+
html = html.replace(/<html\s[^>]*>/i, (match: string) => {
|
|
916
918
|
if (/lang=/.test(match)) {
|
|
917
919
|
return match.replace(/lang="[^"]*"/, `lang="${lang}"`);
|
|
918
920
|
}
|
|
@@ -933,7 +935,7 @@ function injectHead(html, headEntries, lang) {
|
|
|
933
935
|
* @param {string} trailingSlash
|
|
934
936
|
* @returns {string}
|
|
935
937
|
*/
|
|
936
|
-
function routeToOutputPath(urlPattern, outDir, trailingSlash) {
|
|
938
|
+
function routeToOutputPath(urlPattern: string, outDir: string, trailingSlash: string) {
|
|
937
939
|
if (urlPattern === "/") {
|
|
938
940
|
return join(outDir, "index.html");
|
|
939
941
|
}
|
|
@@ -956,10 +958,12 @@ function routeToOutputPath(urlPattern, outDir, trailingSlash) {
|
|
|
956
958
|
* @param {string} outDir
|
|
957
959
|
* @returns {number} Number of files written
|
|
958
960
|
*/
|
|
959
|
-
function generateRedirects(
|
|
961
|
+
function generateRedirects(
|
|
962
|
+
redirects: Record<string, string | { destination: string; status?: number }>,
|
|
963
|
+
outDir: string,
|
|
964
|
+
) {
|
|
960
965
|
let count = 0;
|
|
961
|
-
|
|
962
|
-
const redirectLines = [];
|
|
966
|
+
const redirectLines: string[] = [];
|
|
963
967
|
|
|
964
968
|
for (const [source, target] of Object.entries(redirects)) {
|
|
965
969
|
const dest = typeof target === "object" ? target.destination : target;
|
|
@@ -1004,7 +1008,7 @@ function generateRedirects(redirects, outDir) {
|
|
|
1004
1008
|
* @param {string} str
|
|
1005
1009
|
* @returns {string}
|
|
1006
1010
|
*/
|
|
1007
|
-
function escapeHtml(str) {
|
|
1011
|
+
function escapeHtml(str: string) {
|
|
1008
1012
|
return String(str).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1009
1013
|
}
|
|
1010
1014
|
|
|
@@ -1012,6 +1016,6 @@ function escapeHtml(str) {
|
|
|
1012
1016
|
* @param {string} str
|
|
1013
1017
|
* @returns {string}
|
|
1014
1018
|
*/
|
|
1015
|
-
function escapeAttr(str) {
|
|
1019
|
+
function escapeAttr(str: string) {
|
|
1016
1020
|
return String(str).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
1017
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
|
|