@jxsuite/compiler 0.23.2 → 0.25.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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Site-build.js — Multi-page build orchestrator
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.js";
24
- import { discoverPages, expandDynamicRoutes } from "./pages-discovery.js";
25
- import { resolveLayout } from "./layout-resolver.js";
26
- import { mergeHead, renderHead } from "./head-merger.js";
27
- import { injectContext } from "./context-injection.js";
28
- import { compile, compileServer, compileSiteServer, compilePagesFunctions } from "../compiler.js";
29
- import { compileElement } from "../targets/compile-element.js";
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.js";
44
- import { loadContentTypes, loadContentConfig, resolveContentTypeRefs } from "./content-loader.js";
45
- import { resolvePrototypes } from "./prototype-resolver.js";
46
- import { compileMarkdown } from "../targets/compile-markdown.js";
47
- import { transformImageNodes } from "./image-transform.js";
48
- import { loadCache, saveCache } from "./image-cache.js";
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(projectRoot, options = {}) {
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
- /** @type {string[]} */
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
- /** @type {string[]} */
122
- const compiledComponentTags = [];
123
- /** @type {Map<string, string>} */
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
- (/** @type {string} */ f) => f.endsWith(".json") || f.endsWith(".md"),
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 = /** @type {Error} */ (e);
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
- /** @type {Set<string>} */
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 = /** @type {Error} */ (e);
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 = /** @type {Error} */ (e);
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, /** @type {string} */ (src));
345
- const destPath = resolve(outDir, /** @type {string} */ (dest));
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 (/** @type {string} */ (route.sourcePath).endsWith(".md")) {
399
+ if ((route.sourcePath as string).endsWith(".md")) {
391
400
  const { transpileJxMarkdown } = await import("@jxsuite/parser/transpile");
392
- pageDoc = transpileJxMarkdown(readFileSync(/** @type {string} */ (route.sourcePath), "utf8"));
401
+ pageDoc = transpileJxMarkdown(readFileSync(route.sourcePath as string, "utf8"));
393
402
  } else {
394
- pageDoc = JSON.parse(readFileSync(/** @type {string} */ (route.sourcePath), "utf8"));
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 = /** @type {JxHeadEntry[]} */ (pageDoc.$head ?? layoutDoc._pageHead ?? []);
410
+ const pageHead = pageDoc.$head ?? layoutDoc._pageHead ?? ([] as JxHeadEntry[]);
402
411
  const layoutHead = layoutDoc.$head ?? [];
403
- const pageTitle = /** @type {string | null} */ (pageDoc.title ?? layoutDoc._pageTitle ?? null);
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 = /** @type {string} */ (evaluateStaticTemplate(title, scope) ?? 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
- /** @type {JxMutableNode} */ (def).timing === "compiler"
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(layoutDoc, projectConfig.images, projectRoot, outDir, imageCache);
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
- (/** @type {JxElement | string} */ e) =>
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
- /** @type {string | null} */
501
- let serverHandler = null;
514
+ let serverHandler: string | null = null;
502
515
  if (!projectConfig.build?.adapter) {
503
516
  try {
504
- const serverResult = await compileServer(/** @type {string} */ (route.sourcePath));
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((/** @type {JxHeadEntry} */ entry) => {
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] = /** @type {string | boolean} */ (
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 = /** @type {string | undefined} */ (
539
- evaluateStaticTemplate(resolved.textContent, scope) ?? resolved.textContent
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((/** @type {JxHeadEntry} */ entry) => {
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 = /** @type {JxMutableNode} */ ({});
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 = /** @type {Record<string, unknown>} */ ({ ...v });
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 = /** @type {Record<string, unknown>} */ ({ ...v });
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 = /** @type {Record<string, unknown>} */ (scope.$map)?.item;
636
- const index = /** @type {Record<string, unknown>} */ (scope.$map)?.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 = /** @type {string | null} */ (
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] = /** @type {string | number | JxStyle} */ (
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 = /** @type {unknown} */ (node.children);
697
+ const rawChildren = node.children as unknown;
690
698
  if (
691
699
  rawChildren &&
692
700
  typeof rawChildren === "object" &&
693
701
  !Array.isArray(rawChildren) &&
694
- /** @type {JxMutableNode} */ (rawChildren).$prototype === "Array"
702
+ (rawChildren as JxMutableNode).$prototype === "Array"
695
703
  ) {
696
- const arrayDef = /** @type {JxMutableNode} */ (rawChildren);
704
+ const arrayDef = rawChildren as JxMutableNode;
697
705
  const itemsSrc = arrayDef.items;
698
706
  let items = null;
699
- if (itemsSrc && typeof itemsSrc === "object" && /** @type {JxMutableNode} */ (itemsSrc).$ref) {
700
- const ref = /** @type {string} */ (/** @type {JxMutableNode} */ (itemsSrc).$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 = /** @type {(string | JxElement)[]} */ (
707
- items.map((item, index) => {
708
- const childScope = Object.create(scope);
709
- childScope.$map = { item, index };
710
- childScope["$map/item"] = item;
711
- childScope["$map/index"] = index;
712
- const expanded = expandMapTemplate(arrayDef.map, childScope);
713
- resolveDocTemplates(/** @type {JxElement} */ (expanded), childScope);
714
- return expanded;
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 = /** @type {(JxElement | string)[]} */ (resolved);
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 = /** @type {(JxElement | string)[]} */ (resolved);
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(/** @type {string} */ (node.tagName));
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
- /** @type {Record<string, JxStateDefinition>} */
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] = /** @type {JxStateDefinition} */ (value);
793
- else stateDefs[key] = /** @type {JxStateDefinition} */ (value);
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
- /** @type {Record<string, unknown>} */
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 = /** @type {JxStyle} */ ({ ...node.style, ...resolvedStyle });
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
- (/** @type {string} */ tag) => html.includes(`<${tag}`), // matches <tag> and <tag ...>
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((/** @type {string} */ tag) => cssMap.has(tag))
841
- .map((/** @type {string} */ tag) => `<link rel="stylesheet" href="/components/${tag}.css">`)
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((/** @type {string} */ tag) => !staticTags.has(tag));
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, (/** @type {string} */ match) => {
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(redirects, outDir) {
961
+ function generateRedirects(
962
+ redirects: Record<string, string | { destination: string; status?: number }>,
963
+ outDir: string,
964
+ ) {
964
965
  let count = 0;
965
- /** @type {string[]} */
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
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 = /** @type {Error} */ (e);
64
+ const err = e as Error;
65
65
  throw new Error(`Invalid JSON in ${configPath}: ${err.message}`);
66
66
  }
67
67