astro 7.2.0 → 7.2.2
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/fonts/constants.d.ts +8 -0
- package/dist/assets/fonts/constants.js +2 -0
- package/dist/assets/fonts/vite-plugin-fonts.js +3 -1
- package/dist/assets/utils/node.js +35 -3
- package/dist/assets/utils/resolveImports.d.ts +0 -1
- package/dist/assets/utils/resolveImports.js +1 -4
- package/dist/cli/dev/index.js +2 -2
- package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
- package/dist/cli/preview/index.js +1 -1
- package/dist/cli/server.js +4 -4
- package/dist/content/content-layer.js +63 -3
- package/dist/content/loaders/file.js +20 -5
- package/dist/content/loaders/glob.js +18 -4
- 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/generate.js +10 -1
- package/dist/core/build/plugins/plugin-incremental.js +162 -36
- 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/dev/lockfile.d.ts +19 -2
- package/dist/core/dev/lockfile.js +25 -2
- 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/core/util.js +2 -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/dist/vite-plugin-css/index.js +2 -2
- package/package.json +5 -4
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
+
import { FONTS_SERVER_ADDRESS_PLACEHOLDER } from "../../../assets/fonts/constants.js";
|
|
2
3
|
import { PROPAGATED_ASSET_FLAG } from "../../../content/consts.js";
|
|
3
4
|
import { hasContentFlag } from "../../../content/utils.js";
|
|
4
5
|
import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../../constants.js";
|
|
@@ -7,24 +8,29 @@ import { rootRelativePath } from "../../viteUtils.js";
|
|
|
7
8
|
import { moduleIsTopLevelPage } from "../graph.js";
|
|
8
9
|
import { isContentDataIncrementalModule } from "../incremental-metadata.js";
|
|
9
10
|
import { getPageDataByViteID } from "../internal.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
11
|
+
const ASSET_PLACEHOLDERS = [
|
|
12
|
+
{ token: "__ASTRO_ASSET_IMAGE__", pattern: /__ASTRO_ASSET_IMAGE__([\w$]+)__(?:_(.*?)__)?/g },
|
|
13
|
+
{ token: "__VITE_ASSET__", pattern: /__VITE_ASSET__([\w$]+)__(?:\$_(.*?)__)?/g }
|
|
14
|
+
];
|
|
15
|
+
const FONTS_ADDRESS_DECLARATION = new RegExp(
|
|
16
|
+
`(?:const|let|var)\\s+${FONTS_SERVER_ADDRESS_PLACEHOLDER}\\s*=[^;]+;`
|
|
17
|
+
);
|
|
18
|
+
function resolveAssetPlaceholders(graph, code) {
|
|
19
|
+
let resolved = code;
|
|
20
|
+
for (const { token, pattern } of ASSET_PLACEHOLDERS) {
|
|
21
|
+
if (!resolved.includes(token)) continue;
|
|
22
|
+
resolved = resolved.replace(pattern, (placeholder, handle, postfix = "") => {
|
|
23
|
+
try {
|
|
24
|
+
return graph.getFileName(handle) + postfix;
|
|
25
|
+
} catch {
|
|
26
|
+
return placeholder;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (resolved.includes(FONTS_SERVER_ADDRESS_PLACEHOLDER)) {
|
|
31
|
+
resolved = resolved.replace(FONTS_ADDRESS_DECLARATION, "");
|
|
26
32
|
}
|
|
27
|
-
return
|
|
33
|
+
return resolved;
|
|
28
34
|
}
|
|
29
35
|
function hashModules(graph, sortedIds) {
|
|
30
36
|
const hasher = crypto.createHash("sha256");
|
|
@@ -33,17 +39,137 @@ function hashModules(graph, sortedIds) {
|
|
|
33
39
|
hasher.update("\n");
|
|
34
40
|
const code = graph.getModuleInfo(id)?.code;
|
|
35
41
|
if (code != null) {
|
|
36
|
-
hasher.update(code);
|
|
42
|
+
hasher.update(resolveAssetPlaceholders(graph, code));
|
|
37
43
|
}
|
|
38
44
|
hasher.update("\n");
|
|
39
45
|
}
|
|
40
46
|
return hasher.digest("hex");
|
|
41
47
|
}
|
|
42
|
-
function
|
|
48
|
+
function createTransitiveGraphCache(graph) {
|
|
49
|
+
const modules = /* @__PURE__ */ new Map();
|
|
50
|
+
const dependencies = /* @__PURE__ */ new Map();
|
|
51
|
+
const excludedModules = /* @__PURE__ */ new Set();
|
|
52
|
+
const pending = [...graph.getModuleIds()];
|
|
53
|
+
for (const id of pending) {
|
|
54
|
+
if (modules.has(id)) continue;
|
|
55
|
+
const info = graph.getModuleInfo(id);
|
|
56
|
+
modules.set(id, info);
|
|
57
|
+
if (isContentDataIncrementalModule(info)) {
|
|
58
|
+
excludedModules.add(id);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const importedIds = [...info?.importedIds ?? [], ...info?.dynamicallyImportedIds ?? []];
|
|
62
|
+
dependencies.set(id, importedIds);
|
|
63
|
+
pending.push(...importedIds);
|
|
64
|
+
}
|
|
65
|
+
for (const id of excludedModules) modules.delete(id);
|
|
66
|
+
for (const [id, importedIds] of dependencies) {
|
|
67
|
+
dependencies.set(
|
|
68
|
+
id,
|
|
69
|
+
importedIds.filter((importedId) => !excludedModules.has(importedId))
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const reverseDependencies = /* @__PURE__ */ new Map();
|
|
73
|
+
for (const id of modules.keys()) reverseDependencies.set(id, []);
|
|
74
|
+
for (const [id, importedIds] of dependencies) {
|
|
75
|
+
for (const importedId of importedIds) reverseDependencies.get(importedId)?.push(id);
|
|
76
|
+
}
|
|
77
|
+
const visited = /* @__PURE__ */ new Set();
|
|
78
|
+
const finishOrder = [];
|
|
79
|
+
for (const rootId of modules.keys()) {
|
|
80
|
+
if (visited.has(rootId)) continue;
|
|
81
|
+
visited.add(rootId);
|
|
82
|
+
const stack = [[rootId, 0]];
|
|
83
|
+
while (stack.length > 0) {
|
|
84
|
+
const frame = stack[stack.length - 1];
|
|
85
|
+
const importedIds = dependencies.get(frame[0]) ?? [];
|
|
86
|
+
if (frame[1] < importedIds.length) {
|
|
87
|
+
const importedId = importedIds[frame[1]++];
|
|
88
|
+
if (!visited.has(importedId)) {
|
|
89
|
+
visited.add(importedId);
|
|
90
|
+
stack.push([importedId, 0]);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
finishOrder.push(frame[0]);
|
|
94
|
+
stack.pop();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const componentByModule = /* @__PURE__ */ new Map();
|
|
99
|
+
const components = [];
|
|
100
|
+
for (const rootId of finishOrder.toReversed()) {
|
|
101
|
+
if (componentByModule.has(rootId)) continue;
|
|
102
|
+
const componentIndex = components.length;
|
|
103
|
+
const component = [];
|
|
104
|
+
const stack = [rootId];
|
|
105
|
+
componentByModule.set(rootId, componentIndex);
|
|
106
|
+
while (stack.length > 0) {
|
|
107
|
+
const id = stack.pop();
|
|
108
|
+
component.push(id);
|
|
109
|
+
for (const importerId of reverseDependencies.get(id) ?? []) {
|
|
110
|
+
if (!componentByModule.has(importerId)) {
|
|
111
|
+
componentByModule.set(importerId, componentIndex);
|
|
112
|
+
stack.push(importerId);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
components.push(component.sort());
|
|
117
|
+
}
|
|
118
|
+
const componentDependencies = components.map(() => /* @__PURE__ */ new Set());
|
|
119
|
+
const componentImporters = components.map(() => /* @__PURE__ */ new Set());
|
|
120
|
+
for (const [id, importedIds] of dependencies) {
|
|
121
|
+
const componentIndex = componentByModule.get(id);
|
|
122
|
+
for (const importedId of importedIds) {
|
|
123
|
+
const dependencyIndex = componentByModule.get(importedId);
|
|
124
|
+
if (dependencyIndex === componentIndex) continue;
|
|
125
|
+
componentDependencies[componentIndex].add(dependencyIndex);
|
|
126
|
+
componentImporters[dependencyIndex].add(componentIndex);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const componentHashes = /* @__PURE__ */ new Map();
|
|
130
|
+
const componentHasServerIsland = /* @__PURE__ */ new Map();
|
|
131
|
+
const unresolvedDependencies = componentDependencies.map((items) => items.size);
|
|
132
|
+
const ready = unresolvedDependencies.flatMap((count, index) => count === 0 ? [index] : []);
|
|
133
|
+
for (const componentIndex of ready) {
|
|
134
|
+
const hasher = crypto.createHash("sha256");
|
|
135
|
+
hasher.update(hashModules(graph, components[componentIndex]));
|
|
136
|
+
const dependencyHashes = [...componentDependencies[componentIndex]].map((dependencyIndex) => componentHashes.get(dependencyIndex)).sort();
|
|
137
|
+
for (const dependencyHash of dependencyHashes) {
|
|
138
|
+
hasher.update("\n");
|
|
139
|
+
hasher.update(dependencyHash);
|
|
140
|
+
}
|
|
141
|
+
componentHashes.set(componentIndex, hasher.digest("hex"));
|
|
142
|
+
componentHasServerIsland.set(
|
|
143
|
+
componentIndex,
|
|
144
|
+
components[componentIndex].some(
|
|
145
|
+
(id) => (modules.get(id)?.meta?.astro?.serverComponents?.length ?? 0) > 0
|
|
146
|
+
) || [...componentDependencies[componentIndex]].some(
|
|
147
|
+
(dependencyIndex) => componentHasServerIsland.get(dependencyIndex)
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
for (const importerIndex of componentImporters[componentIndex]) {
|
|
151
|
+
unresolvedDependencies[importerIndex]--;
|
|
152
|
+
if (unresolvedDependencies[importerIndex] === 0) ready.push(importerIndex);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
hashes: new Map(
|
|
157
|
+
[...componentByModule].map(([id, componentIndex]) => [
|
|
158
|
+
id,
|
|
159
|
+
componentHashes.get(componentIndex)
|
|
160
|
+
])
|
|
161
|
+
),
|
|
162
|
+
serverIslandModules: new Set(
|
|
163
|
+
[...componentByModule].filter(([, componentIndex]) => componentHasServerIsland.get(componentIndex)).map(([id]) => id)
|
|
164
|
+
)
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function collectClientEntrypointHashes(transitiveHashes, entrypointIds, pagesByEntrypoint, hashesByComponent) {
|
|
43
168
|
for (const entrypointId of entrypointIds) {
|
|
44
169
|
const pages = pagesByEntrypoint.get(entrypointId);
|
|
45
170
|
if (!pages?.size) continue;
|
|
46
|
-
const hash =
|
|
171
|
+
const hash = transitiveHashes.get(entrypointId);
|
|
172
|
+
if (!hash) continue;
|
|
47
173
|
for (const pageData of pages) {
|
|
48
174
|
let list = hashesByComponent.get(pageData.component);
|
|
49
175
|
if (!list) {
|
|
@@ -57,15 +183,16 @@ function collectClientEntrypointHashes(graph, entrypointIds, pagesByEntrypoint,
|
|
|
57
183
|
function foldClientDependencies(graph, internals) {
|
|
58
184
|
const baseHashes = internals.pageDependencyHashes;
|
|
59
185
|
if (!baseHashes) return;
|
|
186
|
+
const { hashes: transitiveHashes } = createTransitiveGraphCache(graph);
|
|
60
187
|
const hashesByComponent = /* @__PURE__ */ new Map();
|
|
61
188
|
collectClientEntrypointHashes(
|
|
62
|
-
|
|
189
|
+
transitiveHashes,
|
|
63
190
|
internals.discoveredClientOnlyComponents.keys(),
|
|
64
191
|
internals.pagesByClientOnly,
|
|
65
192
|
hashesByComponent
|
|
66
193
|
);
|
|
67
194
|
collectClientEntrypointHashes(
|
|
68
|
-
|
|
195
|
+
transitiveHashes,
|
|
69
196
|
internals.discoveredScripts,
|
|
70
197
|
internals.pagesByScriptId,
|
|
71
198
|
hashesByComponent
|
|
@@ -80,24 +207,17 @@ function foldClientDependencies(graph, internals) {
|
|
|
80
207
|
baseHashes.set(component, hasher.digest("hex"));
|
|
81
208
|
}
|
|
82
209
|
}
|
|
83
|
-
function collectContentEntryHashes(graph, root) {
|
|
210
|
+
function collectContentEntryHashes(graph, root, transitiveHashes) {
|
|
84
211
|
const entryHashes = /* @__PURE__ */ new Map();
|
|
85
212
|
for (const id of graph.getModuleIds()) {
|
|
86
213
|
if (!hasContentFlag(id, PROPAGATED_ASSET_FLAG)) continue;
|
|
87
214
|
const renderModuleId = removeQueryString(id);
|
|
88
215
|
const key = rootRelativePath(root, renderModuleId, false);
|
|
89
|
-
const
|
|
90
|
-
entryHashes.set(key,
|
|
216
|
+
const hash = transitiveHashes.get(renderModuleId);
|
|
217
|
+
if (hash) entryHashes.set(key, hash);
|
|
91
218
|
}
|
|
92
219
|
return entryHashes;
|
|
93
220
|
}
|
|
94
|
-
function pageContainsServerIsland(graph, ids) {
|
|
95
|
-
for (const id of ids) {
|
|
96
|
-
const serverComponents = graph.getModuleInfo(id)?.meta?.astro?.serverComponents;
|
|
97
|
-
if (serverComponents?.length) return true;
|
|
98
|
-
}
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
221
|
function pluginIncremental(internals, root) {
|
|
102
222
|
return {
|
|
103
223
|
name: "@astro/plugin-incremental",
|
|
@@ -109,6 +229,7 @@ function pluginIncremental(internals, root) {
|
|
|
109
229
|
foldClientDependencies(this, internals);
|
|
110
230
|
return;
|
|
111
231
|
}
|
|
232
|
+
const transitiveGraph = createTransitiveGraphCache(this);
|
|
112
233
|
const hashes = /* @__PURE__ */ new Map();
|
|
113
234
|
const serverIslandComponents = /* @__PURE__ */ new Set();
|
|
114
235
|
for (const id of this.getModuleIds()) {
|
|
@@ -117,14 +238,19 @@ function pluginIncremental(internals, root) {
|
|
|
117
238
|
if (!moduleIsTopLevelPage(info)) continue;
|
|
118
239
|
const pageData = getPageDataByViteID(internals, info.id);
|
|
119
240
|
if (!pageData) continue;
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
241
|
+
const hash = transitiveGraph.hashes.get(info.id);
|
|
242
|
+
if (!hash) continue;
|
|
243
|
+
hashes.set(pageData.component, hash);
|
|
244
|
+
if (transitiveGraph.serverIslandModules.has(info.id)) {
|
|
123
245
|
serverIslandComponents.add(pageData.component);
|
|
124
246
|
}
|
|
125
247
|
}
|
|
126
248
|
internals.pageDependencyHashes = hashes;
|
|
127
|
-
internals.contentEntryRenderHashes = collectContentEntryHashes(
|
|
249
|
+
internals.contentEntryRenderHashes = collectContentEntryHashes(
|
|
250
|
+
this,
|
|
251
|
+
root,
|
|
252
|
+
transitiveGraph.hashes
|
|
253
|
+
);
|
|
128
254
|
internals.serverIslandPageComponents = serverIslandComponents;
|
|
129
255
|
}
|
|
130
256
|
};
|
|
@@ -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.2";
|
|
30
30
|
const isPrerelease = currentVersion.includes("-");
|
|
31
31
|
if (!isPrerelease) {
|
|
32
32
|
try {
|
|
@@ -32,6 +32,22 @@ export declare function serializeLockFile(data: LockFileData): string;
|
|
|
32
32
|
* Signal 0 does not kill the process — it only checks whether the process exists.
|
|
33
33
|
*/
|
|
34
34
|
export declare function isProcessAlive(pid: number): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Check whether a process command points to the Astro CLI.
|
|
37
|
+
*/
|
|
38
|
+
export declare function isAstroCommand(command: string): boolean;
|
|
39
|
+
interface ProcessInfo {
|
|
40
|
+
pid: number;
|
|
41
|
+
cmd?: string;
|
|
42
|
+
}
|
|
43
|
+
type ProcessLookup = (by: 'pid', value: number, options: {
|
|
44
|
+
logLevel: 'error';
|
|
45
|
+
}) => Promise<ProcessInfo[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Check whether the live process recorded in a lock file is still Astro.
|
|
48
|
+
* If the command cannot be inspected, keep the existing PID-only behavior.
|
|
49
|
+
*/
|
|
50
|
+
export declare function isLockFileProcessAlive(data: LockFileData, find?: ProcessLookup): Promise<boolean>;
|
|
35
51
|
/**
|
|
36
52
|
* Read the lock file from disk. Returns null if it doesn't exist or is invalid.
|
|
37
53
|
*/
|
|
@@ -58,8 +74,9 @@ export declare function evaluateExistingServer(data: LockFileData | null, alive:
|
|
|
58
74
|
*/
|
|
59
75
|
export declare function killDevServer(root: URL, data: LockFileData): Promise<void>;
|
|
60
76
|
/**
|
|
61
|
-
* Check for an existing server by reading the lock file and checking process
|
|
77
|
+
* Check for an existing server by reading the lock file and checking process identity.
|
|
62
78
|
* Automatically cleans up stale lock files.
|
|
63
79
|
* Returns the server info if a live server is found, null otherwise.
|
|
64
80
|
*/
|
|
65
|
-
export declare function checkExistingServer(root: URL, command?: ServerCommand): LockFileData | null
|
|
81
|
+
export declare function checkExistingServer(root: URL, command?: ServerCommand): Promise<LockFileData | null>;
|
|
82
|
+
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, unlinkSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
+
import findProcess from "find-process";
|
|
3
4
|
const GRACEFUL_SHUTDOWN_TIMEOUT = 5e3;
|
|
4
5
|
function getLockFileURL(root, command = "dev") {
|
|
5
6
|
return new URL(`.astro/${command}.json`, root);
|
|
@@ -42,6 +43,23 @@ function isProcessAlive(pid) {
|
|
|
42
43
|
return false;
|
|
43
44
|
}
|
|
44
45
|
}
|
|
46
|
+
const ASTRO_COMMAND_PATTERN = /(?:^|[\\/\s"'])(?:astro[\\/]bin[\\/]astro\.mjs|\.bin[\\/]astro(?:\.cmd)?)(?=$|[\s"'])/i;
|
|
47
|
+
function isAstroCommand(command) {
|
|
48
|
+
return ASTRO_COMMAND_PATTERN.test(command);
|
|
49
|
+
}
|
|
50
|
+
async function isLockFileProcessAlive(data, find = findProcess) {
|
|
51
|
+
if (!isProcessAlive(data.pid)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const processInfo = (await find("pid", data.pid, { logLevel: "error" })).find(
|
|
56
|
+
({ pid }) => pid === data.pid
|
|
57
|
+
);
|
|
58
|
+
return processInfo?.cmd === void 0 || isAstroCommand(processInfo.cmd);
|
|
59
|
+
} catch {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
45
63
|
function readLockFile(root, command = "dev") {
|
|
46
64
|
const lockFileURL = getLockFileURL(root, command);
|
|
47
65
|
try {
|
|
@@ -98,9 +116,12 @@ async function killDevServer(root, data) {
|
|
|
98
116
|
}
|
|
99
117
|
removeLockFile(root);
|
|
100
118
|
}
|
|
101
|
-
function checkExistingServer(root, command = "dev") {
|
|
119
|
+
async function checkExistingServer(root, command = "dev") {
|
|
102
120
|
const data = readLockFile(root, command);
|
|
103
|
-
const result = evaluateExistingServer(
|
|
121
|
+
const result = evaluateExistingServer(
|
|
122
|
+
data,
|
|
123
|
+
data !== null && await isLockFileProcessAlive(data)
|
|
124
|
+
);
|
|
104
125
|
if (result === null) {
|
|
105
126
|
return null;
|
|
106
127
|
}
|
|
@@ -115,6 +136,8 @@ export {
|
|
|
115
136
|
checkExistingServer,
|
|
116
137
|
evaluateExistingServer,
|
|
117
138
|
getLogFileURL,
|
|
139
|
+
isAstroCommand,
|
|
140
|
+
isLockFileProcessAlive,
|
|
118
141
|
isProcessAlive,
|
|
119
142
|
killDevServer,
|
|
120
143
|
parseLockFile,
|
|
@@ -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/core/util.js
CHANGED
|
@@ -47,8 +47,8 @@ function resolvePages(config) {
|
|
|
47
47
|
return new URL("./pages", config.srcDir);
|
|
48
48
|
}
|
|
49
49
|
function isInPagesDir(file, config) {
|
|
50
|
-
const pagesDir = resolvePages(config)
|
|
51
|
-
return file.toString().startsWith(pagesDir
|
|
50
|
+
const pagesDir = `${resolvePages(config).toString()}/`;
|
|
51
|
+
return file.toString().startsWith(pagesDir);
|
|
52
52
|
}
|
|
53
53
|
function isInjectedRoute(file, settings) {
|
|
54
54
|
let fileURL = file.toString();
|
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
|
}
|