evolit 0.1.0-alpha.12 → 0.1.0-alpha.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evolit",
3
- "version": "0.1.0-alpha.12",
3
+ "version": "0.1.0-alpha.13",
4
4
  "description": "A convention-driven application framework for LitSX and web components.",
5
5
  "type": "module",
6
6
  "packageManager": "yarn@4.10.3",
@@ -229,9 +229,12 @@ function createSharedEntrySource(specifier) {
229
229
  ].join("\n");
230
230
  }
231
231
 
232
- export async function collectSharedVendorSpecifiers(additionalEntrySpecifiers = []) {
232
+ export async function collectSharedVendorSpecifiers(
233
+ additionalEntrySpecifiers = [],
234
+ options = {},
235
+ ) {
233
236
  return [...new Set([
234
- ...SHARED_VENDOR_SPECIFIERS,
237
+ ...(options.includeBase === false ? [] : SHARED_VENDOR_SPECIFIERS),
235
238
  ...additionalEntrySpecifiers.filter((specifier) => isBareSpecifier(specifier)),
236
239
  ])].sort();
237
240
  }
@@ -426,20 +429,52 @@ export async function buildSharedVendorRuntime(
426
429
  ? options.additionalEntrySpecifiers.filter((specifier) => isBareSpecifier(specifier))
427
430
  : [],
428
431
  )].sort();
429
- const cacheKey = `${projectRoot}::${mode}::${additionalEntrySpecifiers.join("|")}`;
432
+ if (mode === "development" && !options.vendorGroup) {
433
+ const baseRuntime = await buildSharedVendorRuntime(projectRoot, mode, {
434
+ ...options,
435
+ additionalEntrySpecifiers: [],
436
+ vendorGroup: "base",
437
+ includeBase: true,
438
+ });
439
+ const additionalRuntimes = await Promise.all(
440
+ additionalEntrySpecifiers
441
+ .filter((specifier) => !SHARED_VENDOR_SPECIFIERS.includes(specifier))
442
+ .map((specifier) => buildSharedVendorRuntime(projectRoot, mode, {
443
+ ...options,
444
+ additionalEntrySpecifiers: [specifier],
445
+ vendorGroup: `vendor-${crypto.createHash("sha1").update(specifier).digest("hex").slice(0, 10)}`,
446
+ includeBase: false,
447
+ })),
448
+ );
449
+ return {
450
+ imports: Object.assign(
451
+ {},
452
+ baseRuntime.imports,
453
+ ...additionalRuntimes.map((runtime) => runtime.imports),
454
+ ),
455
+ outputRoot: getSharedOutputRoot(projectRoot, mode),
456
+ };
457
+ }
458
+
459
+ const vendorGroup = options.vendorGroup ?? null;
460
+ const cacheKey = `${projectRoot}::${mode}::${vendorGroup ?? "all"}::${additionalEntrySpecifiers.join("|")}`;
430
461
  if (sharedRuntimeBuildCache.has(cacheKey)) {
431
462
  return sharedRuntimeBuildCache.get(cacheKey);
432
463
  }
433
464
 
