@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.
@@ -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,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
- node.style = { ...v };
602
- for (const [sk, sv] of Object.entries(node.style)) {
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
- node.style[sk] = evaluateMapTemplate(sv, scope) ?? sv;
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
- node.attributes = { ...v };
609
- for (const [ak, av] of Object.entries(node.attributes)) {
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
- node.attributes[ak] = evaluateMapTemplate(av, scope) ?? av;
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 = /** @type {Record<string, unknown>} */ (scope.$map)?.item;
634
- 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;
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 = /** @type {string | null} */ (
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] = /** @type {string | number | JxStyle} */ (
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 = /** @type {unknown} */ (node.children);
697
+ const rawChildren = node.children as unknown;
688
698
  if (
689
699
  rawChildren &&
690
700
  typeof rawChildren === "object" &&
691
701
  !Array.isArray(rawChildren) &&
692
- /** @type {JxMutableNode} */ (rawChildren).$prototype === "Array"
702
+ (rawChildren as JxMutableNode).$prototype === "Array"
693
703
  ) {
694
- const arrayDef = /** @type {JxMutableNode} */ (rawChildren);
704
+ const arrayDef = rawChildren as JxMutableNode;
695
705
  const itemsSrc = arrayDef.items;
696
706
  let items = null;
697
- if (itemsSrc && typeof itemsSrc === "object" && /** @type {JxMutableNode} */ (itemsSrc).$ref) {
698
- 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;
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 = /** @type {(JxElement | string)[]} */ (resolved);
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 = /** @type {(JxElement | string)[]} */ (resolved);
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(/** @type {string} */ (node.tagName));
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
- /** @type {Record<string, JxStateDefinition>} */
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] = /** @type {JxStateDefinition} */ (value);
789
- else stateDefs[key] = /** @type {JxStateDefinition} */ (value);
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
- /** @type {Record<string, unknown>} */
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 = /** @type {JxStyle} */ ({ ...node.style, ...resolvedStyle });
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
- (/** @type {string} */ tag) => html.includes(`<${tag}`), // matches <tag> and <tag ...>
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((/** @type {string} */ tag) => cssMap.has(tag))
837
- .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">`)
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((/** @type {string} */ tag) => !staticTags.has(tag));
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, (/** @type {string} */ match) => {
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(redirects, outDir) {
961
+ function generateRedirects(
962
+ redirects: Record<string, string | { destination: string; status?: number }>,
963
+ outDir: string,
964
+ ) {
960
965
  let count = 0;
961
- /** @type {string[]} */
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
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 = /** @type {Error} */ (e);
64
+ const err = e as Error;
65
65
  throw new Error(`Invalid JSON in ${configPath}: ${err.message}`);
66
66
  }
67
67