astro 7.1.5 → 7.2.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.
Files changed (113) hide show
  1. package/dist/assets/build/generate.d.ts +8 -1
  2. package/dist/assets/build/generate.js +21 -1
  3. package/dist/assets/fonts/providers/index.js +12 -2
  4. package/dist/assets/types.d.ts +13 -0
  5. package/dist/assets/vite-plugin-assets.js +8 -0
  6. package/dist/cli/add/index.js +1 -1
  7. package/dist/cli/agent.d.ts +1 -0
  8. package/dist/cli/agent.js +11 -0
  9. package/dist/cli/dev/background.d.ts +3 -16
  10. package/dist/cli/dev/background.js +7 -93
  11. package/dist/cli/dev/index.d.ts +12 -0
  12. package/dist/cli/dev/index.js +30 -23
  13. package/dist/cli/dev/logs.js +2 -64
  14. package/dist/cli/dev/status.d.ts +2 -9
  15. package/dist/cli/dev/status.js +6 -24
  16. package/dist/cli/dev/stop.d.ts +2 -6
  17. package/dist/cli/dev/stop.js +6 -14
  18. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  19. package/dist/cli/install-package.js +1 -1
  20. package/dist/cli/preview/index.js +77 -3
  21. package/dist/cli/server.d.ts +60 -0
  22. package/dist/cli/server.js +274 -0
  23. package/dist/content/consts.d.ts +4 -0
  24. package/dist/content/consts.js +8 -0
  25. package/dist/content/content-layer.js +3 -3
  26. package/dist/content/data-store-writer.d.ts +1 -1
  27. package/dist/content/data-store-writer.js +4 -3
  28. package/dist/content/data-store.d.ts +3 -3
  29. package/dist/content/data-store.js +3 -3
  30. package/dist/content/mutable-data-store.d.ts +3 -3
  31. package/dist/content/mutable-data-store.js +5 -5
  32. package/dist/content/paths.d.ts +2 -1
  33. package/dist/content/paths.js +11 -0
  34. package/dist/content/runtime.d.ts +2 -0
  35. package/dist/content/runtime.js +2 -0
  36. package/dist/content/types-generator.js +1 -0
  37. package/dist/content/vite-plugin-content-assets.js +6 -1
  38. package/dist/content/vite-plugin-content-imports.js +12 -3
  39. package/dist/content/vite-plugin-content-virtual-mod.js +69 -31
  40. package/dist/core/app/entrypoints/index.d.ts +2 -0
  41. package/dist/core/app/entrypoints/index.js +14 -0
  42. package/dist/core/build/config-hash/index.d.ts +8 -0
  43. package/dist/core/build/config-hash/index.js +23 -0
  44. package/dist/core/build/config-hash/input.d.ts +199 -0
  45. package/dist/core/build/config-hash/input.js +43 -0
  46. package/dist/core/build/generate.d.ts +3 -1
  47. package/dist/core/build/generate.js +204 -62
  48. package/dist/core/build/incremental-content-collector.d.ts +10 -0
  49. package/dist/core/build/incremental-content-collector.js +33 -0
  50. package/dist/core/build/incremental-image-collector.d.ts +11 -0
  51. package/dist/core/build/incremental-image-collector.js +32 -0
  52. package/dist/core/build/incremental-metadata.d.ts +9 -0
  53. package/dist/core/build/incremental-metadata.js +17 -0
  54. package/dist/core/build/incremental.d.ts +123 -0
  55. package/dist/core/build/incremental.js +178 -0
  56. package/dist/core/build/index.d.ts +3 -0
  57. package/dist/core/build/index.js +6 -2
  58. package/dist/core/build/internal.d.ts +19 -0
  59. package/dist/core/build/lockfile/finder.d.ts +22 -0
  60. package/dist/core/build/lockfile/finder.js +45 -0
  61. package/dist/core/build/lockfile/hasher.d.ts +16 -0
  62. package/dist/core/build/lockfile/hasher.js +46 -0
  63. package/dist/core/build/lockfile/index.d.ts +9 -0
  64. package/dist/core/build/lockfile/index.js +16 -0
  65. package/dist/core/build/plugins/index.js +3 -1
  66. package/dist/core/build/plugins/plugin-incremental.d.ts +14 -0
  67. package/dist/core/build/plugins/plugin-incremental.js +134 -0
  68. package/dist/core/build/types.d.ts +2 -0
  69. package/dist/core/config/schemas/base.d.ts +7 -6
  70. package/dist/core/config/schemas/base.js +9 -1
  71. package/dist/core/config/schemas/defaults.d.ts +1 -0
  72. package/dist/core/config/schemas/defaults.js +1 -0
  73. package/dist/core/config/schemas/relative.d.ts +19 -10
  74. package/dist/core/constants.js +1 -1
  75. package/dist/core/create-vite.js +2 -1
  76. package/dist/core/dev/dev.js +5 -4
  77. package/dist/core/dev/lockfile.d.ts +8 -7
  78. package/dist/core/dev/lockfile.js +13 -13
  79. package/dist/core/encryption.d.ts +6 -0
  80. package/dist/core/encryption.js +14 -2
  81. package/dist/core/errors/default-handler.js +4 -5
  82. package/dist/core/fetch/index.js +1 -1
  83. package/dist/core/logger/config.d.ts +1 -1
  84. package/dist/core/logger/load.d.ts +3 -1
  85. package/dist/core/logger/load.js +3 -3
  86. package/dist/core/logger/utils.d.ts +6 -4
  87. package/dist/core/logger/utils.js +13 -5
  88. package/dist/core/logger/vite-plugin.js +1 -1
  89. package/dist/core/messages/runtime.js +1 -1
  90. package/dist/core/preview/static-preview-server.d.ts +2 -0
  91. package/dist/core/preview/static-preview-server.js +1 -0
  92. package/dist/core/routing/handler.js +1 -1
  93. package/dist/core/session/config.d.ts +2 -2
  94. package/dist/core/session/config.js +2 -1
  95. package/dist/core/session/provider-disabled.d.ts +2 -0
  96. package/dist/core/session/provider-disabled.js +7 -0
  97. package/dist/core/session/provider.d.ts +1 -0
  98. package/dist/core/session/provider.js +4 -0
  99. package/dist/core/session/utils.js +3 -0
  100. package/dist/core/session/vite-plugin.d.ts +3 -0
  101. package/dist/core/session/vite-plugin.js +41 -6
  102. package/dist/core/sync/index.js +5 -4
  103. package/dist/integrations/hooks.js +3 -1
  104. package/dist/runtime/prerender/static-paths.js +5 -1
  105. package/dist/runtime/server/render/util.js +3 -0
  106. package/dist/types/public/common.d.ts +1 -0
  107. package/dist/types/public/config.d.ts +73 -7
  108. package/dist/types/public/integrations.d.ts +29 -2
  109. package/dist/types/public/preview.d.ts +2 -0
  110. package/dist/vite-plugin-config-alias/index.js +1 -0
  111. package/dist/vite-plugin-environment/index.js +4 -1
  112. package/dist/vite-plugin-hmr-reload/index.js +60 -18
  113. package/package.json +2 -3
