astro 7.2.0 → 7.2.1
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/utils/resolveImports.d.ts +0 -1
- package/dist/assets/utils/resolveImports.js +1 -4
- package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
- package/dist/content/content-layer.js +63 -3
- package/dist/content/loaders/file.js +1 -1
- package/dist/content/loaders/glob.js +3 -2
- package/dist/content/mutable-data-store.js +3 -3
- package/dist/content/runtime.d.ts +1 -0
- package/dist/content/runtime.js +6 -2
- package/dist/core/app/types.d.ts +2 -0
- package/dist/core/build/plugins/plugin-incremental.js +19 -1
- package/dist/core/build/plugins/plugin-manifest.js +11 -2
- package/dist/core/config/settings.js +1 -1
- package/dist/core/constants.js +1 -1
- package/dist/core/dev/dev.js +1 -1
- package/dist/core/fetch/fetch-state.js +1 -0
- package/dist/core/messages/runtime.js +1 -1
- package/dist/core/middleware/vite-plugin.d.ts +1 -1
- package/dist/core/middleware/vite-plugin.js +11 -24
- package/dist/core/routing/dev.d.ts +3 -1
- package/dist/core/routing/dev.js +10 -2
- package/dist/prefetch/index.js +18 -1
- package/dist/prefetch/speculation-rules.d.ts +6 -0
- package/dist/prefetch/speculation-rules.js +22 -0
- package/dist/runtime/server/render/head.js +10 -0
- package/dist/transitions/swap-functions.js +49 -4
- package/dist/types/public/config.d.ts +22 -7
- package/dist/types/public/internal.d.ts +2 -0
- package/dist/vite-plugin-app/app.d.ts +3 -1
- package/dist/vite-plugin-app/app.js +4 -3
- package/package.json +1 -1
|
@@ -6,4 +6,3 @@
|
|
|
6
6
|
* @returns A module id of the image that can be resolved by Vite, or undefined if it is not a local image
|
|
7
7
|
*/
|
|
8
8
|
export declare function imageSrcToImportId(imageSrc: string, filePath?: string): string | undefined;
|
|
9
|
-
export declare const importIdToSymbolName: (importId: string) => string;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { isRemotePath, removeBase } from "@astrojs/internal-helpers/path";
|
|
2
2
|
import { CONTENT_IMAGE_FLAG, IMAGE_IMPORT_PREFIX } from "../../content/consts.js";
|
|
3
|
-
import { shorthash } from "../../runtime/server/shorthash.js";
|
|
4
3
|
import { VALID_INPUT_FORMATS } from "../consts.js";
|
|
5
4
|
function imageSrcToImportId(imageSrc, filePath) {
|
|
6
5
|
imageSrc = removeBase(imageSrc, IMAGE_IMPORT_PREFIX);
|
|
@@ -17,8 +16,6 @@ function imageSrcToImportId(imageSrc, filePath) {
|
|
|
17
16
|
}
|
|
18
17
|
return `${imageSrc}?${params.toString()}`;
|
|
19
18
|
}
|
|
20
|
-
const importIdToSymbolName = (importId) => `__ASTRO_IMAGE_IMPORT_${shorthash(importId)}`;
|
|
21
19
|
export {
|
|
22
|
-
imageSrcToImportId
|
|
23
|
-
importIdToSymbolName
|
|
20
|
+
imageSrcToImportId
|
|
24
21
|
};
|
|
@@ -196,7 +196,7 @@ ${contentConfig.error.message}`
|
|
|
196
196
|
logger.info("Content config changed");
|
|
197
197
|
shouldClear = true;
|
|
198
198
|
}
|
|
199
|
-
if (previousAstroVersion && previousAstroVersion !== "7.2.
|
|
199
|
+
if (previousAstroVersion && previousAstroVersion !== "7.2.1") {
|
|
200
200
|
logger.info("Astro version changed");
|
|
201
201
|
shouldClear = true;
|
|
202
202
|
}
|
|
@@ -204,8 +204,8 @@ ${contentConfig.error.message}`
|
|
|
204
204
|
logger.info("Clearing content store");
|
|
205
205
|
this.#store.clearAll();
|
|
206
206
|
}
|
|
207
|
-
if ("7.2.
|
|
208
|
-
this.#store.metaStore().set("astro-version", "7.2.
|
|
207
|
+
if ("7.2.1") {
|
|
208
|
+
this.#store.metaStore().set("astro-version", "7.2.1");
|
|
209
209
|
}
|
|
210
210
|
if (currentConfigDigest) {
|
|
211
211
|
this.#store.metaStore().set("content-config-digest", currentConfigDigest);
|
|
@@ -265,6 +265,7 @@ ${contentConfig.error.message}`
|
|
|
265
265
|
}
|
|
266
266
|
})
|
|
267
267
|
);
|
|
268
|
+
this.#validateReferences(contentConfig.config.collections, logger);
|
|
268
269
|
await fs.mkdir(this.#settings.config.cacheDir, { recursive: true });
|
|
269
270
|
await fs.mkdir(this.#settings.dotAstroDir, { recursive: true });
|
|
270
271
|
const assetImportsFile = new URL(ASSET_IMPORTS_FILE, this.#settings.dotAstroDir);
|
|
@@ -277,6 +278,65 @@ ${contentConfig.error.message}`
|
|
|
277
278
|
await this.regenerateCollectionFileManifest();
|
|
278
279
|
}
|
|
279
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* After all loaders complete, walks every entry's data to find reference objects
|
|
283
|
+
* (`{ id, collection }`) and checks that the referenced entry exists in the store.
|
|
284
|
+
* This replaces the inline Zod validation that was removed in the Zod 4 upgrade.
|
|
285
|
+
*/
|
|
286
|
+
#validateReferences(collections, logger) {
|
|
287
|
+
const collectionNames = new Set(Object.keys(collections));
|
|
288
|
+
for (const collectionName of collectionNames) {
|
|
289
|
+
for (const entry of this.#store.values(collectionName)) {
|
|
290
|
+
if (entry?.data) {
|
|
291
|
+
this.#findInvalidReferences(
|
|
292
|
+
entry.data,
|
|
293
|
+
collectionNames,
|
|
294
|
+
collectionName,
|
|
295
|
+
entry.id,
|
|
296
|
+
logger,
|
|
297
|
+
""
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
#findInvalidReferences(value, collectionNames, ownerCollection, ownerId, logger, path) {
|
|
304
|
+
if (value == null || typeof value !== "object") return;
|
|
305
|
+
if (Array.isArray(value)) {
|
|
306
|
+
for (let i = 0; i < value.length; i++) {
|
|
307
|
+
this.#findInvalidReferences(
|
|
308
|
+
value[i],
|
|
309
|
+
collectionNames,
|
|
310
|
+
ownerCollection,
|
|
311
|
+
ownerId,
|
|
312
|
+
logger,
|
|
313
|
+
`${path}[${i}]`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const obj = value;
|
|
319
|
+
if (typeof obj.collection === "string" && collectionNames.has(obj.collection)) {
|
|
320
|
+
const refId = typeof obj.id === "string" ? obj.id : typeof obj.slug === "string" ? obj.slug : void 0;
|
|
321
|
+
if (refId !== void 0 && !this.#store.has(obj.collection, refId)) {
|
|
322
|
+
const fieldPath = path ? ` (field: ${path})` : "";
|
|
323
|
+
logger.error(
|
|
324
|
+
`Invalid content reference: entry "${ownerId}" in collection "${ownerCollection}"${fieldPath} references "${refId}" in collection "${obj.collection}", but that entry does not exist.`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
330
|
+
this.#findInvalidReferences(
|
|
331
|
+
val,
|
|
332
|
+
collectionNames,
|
|
333
|
+
ownerCollection,
|
|
334
|
+
ownerId,
|
|
335
|
+
logger,
|
|
336
|
+
path ? `${path}.${key}` : key
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
280
340
|
async regenerateCollectionFileManifest() {
|
|
281
341
|
const collectionsManifest = new URL(COLLECTIONS_MANIFEST_FILE, this.#settings.dotAstroDir);
|
|
282
342
|
this.#logger.debug("content", "Regenerating collection file manifest");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, promises as fs } from "node:fs";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import yaml from "js-yaml";
|
|
3
|
+
import * as yaml from "js-yaml";
|
|
4
4
|
import * as toml from "smol-toml";
|
|
5
5
|
import { FileGlobNotSupported, FileParserNotFound } from "../../core/errors/errors-data.js";
|
|
6
6
|
import { AstroError } from "../../core/errors/index.js";
|
|
@@ -8,7 +8,7 @@ import { glob as tinyglobby } from "tinyglobby";
|
|
|
8
8
|
import { getContentEntryIdAndSlug, posixRelative } from "../utils.js";
|
|
9
9
|
function generateIdDefault({ entry, base, data }, isLegacy) {
|
|
10
10
|
if (data.slug) {
|
|
11
|
-
return data.slug;
|
|
11
|
+
return String(data.slug);
|
|
12
12
|
}
|
|
13
13
|
const entryURL = new URL(encodeURI(entry), base);
|
|
14
14
|
if (isLegacy) {
|
|
@@ -45,7 +45,8 @@ function glob(globOptions) {
|
|
|
45
45
|
);
|
|
46
46
|
}
|
|
47
47
|
const isLegacy = !!globOptions[secretLegacyFlag];
|
|
48
|
-
const
|
|
48
|
+
const userGenerateId = globOptions?.generateId ?? ((opts) => generateIdDefault(opts, isLegacy));
|
|
49
|
+
const generateId = (opts) => String(userGenerateId(opts));
|
|
49
50
|
const fileToIdMap = /* @__PURE__ */ new Map();
|
|
50
51
|
return {
|
|
51
52
|
name: "glob-loader",
|
|
@@ -2,7 +2,7 @@ import { existsSync, promises as fs } from "node:fs";
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import * as devalue from "devalue";
|
|
4
4
|
import { forEach } from "neotraverse";
|
|
5
|
-
import { imageSrcToImportId
|
|
5
|
+
import { imageSrcToImportId } from "../assets/utils/resolveImports.js";
|
|
6
6
|
import { AstroError, AstroErrorData } from "../core/errors/index.js";
|
|
7
7
|
import { DATA_STORE_MANIFEST_FILE, IMAGE_IMPORT_PREFIX } from "./consts.js";
|
|
8
8
|
import {
|
|
@@ -109,8 +109,8 @@ class MutableDataStore extends ImmutableDataStore {
|
|
|
109
109
|
const imports = [];
|
|
110
110
|
const exports = [];
|
|
111
111
|
const sortedAssetImports = [...this.#assetImports].sort();
|
|
112
|
-
sortedAssetImports.forEach((id) => {
|
|
113
|
-
const symbol =
|
|
112
|
+
sortedAssetImports.forEach((id, index) => {
|
|
113
|
+
const symbol = `__ASTRO_IMAGE_IMPORT_${index}`;
|
|
114
114
|
imports.push(`import ${symbol} from ${JSON.stringify(id)};`);
|
|
115
115
|
exports.push(`[${JSON.stringify(id)}, ${symbol}]`);
|
|
116
116
|
});
|
|
@@ -71,6 +71,7 @@ type RenderResult = {
|
|
|
71
71
|
remarkPluginFrontmatter: Record<string, any>;
|
|
72
72
|
};
|
|
73
73
|
export declare function updateImageReferencesInData<T extends Record<string, unknown>>(data: T, fileName?: string, imageAssetMap?: Map<string, ImageMetadata>): T;
|
|
74
|
+
export declare function resolveEntryData<T extends Record<string, unknown>>(entry: DataEntry<T>, imageAssetMap?: Map<string, ImageMetadata>): T;
|
|
74
75
|
export declare function renderEntry(entry: DataEntry): Promise<RenderResult>;
|
|
75
76
|
export declare function createReference(): (collection: string) => z.ZodPipe<z.ZodUnion<readonly [z.ZodPipe<z.ZodNumber, z.ZodTransform<string, number>>, z.ZodString, z.ZodObject<{
|
|
76
77
|
id: z.ZodString;
|
package/dist/content/runtime.js
CHANGED
|
@@ -78,7 +78,7 @@ function createGetCollection({
|
|
|
78
78
|
const { default: imageAssetMap } = await import("astro:asset-imports");
|
|
79
79
|
const result = [];
|
|
80
80
|
for (const rawEntry of await store.values(collection)) {
|
|
81
|
-
const data =
|
|
81
|
+
const data = resolveEntryData(rawEntry, imageAssetMap);
|
|
82
82
|
let entry = {
|
|
83
83
|
...rawEntry,
|
|
84
84
|
data,
|
|
@@ -135,7 +135,7 @@ function createGetEntry({ liveCollections }) {
|
|
|
135
135
|
return;
|
|
136
136
|
}
|
|
137
137
|
const { default: imageAssetMap } = await import("astro:asset-imports");
|
|
138
|
-
const data =
|
|
138
|
+
const data = resolveEntryData(entry, imageAssetMap);
|
|
139
139
|
const result = {
|
|
140
140
|
...entry,
|
|
141
141
|
data,
|
|
@@ -382,6 +382,9 @@ function updateImageReferencesInData(data, fileName, imageAssetMap) {
|
|
|
382
382
|
});
|
|
383
383
|
return copy;
|
|
384
384
|
}
|
|
385
|
+
function resolveEntryData(entry, imageAssetMap) {
|
|
386
|
+
return entry.assetImports?.length ? updateImageReferencesInData(entry.data, entry.filePath, imageAssetMap) : structuredClone(entry.data);
|
|
387
|
+
}
|
|
385
388
|
async function renderEntry(entry) {
|
|
386
389
|
if (!entry) {
|
|
387
390
|
throw new AstroError(AstroErrorData.RenderUndefinedEntryError);
|
|
@@ -563,5 +566,6 @@ export {
|
|
|
563
566
|
defineCollection,
|
|
564
567
|
defineLiveCollection,
|
|
565
568
|
renderEntry,
|
|
569
|
+
resolveEntryData,
|
|
566
570
|
updateImageReferencesInData
|
|
567
571
|
};
|
package/dist/core/app/types.d.ts
CHANGED
|
@@ -193,6 +193,8 @@ export type SSRManifestCSP = {
|
|
|
193
193
|
resources: CspResourceEntry[];
|
|
194
194
|
hashes: CspHashEntry[];
|
|
195
195
|
};
|
|
196
|
+
/** Static speculation rules JSON to inject in the head when CSP + clientPrerender are both enabled. */
|
|
197
|
+
speculationRulesContent?: string;
|
|
196
198
|
};
|
|
197
199
|
export interface SSRManifestSession extends BaseSessionConfig {
|
|
198
200
|
driver: string;
|
|
@@ -26,6 +26,24 @@ function collectTransitiveDeps(graph, rootId) {
|
|
|
26
26
|
}
|
|
27
27
|
return [...deps].sort();
|
|
28
28
|
}
|
|
29
|
+
const ASSET_PLACEHOLDERS = [
|
|
30
|
+
{ token: "__ASTRO_ASSET_IMAGE__", pattern: /__ASTRO_ASSET_IMAGE__([\w$]+)__(?:_(.*?)__)?/g },
|
|
31
|
+
{ token: "__VITE_ASSET__", pattern: /__VITE_ASSET__([\w$]+)__(?:\$_(.*?)__)?/g }
|
|
32
|
+
];
|
|
33
|
+
function resolveAssetPlaceholders(graph, code) {
|
|
34
|
+
let resolved = code;
|
|
35
|
+
for (const { token, pattern } of ASSET_PLACEHOLDERS) {
|
|
36
|
+
if (!resolved.includes(token)) continue;
|
|
37
|
+
resolved = resolved.replace(pattern, (placeholder, handle, postfix = "") => {
|
|
38
|
+
try {
|
|
39
|
+
return graph.getFileName(handle) + postfix;
|
|
40
|
+
} catch {
|
|
41
|
+
return placeholder;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return resolved;
|
|
46
|
+
}
|
|
29
47
|
function hashModules(graph, sortedIds) {
|
|
30
48
|
const hasher = crypto.createHash("sha256");
|
|
31
49
|
for (const id of sortedIds) {
|
|
@@ -33,7 +51,7 @@ function hashModules(graph, sortedIds) {
|
|
|
33
51
|
hasher.update("\n");
|
|
34
52
|
const code = graph.getModuleInfo(id)?.code;
|
|
35
53
|
if (code != null) {
|
|
36
|
-
hasher.update(code);
|
|
54
|
+
hasher.update(resolveAssetPlaceholders(graph, code));
|
|
37
55
|
}
|
|
38
56
|
hasher.update("\n");
|
|
39
57
|
}
|
|
@@ -21,7 +21,8 @@ import {
|
|
|
21
21
|
trackStyleHashes
|
|
22
22
|
} from "../../csp/common.js";
|
|
23
23
|
import { partitionByKind } from "../../csp/runtime.js";
|
|
24
|
-
import {
|
|
24
|
+
import { generateSpeculationRulesContent } from "../../../prefetch/speculation-rules.js";
|
|
25
|
+
import { encodeKey, generateCspDigest } from "../../encryption.js";
|
|
25
26
|
import { fileExtension, joinPaths, prependForwardSlash } from "../../path.js";
|
|
26
27
|
import { DEFAULT_COMPONENTS } from "../../routing/default.js";
|
|
27
28
|
import { getOutFile, getOutFolder } from "../common.js";
|
|
@@ -204,6 +205,13 @@ async function buildManifest(opts, internals, staticFiles, encodedKey) {
|
|
|
204
205
|
...settings.injectedCsp.styleHashes,
|
|
205
206
|
...await trackStyleHashes(internals, settings, algorithm)
|
|
206
207
|
];
|
|
208
|
+
let speculationRulesContent;
|
|
209
|
+
if (settings.config.experimental.clientPrerender && settings.config.prefetch) {
|
|
210
|
+
const prefetchAll = typeof settings.config.prefetch === "object" ? settings.config.prefetch.prefetchAll ?? false : false;
|
|
211
|
+
speculationRulesContent = generateSpeculationRulesContent(prefetchAll);
|
|
212
|
+
const speculationRulesHash = await generateCspDigest(speculationRulesContent, algorithm);
|
|
213
|
+
scriptHashes.push(speculationRulesHash);
|
|
214
|
+
}
|
|
207
215
|
const scriptDirective = {
|
|
208
216
|
resources: getScriptResources(cspConfig),
|
|
209
217
|
hashes: scriptHashes,
|
|
@@ -225,7 +233,8 @@ async function buildManifest(opts, internals, staticFiles, encodedKey) {
|
|
|
225
233
|
styleHashes: styleDefault.hashes,
|
|
226
234
|
styleResources: styleDefault.resources,
|
|
227
235
|
scriptDirective,
|
|
228
|
-
styleDirective
|
|
236
|
+
styleDirective,
|
|
237
|
+
speculationRulesContent
|
|
229
238
|
};
|
|
230
239
|
}
|
|
231
240
|
let internalFetchHeaders = void 0;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
|
-
import yaml from "js-yaml";
|
|
3
|
+
import * as yaml from "js-yaml";
|
|
4
4
|
import * as toml from "smol-toml";
|
|
5
5
|
import { getContentPaths } from "../../content/index.js";
|
|
6
6
|
import createPreferences from "../../preferences/index.js";
|
package/dist/core/constants.js
CHANGED
package/dist/core/dev/dev.js
CHANGED
|
@@ -26,7 +26,7 @@ async function dev(inlineConfig) {
|
|
|
26
26
|
await telemetry.record([]);
|
|
27
27
|
const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
|
|
28
28
|
const logger = restart.container.logger;
|
|
29
|
-
const currentVersion = "7.2.
|
|
29
|
+
const currentVersion = "7.2.1";
|
|
30
30
|
const isPrerelease = currentVersion.includes("-");
|
|
31
31
|
if (!isPrerelease) {
|
|
32
32
|
try {
|
|
@@ -330,6 +330,7 @@ class FetchState {
|
|
|
330
330
|
resources: manifest.csp?.styleDirective ? [...manifest.csp.styleDirective.resources] : [],
|
|
331
331
|
hashes: manifest.csp?.styleDirective ? [...manifest.csp.styleDirective.hashes] : []
|
|
332
332
|
},
|
|
333
|
+
speculationRulesContent: manifest.csp?.speculationRulesContent,
|
|
333
334
|
internalFetchHeaders: manifest.internalFetchHeaders
|
|
334
335
|
};
|
|
335
336
|
this.result = result;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Plugin as VitePlugin } from 'vite';
|
|
2
2
|
import type { AstroSettings } from '../../types/astro.js';
|
|
3
3
|
import type { BuildInternals } from '../build/internal.js';
|
|
4
4
|
import type { StaticBuildOptions } from '../build/types.js';
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import { fileURLToPath } from "node:url";
|
|
2
|
-
import {
|
|
3
|
-
normalizePath as viteNormalizePath
|
|
4
|
-
} from "vite";
|
|
5
1
|
import { getServerOutputDirectory } from "../../prerender/utils.js";
|
|
6
2
|
import { addRolldownInput } from "../build/add-rolldown-input.js";
|
|
7
3
|
import { ASTRO_VITE_ENVIRONMENT_NAMES, MIDDLEWARE_PATH_SEGMENT_NAME } from "../constants.js";
|
|
8
4
|
import { MissingMiddlewareForInternationalization } from "../errors/errors-data.js";
|
|
9
5
|
import { AstroError } from "../errors/index.js";
|
|
10
6
|
import { normalizePath } from "../viteUtils.js";
|
|
7
|
+
import { isAstroServerEnvironment } from "../../environments.js";
|
|
11
8
|
const MIDDLEWARE_MODULE_ID = "virtual:astro:middleware";
|
|
12
9
|
const MIDDLEWARE_RESOLVED_MODULE_ID = "\0" + MIDDLEWARE_MODULE_ID;
|
|
13
10
|
const NOOP_MIDDLEWARE = "\0noop-middleware";
|
|
@@ -18,31 +15,21 @@ function vitePluginMiddleware({ settings }) {
|
|
|
18
15
|
let resolvedMiddlewareId = void 0;
|
|
19
16
|
const hasIntegrationMiddleware = settings.middlewares.pre.length > 0 || settings.middlewares.post.length > 0;
|
|
20
17
|
let userMiddlewareIsPresent = false;
|
|
21
|
-
const normalizedSrcDir = viteNormalizePath(fileURLToPath(settings.config.srcDir));
|
|
22
18
|
return {
|
|
23
19
|
name: "@astro/plugin-middleware",
|
|
24
20
|
applyToEnvironment(environment) {
|
|
25
21
|
return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.astro || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender;
|
|
26
22
|
},
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const environment = server.environments[name];
|
|
38
|
-
if (!environment) continue;
|
|
39
|
-
const virtualMod = environment.moduleGraph.getModuleById(MIDDLEWARE_RESOLVED_MODULE_ID);
|
|
40
|
-
if (virtualMod) {
|
|
41
|
-
environment.moduleGraph.invalidateModule(virtualMod);
|
|
42
|
-
}
|
|
43
|
-
environment.hot.send("astro:middleware-updated", {});
|
|
44
|
-
}
|
|
45
|
-
});
|
|
23
|
+
hotUpdate: {
|
|
24
|
+
handler() {
|
|
25
|
+
if (!isAstroServerEnvironment(this.environment)) return;
|
|
26
|
+
const middlewareVirtualMod = this.environment.moduleGraph.getModuleById(
|
|
27
|
+
MIDDLEWARE_RESOLVED_MODULE_ID
|
|
28
|
+
);
|
|
29
|
+
if (!middlewareVirtualMod) return;
|
|
30
|
+
this.environment.moduleGraph.invalidateModule(middlewareVirtualMod);
|
|
31
|
+
this.environment.hot.send("astro:middleware-updated", {});
|
|
32
|
+
}
|
|
46
33
|
},
|
|
47
34
|
resolveId: {
|
|
48
35
|
filter: {
|
|
@@ -10,5 +10,7 @@ interface MatchedRoute {
|
|
|
10
10
|
filePath: URL;
|
|
11
11
|
resolvedPathname: string;
|
|
12
12
|
}
|
|
13
|
-
export declare function matchRoute(pathname: string, routesList: RoutesList, pipeline: RunnablePipeline, manifest: SSRManifest
|
|
13
|
+
export declare function matchRoute(pathname: string, routesList: RoutesList, pipeline: RunnablePipeline, manifest: SSRManifest, { prerenderOnly }?: {
|
|
14
|
+
prerenderOnly?: boolean;
|
|
15
|
+
}): Promise<MatchedRoute | undefined>;
|
|
14
16
|
export {};
|
package/dist/core/routing/dev.js
CHANGED
|
@@ -5,7 +5,7 @@ import { getCustom404Route } from "./helpers.js";
|
|
|
5
5
|
import { NoMatchingStaticPathFound } from "../errors/errors-data.js";
|
|
6
6
|
import { isAstroError } from "../errors/errors.js";
|
|
7
7
|
import { getErrorRoutePath } from "../../i18n/error-routes.js";
|
|
8
|
-
async function matchRoute(pathname, routesList, pipeline, manifest) {
|
|
8
|
+
async function matchRoute(pathname, routesList, pipeline, manifest, { prerenderOnly } = {}) {
|
|
9
9
|
const { logger, routeCache } = pipeline;
|
|
10
10
|
const matches = matchAllRoutes(pathname, routesList);
|
|
11
11
|
const preloadedMatches = getSortedPreloadedMatches({
|
|
@@ -13,7 +13,12 @@ async function matchRoute(pathname, routesList, pipeline, manifest) {
|
|
|
13
13
|
manifest
|
|
14
14
|
});
|
|
15
15
|
let firstError = null;
|
|
16
|
+
let skippedPrerenderOnly = false;
|
|
16
17
|
for await (const { route: maybeRoute, filePath } of preloadedMatches) {
|
|
18
|
+
if (prerenderOnly && !maybeRoute.prerender) {
|
|
19
|
+
skippedPrerenderOnly = true;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
17
22
|
try {
|
|
18
23
|
await getProps({
|
|
19
24
|
mod: await pipeline.getComponentByRoute(maybeRoute),
|
|
@@ -43,7 +48,10 @@ async function matchRoute(pathname, routesList, pipeline, manifest) {
|
|
|
43
48
|
}
|
|
44
49
|
const altPathname = pathname.replace(/\/index\.html$/, "/").replace(/\.html$/, "");
|
|
45
50
|
if (altPathname !== pathname) {
|
|
46
|
-
return await matchRoute(altPathname, routesList, pipeline, manifest);
|
|
51
|
+
return await matchRoute(altPathname, routesList, pipeline, manifest, { prerenderOnly });
|
|
52
|
+
}
|
|
53
|
+
if (skippedPrerenderOnly) {
|
|
54
|
+
return void 0;
|
|
47
55
|
}
|
|
48
56
|
if (matches.length) {
|
|
49
57
|
const possibleRoutes = matches.flatMap((route) => route.component);
|
package/dist/prefetch/index.js
CHANGED
|
@@ -129,7 +129,7 @@ function prefetch(url, opts) {
|
|
|
129
129
|
const ignoreSlowConnection = opts?.ignoreSlowConnection ?? false;
|
|
130
130
|
if (!canPrefetchUrl(url, ignoreSlowConnection)) return;
|
|
131
131
|
prefetchedUrls.add(url);
|
|
132
|
-
if (clientPrerender && HTMLScriptElement.supports?.("speculationrules")) {
|
|
132
|
+
if (clientPrerender && HTMLScriptElement.supports?.("speculationrules") && !hasStaticSpeculationRules()) {
|
|
133
133
|
debug?.(`[astro] Prefetching ${url} with <script type="speculationrules">`);
|
|
134
134
|
appendSpeculationRules(url, opts?.eagerness ?? "immediate");
|
|
135
135
|
} else if (document.createElement("link").relList?.supports?.("prefetch")) {
|
|
@@ -203,6 +203,23 @@ function onPageLoad(cb) {
|
|
|
203
203
|
}
|
|
204
204
|
}).observe(document.body, { childList: true, subtree: true });
|
|
205
205
|
}
|
|
206
|
+
let _hasStaticRules;
|
|
207
|
+
function hasStaticSpeculationRules() {
|
|
208
|
+
if (_hasStaticRules === void 0) {
|
|
209
|
+
_hasStaticRules = Array.from(document.querySelectorAll('script[type="speculationrules"]')).some(
|
|
210
|
+
(el) => {
|
|
211
|
+
try {
|
|
212
|
+
const rules = JSON.parse(el.textContent ?? "");
|
|
213
|
+
const entries = [...rules.prerender ?? [], ...rules.prefetch ?? []];
|
|
214
|
+
return entries.some((entry) => entry.source === "document");
|
|
215
|
+
} catch {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return _hasStaticRules;
|
|
222
|
+
}
|
|
206
223
|
function appendSpeculationRules(url, eagerness) {
|
|
207
224
|
const script = document.createElement("script");
|
|
208
225
|
script.type = "speculationrules";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates static speculation rules JSON using `"source": "document"` with CSS selector matching.
|
|
3
|
+
* This produces a deterministic payload that can be hashed at build time for CSP compatibility,
|
|
4
|
+
* unlike the dynamic per-URL `"source": "list"` approach in `appendSpeculationRules()`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateSpeculationRulesContent(prefetchAll: boolean): string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function generateSpeculationRulesContent(prefetchAll) {
|
|
2
|
+
const selector = prefetchAll ? "a" : "a[data-astro-prefetch]";
|
|
3
|
+
return JSON.stringify({
|
|
4
|
+
prerender: [
|
|
5
|
+
{
|
|
6
|
+
source: "document",
|
|
7
|
+
where: { selector_matches: selector },
|
|
8
|
+
eagerness: "moderate"
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
prefetch: [
|
|
12
|
+
{
|
|
13
|
+
source: "document",
|
|
14
|
+
where: { selector_matches: selector },
|
|
15
|
+
eagerness: "moderate"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
generateSpeculationRulesContent
|
|
22
|
+
};
|
|
@@ -53,6 +53,16 @@ function renderAllHeadContent(result) {
|
|
|
53
53
|
);
|
|
54
54
|
const sep = result.compressHTML === true || result.compressHTML === "jsx" ? "" : "\n";
|
|
55
55
|
content += styles.join(sep) + links.join(sep) + scripts.join(sep);
|
|
56
|
+
if (result.speculationRulesContent) {
|
|
57
|
+
content += renderElement(
|
|
58
|
+
"script",
|
|
59
|
+
{
|
|
60
|
+
props: { type: "speculationrules" },
|
|
61
|
+
children: result.speculationRulesContent
|
|
62
|
+
},
|
|
63
|
+
false
|
|
64
|
+
);
|
|
65
|
+
}
|
|
56
66
|
content += result._metadata.extraHead.join("");
|
|
57
67
|
return markHTMLString(content);
|
|
58
68
|
}
|
|
@@ -1,7 +1,30 @@
|
|
|
1
1
|
import { SERVER_ISLAND_START } from "../runtime/server/render/server-islands-shared.js";
|
|
2
2
|
const PERSIST_ATTR = "data-astro-transition-persist";
|
|
3
3
|
const NON_OVERRIDABLE_ASTRO_ATTRS = ["data-astro-transition", "data-astro-transition-fallback"];
|
|
4
|
-
const
|
|
4
|
+
const viteStyleState = import.meta.env.DEV ? /* @__PURE__ */ (() => {
|
|
5
|
+
const styles = /* @__PURE__ */ new Map();
|
|
6
|
+
let observer;
|
|
7
|
+
return {
|
|
8
|
+
styles,
|
|
9
|
+
observe() {
|
|
10
|
+
if (observer) return;
|
|
11
|
+
observer = new MutationObserver((records) => {
|
|
12
|
+
for (const record of records) {
|
|
13
|
+
for (const node of record.addedNodes) {
|
|
14
|
+
if (!(node instanceof HTMLStyleElement)) continue;
|
|
15
|
+
const viteDevId = node.dataset.viteDevId;
|
|
16
|
+
if (!viteDevId) continue;
|
|
17
|
+
const knownStyle = styles.get(viteDevId);
|
|
18
|
+
if (node === knownStyle) continue;
|
|
19
|
+
knownStyle?.remove();
|
|
20
|
+
styles.set(viteDevId, node);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
observer.observe(document.head, { childList: true });
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
})() : void 0;
|
|
5
28
|
const scriptsAlreadyRan = /* @__PURE__ */ new Set();
|
|
6
29
|
function detectScriptExecuted(script) {
|
|
7
30
|
const key = script.src ? new URL(script.src, location.href).href : script.textContent;
|
|
@@ -41,8 +64,8 @@ function swapHeadElements(doc) {
|
|
|
41
64
|
newEl.remove();
|
|
42
65
|
} else {
|
|
43
66
|
if (import.meta.env.DEV && el instanceof HTMLStyleElement) {
|
|
44
|
-
const viteDevId =
|
|
45
|
-
viteDevId &&
|
|
67
|
+
const viteDevId = el.dataset.viteDevId;
|
|
68
|
+
viteDevId && viteStyleState?.styles.set(viteDevId, el);
|
|
46
69
|
}
|
|
47
70
|
el.remove();
|
|
48
71
|
}
|
|
@@ -50,7 +73,18 @@ function swapHeadElements(doc) {
|
|
|
50
73
|
relevantNodes(document.head, "commentsOnly").forEach((node) => node.remove());
|
|
51
74
|
if (import.meta.env.DEV) {
|
|
52
75
|
relevantNodes(doc.head).forEach((child) => {
|
|
53
|
-
|
|
76
|
+
const viteDevId = child instanceof HTMLStyleElement && child.dataset.viteDevId;
|
|
77
|
+
const knownStyle = viteDevId && viteStyleState?.styles.get(viteDevId);
|
|
78
|
+
if (knownStyle) {
|
|
79
|
+
if (!vueScopedStyleId(knownStyle)) knownStyle.textContent = child.textContent;
|
|
80
|
+
document.head.append(knownStyle);
|
|
81
|
+
} else {
|
|
82
|
+
if (viteDevId) {
|
|
83
|
+
viteStyleState?.styles.set(viteDevId, child);
|
|
84
|
+
viteStyleState?.observe();
|
|
85
|
+
}
|
|
86
|
+
document.head.append(child);
|
|
87
|
+
}
|
|
54
88
|
});
|
|
55
89
|
} else {
|
|
56
90
|
document.head.append(...relevantNodes(doc.head));
|
|
@@ -85,6 +119,17 @@ function swapBodyElement(newElement, oldElement) {
|
|
|
85
119
|
}
|
|
86
120
|
}
|
|
87
121
|
attachShadowRoots(newElement);
|
|
122
|
+
reifyMediaElements(newElement);
|
|
123
|
+
}
|
|
124
|
+
function reifyMediaElements(root) {
|
|
125
|
+
for (const media of root.querySelectorAll("video, audio")) {
|
|
126
|
+
const fresh = document.createElement(media.localName);
|
|
127
|
+
for (const attr of media.attributes) {
|
|
128
|
+
fresh.setAttribute(attr.name, attr.value);
|
|
129
|
+
}
|
|
130
|
+
fresh.innerHTML = media.innerHTML;
|
|
131
|
+
media.replaceWith(fresh);
|
|
132
|
+
}
|
|
88
133
|
}
|
|
89
134
|
function attachShadowRoots(root) {
|
|
90
135
|
root.querySelectorAll("template[shadowrootmode]").forEach((template) => {
|
|
@@ -2246,7 +2246,9 @@ export interface AstroUserConfig<TLocales extends Locales = never, TDriver exten
|
|
|
2246
2246
|
* @docs
|
|
2247
2247
|
* @name markdown.remarkPlugins
|
|
2248
2248
|
* @type {RemarkPlugins}
|
|
2249
|
-
* @deprecated
|
|
2249
|
+
* @deprecated This property is deprecated and will be removed in a future major version. Pass plugins to the configured [`markdown.processor`](https://docs.astro.build/en/reference/configuration-reference/#markdownprocessor) instead.
|
|
2250
|
+
*
|
|
2251
|
+
* Learn more about [setting up a Markdown processor](https://docs.astro.build/en/guides/markdown-content/#setting-up-a-markdown-processor) and [using plugins](https://docs.astro.build/en/guides/markdown-content/#markdown-processor-plugins) in the Markdown guide.
|
|
2250
2252
|
* @description
|
|
2251
2253
|
* Pass [remark plugins](https://github.com/remarkjs/remark) to customize how your Markdown is built. You can import and apply the plugin function (recommended), or pass the plugin name as a string.
|
|
2252
2254
|
*
|
|
@@ -2264,7 +2266,9 @@ export interface AstroUserConfig<TLocales extends Locales = never, TDriver exten
|
|
|
2264
2266
|
* @docs
|
|
2265
2267
|
* @name markdown.rehypePlugins
|
|
2266
2268
|
* @type {RehypePlugins}
|
|
2267
|
-
* @deprecated
|
|
2269
|
+
* @deprecated This property is deprecated and will be removed in a future major version. Pass plugins to the configured [`markdown.processor`](https://docs.astro.build/en/reference/configuration-reference/#markdownprocessor) instead.
|
|
2270
|
+
*
|
|
2271
|
+
* Learn more about [setting up a Markdown processor](https://docs.astro.build/en/guides/markdown-content/#setting-up-a-markdown-processor) and [using plugins](https://docs.astro.build/en/guides/markdown-content/#markdown-processor-plugins) in the Markdown guide.
|
|
2268
2272
|
* @description
|
|
2269
2273
|
* Pass [rehype plugins](https://github.com/remarkjs/remark-rehype) to customize how your Markdown's output HTML is processed. You can import and apply the plugin function (recommended), or pass the plugin name as a string.
|
|
2270
2274
|
*
|
|
@@ -2284,7 +2288,9 @@ export interface AstroUserConfig<TLocales extends Locales = never, TDriver exten
|
|
|
2284
2288
|
* @type {boolean}
|
|
2285
2289
|
* @default `true`
|
|
2286
2290
|
* @version 2.0.0
|
|
2287
|
-
* @deprecated
|
|
2291
|
+
* @deprecated This property is deprecated and will be removed in a future major version. Pass `gfm` to the configured [`markdown.processor`](https://docs.astro.build/en/reference/configuration-reference/#markdownprocessor) instead.
|
|
2292
|
+
*
|
|
2293
|
+
* Learn more about [setting up a Markdown processor](https://docs.astro.build/en/guides/markdown-content/#setting-up-a-markdown-processor) and [using GitHub-flavored Markdown](https://docs.astro.build/en/guides/markdown-content/#github-flavored-markdown) in the Markdown guide.
|
|
2288
2294
|
* @description
|
|
2289
2295
|
* Astro uses [GitHub-flavored Markdown](https://github.com/remarkjs/remark-gfm) by default. To disable this, set the `gfm` flag to `false`:
|
|
2290
2296
|
*
|
|
@@ -2303,7 +2309,9 @@ export interface AstroUserConfig<TLocales extends Locales = never, TDriver exten
|
|
|
2303
2309
|
* @type {boolean | Smartypants}
|
|
2304
2310
|
* @default `true`
|
|
2305
2311
|
* @version 2.0.0
|
|
2306
|
-
* @deprecated Pass
|
|
2312
|
+
* @deprecated This property is deprecated and will be removed in a future major version. Pass it to the configured [`markdown.processor`](https://docs.astro.build/en/reference/configuration-reference/#markdownprocessor) instead. Use `smartypants` for `unified()` or `smartPunctuation` for `satteri()`.
|
|
2313
|
+
*
|
|
2314
|
+
* Learn more about [setting up a Markdown processor](https://docs.astro.build/en/guides/markdown-content/#setting-up-a-markdown-processor) and [using smart punctuation](https://docs.astro.build/en/guides/markdown-content/#smart-punctuation) in the Markdown guide.
|
|
2307
2315
|
* @description
|
|
2308
2316
|
* Whether to use the [SmartyPants formatter](https://daringfireball.net/projects/smartypants/) to transform straight quotes into smart quotes, dashes into en/em dashes, and triple dots into ellipses.
|
|
2309
2317
|
*
|
|
@@ -2316,7 +2324,11 @@ export interface AstroUserConfig<TLocales extends Locales = never, TDriver exten
|
|
|
2316
2324
|
* @docs
|
|
2317
2325
|
* @name markdown.remarkRehype
|
|
2318
2326
|
* @type {RemarkRehype}
|
|
2319
|
-
* @deprecated
|
|
2327
|
+
* @deprecated This property is deprecated and will be removed in a future major version.
|
|
2328
|
+
*
|
|
2329
|
+
* To configure footnotes, pass `remarkRehype` to the `unified()` processor or `gfm.footnotes` to the `satteri()` processor instead. Other `remark-rehype` options are only supported when using `unified()`.
|
|
2330
|
+
*
|
|
2331
|
+
* Learn more about [setting up a Markdown processor](https://docs.astro.build/en/guides/markdown-content/#setting-up-a-markdown-processor) and [using built-in features](https://docs.astro.build/en/guides/markdown-content/#built-in-features) in the Markdown guide.
|
|
2320
2332
|
* @description
|
|
2321
2333
|
* Pass options to [remark-rehype](https://github.com/remarkjs/remark-rehype#api).
|
|
2322
2334
|
*
|
|
@@ -2336,8 +2348,9 @@ export interface AstroUserConfig<TLocales extends Locales = never, TDriver exten
|
|
|
2336
2348
|
* @type {MarkdownProcessor}
|
|
2337
2349
|
* @version 6.4.0
|
|
2338
2350
|
* @description
|
|
2339
|
-
* Configures the Markdown processor used to render `.md` files.
|
|
2340
|
-
*
|
|
2351
|
+
* Configures the [Markdown processor](https://docs.astro.build/en/guides/markdown-content/#markdown-processors) used to render `.md` files.
|
|
2352
|
+
*
|
|
2353
|
+
* Sätteri, Astro’s native Markdown pipeline, is the default processor. To configure it, install `@astrojs/markdown-satteri` and pass options to `satteri()`:
|
|
2341
2354
|
*
|
|
2342
2355
|
* ```js
|
|
2343
2356
|
* // astro.config.mjs
|
|
@@ -2369,6 +2382,8 @@ export interface AstroUserConfig<TLocales extends Locales = never, TDriver exten
|
|
|
2369
2382
|
* },
|
|
2370
2383
|
* });
|
|
2371
2384
|
* ```
|
|
2385
|
+
*
|
|
2386
|
+
* Learn more about the [official Markdown processors and how to choose one](https://docs.astro.build/en/guides/markdown-content/#choosing-a-markdown-processor) in the Markdown guide.
|
|
2372
2387
|
*/
|
|
2373
2388
|
processor?: MarkdownProcessor;
|
|
2374
2389
|
};
|
|
@@ -231,6 +231,8 @@ export interface SSRResult {
|
|
|
231
231
|
isStrictDynamic: SSRManifestCSP['isStrictDynamic'];
|
|
232
232
|
scriptDirective: SSRManifestCSP['scriptDirective'];
|
|
233
233
|
styleDirective: SSRManifestCSP['styleDirective'];
|
|
234
|
+
/** Static speculation rules JSON to inject in the head when CSP + clientPrerender are both enabled. */
|
|
235
|
+
speculationRulesContent?: string;
|
|
234
236
|
internalFetchHeaders?: Record<string, string>;
|
|
235
237
|
}
|
|
236
238
|
/**
|
|
@@ -35,7 +35,9 @@ export declare class AstroServerApp extends BaseApp<RunnablePipeline> {
|
|
|
35
35
|
* Called via HMR when action files change.
|
|
36
36
|
*/
|
|
37
37
|
clearActions(): void;
|
|
38
|
-
devMatch(pathname: string
|
|
38
|
+
devMatch(pathname: string, { prerenderOnly }?: {
|
|
39
|
+
prerenderOnly?: boolean;
|
|
40
|
+
}): Promise<DevMatch | undefined>;
|
|
39
41
|
static create(manifest: SSRManifest, routesList: RoutesList, logger: AstroLogger, loader: ModuleLoader, settings: AstroSettings, getDebugInfo: () => Promise<string>): Promise<AstroServerApp>;
|
|
40
42
|
createPipeline(_streaming: boolean, manifest: SSRManifest, settings: AstroSettings, logger: AstroLogger, loader: ModuleLoader, manifestData: RoutesList, getDebugInfo: () => Promise<string>): RunnablePipeline;
|
|
41
43
|
/**
|
|
@@ -70,12 +70,13 @@ class AstroServerApp extends BaseApp {
|
|
|
70
70
|
clearActions() {
|
|
71
71
|
this.pipeline.clearActions();
|
|
72
72
|
}
|
|
73
|
-
async devMatch(pathname) {
|
|
73
|
+
async devMatch(pathname, { prerenderOnly } = {}) {
|
|
74
74
|
const matchedRoute = await matchRoute(
|
|
75
75
|
pathname,
|
|
76
76
|
this.manifestData,
|
|
77
77
|
this.pipeline,
|
|
78
|
-
this.manifest
|
|
78
|
+
this.manifest,
|
|
79
|
+
{ prerenderOnly }
|
|
79
80
|
);
|
|
80
81
|
if (!matchedRoute) {
|
|
81
82
|
return void 0;
|
|
@@ -132,7 +133,7 @@ class AstroServerApp extends BaseApp {
|
|
|
132
133
|
controller,
|
|
133
134
|
pathname,
|
|
134
135
|
async run() {
|
|
135
|
-
const matchedRoute = await self.devMatch(pathname);
|
|
136
|
+
const matchedRoute = await self.devMatch(pathname, { prerenderOnly });
|
|
136
137
|
if (!matchedRoute) {
|
|
137
138
|
if (prerenderOnly) {
|
|
138
139
|
handled = false;
|