434
465
  const pendingBuild = (async () => {
435
- const sharedRoot = getSharedOutputRoot(projectRoot, mode);
466
+ const sharedRootBase = getSharedOutputRoot(projectRoot, mode);
467
+ const sharedRoot = vendorGroup ? path.join(sharedRootBase, vendorGroup) : sharedRootBase;
436
468
  const entriesRoot = path.join(
437
469
  projectRoot,
438
470
  INTERNAL_DIRECTORY,
439
471
  mode === "development" ? DEV_DIRECTORY : BUILD_DIRECTORY,
440
472
  "__shared_entries__",
473
+ ...(vendorGroup ? [vendorGroup] : []),
441
474
  );
442
- const specifiers = await collectSharedVendorSpecifiers(additionalEntrySpecifiers);
475
+ const specifiers = await collectSharedVendorSpecifiers(additionalEntrySpecifiers, {
476
+ includeBase: options.includeBase !== false,
477
+ });
443
478
  const inputEntries = await writeSharedRuntimeEntrySources(entriesRoot, specifiers);
444
479
 
445
480
  await fs.rm(sharedRoot, { recursive: true, force: true });
@@ -494,7 +529,7 @@ export async function buildSharedVendorRuntime(
494
529
  continue;
495
530
  }
496
531
 
497
- imports[specifier] = `/_evolit/shared/${chunk.fileName}`;
532
+ imports[specifier] = `/_evolit/shared/${vendorGroup ? `${vendorGroup}/` : ""}${chunk.fileName}`;
498
533
  }
499
534
 
500
535
  return {
@@ -1134,7 +1169,32 @@ function rewriteStaticAssetPlaceholdersWithMap(source, publicPathByRelativePath)
1134
1169
  }
1135
1170
 
1136
1171
  function rewriteRelativeCssAssetUrls(source, fileRelativePath, publicPathByRelativePath) {
1137
- return source.replaceAll(
1172
+ const rewrittenImports = source.replaceAll(
1173
+ /@import(\s+)(url\(\s*)?(?:(["'])([^"']+)\3|([^\s;)]+))(\s*\))?/g,
1174
+ (match, whitespace, urlPrefix = "", quote = "", quotedSpecifier, unquotedSpecifier, closing = "") => {
1175
+ const specifier = quotedSpecifier ?? unquotedSpecifier;
1176
+ if (!isRelativeSpecifier(specifier)) {
1177
+ return match;
1178
+ }
1179
+
1180
+ const resolvedRelativePath = resolveRelativeClientImportPath(fileRelativePath, specifier);
1181
+ const rewrittenTarget = publicPathByRelativePath.get(resolvedRelativePath);
1182
+ if (!rewrittenTarget) {
1183
+ return match;
1184
+ }
1185
+
1186
+ const cleanSpecifier = stripImportSearchAndHash(specifier);
1187
+ const rewrittenSpecifier = `${normalizeRelativeImportPath(
1188
+ path.relative(
1189
+ path.dirname(publicPathByRelativePath.get(fileRelativePath)),
1190
+ rewrittenTarget,
1191
+ ),
1192
+ )}${specifier.slice(cleanSpecifier.length)}`;
1193
+ return `@import${whitespace}${urlPrefix}${quote}${rewrittenSpecifier}${quote}${closing}`;
1194
+ },
1195
+ );
1196
+
1197
+ return rewrittenImports.replaceAll(
1138
1198
  /url\(\s*(['"]?)([^"'()]+)\1\s*\)/g,
1139
1199
  (match, quote = "", rawSpecifier) => {
1140
1200
  const specifier = String(rawSpecifier).trim();
@@ -1460,7 +1520,9 @@ async function bundleClientAssets(projectRoot, options = {}) {
1460
1520
  publicPathByRelativePath.set(entry.relativePath, hashedRelativePath);
1461
1521
  }
1462
1522
 
1463
- await fs.rm(staticRoot, { recursive: true, force: true });
1523
+ if (mode !== "development") {
1524
+ await fs.rm(staticRoot, { recursive: true, force: true });
1525
+ }
1464
1526
  await ensureDirectory(staticRoot);
1465
1527
 
1466
1528
  const nonScriptAssets = [];
@@ -1670,30 +1732,43 @@ async function bundleClientAssets(projectRoot, options = {}) {
1670
1732
  }
1671
1733
 
1672
1734
  const mapAssets = [];
1673
- const staticFiles = await walkFiles(staticRoot);
1674
- for (const filePath of staticFiles) {
1675
- if (!filePath.endsWith(".map")) {
1676
- continue;
1735
+ for (const asset of scriptAssetRecords) {
1736
+ const mapPath = `${asset.outputPath}.map`;
1737
+ try {
1738
+ const stat = await fs.stat(mapPath);
1739
+ const relativePath = path.relative(staticRoot, mapPath).split(path.sep).join("/");
1740
+ const publicUrl = toStaticPublicUrl(relativePath);
1741
+ byPublicPath[publicUrl] = mapPath;
1742
+ mapAssets.push({
1743
+ clientModule: relativePath,
1744
+ type: "asset",
1745
+ kind: "asset",
1746
+ publicUrl,
1747
+ outputPath: mapPath,
1748
+ hash: null,
1749
+ size: stat.size,
1750
+ imports: [],
1751
+ importUrls: [],
1752
+ styleImports: [],
1753
+ styleUrls: [],
1754
+ assetImports: [],
1755
+ assetUrls: [],
1756
+ });
1757
+ } catch {
1758
+ // Source maps are optional in non-development builds.
1677
1759
  }
1760
+ }
1678
1761
 
1679
- const relativePath = path.relative(staticRoot, filePath).split(path.sep).join("/");
1680
- const publicUrl = toStaticPublicUrl(relativePath);
1681
- byPublicPath[publicUrl] = filePath;
1682
- mapAssets.push({
1683
- clientModule: relativePath,
1684
- type: "asset",
1685
- kind: "asset",
1686
- publicUrl,
1687
- outputPath: filePath,
1688
- hash: null,
1689
- size: (await fs.stat(filePath)).size,
1690
- imports: [],
1691
- importUrls: [],
1692
- styleImports: [],
1693
- styleUrls: [],
1694
- assetImports: [],
1695
- assetUrls: [],
1696
- });
1762
+ if (mode === "development") {
1763
+ const retainedOutputPaths = new Set(
1764
+ [...scriptAssetRecords, ...nonScriptAssets, ...mapAssets]
1765
+ .map((asset) => asset.outputPath),
1766
+ );
1767
+ for (const filePath of await walkFiles(staticRoot)) {
1768
+ if (!retainedOutputPaths.has(filePath)) {
1769
+ await fs.rm(filePath, { force: true });
1770
+ }
1771
+ }
1697
1772
  }
1698
1773
 
1699
1774
  const assets = [...scriptAssetRecords, ...nonScriptAssets, ...mapAssets]
package/src/compiler.js CHANGED
@@ -21,6 +21,10 @@ const RESOLVABLE_IMPORT_EXTENSIONS = [
21
21
  ...MODULE_EXTENSIONS,
22
22
  ...STATIC_ASSET_EXTENSIONS,
23
23
  ];
24
+ const developmentGraphCache = new Map();
25
+ const developmentGraphDependencies = new Map();
26
+ const developmentModuleNamespaceCache = new Map();
27
+ const developmentGraphVersions = new Map();
24
28
 
25
29
  function isRelativeSpecifier(specifier) {
26
30
  return specifier.startsWith("./") || specifier.startsWith("../");
@@ -272,7 +276,46 @@ async function rewriteRelativeSpecifiers({
272
276
  };
273
277
  }
274
278
 
275
- export async function compileModuleGraph(entryPath, options = {}) {
279
+ function getDevelopmentGraphCacheKey(entryPath, options) {
280
+ return [
281
+ options.projectRoot,
282
+ entryPath,
283
+ options.target ?? "server",
284
+ options.ssr === true ? "ssr" : "client",
285
+ options.sourceMaps === false ? "without-maps" : "with-maps",
286
+ ].join("::");
287
+ }
288
+
289
+ export function invalidateDevelopmentCompilationCache(projectRoot, changedPaths = null) {
290
+ const prefix = `${path.resolve(projectRoot)}::`;
291
+ const normalizedChangedPaths = Array.isArray(changedPaths) && changedPaths.length > 0
292
+ ? new Set(changedPaths.map((changedPath) => path.resolve(changedPath)))
293
+ : null;
294
+ let invalidated = false;
295
+
296
+ for (const key of developmentGraphCache.keys()) {
297
+ if (!key.startsWith(prefix)) {
298
+ continue;
299
+ }
300
+
301
+ const dependencies = developmentGraphDependencies.get(key);
302
+ const shouldInvalidate =
303
+ normalizedChangedPaths == null ||
304
+ !dependencies ||
305
+ [...dependencies].some((dependency) => normalizedChangedPaths.has(dependency));
306
+ if (shouldInvalidate) {
307
+ developmentGraphCache.delete(key);
308
+ developmentGraphDependencies.delete(key);
309
+ developmentModuleNamespaceCache.delete(key);
310
+ developmentGraphVersions.set(key, (developmentGraphVersions.get(key) ?? 0) + 1);
311
+ invalidated = true;
312
+ }
313
+ }
314
+
315
+ return invalidated;
316
+ }
317
+
318
+ async function compileModuleGraphUncached(entryPath, options = {}) {
276
319
  const {
277
320
  projectRoot,
278
321
  mode = "development",
@@ -346,16 +389,77 @@ export async function compileModuleGraph(entryPath, options = {}) {
346
389
  return {
347
390
  entrypoint: await compileModule(entryPath),
348
391
  outputRoot,
392
+ sourceFiles: [...visited.keys()],
393
+ };
394
+ }
395
+
396
+ export async function compileModuleGraph(entryPath, options = {}) {
397
+ const mode = options.mode ?? "development";
398
+ if (mode !== "development") {
399
+ return compileModuleGraphUncached(entryPath, options);
400
+ }
401
+
402
+ const normalizedOptions = {
403
+ ...options,
404
+ projectRoot: path.resolve(options.projectRoot ?? process.cwd()),
349
405
  };
406
+ const cacheKey = getDevelopmentGraphCacheKey(
407
+ path.resolve(entryPath),
408
+ normalizedOptions,
409
+ );
410
+ let pendingCompile = developmentGraphCache.get(cacheKey);
411
+ if (!pendingCompile) {
412
+ pendingCompile = compileModuleGraphUncached(entryPath, normalizedOptions);
413
+ developmentGraphCache.set(cacheKey, pendingCompile);
414
+ pendingCompile.then((result) => {
415
+ if (developmentGraphCache.get(cacheKey) === pendingCompile) {
416
+ developmentGraphDependencies.set(cacheKey, new Set(result.sourceFiles));
417
+ }
418
+ }, () => {});
419
+ }
420
+
421
+ try {
422
+ return await pendingCompile;
423
+ } catch (error) {
424
+ if (developmentGraphCache.get(cacheKey) === pendingCompile) {
425
+ developmentGraphCache.delete(cacheKey);
426
+ developmentGraphDependencies.delete(cacheKey);
427
+ }
428
+ throw error;
429
+ }
350
430
  }
351
431
 
352
432
  export async function importCompiledModule(entryPath, options = {}) {
353
- const { entrypoint } = await compileModuleGraph(entryPath, options);
433
+ const mode = options.mode ?? "development";
434
+ const normalizedOptions = mode === "development"
435
+ ? { ...options, projectRoot: path.resolve(options.projectRoot ?? process.cwd()) }
436
+ : options;
437
+ const cacheKey = mode === "development"
438
+ ? getDevelopmentGraphCacheKey(path.resolve(entryPath), normalizedOptions)
439
+ : null;
440
+ const cachedModule = cacheKey ? developmentModuleNamespaceCache.get(cacheKey) : null;
441
+ if (cachedModule) {
442
+ return cachedModule;
443
+ }
444
+
445
+ const { entrypoint } = await compileModuleGraph(entryPath, normalizedOptions);
354
446
  const moduleUrl = new URL(pathToFileURL(entrypoint).href);
355
447
 
356
- if (options.mode === "development") {
357
- moduleUrl.searchParams.set("t", String(Date.now()));
448
+ if (mode === "development") {
449
+ moduleUrl.searchParams.set("t", String(developmentGraphVersions.get(cacheKey) ?? 0));
358
450
  }
359
451
 
360
- return import(moduleUrl.href);
452
+ const pendingModule = import(moduleUrl.href);
453
+ if (cacheKey) {
454
+ developmentModuleNamespaceCache.set(cacheKey, pendingModule);
455
+ }
456
+
457
+ try {
458
+ return await pendingModule;
459
+ } catch (error) {
460
+ if (developmentModuleNamespaceCache.get(cacheKey) === pendingModule) {
461
+ developmentModuleNamespaceCache.delete(cacheKey);
462
+ }
463
+ throw error;
464
+ }
361
465
  }
@@ -1,6 +1,9 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { compileModuleGraph } from "./compiler.js";
3
+ import {
4
+ compileModuleGraph,
5
+ invalidateDevelopmentCompilationCache,
6
+ } from "./compiler.js";
4
7
  import {
5
8
  collectTransitiveAssetPreloads,
6
9
  collectTransitiveStyleUrls,
@@ -220,8 +223,27 @@ export async function createRequestRenderer({ projectRoot, mode, assetManifest,
220
223
  "@litsx/ssr/hydration",
221
224
  );
222
225
  const devBundledEntries = new Set();
226
+ const devPreparedClientModules = new Set();
227
+ const devClientModuleDependencies = new Map();
223
228
  let devRollupCache = null;
229
+ let devClientAssetWork = Promise.resolve();
230
+ const developmentMetrics = {
231
+ clientArtifactBuilds: 0,
232
+ clientArtifactCacheHits: 0,
233
+ invalidations: 0,
234
+ selectivelyInvalidatedClientEntries: 0,
235
+ };
224
236
  const usesFrameworkRouteResolver = !routeResolver;
237
+
238
+ function enqueueDevClientAssetWork(work) {
239
+ const nextWork = devClientAssetWork.then(work, work);
240
+ devClientAssetWork = nextWork.catch(() => {});
241
+ return nextWork;
242
+ }
243
+
244
+ function getDevClientModuleKey(clientModule) {
245
+ return path.resolve(projectRoot, clientModule);
246
+ }
225
247
  async function createFrameworkRouteResolver() {
226
248
  return createRouteResolver(projectRoot, mode, {
227
249
  getStaticAssetPublicUrls() {
@@ -282,21 +304,44 @@ export async function createRequestRenderer({ projectRoot, mode, assetManifest,
282
304
  return effectiveRouteResolver;
283
305
  },
284
306
  responseCacheController,
285
- async invalidateDevelopmentState() {
307
+ async invalidateDevelopmentState(changedPaths = null) {
286
308
  if (mode !== "development") {
287
309
  return;
288
310
  }
289
311
 
290
- currentAssetManifest = null;
291
- currentAssetResolver = createAssetResolver(projectRoot, {
292
- assetManifest: currentAssetManifest,
293
- });
294
- devBundledEntries.clear();
295
- devRollupCache = null;
312
+ return enqueueDevClientAssetWork(async () => {
313
+ developmentMetrics.invalidations += 1;
314
+ const normalizedChangedPaths = Array.isArray(changedPaths) && changedPaths.length > 0
315
+ ? new Set(changedPaths.map((changedPath) => path.resolve(changedPath)))
316
+ : null;
317
+ invalidateDevelopmentCompilationCache(projectRoot, changedPaths);
318
+ const affectedClientModules = normalizedChangedPaths == null
319
+ ? new Set(devPreparedClientModules)
320
+ : new Set(
321
+ [...devClientModuleDependencies]
322
+ .filter(([, dependencies]) =>
323
+ [...dependencies].some((dependency) => normalizedChangedPaths.has(dependency))
324
+ )
325
+ .map(([clientModule]) => clientModule),
326
+ );
327
+
328
+ if (affectedClientModules.size > 0 || normalizedChangedPaths == null) {
329
+ developmentMetrics.selectivelyInvalidatedClientEntries += affectedClientModules.size;
330
+ currentAssetManifest = null;
331
+ currentAssetResolver = createAssetResolver(projectRoot, {
332
+ assetManifest: currentAssetManifest,
333
+ });
334
+ devRollupCache = null;
335
+ for (const clientModule of affectedClientModules) {
336
+ devPreparedClientModules.delete(clientModule);
337
+ devClientModuleDependencies.delete(clientModule);
338
+ }
339
+ }
296
340
 
297
- if (usesFrameworkRouteResolver) {
298
- effectiveRouteResolver = await createFrameworkRouteResolver();
299
- }
341
+ if (usesFrameworkRouteResolver) {
342
+ effectiveRouteResolver = await createFrameworkRouteResolver();
343
+ }
344
+ });
300
345
  },
301
346
  async prepareRouteClientArtifacts(routeResult) {
302
347
  if (routeResult.type !== "route" && !routeResult.boundaryModule) {
@@ -308,19 +353,49 @@ export async function createRequestRenderer({ projectRoot, mode, assetManifest,
308
353
  ...(routeResult.boundaryModule ? [routeResult.boundaryModule] : []),
309
354
  ];
310
355
 
311
- for (const clientModule of new Set(clientModules)) {
312
- const clientBuild = await compileModuleGraph(clientModule, {
313
- projectRoot,
314
- mode,
315
- sourceMaps: mode === "development",
316
- target: "client",
317
- });
318
- devBundledEntries.add(
319
- path.relative(clientBuild.outputRoot, clientBuild.entrypoint).split(path.sep).join("/"),
320
- );
356
+ if (mode !== "development") {
357
+ for (const clientModule of new Set(clientModules)) {
358
+ await compileModuleGraph(clientModule, {
359
+ projectRoot,
360
+ mode,
361
+ sourceMaps: false,
362
+ target: "client",
363
+ });
364
+ }
365
+ return;
321
366
  }
322
367
 
323
- if (mode === "development") {
368
+ return enqueueDevClientAssetWork(async () => {
369
+ let discoveredClientEntry = false;
370
+ for (const clientModule of new Set(clientModules)) {
371
+ const clientModuleKey = getDevClientModuleKey(clientModule);
372
+ if (devPreparedClientModules.has(clientModuleKey)) {
373
+ continue;
374
+ }
375
+
376
+ const clientBuild = await compileModuleGraph(clientModule, {
377
+ projectRoot,
378
+ mode,
379
+ sourceMaps: true,
380
+ target: "client",
381
+ });
382
+ devPreparedClientModules.add(clientModuleKey);
383
+ devClientModuleDependencies.set(
384
+ clientModuleKey,
385
+ new Set(clientBuild.sourceFiles ?? [clientModuleKey]),
386
+ );
387
+ devBundledEntries.add(
388
+ path.relative(clientBuild.outputRoot, clientBuild.entrypoint).split(path.sep).join("/"),
389
+ );
390
+ discoveredClientEntry = true;
391
+ }
392
+
393
+ if (!discoveredClientEntry && currentAssetManifest) {
394
+ developmentMetrics.clientArtifactCacheHits += 1;
395
+ return;
396
+ }
397
+
398
+ developmentMetrics.clientArtifactBuilds += 1;
324
399
  const bundledClientAssets = await emitBundledClientAssetsWithState(projectRoot, {
325
400
  mode: "development",
326
401
  entryClientModules: devBundledEntries,
@@ -340,19 +415,20 @@ export async function createRequestRenderer({ projectRoot, mode, assetManifest,
340
415
  },
341
416
  ) ?? currentHydrationModuleUrl;
342
417
  devRollupCache = bundledClientAssets.rollupCache;
343
- }
418
+ });
344
419
  },
345
420
  async resolveRoutePolicy(request) {
346
421
  return effectiveRouteResolver.resolveRoutePolicy(request);
347
422
  },
348
423
  async renderRoute(request, routePolicyResult = null) {
349
424
  const shouldPrepareBeforeResolve = mode === "development" && !currentAssetManifest;
425
+ let resolvedRoutePolicyResult = routePolicyResult;
350
426
  if (shouldPrepareBeforeResolve) {
351
- const resolvedRoutePolicyResult = routePolicyResult ?? await effectiveRouteResolver.resolveRoutePolicy(request);
427
+ resolvedRoutePolicyResult ??= await effectiveRouteResolver.resolveRoutePolicy(request);
352
428
  await this.prepareRouteClientArtifacts(resolvedRoutePolicyResult);
353
429
  }
354
430
 
355
- const routeResult = await effectiveRouteResolver.resolveRequest(request);
431
+ const routeResult = await effectiveRouteResolver.resolveRequest(request, resolvedRoutePolicyResult);
356
432
  if (!shouldPrepareBeforeResolve || routeResult.boundaryModule) {
357
433
  await this.prepareRouteClientArtifacts(routeResult);
358
434
  }
@@ -365,6 +441,9 @@ export async function createRequestRenderer({ projectRoot, mode, assetManifest,
365
441
  get assetManifest() {
366
442
  return currentAssetManifest;
367
443
  },
444
+ get developmentMetrics() {
445
+ return mode === "development" ? { ...developmentMetrics } : null;
446
+ },
368
447
  };
369
448
  }
370
449
 
@@ -403,12 +482,12 @@ export async function createDeploymentRuntime({
403
482
  assets,
404
483
  cache: renderer.responseCacheController,
405
484
  renderer,
406
- async invalidateDevelopmentState() {
485
+ async invalidateDevelopmentState(changedPaths = null) {
407
486
  if (mode !== "development") {
408
487
  return;
409
488
  }
410
489
 
411
- await renderer.invalidateDevelopmentState();
490
+ await renderer.invalidateDevelopmentState(changedPaths);
412
491
  if (typeof effectiveResponseCacheRuntime.store.clear === "function") {
413
492
  await effectiveResponseCacheRuntime.store.clear();
414
493
  }
package/src/render.js CHANGED
@@ -384,7 +384,10 @@ export async function createRouteResolver(projectRoot, mode = "development", res
384
384
  return renderNotFound(request);
385
385
  }
386
386
 
387
- const { pageModule, layoutModules } = await loadRouteModules(
387
+ const preloadedModules = options.routePolicyResult?.route === match.route
388
+ ? options.routePolicyResult.__evolitLoadedModules
389
+ : null;
390
+ const { pageModule, layoutModules } = preloadedModules ?? await loadRouteModules(
388
391
  match.route,
389
392
  projectRoot,
390
393
  mode,
@@ -394,7 +397,7 @@ export async function createRouteResolver(projectRoot, mode = "development", res
394
397
  const cachePolicy = normalizeRouteCachePolicy(routeConfig);
395
398
 
396
399
  if (options.renderTree === false) {
397
- return {
400
+ const routePolicyResult = {
398
401
  type: "route",
399
402
  status: 200,
400
403
  route: match.route,
@@ -404,6 +407,10 @@ export async function createRouteResolver(projectRoot, mode = "development", res
404
407
  cachePolicy,
405
408
  cacheKey: createRequestCacheKey(url),
406
409
  };
410
+ Object.defineProperty(routePolicyResult, "__evolitLoadedModules", {
411
+ value: { pageModule, layoutModules },
412
+ });
413
+ return routePolicyResult;
407
414
  }
408
415
 
409
416
  const pageComponent = assertServerComponent(
@@ -509,8 +516,8 @@ export async function createRouteResolver(projectRoot, mode = "development", res
509
516
  async resolveRoutePolicy(request) {
510
517
  return resolveMatchedRequest(request, { renderTree: false });
511
518
  },
512
- async resolveRequest(request) {
513
- return resolveMatchedRequest(request, { renderTree: true });
519
+ async resolveRequest(request, routePolicyResult = null) {
520
+ return resolveMatchedRequest(request, { renderTree: true, routePolicyResult });
514
521
  },
515
522
  };
516
523
  }
package/src/server.js CHANGED
@@ -78,7 +78,7 @@ async function createRecursiveDirectoryWatcher(rootDirectory, onChange) {
78
78
  let watcher;
79
79
  try {
80
80
  watcher = watch(directory, (eventType, fileName) => {
81
- onChange();
81
+ onChange(fileName ? path.join(directory, String(fileName)) : null);
82
82
 
83
83
  if (eventType === "rename" && fileName) {
84
84
  void watchNewDirectory(path.join(directory, fileName));
@@ -165,8 +165,12 @@ async function createServer(projectRoot, mode, explicitPort, options = {}) {
165
165
  let pendingInvalidation = null;
166
166
  let resolvePendingInvalidation = null;
167
167
  let invalidationPromise = Promise.resolve();
168
+ const pendingChangedPaths = new Set();
168
169
 
169
- function scheduleDevelopmentInvalidation() {
170
+ function scheduleDevelopmentInvalidation(changedPath = null) {
171
+ if (changedPath) {
172
+ pendingChangedPaths.add(path.resolve(changedPath));
173
+ }
170
174
  if (invalidationTimer) {
171
175
  clearTimeout(invalidationTimer);
172
176
  }
@@ -180,11 +184,15 @@ async function createServer(projectRoot, mode, explicitPort, options = {}) {
180
184
  invalidationTimer = setTimeout(() => {
181
185
  invalidationTimer = null;
182
186
  const resolve = resolvePendingInvalidation;
187
+ const changedPaths = pendingChangedPaths.size > 0
188
+ ? [...pendingChangedPaths]
189
+ : null;
190
+ pendingChangedPaths.clear();
183
191
  pendingInvalidation = null;
184
192
  resolvePendingInvalidation = null;
185
193
  invalidationPromise = invalidationPromise
186
194
  .catch(() => {})
187
- .then(() => deploymentRuntime.invalidateDevelopmentState());
195
+ .then(() => deploymentRuntime.invalidateDevelopmentState(changedPaths));
188
196
  invalidationPromise.then(
189
197
  () => {
190
198
  for (const socket of liveReloadSockets) {