@@ -1,12 +1,14 @@
1
1
  import nodeFs from "node:fs";
2
2
  import os from "node:os";
3
+ import { fileURLToPath } from "node:url";
3
4
  import PLimit from "p-limit";
4
5
  import PQueue from "p-queue";
5
6
  import colors from "piccolore";
6
7
  import {
7
8
  generateImagesForPath,
8
9
  getStaticImageList,
9
- prepareAssetsGenerationEnv
10
+ prepareAssetsGenerationEnv,
11
+ restoreStaticImages
10
12
  } from "../../assets/build/generate.js";
11
13
  import {
12
14
  appendForwardSlash,
@@ -18,6 +20,7 @@ import {
18
20
  trimSlashes
19
21
  } from "../../core/path.js";
20
22
  import { runHookBuildGenerated, toIntegrationResolvedRoute } from "../../integrations/hooks.js";
23
+ import { hashCryptoKey } from "../encryption.js";
21
24
  import { AstroError, AstroErrorData } from "../errors/index.js";
22
25
  import { getRedirectLocationOrThrow } from "../redirects/index.js";
23
26
  import { createRequest } from "../request.js";
@@ -27,6 +30,14 @@ import { matchRoute } from "../routing/match.js";
27
30
  import { getOutputFilename } from "../output-filename.js";
28
31
  import { getOutFile, getOutFolder } from "./common.js";
29
32
  import { createDefaultPrerenderer } from "./default-prerenderer.js";
33
+ import {
34
+ beginContentEntryCollection,
35
+ endContentEntryCollection
36
+ } from "./incremental-content-collector.js";
37
+ import { beginImageCollection, endImageCollection } from "./incremental-image-collector.js";
38
+ import { IncrementalBuildCache } from "./incremental.js";
39
+ import { computeConfigHash } from "./config-hash/index.js";
40
+ import { computeLockfileHash } from "./lockfile/index.js";
30
41
  import { hasPrerenderedPages } from "./internal.js";
31
42
  import { getTimeStat, shouldAppendForwardSlash } from "./util.js";
32
43
  async function generatePages(options, internals, prerenderOutputDir) {
@@ -64,12 +75,38 @@ async function generatePages(options, internals, prerenderOutputDir) {
64
75
  ${colors.bgGreen(colors.black(` ${verb} static routes `))}`);
65
76
  const routeToHeaders = /* @__PURE__ */ new Map();
66
77
  let staticImageList = getStaticImageList();
78
+ let cache = null;
79
+ if (options.settings.config.experimental.incrementalBuild) {
80
+ if (options.settings.config.build.concurrency > 1) {
81
+ logger.warn(
82
+ "build",
83
+ "The incremental build cache is disabled because `build.concurrency` is greater than 1."
84
+ );
85
+ } else {
86
+ const [configHash, lockfileHash, keyDigest] = await Promise.all([
87
+ computeConfigHash(options.settings.config),
88
+ computeLockfileHash(fileURLToPath(options.settings.config.root)),
89
+ options.key.then(hashCryptoKey)
90
+ ]);
91
+ cache = IncrementalBuildCache.load(
92
+ options.settings,
93
+ configHash,
94
+ lockfileHash,
95
+ keyDigest,
96
+ internals.contentEntryRenderHashes ?? /* @__PURE__ */ new Map(),
97
+ options.force
98
+ );
99
+ }
100
+ }
67
101
  try {
68
102
  const pathsWithRoutes = await prerenderer.getStaticPaths();
69
103
  const hasI18nDomains = ssr && options.settings.config.i18n?.domains && Object.keys(options.settings.config.i18n.domains).length > 0;
70
104
  const { config } = options.settings;
71
105
  const builtPaths = /* @__PURE__ */ new Set();
72
- const filteredPaths = pathsWithRoutes.filter(({ pathname, route }) => {
106
+ const filteredPaths = [];
107
+ const fallbackPaths = [];
108
+ for (const pathWithRoute of pathsWithRoutes) {
109
+ const { pathname, route } = pathWithRoute;
73
110
  if (hasI18nDomains && route.prerender) {
74
111
  throw new AstroError({
75
112
  ...AstroErrorData.NoPrerenderedRoutesWithDomains,
@@ -79,79 +116,108 @@ ${colors.bgGreen(colors.black(` ${verb} static routes `))}`);
79
116
  const normalized = removeTrailingForwardSlash(pathname);
80
117
  if (!builtPaths.has(normalized)) {
81
118
  builtPaths.add(normalized);
82
- return true;
83
- }
84
- const matchedRoute = matchRoute(decodeURI(pathname), options.routesList);
85
- if (!matchedRoute) {
86
- return false;
87
- }
88
- if (matchedRoute === route) {
89
- return true;
90
- }
91
- if (config.prerenderConflictBehavior === "error") {
92
- throw new AstroError({
93
- ...AstroErrorData.PrerenderRouteConflict,
94
- message: AstroErrorData.PrerenderRouteConflict.message(
95
- matchedRoute.route,
96
- route.route,
97
- normalized
98
- ),
99
- hint: AstroErrorData.PrerenderRouteConflict.hint(matchedRoute.route, route.route)
100
- });
101
- } else if (config.prerenderConflictBehavior === "warn") {
102
- const msg = AstroErrorData.PrerenderRouteConflict.message(
103
- matchedRoute.route,
104
- route.route,
105
- normalized
106
- );
107
- logger.warn("build", msg);
119
+ } else {
120
+ const matchedRoute = matchRoute(decodeURI(pathname), options.routesList);
121
+ if (!matchedRoute) {
122
+ continue;
123
+ }
124
+ if (matchedRoute !== route) {
125
+ if (config.prerenderConflictBehavior === "error") {
126
+ throw new AstroError({
127
+ ...AstroErrorData.PrerenderRouteConflict,
128
+ message: AstroErrorData.PrerenderRouteConflict.message(
129
+ matchedRoute.route,
130
+ route.route,
131
+ normalized
132
+ ),
133
+ hint: AstroErrorData.PrerenderRouteConflict.hint(matchedRoute.route, route.route)
134
+ });
135
+ } else if (config.prerenderConflictBehavior === "warn") {
136
+ const msg = AstroErrorData.PrerenderRouteConflict.message(
137
+ matchedRoute.route,
138
+ route.route,
139
+ normalized
140
+ );
141
+ logger.warn("build", msg);
142
+ }
143
+ continue;
144
+ }
108
145
  }
109
- return false;
110
- });
146
+ const paths = route.type === "fallback" ? fallbackPaths : filteredPaths;
147
+ paths.push(pathWithRoute);
148
+ }
149
+ const generationPhases = [filteredPaths, fallbackPaths];
111
150
  if (config.build.concurrency > 1) {
112
151
  const limit = PLimit(config.build.concurrency);
113
152
  const BATCH_SIZE = 1e5;
114
- for (let i = 0; i < filteredPaths.length; i += BATCH_SIZE) {
115
- const batch = filteredPaths.slice(i, i + BATCH_SIZE);
116
- const promises = [];
117
- for (const { pathname, route } of batch) {
118
- promises.push(
119
- limit(
153
+ for (const paths of generationPhases) {
154
+ for (let i = 0; i < paths.length; i += BATCH_SIZE) {
155
+ const promises = paths.slice(i, i + BATCH_SIZE).map(
156
+ ({ pathname, route, cacheKey }) => limit(
120
157
  () => generatePathWithPrerenderer(
121
158
  prerenderer,
122
159
  pathname,
123
160
  route,
124
161
  options,
162
+ internals,
125
163
  routeToHeaders,
126
- logger
164
+ logger,
165
+ cache,
166
+ cacheKey
127
167
  )
128
168
  )
129
169
  );
170
+ await Promise.all(promises);
130
171
  }
131
- await Promise.all(promises);
132
172
  }
133
173
  } else {
134
- for (const { pathname, route } of filteredPaths) {
135
- await generatePathWithPrerenderer(
136
- prerenderer,
137
- pathname,
138
- route,
139
- options,
140
- routeToHeaders,
141
- logger
142
- );
174
+ for (const paths of generationPhases) {
175
+ for (const { pathname, route, cacheKey } of paths) {
176
+ await generatePathWithPrerenderer(
177
+ prerenderer,
178
+ pathname,
179
+ route,
180
+ options,
181
+ internals,
182
+ routeToHeaders,
183
+ logger,
184
+ cache,
185
+ cacheKey
186
+ );
187
+ }
143
188
  }
144
189
  }
145
- for (const { route: generatedRoute } of filteredPaths) {
146
- if (generatedRoute.distURL && generatedRoute.distURL.length > 0) {
147
- for (const pageData of Object.values(options.allPages)) {
148
- if (pageData.route.route === generatedRoute.route && pageData.route.component === generatedRoute.component) {
149
- pageData.route.distURL = generatedRoute.distURL;
150
- break;
190
+ for (const paths of generationPhases) {
191
+ for (const { route: generatedRoute } of paths) {
192
+ if (generatedRoute.distURL && generatedRoute.distURL.length > 0) {
193
+ for (const pageData of Object.values(options.allPages)) {
194
+ if (pageData.route.route === generatedRoute.route && pageData.route.component === generatedRoute.component) {
195
+ pageData.route.distURL = generatedRoute.distURL;
196
+ break;
197
+ }
151
198
  }
152
199
  }
153
200
  }
154
201
  }
202
+ if (cache) {
203
+ const orphans = cache.findOrphanedFiles();
204
+ let pruned = 0;
205
+ for (const orphanFile of orphans) {
206
+ try {
207
+ await cache.deleteOutputFile(options.settings, orphanFile);
208
+ pruned++;
209
+ } catch (err) {
210
+ logger.warn(
211
+ "build",
212
+ `Could not prune stale incremental cache file ${orphanFile}: ${err}`
213
+ );
214
+ }
215
+ }
216
+ if (pruned > 0) {
217
+ logger.info("build", `Pruned ${pruned} stale file(s) from the incremental cache.`);
218
+ }
219
+ cache.writeManifest(options.settings);
220
+ }
155
221
  staticImageList = getStaticImageList();
156
222
  if (prerenderer.collectStaticImages) {
157
223
  const adapterImages = await prerenderer.collectStaticImages();
@@ -199,6 +265,9 @@ ${colors.bgGreen(colors.black(` ${verb} static routes `))}`);
199
265
  });
200
266
  }
201
267
  const THRESHOLD_SLOW_RENDER_TIME_MS = 500;
268
+ function normalizePrerenderResult(result) {
269
+ return result instanceof Response ? { response: result } : result;
270
+ }
202
271
  async function renderPath({
203
272
  prerenderer,
204
273
  pathname,
@@ -232,7 +301,8 @@ async function renderPath({
232
301
  options.origin,
233
302
  config.build.format,
234
303
  config.trailingSlash,
235
- route.type
304
+ route.type,
305
+ route.isIndex
236
306
  );
237
307
  const request = createRequest({
238
308
  url,
@@ -242,8 +312,13 @@ async function renderPath({
242
312
  routePattern: route.component
243
313
  });
244
314
  let response;
315
+ let metadata;
245
316
  try {
246
- response = await prerenderer.render(request, { routeData: route });
317
+ const rendered = normalizePrerenderResult(
318
+ await prerenderer.render(request, { routeData: route })
319
+ );
320
+ response = rendered.response;
321
+ metadata = rendered.metadata;
247
322
  } catch (err) {
248
323
  logger.error("build", `Caught error rendering ${pathname}: ${err}`);
249
324
  if (err && !AstroError.is(err) && !err.id && typeof err === "object") {
@@ -292,16 +367,65 @@ async function renderPath({
292
367
  routeToHeaders.set(pathname, { headers: responseHeaders, route: integrationRoute });
293
368
  }
294
369
  if (checkPublicConflict(outFile, route, options.settings, logger)) return null;
295
- return { body, outFile, outFolder };
370
+ return { body, outFile, outFolder, metadata };
296
371
  }
297
- async function generatePathWithPrerenderer(prerenderer, pathname, route, options, routeToHeaders, logger) {
372
+ async function generatePathWithPrerenderer(prerenderer, pathname, route, options, internals, routeToHeaders, logger, cache, cacheKey) {
298
373
  const timeStart = performance.now();
299
374
  const { config } = options.settings;
300
375
  const filePath = getOutputFilename(config.build.format, pathname, route);
376
+ const encodedPath = encodeURI(pathname);
377
+ const outFolder = getOutFolder(options.settings, encodedPath, route);
378
+ const outFile = getOutFile(config.build.format, outFolder, encodedPath, route);
379
+ const relativeOutFile = outFile.href.slice(config.outDir.href.length);
380
+ const dependencyHash = internals.pageDependencyHashes?.get(route.component) ?? "";
381
+ const hasServerIsland = internals.serverIslandPageComponents?.has(route.component) ?? false;
382
+ if (cacheKey !== void 0 && cache?.canSkip(route.component, pathname, dependencyHash, cacheKey, hasServerIsland)) {
383
+ const existsInDist = nodeFs.existsSync(outFile);
384
+ const restored = !existsInDist && await cache.restoreOutputFile(options.settings, relativeOutFile, outFile);
385
+ if (existsInDist || restored) {
386
+ const restoredImages = cache.previousStaticImages(route.component, pathname);
387
+ if (restoredImages) restoreStaticImages(restoredImages);
388
+ const restoredHeaders = cache.previousHeaders(route.component, pathname);
389
+ if (restoredHeaders && options.settings.adapter?.adapterFeatures?.staticHeaders) {
390
+ routeToHeaders.set(pathname, {
391
+ headers: new Headers(restoredHeaders),
392
+ route: toIntegrationResolvedRoute(route, config.trailingSlash)
393
+ });
394
+ }
395
+ cache.record(
396
+ route.component,
397
+ dependencyHash,
398
+ pathname,
399
+ cacheKey,
400
+ relativeOutFile,
401
+ cache.previousContentEntryKeys(route.component, pathname),
402
+ restoredImages,
403
+ restoredHeaders
404
+ );
405
+ if (route.type === "page") {
406
+ addPageName(pathname, options);
407
+ }
408
+ if (route.distURL) {
409
+ route.distURL.push(outFile);
410
+ } else {
411
+ route.distURL = [outFile];
412
+ }
413
+ logger.info(null, ` ${colors.green("\u251C\u2500")} ${colors.dim(filePath)}`, false);
414
+ logger.info(
415
+ "SKIP_FORMAT",
416
+ restored ? ` ${colors.green("(restored)")}` : ` ${colors.green("(cached)")}`
417
+ );
418
+ return;
419
+ }
420
+ }
301
421
  logger.info(null, ` ${colors.blue("\u251C\u2500")} ${colors.dim(filePath)}`, false);
302
422
  if (route.type === "page") {
303
423
  addPageName(pathname, options);
304
424
  }
425
+ if (cache) {
426
+ beginContentEntryCollection();
427
+ beginImageCollection();
428
+ }
305
429
  const result = await renderPath({
306
430
  prerenderer,
307
431
  pathname,
@@ -310,12 +434,30 @@ async function generatePathWithPrerenderer(prerenderer, pathname, route, options
310
434
  routeToHeaders,
311
435
  logger
312
436
  });
437
+ const collectedContentEntryKeys = cache ? endContentEntryCollection() : void 0;
438
+ const collectedStaticImages = cache ? endImageCollection() : void 0;
439
+ const contentEntryKeys = result?.metadata?.contentEntryKeys ?? collectedContentEntryKeys;
440
+ const staticImages = result?.metadata?.staticImages ?? collectedStaticImages;
441
+ const headers = cache ? [...routeToHeaders.get(pathname)?.headers ?? []] : void 0;
313
442
  if (!result) {
314
443
  logRenderTime(logger, timeStart, true);
315
444
  return;
316
445
  }
317
446
  await nodeFs.promises.mkdir(result.outFolder, { recursive: true });
318
447
  await nodeFs.promises.writeFile(result.outFile, result.body);
448
+ if (cache && cacheKey !== void 0) {
449
+ await cache.writeOutputFile(options.settings, relativeOutFile, result.body);
450
+ cache.record(
451
+ route.component,
452
+ dependencyHash,
453
+ pathname,
454
+ cacheKey,
455
+ relativeOutFile,
456
+ contentEntryKeys,
457
+ staticImages,
458
+ headers
459
+ );
460
+ }
319
461
  logRenderTime(logger, timeStart, false);
320
462
  }
321
463
  function logRenderTime(logger, timeStart, notCreated) {
@@ -331,11 +473,11 @@ function addPageName(pathname, opts) {
331
473
  const pageName = shouldAppendForwardSlash(trailingSlash, buildFormat) ? pathname.replace(/\/?$/, "/").replace(/^\//, "") : pathname.replace(/^\//, "");
332
474
  opts.pageNames.push(pageName);
333
475
  }
334
- function getUrlForPath(pathname, base, origin, format, trailingSlash, routeType) {
476
+ function getUrlForPath(pathname, base, origin, format, trailingSlash, routeType, isIndex) {
335
477
  let ending;
336
- switch (format) {
337
- case "directory":
338
- case "preserve": {
478
+ const effectiveFormat = format === "preserve" ? isIndex ? "directory" : "file" : format;
479
+ switch (effectiveFormat) {
480
+ case "directory": {
339
481
  ending = trailingSlash === "never" ? "" : "/";
340
482
  break;
341
483
  }
@@ -347,7 +489,7 @@ function getUrlForPath(pathname, base, origin, format, trailingSlash, routeType)
347
489
  }
348
490
  let buildPathname;
349
491
  if (pathname === "/" || pathname === "") {
350
- if (format === "file") {
492
+ if (effectiveFormat === "file") {
351
493
  buildPathname = joinPaths(base, "index.html");
352
494
  } else {
353
495
  buildPathname = collapseDuplicateTrailingSlashes(base + ending, trailingSlash !== "never");
@@ -0,0 +1,10 @@
1
+ /** Start collecting the content entries rendered on the current path. */
2
+ export declare function beginContentEntryCollection(): void;
3
+ /** Record that a content entry was rendered, keyed by its root-relative `filePath`. */
4
+ export declare function recordContentEntryRender(filePath: string | undefined): void;
5
+ /**
6
+ * Finish collection and return the rendered entries, or `undefined` when no
7
+ * collection was active (so callers can distinguish "nothing rendered" from
8
+ * "not tracked").
9
+ */
10
+ export declare function endContentEntryCollection(): string[] | undefined;
@@ -0,0 +1,33 @@
1
+ const COLLECTOR_KEY = /* @__PURE__ */ Symbol.for("astro:incremental-content-entries");
2
+ function collector() {
3
+ const host = globalThis;
4
+ let value = host[COLLECTOR_KEY];
5
+ if (!value) {
6
+ value = { current: void 0 };
7
+ Object.defineProperty(host, COLLECTOR_KEY, {
8
+ value,
9
+ configurable: false,
10
+ writable: false,
11
+ enumerable: false
12
+ });
13
+ }
14
+ return value;
15
+ }
16
+ function beginContentEntryCollection() {
17
+ collector().current = /* @__PURE__ */ new Set();
18
+ }
19
+ function recordContentEntryRender(filePath) {
20
+ if (!filePath) return;
21
+ collector().current?.add(filePath);
22
+ }
23
+ function endContentEntryCollection() {
24
+ const c = collector();
25
+ const entries = c.current;
26
+ c.current = void 0;
27
+ return entries ? [...entries] : void 0;
28
+ }
29
+ export {
30
+ beginContentEntryCollection,
31
+ endContentEntryCollection,
32
+ recordContentEntryRender
33
+ };
@@ -0,0 +1,11 @@
1
+ import type { SerializedStaticImage } from '../../assets/types.js';
2
+ /** Start collecting the image transforms resolved on the current path. */
3
+ export declare function beginImageCollection(): void;
4
+ /** Record an image transform resolved while rendering the current path. */
5
+ export declare function recordStaticImage(image: SerializedStaticImage): void;
6
+ /**
7
+ * Finish collection and return the collected transforms, or `undefined` when no
8
+ * collection was active (so callers can distinguish "no images" from
9
+ * "not tracked").
10
+ */
11
+ export declare function endImageCollection(): SerializedStaticImage[] | undefined;
@@ -0,0 +1,32 @@
1
+ const COLLECTOR_KEY = /* @__PURE__ */ Symbol.for("astro:incremental-static-images");
2
+ function collector() {
3
+ const host = globalThis;
4
+ let value = host[COLLECTOR_KEY];
5
+ if (!value) {
6
+ value = { current: void 0 };
7
+ Object.defineProperty(host, COLLECTOR_KEY, {
8
+ value,
9
+ configurable: false,
10
+ writable: false,
11
+ enumerable: false
12
+ });
13
+ }
14
+ return value;
15
+ }
16
+ function beginImageCollection() {
17
+ collector().current = [];
18
+ }
19
+ function recordStaticImage(image) {
20
+ collector().current?.push(image);
21
+ }
22
+ function endImageCollection() {
23
+ const c = collector();
24
+ const images = c.current;
25
+ c.current = void 0;
26
+ return images;
27
+ }
28
+ export {
29
+ beginImageCollection,
30
+ endImageCollection,
31
+ recordStaticImage
32
+ };
@@ -0,0 +1,9 @@
1
+ export declare const ASTRO_INCREMENTAL_META_KEY = "astroIncremental";
2
+ export declare function createContentDataIncrementalMetadata(): {
3
+ astroIncremental: {
4
+ kind: string;
5
+ };
6
+ };
7
+ export declare function isContentDataIncrementalModule(info: {
8
+ meta?: Record<string, any>;
9
+ } | null | undefined): boolean;
@@ -0,0 +1,17 @@
1
+ const ASTRO_INCREMENTAL_META_KEY = "astroIncremental";
2
+ function createContentDataIncrementalMetadata() {
3
+ return {
4
+ [ASTRO_INCREMENTAL_META_KEY]: {
5
+ kind: "content-data"
6
+ }
7
+ };
8
+ }
9
+ function isContentDataIncrementalModule(info) {
10
+ const metadata = info?.meta?.[ASTRO_INCREMENTAL_META_KEY];
11
+ return metadata?.kind === "content-data";
12
+ }
13
+ export {
14
+ ASTRO_INCREMENTAL_META_KEY,
15
+ createContentDataIncrementalMetadata,
16
+ isContentDataIncrementalModule
17
+ };
@@ -0,0 +1,123 @@
1
+ import type { SerializedStaticImage } from '../../assets/types.js';
2
+ import type { AstroSettings } from '../../types/astro.js';
3
+ export interface IncrementalPathEntry {
4
+ cacheKey: string;
5
+ outputFile: string;
6
+ /**
7
+ * Render-graph hashes of the content entries this path rendered, keyed by the
8
+ * entry's root-relative `filePath`. A change to any of these invalidates the
9
+ * path even when its template and data are unchanged, since content entries
10
+ * render behind `content-data` bridges the per-route hash cannot cross.
11
+ */
12
+ contentHashes?: Record<string, string>;
13
+ /**
14
+ * Optimized-image transforms this path resolved while rendering. Registered
15
+ * transforms are drained into the output after generation, but `dist/` is
16
+ * emptied each build, so a skipped path replays these into the global list to
17
+ * keep the images its restored HTML references from 404ing.
18
+ */
19
+ staticImages?: SerializedStaticImage[];
20
+ /**
21
+ * Response header name/value pairs a `staticHeaders` adapter collected for this
22
+ * path (chiefly the CSP header when delivered as a header rather than a `<meta>`
23
+ * tag). The `astro:build:generated` hook writes these to a static headers file,
24
+ * so a skipped path replays them to keep its route in that file.
25
+ */
26
+ headers?: [string, string][];
27
+ }
28
+ export interface IncrementalRouteEntry {
29
+ dependencyHash: string;
30
+ paths: Record<string, IncrementalPathEntry>;
31
+ }
32
+ /**
33
+ * On-disk shape of the incremental build cache.
34
+ */
35
+ export interface IncrementalManifest {
36
+ version: number;
37
+ /**
38
+ * Hash of the output-affecting subset of the resolved config. A mismatch
39
+ * invalidates the whole cache, since config baked into compiled output or
40
+ * inlined via Vite cannot be seen by the per-route dependency hash.
41
+ */
42
+ configHash: string;
43
+ /**
44
+ * Hash of the project's lockfiles. Externalized dependencies are leaf nodes
45
+ * in the bundle graph with no code and a versionless id, so the per-route
46
+ * dependency hash cannot see when a (possibly transitive) dependency changes.
47
+ * A mismatch invalidates the whole cache.
48
+ */
49
+ lockfileHash: string;
50
+ /**
51
+ * Hash of the server-island encryption key. A page's island props are baked
52
+ * into its HTML as ciphertext bound to this key, so a restored page whose key
53
+ * has changed would be undecryptable at runtime. Server-island pages are only
54
+ * reused when this matches; it does not affect pages without islands.
55
+ */
56
+ keyDigest: string;
57
+ routes: Record<string, IncrementalRouteEntry>;
58
+ }
59
+ /**
60
+ * Tracks which prerendered paths can be reused from a previous build.
61
+ *
62
+ * The invalidation logic (`canSkip`, `record`, `findOrphanedFiles`) is pure and
63
+ * operates on the previous and next manifests held in memory. Disk access is
64
+ * confined to `load` and the output-file methods.
65
+ */
66
+ export declare class IncrementalBuildCache {
67
+ #private;
68
+ constructor(configHash: string, lockfileHash: string, keyDigest: string, contentEntryHashes?: Map<string, string>, previous?: IncrementalManifest | null);
69
+ /**
70
+ * Load the cache from disk. When no valid manifest exists (missing, wrong
71
+ * version, or a config or lockfile hash mismatch) the returned cache has no
72
+ * previous build, so every path is rendered as a full build.
73
+ *
74
+ * `contentEntryHashes` is this build's map of content-entry render hashes,
75
+ * used to detect when the content a path renders has changed.
76
+ *
77
+ * `force` ignores any existing manifest so every path is rebuilt, while still
78
+ * recording a fresh cache for the next build.
79
+ */
80
+ static load(settings: AstroSettings, configHash: string, lockfileHash: string, keyDigest: string, contentEntryHashes?: Map<string, string>, force?: boolean): IncrementalBuildCache;
81
+ /**
82
+ * Determine if a path can be reused from the previous build. A path is
83
+ * skippable when:
84
+ * 1. It returned a cacheKey in this build.
85
+ * 2. The previous cache has an entry for the route.
86
+ * 3. The route's dependency hash matches the previous build (template code is identical).
87
+ * 4. The previous cache has an entry for this exact path.
88
+ * 5. The path's cacheKey matches the previous build (user data is identical).
89
+ * 6. Every content entry the path rendered last build still has a matching
90
+ * render hash (imported components inside that content are unchanged).
91
+ * 7. If the path renders a server island, the encryption key is unchanged, so
92
+ * the ciphertext baked into the restored HTML is still decryptable.
93
+ */
94
+ canSkip(routeComponent: string, pathname: string, dependencyHash: string, cacheKey: string, hasServerIsland?: boolean): boolean;
95
+ /**
96
+ * The content entries a path rendered in the previous build, so a skipped path
97
+ * can carry its content-entry tracking forward without re-rendering.
98
+ */
99
+ previousContentEntryKeys(routeComponent: string, pathname: string): string[] | undefined;
100
+ /**
101
+ * The image transforms a path resolved in the previous build, so a skipped
102
+ * path can replay them and carry them forward without re-rendering.
103
+ */
104
+ previousStaticImages(routeComponent: string, pathname: string): SerializedStaticImage[] | undefined;
105
+ /**
106
+ * The response headers a path collected in the previous build, so a skipped
107
+ * path can replay them into a `staticHeaders` adapter's headers file.
108
+ */
109
+ previousHeaders(routeComponent: string, pathname: string): [string, string][] | undefined;
110
+ /** Record a path in the next manifest so a later build can skip or prune it. */
111
+ record(routeComponent: string, dependencyHash: string, pathname: string, cacheKey: string, outputFile: string, contentEntryKeys?: string[], staticImages?: SerializedStaticImage[], headers?: [string, string][]): void;
112
+ /**
113
+ * Cache copies recorded in the previous build that are no longer keyed in this
114
+ * one, either because the path was removed from `getStaticPaths()` or dropped
115
+ * its `cacheKey`. Their stored copies are stale and should be pruned. Paths
116
+ * that are still keyed keep their copies, even when the `cacheKey` changed.
117
+ */
118
+ findOrphanedFiles(): string[];
119
+ writeManifest(settings: AstroSettings): void;
120
+ restoreOutputFile(settings: AstroSettings, outputFile: string, destination: URL): Promise<boolean>;
121
+ writeOutputFile(settings: AstroSettings, outputFile: string, body: string | Uint8Array): Promise<void>;
122
+ deleteOutputFile(settings: AstroSettings, outputFile: string): Promise<void>;
123
+ }