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.
- package/dist/assets/build/generate.d.ts +8 -1
- package/dist/assets/build/generate.js +21 -1
- package/dist/assets/fonts/providers/index.js +12 -2
- package/dist/assets/types.d.ts +13 -0
- package/dist/assets/vite-plugin-assets.js +8 -0
- package/dist/cli/add/index.js +1 -1
- package/dist/cli/agent.d.ts +1 -0
- package/dist/cli/agent.js +11 -0
- package/dist/cli/dev/background.d.ts +3 -16
- package/dist/cli/dev/background.js +7 -93
- package/dist/cli/dev/index.d.ts +12 -0
- package/dist/cli/dev/index.js +30 -23
- package/dist/cli/dev/logs.js +2 -64
- package/dist/cli/dev/status.d.ts +2 -9
- package/dist/cli/dev/status.js +6 -24
- package/dist/cli/dev/stop.d.ts +2 -6
- package/dist/cli/dev/stop.js +6 -14
- package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
- package/dist/cli/install-package.js +1 -1
- package/dist/cli/preview/index.js +77 -3
- package/dist/cli/server.d.ts +60 -0
- package/dist/cli/server.js +274 -0
- package/dist/content/consts.d.ts +4 -0
- package/dist/content/consts.js +8 -0
- package/dist/content/content-layer.js +3 -3
- package/dist/content/data-store-writer.d.ts +1 -1
- package/dist/content/data-store-writer.js +4 -3
- package/dist/content/data-store.d.ts +3 -3
- package/dist/content/data-store.js +3 -3
- package/dist/content/mutable-data-store.d.ts +3 -3
- package/dist/content/mutable-data-store.js +5 -5
- package/dist/content/paths.d.ts +2 -1
- package/dist/content/paths.js +11 -0
- package/dist/content/runtime.d.ts +2 -0
- package/dist/content/runtime.js +2 -0
- package/dist/content/types-generator.js +1 -0
- package/dist/content/vite-plugin-content-assets.js +6 -1
- package/dist/content/vite-plugin-content-imports.js +12 -3
- package/dist/content/vite-plugin-content-virtual-mod.js +69 -31
- package/dist/core/app/entrypoints/index.d.ts +2 -0
- package/dist/core/app/entrypoints/index.js +14 -0
- package/dist/core/build/config-hash/index.d.ts +8 -0
- package/dist/core/build/config-hash/index.js +23 -0
- package/dist/core/build/config-hash/input.d.ts +199 -0
- package/dist/core/build/config-hash/input.js +43 -0
- package/dist/core/build/generate.d.ts +3 -1
- package/dist/core/build/generate.js +204 -62
- package/dist/core/build/incremental-content-collector.d.ts +10 -0
- package/dist/core/build/incremental-content-collector.js +33 -0
- package/dist/core/build/incremental-image-collector.d.ts +11 -0
- package/dist/core/build/incremental-image-collector.js +32 -0
- package/dist/core/build/incremental-metadata.d.ts +9 -0
- package/dist/core/build/incremental-metadata.js +17 -0
- package/dist/core/build/incremental.d.ts +123 -0
- package/dist/core/build/incremental.js +178 -0
- package/dist/core/build/index.d.ts +3 -0
- package/dist/core/build/index.js +6 -2
- package/dist/core/build/internal.d.ts +19 -0
- package/dist/core/build/lockfile/finder.d.ts +22 -0
- package/dist/core/build/lockfile/finder.js +45 -0
- package/dist/core/build/lockfile/hasher.d.ts +16 -0
- package/dist/core/build/lockfile/hasher.js +46 -0
- package/dist/core/build/lockfile/index.d.ts +9 -0
- package/dist/core/build/lockfile/index.js +16 -0
- package/dist/core/build/plugins/index.js +3 -1
- package/dist/core/build/plugins/plugin-incremental.d.ts +14 -0
- package/dist/core/build/plugins/plugin-incremental.js +134 -0
- package/dist/core/build/types.d.ts +2 -0
- package/dist/core/config/schemas/base.d.ts +7 -6
- package/dist/core/config/schemas/base.js +9 -1
- package/dist/core/config/schemas/defaults.d.ts +1 -0
- package/dist/core/config/schemas/defaults.js +1 -0
- package/dist/core/config/schemas/relative.d.ts +19 -10
- package/dist/core/constants.js +1 -1
- package/dist/core/create-vite.js +2 -1
- package/dist/core/dev/dev.js +5 -4
- package/dist/core/dev/lockfile.d.ts +8 -7
- package/dist/core/dev/lockfile.js +13 -13
- package/dist/core/encryption.d.ts +6 -0
- package/dist/core/encryption.js +14 -2
- package/dist/core/errors/default-handler.js +4 -5
- package/dist/core/fetch/index.js +1 -1
- package/dist/core/logger/config.d.ts +1 -1
- package/dist/core/logger/load.d.ts +3 -1
- package/dist/core/logger/load.js +3 -3
- package/dist/core/logger/utils.d.ts +6 -4
- package/dist/core/logger/utils.js +13 -5
- package/dist/core/logger/vite-plugin.js +1 -1
- package/dist/core/messages/runtime.js +1 -1
- package/dist/core/preview/static-preview-server.d.ts +2 -0
- package/dist/core/preview/static-preview-server.js +1 -0
- package/dist/core/routing/handler.js +1 -1
- package/dist/core/session/config.d.ts +2 -2
- package/dist/core/session/config.js +2 -1
- package/dist/core/session/provider-disabled.d.ts +2 -0
- package/dist/core/session/provider-disabled.js +7 -0
- package/dist/core/session/provider.d.ts +1 -0
- package/dist/core/session/provider.js +4 -0
- package/dist/core/session/utils.js +3 -0
- package/dist/core/session/vite-plugin.d.ts +3 -0
- package/dist/core/session/vite-plugin.js +41 -6
- package/dist/core/sync/index.js +5 -4
- package/dist/integrations/hooks.js +3 -1
- package/dist/runtime/prerender/static-paths.js +5 -1
- package/dist/runtime/server/render/util.js +3 -0
- package/dist/types/public/common.d.ts +1 -0
- package/dist/types/public/config.d.ts +73 -7
- package/dist/types/public/integrations.d.ts +29 -2
- package/dist/types/public/preview.d.ts +2 -0
- package/dist/vite-plugin-config-alias/index.js +1 -0
- package/dist/vite-plugin-environment/index.js +4 -1
- package/dist/vite-plugin-hmr-reload/index.js +60 -18
- 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 =
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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 (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
pageData.route.
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
case "
|
|
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 (
|
|
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
|
+
}
|