evolit 0.1.0-alpha.2 → 0.1.0-alpha.21

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.
@@ -3,6 +3,7 @@ import { existsSync, realpathSync } from "node:fs";
3
3
  import crypto from "node:crypto";
4
4
  import path from "node:path";
5
5
  import { createRequire } from "node:module";
6
+ import { fileURLToPath } from "node:url";
6
7
  import MagicString from "magic-string";
7
8
  import { rollup } from "rollup";
8
9
  import { nodeResolve } from "@rollup/plugin-node-resolve";
@@ -24,10 +25,12 @@ const MODULE_SPECIFIER_PATTERN =
24
25
  /\b(?:import|export)\s*[^"']*?from\s*["']([^"']+)["']|\bimport\s*["']([^"']+)["']|\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
25
26
 
26
27
  const requireFromHere = createRequire(import.meta.url);
28
+ const EVOLIT_NAVIGATION_BROWSER_ENTRY = fileURLToPath(new URL("./navigation-client.js", import.meta.url));
27
29
  const SHARED_VENDOR_SPECIFIERS = [
28
30
  "@litsx/core",
29
31
  "@litsx/core/elements",
30
32
  "@litsx/ssr/hydration",
33
+ "evolit/navigation",
31
34
  "lit",
32
35
  ];
33
36
  const browserSpecifierFilePathCache = new Map();
@@ -36,6 +39,23 @@ const sharedRuntimeBuildCache = new Map();
36
39
  const clientVendorSpecifierCache = new Map();
37
40
  const clientModuleMetadataCache = new Map();
38
41
 
42
+ export function resetDevelopmentAssetCaches(projectRoot) {
43
+ const normalizedProjectRoot = path.resolve(projectRoot);
44
+ const developmentPrefix = `${normalizedProjectRoot}::development::`;
45
+
46
+ for (const cache of [
47
+ sharedRuntimeBuildCache,
48
+ clientVendorSpecifierCache,
49
+ clientModuleMetadataCache,
50
+ ]) {
51
+ for (const key of cache.keys()) {
52
+ if (key.startsWith(developmentPrefix)) {
53
+ cache.delete(key);
54
+ }
55
+ }
56
+ }
57
+ }
58
+
39
59
  function isBareSpecifier(specifier) {
40
60
  return (
41
61
  typeof specifier === "string" &&
@@ -57,6 +77,31 @@ function isRelativeSpecifier(specifier) {
57
77
  );
58
78
  }
59
79
 
80
+ function stripImportSearchAndHash(specifier) {
81
+ return String(specifier).split("?")[0].split("#")[0];
82
+ }
83
+
84
+ function resolveRelativeClientImportPath(fromClientModule, specifier) {
85
+ const normalizedSpecifier = stripImportSearchAndHash(specifier);
86
+ return path
87
+ .normalize(path.join(path.dirname(fromClientModule), normalizedSpecifier))
88
+ .split(path.sep)
89
+ .join("/");
90
+ }
91
+
92
+ function normalizeClientMetadataAssetPath(fromClientModule, importedAsset) {
93
+ if (typeof importedAsset !== "string") {
94
+ return null;
95
+ }
96
+
97
+ const normalizedAsset = stripImportSearchAndHash(importedAsset);
98
+ if (isRelativeSpecifier(normalizedAsset)) {
99
+ return resolveRelativeClientImportPath(fromClientModule, normalizedAsset);
100
+ }
101
+
102
+ return path.normalize(normalizedAsset).split(path.sep).join("/");
103
+ }
104
+
60
105
  function parsePackageSpecifier(specifier) {
61
106
  if (!isBareSpecifier(specifier)) {
62
107
  return null;
@@ -217,9 +262,12 @@ function createSharedEntrySource(specifier) {
217
262
  ].join("\n");
218
263
  }
219
264
 
220
- export async function collectSharedVendorSpecifiers(additionalEntrySpecifiers = []) {
265
+ export async function collectSharedVendorSpecifiers(
266
+ additionalEntrySpecifiers = [],
267
+ options = {},
268
+ ) {
221
269
  return [...new Set([
222
- ...SHARED_VENDOR_SPECIFIERS,
270
+ ...(options.includeBase === false ? [] : SHARED_VENDOR_SPECIFIERS),
223
271
  ...additionalEntrySpecifiers.filter((specifier) => isBareSpecifier(specifier)),
224
272
  ])].sort();
225
273
  }
@@ -414,20 +462,52 @@ export async function buildSharedVendorRuntime(
414
462
  ? options.additionalEntrySpecifiers.filter((specifier) => isBareSpecifier(specifier))
415
463
  : [],
416
464
  )].sort();
417
- const cacheKey = `${projectRoot}::${mode}::${additionalEntrySpecifiers.join("|")}`;
465
+ if (mode === "development" && !options.vendorGroup) {
466
+ const baseRuntime = await buildSharedVendorRuntime(projectRoot, mode, {
467
+ ...options,
468
+ additionalEntrySpecifiers: [],
469
+ vendorGroup: "base",
470
+ includeBase: true,
471
+ });
472
+ const additionalRuntimes = await Promise.all(
473
+ additionalEntrySpecifiers
474
+ .filter((specifier) => !SHARED_VENDOR_SPECIFIERS.includes(specifier))
475
+ .map((specifier) => buildSharedVendorRuntime(projectRoot, mode, {
476
+ ...options,
477
+ additionalEntrySpecifiers: [specifier],
478
+ vendorGroup: `vendor-${crypto.createHash("sha1").update(specifier).digest("hex").slice(0, 10)}`,
479
+ includeBase: false,
480
+ })),
481
+ );
482
+ return {
483
+ imports: Object.assign(
484
+ {},
485
+ baseRuntime.imports,
486
+ ...additionalRuntimes.map((runtime) => runtime.imports),
487
+ ),
488
+ outputRoot: getSharedOutputRoot(projectRoot, mode),
489
+ };
490
+ }
491
+
492
+ const vendorGroup = options.vendorGroup ?? null;
493
+ const cacheKey = `${projectRoot}::${mode}::${vendorGroup ?? "all"}::${additionalEntrySpecifiers.join("|")}`;
418
494
  if (sharedRuntimeBuildCache.has(cacheKey)) {
419
495
  return sharedRuntimeBuildCache.get(cacheKey);
420
496
  }
421
497
 
422
498
  const pendingBuild = (async () => {
423
- const sharedRoot = getSharedOutputRoot(projectRoot, mode);
499
+ const sharedRootBase = getSharedOutputRoot(projectRoot, mode);
500
+ const sharedRoot = vendorGroup ? path.join(sharedRootBase, vendorGroup) : sharedRootBase;
424
501
  const entriesRoot = path.join(
425
502
  projectRoot,
426
503
  INTERNAL_DIRECTORY,
427
504
  mode === "development" ? DEV_DIRECTORY : BUILD_DIRECTORY,
428
505
  "__shared_entries__",
506
+ ...(vendorGroup ? [vendorGroup] : []),
429
507
  );
430
- const specifiers = await collectSharedVendorSpecifiers(additionalEntrySpecifiers);
508
+ const specifiers = await collectSharedVendorSpecifiers(additionalEntrySpecifiers, {
509
+ includeBase: options.includeBase !== false,
510
+ });
431
511
  const inputEntries = await writeSharedRuntimeEntrySources(entriesRoot, specifiers);
432
512
 
433
513
  await fs.rm(sharedRoot, { recursive: true, force: true });
@@ -439,6 +519,15 @@ export async function buildSharedVendorRuntime(
439
519
  const bundle = await rollup({
440
520
  input: inputEntries,
441
521
  plugins: [
522
+ {
523
+ name: "evolit-browser-exports",
524
+ resolveId(source) {
525
+ // A consuming application does not normally have an `evolit`
526
+ // package nested in its node_modules. Resolve Evolit's own
527
+ // browser-only public entry before node-resolve marks it external.
528
+ return source === "evolit/navigation" ? EVOLIT_NAVIGATION_BROWSER_ENTRY : null;
529
+ },
530
+ },
442
531
  nodeResolve({
443
532
  browser: true,
444
533
  exportConditions: ["browser", "import", "default"],
@@ -482,7 +571,7 @@ export async function buildSharedVendorRuntime(
482
571
  continue;
483
572
  }
484
573
 
485
- imports[specifier] = `/_evolit/shared/${chunk.fileName}`;
574
+ imports[specifier] = `/_evolit/shared/${vendorGroup ? `${vendorGroup}/` : ""}${chunk.fileName}`;
486
575
  }
487
576
 
488
577
  return {
@@ -561,7 +650,10 @@ async function resolveRelativeImportFilePath(fromFilePath, specifier) {
561
650
  return null;
562
651
  }
563
652
 
564
- const candidatePath = path.resolve(path.dirname(fromFilePath), specifier);
653
+ const candidatePath = path.resolve(
654
+ path.dirname(stripImportSearchAndHash(fromFilePath)),
655
+ stripImportSearchAndHash(specifier),
656
+ );
565
657
  const candidatePaths = [
566
658
  candidatePath,
567
659
  `${candidatePath}.js`,
@@ -688,10 +780,16 @@ async function collectTransitiveClientModuleMetadata(projectRoot, mode, clientMo
688
780
  }
689
781
 
690
782
  for (const styleImport of metadata.styleImports) {
691
- styleImports.add(styleImport);
783
+ const normalizedStyleImport = normalizeClientMetadataAssetPath(currentModule, styleImport);
784
+ if (normalizedStyleImport) {
785
+ styleImports.add(normalizedStyleImport);
786
+ }
692
787
  }
693
788
  for (const assetImport of metadata.assetImports) {
694
- assetImports.add(assetImport);
789
+ const normalizedAssetImport = normalizeClientMetadataAssetPath(currentModule, assetImport);
790
+ if (normalizedAssetImport) {
791
+ assetImports.add(normalizedAssetImport);
792
+ }
695
793
  }
696
794
  for (const importedModule of metadata.moduleImports) {
697
795
  pendingModules.push(importedModule);
@@ -837,10 +935,25 @@ export function getClientOutputRoot(projectRoot, mode) {
837
935
  }
838
936
 
839
937
  export function toClientModuleRelativePath(projectRoot, sourcePath) {
840
- const relativePath = path.relative(projectRoot, sourcePath);
938
+ const relativePath = toClientSourceRelativePath(projectRoot, sourcePath);
841
939
  return toCompiledClientModuleRelativePath(relativePath);
842
940
  }
843
941
 
942
+ function toClientSourceRelativePath(projectRoot, sourcePath) {
943
+ const relativePath = path.relative(projectRoot, sourcePath);
944
+ const isOutsideProject = relativePath === ".."
945
+ || relativePath.startsWith(`..${path.sep}`)
946
+ || path.isAbsolute(relativePath);
947
+ if (!isOutsideProject) {
948
+ return relativePath;
949
+ }
950
+
951
+ return path.join(
952
+ "__unmanaged__",
953
+ ...relativePath.split(path.sep).map((segment) => segment === ".." ? "__up__" : segment),
954
+ );
955
+ }
956
+
844
957
  function toCompiledClientModuleRelativePath(relativePath) {
845
958
  const extension = path.extname(relativePath);
846
959
  return extension
@@ -848,17 +961,66 @@ function toCompiledClientModuleRelativePath(relativePath) {
848
961
  : `${relativePath}.mjs`;
849
962
  }
850
963
 
964
+ function toCatchAllClientModuleRelativePath(clientModule) {
965
+ return clientModule
966
+ .split(/[\\/]/)
967
+ .map((segment) => {
968
+ if (segment.startsWith("[[...") && segment.endsWith("]]")) {
969
+ return `**...${segment.slice(5, -2)}**`;
970
+ }
971
+
972
+ if (segment.startsWith("[...") && segment.endsWith("]")) {
973
+ return `*...${segment.slice(4, -1)}*`;
974
+ }
975
+
976
+ return segment;
977
+ })
978
+ .join("/");
979
+ }
980
+
981
+ function toRollupSafeClientModuleRelativePath(clientModule) {
982
+ return clientModule.replaceAll(/[\[\]]/g, "_");
983
+ }
984
+
985
+ function getClientModuleCandidates(clientModule) {
986
+ const normalizedClientModule = clientModule.split(path.sep).join("/");
987
+ return [...new Set([
988
+ normalizedClientModule,
989
+ toCatchAllClientModuleRelativePath(normalizedClientModule),
990
+ toRollupSafeClientModuleRelativePath(normalizedClientModule),
991
+ ])];
992
+ }
993
+
994
+ function getClientModulePublicUrl(assetManifest, clientModule) {
995
+ for (const candidate of getClientModuleCandidates(clientModule)) {
996
+ const publicUrl = assetManifest.byClientModule[candidate];
997
+ if (typeof publicUrl === "string" && publicUrl.length > 0) {
998
+ return publicUrl;
999
+ }
1000
+ }
1001
+
1002
+ const candidates = new Set(getClientModuleCandidates(clientModule));
1003
+ const asset = assetManifest.assets.find((entry) =>
1004
+ candidates.has(entry?.clientModule) && typeof entry?.publicUrl === "string",
1005
+ );
1006
+ if (asset) {
1007
+ return asset.publicUrl;
1008
+ }
1009
+
1010
+ return null;
1011
+ }
1012
+
1013
+ function isVirtualClientModuleId(moduleId) {
1014
+ return moduleId.startsWith("/app/") || moduleId.startsWith("/__unmanaged__/");
1015
+ }
1016
+
851
1017
  function toPublicHydrationModuleId(projectRoot, moduleId) {
852
1018
  if (typeof moduleId !== "string" || moduleId.length === 0) {
853
1019
  return null;
854
1020
  }
855
1021
 
856
1022
  if (path.isAbsolute(moduleId)) {
857
- if (!moduleId.startsWith(projectRoot)) {
858
- return null;
859
- }
860
-
861
- const relativePath = path.relative(projectRoot, moduleId).split(path.sep).join("/");
1023
+ const relativePath = toClientSourceRelativePath(projectRoot, moduleId).split(path.sep).join("/");
862
1024
  return `/${relativePath}`;
863
1025
  }
864
1026
 
@@ -900,14 +1062,10 @@ export function createAssetResolver(projectRoot, options = {}) {
900
1062
  }
901
1063
 
902
1064
  let relativeClientModule = null;
903
- if (moduleId.startsWith("/") && !moduleId.startsWith(projectRoot)) {
904
- relativeClientModule = toCompiledClientModuleRelativePath(moduleId.slice(1));
905
- } else if (path.isAbsolute(moduleId)) {
906
- if (!moduleId.startsWith(projectRoot)) {
907
- return null;
908
- }
909
-
1065
+ if (path.isAbsolute(moduleId) && !isVirtualClientModuleId(moduleId)) {
910
1066
  relativeClientModule = toClientModuleRelativePath(projectRoot, moduleId);
1067
+ } else if (moduleId.startsWith("/")) {
1068
+ relativeClientModule = toCompiledClientModuleRelativePath(moduleId.slice(1));
911
1069
  }
912
1070
 
913
1071
  if (!relativeClientModule) {
@@ -915,10 +1073,7 @@ export function createAssetResolver(projectRoot, options = {}) {
915
1073
  }
916
1074
 
917
1075
  if (assetManifest) {
918
- return getAssetByClientModule(
919
- assetManifest,
920
- relativeClientModule,
921
- )?.publicUrl ?? null;
1076
+ return getClientModulePublicUrl(assetManifest, relativeClientModule);
922
1077
  }
923
1078
 
924
1079
  return null;
@@ -941,10 +1096,11 @@ export function createHydrationBootstrap({
941
1096
  hydrationData,
942
1097
  assetResolver,
943
1098
  hydrationModuleUrl = "@litsx/ssr/hydration",
1099
+ navigationModuleUrl = null,
944
1100
  }) {
945
1101
  const normalizedHydrationData = normalizeHydrationDataForClient(hydrationData);
946
- const moduleUrls = [...new Set(
947
- (normalizedHydrationData?.roots ?? [])
1102
+ const rootModuleUrls = [...new Set([
1103
+ ...(normalizedHydrationData?.roots ?? [])
948
1104
  .map((root) => {
949
1105
  if (typeof root?.moduleId !== "string" || root.moduleId.length === 0) {
950
1106
  return null;
@@ -953,31 +1109,42 @@ export function createHydrationBootstrap({
953
1109
  return assetResolver?.(root.moduleId) ?? null;
954
1110
  })
955
1111
  .filter((moduleUrl) => typeof moduleUrl === "string" && moduleUrl.length > 0),
1112
+ ])];
1113
+ const clientImports = [...new Set(
1114
+ (Array.isArray(normalizedHydrationData?.clientImports)
1115
+ ? normalizedHydrationData.clientImports
1116
+ : []).filter((moduleUrl) => typeof moduleUrl === "string" && moduleUrl.length > 0),
956
1117
  )];
957
1118
 
958
- if (moduleUrls.length === 0) {
1119
+ if (rootModuleUrls.length === 0 && clientImports.length === 0) {
959
1120
  return "";
960
1121
  }
961
1122
 
962
- const moduleLoaders = moduleUrls
1123
+ const moduleLoaders = rootModuleUrls
963
1124
  .map((moduleUrl) => ` () => import(${JSON.stringify(moduleUrl)})`)
964
1125
  .join(",\n");
965
1126
 
966
1127
  const source = `
967
1128
  import { hydratePage, registerHydrationModules } from ${JSON.stringify(hydrationModuleUrl)};
1129
+ ${navigationModuleUrl ? `import { getNavigation } from ${JSON.stringify(navigationModuleUrl)};` : ""}
968
1130
 
969
1131
  await hydratePage({
970
- clientImports: [],
1132
+ clientImports: ${JSON.stringify(clientImports)},
971
1133
  register: () => registerHydrationModules([
972
1134
  ${moduleLoaders}
973
1135
  ])
974
1136
  });
1137
+ ${navigationModuleUrl ? "getNavigation();" : ""}
975
1138
  `;
976
1139
 
977
1140
  return escapeInlineScriptText(source);
978
1141
  }
979
1142
 
980
- export function normalizeHydrationDataForClient(hydrationData, projectRoot = null) {
1143
+ export function normalizeHydrationDataForClient(
1144
+ hydrationData,
1145
+ projectRoot = null,
1146
+ additionalClientImports = [],
1147
+ ) {
981
1148
  if (!hydrationData || typeof hydrationData !== "object") {
982
1149
  return hydrationData ?? null;
983
1150
  }
@@ -1010,7 +1177,10 @@ export function normalizeHydrationDataForClient(hydrationData, projectRoot = nul
1010
1177
  roots,
1011
1178
  };
1012
1179
  const payload = hydrationData.payload;
1013
- const clientImports = hydrationData.clientImports;
1180
+ const clientImports = [...new Set([
1181
+ ...(Array.isArray(hydrationData.clientImports) ? hydrationData.clientImports : []),
1182
+ ...(Array.isArray(additionalClientImports) ? additionalClientImports : []),
1183
+ ].filter((value) => typeof value === "string" && value.length > 0))];
1014
1184
 
1015
1185
  Object.defineProperties(normalizedHydrationData, {
1016
1186
  payload: {
@@ -1119,7 +1289,32 @@ function rewriteStaticAssetPlaceholdersWithMap(source, publicPathByRelativePath)
1119
1289
  }
1120
1290
 
1121
1291
  function rewriteRelativeCssAssetUrls(source, fileRelativePath, publicPathByRelativePath) {
1122
- return source.replaceAll(
1292
+ const rewrittenImports = source.replaceAll(
1293
+ /@import(\s+)(url\(\s*)?(?:(["'])([^"']+)\3|([^\s;)]+))(\s*\))?/g,
1294
+ (match, whitespace, urlPrefix = "", quote = "", quotedSpecifier, unquotedSpecifier, closing = "") => {
1295
+ const specifier = quotedSpecifier ?? unquotedSpecifier;
1296
+ if (!isRelativeSpecifier(specifier)) {
1297
+ return match;
1298
+ }
1299
+
1300
+ const resolvedRelativePath = resolveRelativeClientImportPath(fileRelativePath, specifier);
1301
+ const rewrittenTarget = publicPathByRelativePath.get(resolvedRelativePath);
1302
+ if (!rewrittenTarget) {
1303
+ return match;
1304
+ }
1305
+
1306
+ const cleanSpecifier = stripImportSearchAndHash(specifier);
1307
+ const rewrittenSpecifier = `${normalizeRelativeImportPath(
1308
+ path.relative(
1309
+ path.dirname(publicPathByRelativePath.get(fileRelativePath)),
1310
+ rewrittenTarget,
1311
+ ),
1312
+ )}${specifier.slice(cleanSpecifier.length)}`;
1313
+ return `@import${whitespace}${urlPrefix}${quote}${rewrittenSpecifier}${quote}${closing}`;
1314
+ },
1315
+ );
1316
+
1317
+ return rewrittenImports.replaceAll(
1123
1318
  /url\(\s*(['"]?)([^"'()]+)\1\s*\)/g,
1124
1319
  (match, quote = "", rawSpecifier) => {
1125
1320
  const specifier = String(rawSpecifier).trim();
@@ -1191,9 +1386,7 @@ function collectRelativeClientImportPaths(source, fileRelativePath, publicPathBy
1191
1386
  continue;
1192
1387
  }
1193
1388
 
1194
- const resolvedRelativePath = path.normalize(
1195
- path.join(path.dirname(fileRelativePath), specifier),
1196
- );
1389
+ const resolvedRelativePath = resolveRelativeClientImportPath(fileRelativePath, specifier);
1197
1390
  if (!publicPathByRelativePath.has(resolvedRelativePath)) {
1198
1391
  continue;
1199
1392
  }
@@ -1217,9 +1410,7 @@ function rewriteRelativeClientImports(source, fileRelativePath, publicPathByRela
1217
1410
  continue;
1218
1411
  }
1219
1412
 
1220
- const resolvedRelativePath = path.normalize(
1221
- path.join(path.dirname(fileRelativePath), specifier),
1222
- );
1413
+ const resolvedRelativePath = resolveRelativeClientImportPath(fileRelativePath, specifier);
1223
1414
  const rewrittenTarget = publicPathByRelativePath.get(resolvedRelativePath);
1224
1415
  if (!rewrittenTarget) {
1225
1416
  continue;
@@ -1324,8 +1515,12 @@ export async function emitHashedClientAssets(projectRoot, options = {}) {
1324
1515
  if (entry.type === "script") {
1325
1516
  try {
1326
1517
  const metadata = JSON.parse(await fs.readFile(`${entry.filePath}.meta.json`, "utf8"));
1327
- styleImports = Array.isArray(metadata?.styleImports) ? metadata.styleImports : [];
1328
- assetImports = Array.isArray(metadata?.assetImports) ? metadata.assetImports : [];
1518
+ styleImports = (Array.isArray(metadata?.styleImports) ? metadata.styleImports : [])
1519
+ .map((importedStyle) => normalizeClientMetadataAssetPath(entry.relativePath, importedStyle))
1520
+ .filter((importedStyle) => typeof importedStyle === "string");
1521
+ assetImports = (Array.isArray(metadata?.assetImports) ? metadata.assetImports : [])
1522
+ .map((importedAsset) => normalizeClientMetadataAssetPath(entry.relativePath, importedAsset))
1523
+ .filter((importedAsset) => typeof importedAsset === "string");
1329
1524
  } catch {
1330
1525
  styleImports = [];
1331
1526
  assetImports = [];
@@ -1449,7 +1644,9 @@ async function bundleClientAssets(projectRoot, options = {}) {
1449
1644
  publicPathByRelativePath.set(entry.relativePath, hashedRelativePath);
1450
1645
  }
1451
1646
 
1452
- await fs.rm(staticRoot, { recursive: true, force: true });
1647
+ if (mode !== "development") {
1648
+ await fs.rm(staticRoot, { recursive: true, force: true });
1649
+ }
1453
1650
  await ensureDirectory(staticRoot);
1454
1651
 
1455
1652
  const nonScriptAssets = [];
@@ -1528,17 +1725,36 @@ async function bundleClientAssets(projectRoot, options = {}) {
1528
1725
  return isBareSpecifier(id);
1529
1726
  },
1530
1727
  plugins: [
1728
+ {
1729
+ name: "evolit-client-relative-imports",
1730
+ resolveId(source, importer) {
1731
+ if (!importer || !isRelativeSpecifier(source)) {
1732
+ return null;
1733
+ }
1734
+
1735
+ const cleanSource = stripImportSearchAndHash(source);
1736
+ if (cleanSource === source) {
1737
+ return null;
1738
+ }
1739
+
1740
+ return path.resolve(
1741
+ path.dirname(stripImportSearchAndHash(importer)),
1742
+ cleanSource,
1743
+ );
1744
+ },
1745
+ },
1531
1746
  {
1532
1747
  name: "evolit-client-input-sourcemaps",
1533
1748
  async load(id) {
1534
- if (!id.startsWith(clientRoot) || !id.endsWith(".mjs")) {
1749
+ const filePath = stripImportSearchAndHash(id);
1750
+ if (!filePath.startsWith(clientRoot) || !filePath.endsWith(".mjs")) {
1535
1751
  return null;
1536
1752
  }
1537
1753
 
1538
1754
  try {
1539
1755
  const [code, map] = await Promise.all([
1540
- fs.readFile(id, "utf8"),
1541
- fs.readFile(`${id}.map`, "utf8"),
1756
+ fs.readFile(filePath, "utf8"),
1757
+ fs.readFile(`${filePath}.map`, "utf8"),
1542
1758
  ]);
1543
1759
  return { code, map: JSON.parse(map) };
1544
1760
  } catch {
@@ -1596,9 +1812,14 @@ async function bundleClientAssets(projectRoot, options = {}) {
1596
1812
 
1597
1813
  const outputPath = path.join(staticRoot, chunk.fileName);
1598
1814
  const publicUrl = toStaticPublicUrl(chunk.fileName);
1599
- const isNamedEntry = chunk.isEntry && clientModuleByEntryName.has(chunk.name);
1815
+ const entryClientModule = clientModuleByEntryName.get(chunk.name)
1816
+ ?? [...clientModuleByEntryName.entries()].find(([entryName]) =>
1817
+ toRollupSafeClientModuleRelativePath(entryName) === chunk.name,
1818
+ )?.[1]
1819
+ ?? null;
1820
+ const isNamedEntry = chunk.isEntry && entryClientModule != null;
1600
1821
  const clientModule = isNamedEntry
1601
- ? clientModuleByEntryName.get(chunk.name)
1822
+ ? entryClientModule
1602
1823
  : chunk.fileName;
1603
1824
  const transitiveMetadata = isNamedEntry
1604
1825
  ? await collectTransitiveClientModuleMetadata(projectRoot, mode, clientModule)
@@ -1640,30 +1861,43 @@ async function bundleClientAssets(projectRoot, options = {}) {
1640
1861
  }
1641
1862
 
1642
1863
  const mapAssets = [];
1643
- const staticFiles = await walkFiles(staticRoot);
1644
- for (const filePath of staticFiles) {
1645
- if (!filePath.endsWith(".map")) {
1646
- continue;
1864
+ for (const asset of scriptAssetRecords) {
1865
+ const mapPath = `${asset.outputPath}.map`;
1866
+ try {
1867
+ const stat = await fs.stat(mapPath);
1868
+ const relativePath = path.relative(staticRoot, mapPath).split(path.sep).join("/");
1869
+ const publicUrl = toStaticPublicUrl(relativePath);
1870
+ byPublicPath[publicUrl] = mapPath;
1871
+ mapAssets.push({
1872
+ clientModule: relativePath,
1873
+ type: "asset",
1874
+ kind: "asset",
1875
+ publicUrl,
1876
+ outputPath: mapPath,
1877
+ hash: null,
1878
+ size: stat.size,
1879
+ imports: [],
1880
+ importUrls: [],
1881
+ styleImports: [],
1882
+ styleUrls: [],
1883
+ assetImports: [],
1884
+ assetUrls: [],
1885
+ });
1886
+ } catch {
1887
+ // Source maps are optional in non-development builds.
1647
1888
  }
1889
+ }
1648
1890
 
1649
- const relativePath = path.relative(staticRoot, filePath).split(path.sep).join("/");
1650
- const publicUrl = toStaticPublicUrl(relativePath);
1651
- byPublicPath[publicUrl] = filePath;
1652
- mapAssets.push({
1653
- clientModule: relativePath,
1654
- type: "asset",
1655
- kind: "asset",
1656
- publicUrl,
1657
- outputPath: filePath,
1658
- hash: null,
1659
- size: (await fs.stat(filePath)).size,
1660
- imports: [],
1661
- importUrls: [],
1662
- styleImports: [],
1663
- styleUrls: [],
1664
- assetImports: [],
1665
- assetUrls: [],
1666
- });
1891
+ if (mode === "development") {
1892
+ const retainedOutputPaths = new Set(
1893
+ [...scriptAssetRecords, ...nonScriptAssets, ...mapAssets]
1894
+ .map((asset) => asset.outputPath),
1895
+ );
1896
+ for (const filePath of await walkFiles(staticRoot)) {
1897
+ if (!retainedOutputPaths.has(filePath)) {
1898
+ await fs.rm(filePath, { force: true });
1899
+ }
1900
+ }
1667
1901
  }
1668
1902
 
1669
1903
  const assets = [...scriptAssetRecords, ...nonScriptAssets, ...mapAssets]
@@ -1776,6 +2010,28 @@ export function collectTransitiveAssetPreloads(publicUrls, assetManifest) {
1776
2010
  return discovered;
1777
2011
  }
1778
2012
 
2013
+ export function resolveRouteClientImports(routeResult, projectRoot, assetManifest) {
2014
+ const normalizedManifest = normalizeClientAssetManifest(assetManifest);
2015
+ if (!normalizedManifest) {
2016
+ return [];
2017
+ }
2018
+
2019
+ const routeModules = [
2020
+ routeResult?.route?.page,
2021
+ ...(routeResult?.route?.layouts ?? []),
2022
+ routeResult?.boundaryModule,
2023
+ ].filter((modulePath) => typeof modulePath === "string");
2024
+
2025
+ return [...new Set(routeModules.map((modulePath) => {
2026
+ const relativePath = path.relative(projectRoot, modulePath).split(path.sep).join("/");
2027
+ const extension = path.extname(relativePath);
2028
+ const clientModule = extension
2029
+ ? `${relativePath.slice(0, -extension.length)}.mjs`
2030
+ : `${relativePath}.mjs`;
2031
+ return getClientModulePublicUrl(normalizedManifest, clientModule);
2032
+ }).filter((publicUrl) => typeof publicUrl === "string"))];
2033
+ }
2034
+
1779
2035
  export function collectTransitiveStyleUrls(publicUrls, assetManifest) {
1780
2036
  const normalizedManifest = normalizeClientAssetManifest(assetManifest);
1781
2037
  if (!normalizedManifest) {