astro 6.0.4 → 6.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/bin/astro.mjs +2 -5
  2. package/dist/actions/integration.js +9 -6
  3. package/dist/assets/runtime.d.ts +2 -1
  4. package/dist/assets/runtime.js +15 -4
  5. package/dist/assets/utils/svg.js +25 -7
  6. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  7. package/dist/content/content-layer.js +3 -3
  8. package/dist/content/server-listeners.js +4 -0
  9. package/dist/content/types-generator.js +4 -1
  10. package/dist/core/app/base.js +2 -1
  11. package/dist/core/build/generate.js +92 -84
  12. package/dist/core/build/plugins/plugin-internals.js +12 -13
  13. package/dist/core/build/plugins/plugin-manifest.js +11 -5
  14. package/dist/core/build/static-build.js +9 -9
  15. package/dist/core/constants.js +1 -1
  16. package/dist/core/dev/dev.js +1 -1
  17. package/dist/core/messages/runtime.js +1 -1
  18. package/dist/core/render/params-and-props.js +4 -6
  19. package/dist/core/render/ssr-element.js +26 -4
  20. package/dist/core/routing/helpers.d.ts +13 -0
  21. package/dist/core/routing/helpers.js +15 -0
  22. package/dist/core/server-islands/vite-plugin-server-islands.d.ts +1 -1
  23. package/dist/core/server-islands/vite-plugin-server-islands.js +59 -120
  24. package/dist/core/viteUtils.d.ts +7 -1
  25. package/dist/core/viteUtils.js +18 -1
  26. package/dist/manifest/serialized.d.ts +1 -1
  27. package/dist/manifest/serialized.js +4 -1
  28. package/dist/vite-plugin-astro/hmr.d.ts +3 -1
  29. package/dist/vite-plugin-astro/hmr.js +6 -2
  30. package/dist/vite-plugin-astro/index.js +1 -1
  31. package/dist/vite-plugin-astro-server/plugin.js +8 -12
  32. package/dist/vite-plugin-renderers/index.js +4 -6
  33. package/dist/vite-plugin-routes/index.js +3 -2
  34. package/dist/vite-plugin-scripts/index.js +1 -1
  35. package/package.json +4 -4
  36. package/templates/content/types.d.ts +3 -2
@@ -1,4 +1,5 @@
1
1
  import type { RouteData } from '../../types/public/internal.js';
2
+ import type { IntegrationResolvedRoute } from '../../types/public/integrations.js';
2
3
  import type { RouteInfo } from '../app/types.js';
3
4
  import type { RoutesList } from '../../types/astro.js';
4
5
  type RedirectRouteData = RouteData & {
@@ -31,4 +32,16 @@ export declare function getCustom404Route(manifestData: RoutesList): RouteData |
31
32
  * Return a user-provided 500 route if one exists.
32
33
  */
33
34
  export declare function getCustom500Route(manifestData: RoutesList): RouteData | undefined;
35
+ /**
36
+ * Returns true if the route definition contains `.html` as a static segment part,
37
+ * as is the case for routes like `[slug].html.astro`. Used to avoid stripping the
38
+ * `.html` suffix from pathnames that intentionally include it.
39
+ */
40
+ export declare function routeHasHtmlExtension(route: RouteData): boolean;
41
+ export declare function hasNonPrerenderedProjectRoute(routes: Array<Pick<RouteData, 'type' | 'origin' | 'prerender'>>, options?: {
42
+ includeEndpoints?: boolean;
43
+ }): boolean;
44
+ export declare function hasNonPrerenderedProjectRoute(routes: Array<Pick<IntegrationResolvedRoute, 'type' | 'origin' | 'isPrerendered'>>, options?: {
45
+ includeEndpoints?: boolean;
46
+ }): boolean;
34
47
  export {};
@@ -25,10 +25,25 @@ function getCustom404Route(manifestData) {
25
25
  function getCustom500Route(manifestData) {
26
26
  return manifestData.routes.find((r) => isRoute500(r.route));
27
27
  }
28
+ function routeHasHtmlExtension(route) {
29
+ return route.segments.some(
30
+ (segment) => segment.some((part) => !part.dynamic && part.content.includes(".html"))
31
+ );
32
+ }
33
+ function hasNonPrerenderedProjectRoute(routes, options) {
34
+ const includeEndpoints = options?.includeEndpoints ?? true;
35
+ const routeTypes = includeEndpoints ? ["page", "endpoint"] : ["page"];
36
+ return routes.some((route) => {
37
+ const isPrerendered = "isPrerendered" in route ? route.isPrerendered : route.prerender;
38
+ return routeTypes.includes(route.type) && route.origin === "project" && !isPrerendered;
39
+ });
40
+ }
28
41
  export {
29
42
  getCustom404Route,
30
43
  getCustom500Route,
31
44
  getFallbackRoute,
45
+ hasNonPrerenderedProjectRoute,
46
+ routeHasHtmlExtension,
32
47
  routeIsFallback,
33
48
  routeIsRedirect
34
49
  };
@@ -1,5 +1,5 @@
1
1
  import type { Plugin as VitePlugin } from 'vite';
2
2
  import type { AstroPluginOptions } from '../../types/astro.js';
3
3
  export declare const SERVER_ISLAND_MANIFEST = "virtual:astro:server-island-manifest";
4
- export declare const serverIslandPlaceholderMap = "'$$server-islands-map$$'";
4
+ export declare const SERVER_ISLAND_MAP_MARKER = "$$server-islands-map$$";
5
5
  export declare function vitePluginServerIslands({ settings }: AstroPluginOptions): VitePlugin;
@@ -1,12 +1,12 @@
1
- import fs from "node:fs";
2
- import { getPrerenderOutputDirectory, getServerOutputDirectory } from "../../prerender/utils.js";
3
- import { AstroError, AstroErrorData } from "../errors/index.js";
4
- import { appendForwardSlash } from "../path.js";
5
1
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../constants.js";
2
+ import { AstroError, AstroErrorData } from "../errors/index.js";
6
3
  const SERVER_ISLAND_MANIFEST = "virtual:astro:server-island-manifest";
7
4
  const RESOLVED_SERVER_ISLAND_MANIFEST = "\0" + SERVER_ISLAND_MANIFEST;
8
5
  const serverIslandPlaceholderMap = "'$$server-islands-map$$'";
9
6
  const serverIslandPlaceholderNameMap = "'$$server-islands-name-map$$'";
7
+ const SERVER_ISLAND_MAP_MARKER = "$$server-islands-map$$";
8
+ const serverIslandMapReplaceExp = /['"]\$\$server-islands-map\$\$['"]/g;
9
+ const serverIslandNameMapReplaceExp = /['"]\$\$server-islands-name-map\$\$['"]/g;
10
10
  function createServerIslandImportMapSource(entries, toImportPath) {
11
11
  const mappings = Array.from(entries, ([islandName, fileName]) => {
12
12
  const importPath = toImportPath(fileName);
@@ -22,17 +22,35 @@ function createNameMapSource(entries) {
22
22
  function vitePluginServerIslands({ settings }) {
23
23
  let command = "serve";
24
24
  let ssrEnvironment = null;
25
- const referenceIdMap = /* @__PURE__ */ new Map();
26
25
  const serverIslandMap = /* @__PURE__ */ new Map();
27
26
  const serverIslandNameMap = /* @__PURE__ */ new Map();
28
- const resolvedIslandImports = /* @__PURE__ */ new Map();
29
- let ssrManifestChunk = null;
27
+ const serverIslandSourceMap = /* @__PURE__ */ new Map();
28
+ const referenceIdMap = /* @__PURE__ */ new Map();
29
+ function ensureServerIslandReferenceIds(ctx) {
30
+ for (const [resolvedPath, islandName] of serverIslandNameMap) {
31
+ if (referenceIdMap.has(resolvedPath)) continue;
32
+ const source = serverIslandSourceMap.get(resolvedPath);
33
+ const referenceId = ctx.emitFile({
34
+ type: "chunk",
35
+ id: source?.id ?? resolvedPath,
36
+ importer: source?.importer,
37
+ name: islandName
38
+ });
39
+ referenceIdMap.set(resolvedPath, referenceId);
40
+ }
41
+ }
30
42
  return {
31
43
  name: "astro:server-islands",
32
44
  enforce: "post",
33
45
  config(_config, { command: _command }) {
34
46
  command = _command;
35
47
  },
48
+ buildStart() {
49
+ if (command !== "build" || this.environment?.name !== ASTRO_VITE_ENVIRONMENT_NAMES.ssr) {
50
+ return;
51
+ }
52
+ ensureServerIslandReferenceIds(this);
53
+ },
36
54
  configureServer(server) {
37
55
  ssrEnvironment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
38
56
  },
@@ -59,16 +77,13 @@ export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
59
77
  transform: {
60
78
  filter: {
61
79
  id: {
62
- include: [
63
- // Allows server islands in astro and mdx files
64
- /\.(astro|mdx)$/,
65
- new RegExp(`^${RESOLVED_SERVER_ISLAND_MANIFEST}$`)
66
- ]
80
+ include: [/\.(astro|mdx)$/, new RegExp(`^${RESOLVED_SERVER_ISLAND_MANIFEST}$`)]
67
81
  }
68
82
  },
69
83
  async handler(_code, id) {
70
84
  const info = this.getModuleInfo(id);
71
85
  const astro = info ? info.meta.astro : void 0;
86
+ const isBuildSsr = command === "build" && this.environment?.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr;
72
87
  if (astro) {
73
88
  for (const comp of astro.serverComponents) {
74
89
  if (!serverIslandNameMap.has(comp.resolvedPath)) {
@@ -77,23 +92,23 @@ export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
77
92
  }
78
93
  let name = comp.localName;
79
94
  let idx = 1;
80
- while (true) {
81
- if (!serverIslandMap.has(name)) {
82
- break;
83
- }
95
+ while (serverIslandMap.has(name)) {
84
96
  name += idx++;
85
97
  }
86
98
  serverIslandNameMap.set(comp.resolvedPath, name);
87
99
  serverIslandMap.set(name, comp.resolvedPath);
88
- if (command === "build") {
89
- const referenceId = this.emitFile({
90
- type: "chunk",
91
- id: comp.specifier,
92
- importer: id,
93
- name: comp.localName
94
- });
95
- referenceIdMap.set(comp.resolvedPath, referenceId);
96
- }
100
+ serverIslandSourceMap.set(comp.resolvedPath, { id: comp.specifier, importer: id });
101
+ }
102
+ if (isBuildSsr && !referenceIdMap.has(comp.resolvedPath)) {
103
+ const islandName = serverIslandNameMap.get(comp.resolvedPath);
104
+ const source = serverIslandSourceMap.get(comp.resolvedPath);
105
+ const referenceId = this.emitFile({
106
+ type: "chunk",
107
+ id: source?.id ?? comp.resolvedPath,
108
+ importer: source?.importer,
109
+ name: islandName
110
+ });
111
+ referenceIdMap.set(comp.resolvedPath, referenceId);
97
112
  }
98
113
  }
99
114
  }
@@ -110,7 +125,7 @@ export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
110
125
  throw new AstroError(AstroErrorData.NoAdapterInstalledServerIslands);
111
126
  }
112
127
  }
113
- if (serverIslandNameMap.size > 0 && serverIslandMap.size > 0) {
128
+ if (command !== "build" && serverIslandNameMap.size > 0 && serverIslandMap.size > 0) {
114
129
  const mapSource = createServerIslandImportMapSource(
115
130
  serverIslandMap,
116
131
  (fileName) => fileName
@@ -118,22 +133,21 @@ export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
118
133
  const nameMapSource = createNameMapSource(serverIslandNameMap);
119
134
  return {
120
135
  code: `
121
- export const serverIslandMap = ${mapSource};
122
-
136
+ export const serverIslandMap = ${mapSource};
123
137
 
124
- export const serverIslandNameMap = ${nameMapSource};
125
- `
138
+ export const serverIslandNameMap = ${nameMapSource};
139
+ `
126
140
  };
127
141
  }
128
142
  }
129
143
  }
130
144
  },
131
145
  renderChunk(code, chunk) {
132
- if (code.includes(serverIslandPlaceholderMap)) {
133
- if (command === "build") {
134
- if (referenceIdMap.size === 0) {
135
- return;
136
- }
146
+ if (!code.includes(SERVER_ISLAND_MAP_MARKER)) return;
147
+ if (command === "build") {
148
+ const envName = this.environment?.name;
149
+ let mapSource;
150
+ if (envName === ASTRO_VITE_ENVIRONMENT_NAMES.ssr) {
137
151
  const isRelativeChunk = !chunk.isEntry;
138
152
  const dots = isRelativeChunk ? ".." : ".";
139
153
  const mapEntries = [];
@@ -141,105 +155,30 @@ export const serverIslandNameMap = ${nameMapSource};
141
155
  const fileName = this.getFileName(referenceId);
142
156
  const islandName = serverIslandNameMap.get(resolvedPath);
143
157
  if (!islandName) continue;
144
- if (!resolvedIslandImports.has(islandName)) {
145
- resolvedIslandImports.set(islandName, fileName);
146
- }
147
158
  mapEntries.push([islandName, fileName]);
148
159
  }
149
- const mapSource = createServerIslandImportMapSource(
160
+ mapSource = createServerIslandImportMapSource(
150
161
  mapEntries,
151
162
  (fileName) => `${dots}/${fileName}`
152
163
  );
153
- const nameMapSource = createNameMapSource(serverIslandNameMap);
154
- return {
155
- code: code.replace(serverIslandPlaceholderMap, mapSource).replace(serverIslandPlaceholderNameMap, nameMapSource),
156
- map: null
157
- };
164
+ } else {
165
+ mapSource = createServerIslandImportMapSource(serverIslandMap, (fileName) => fileName);
158
166
  }
167
+ const nameMapSource = createNameMapSource(serverIslandNameMap);
159
168
  return {
160
- code: code.replace(serverIslandPlaceholderMap, "new Map();").replace(serverIslandPlaceholderNameMap, "new Map()"),
169
+ code: code.replace(serverIslandMapReplaceExp, mapSource).replace(serverIslandNameMapReplaceExp, nameMapSource),
161
170
  map: null
162
171
  };
163
172
  }
164
- },
165
- generateBundle(_options, bundle) {
166
- const envName = this.environment?.name;
167
- if (envName === ASTRO_VITE_ENVIRONMENT_NAMES.ssr) {
168
- for (const chunk of Object.values(bundle)) {
169
- if (chunk.type === "chunk" && chunk.code.includes(serverIslandPlaceholderMap)) {
170
- ssrManifestChunk = chunk;
171
- break;
172
- }
173
- }
174
- }
175
- if (envName === ASTRO_VITE_ENVIRONMENT_NAMES.prerender && ssrManifestChunk) {
176
- if (resolvedIslandImports.size > 0) {
177
- const isRelativeChunk = ssrManifestChunk.fileName.includes("/");
178
- const dots = isRelativeChunk ? ".." : ".";
179
- const mapSource = createServerIslandImportMapSource(
180
- resolvedIslandImports,
181
- (fileName) => `${dots}/${fileName}`
182
- );
183
- const nameMapSource = createNameMapSource(serverIslandNameMap);
184
- ssrManifestChunk.code = ssrManifestChunk.code.replace(serverIslandPlaceholderMap, mapSource).replace(serverIslandPlaceholderNameMap, nameMapSource);
185
- } else {
186
- ssrManifestChunk.code = ssrManifestChunk.code.replace(serverIslandPlaceholderMap, "new Map()").replace(serverIslandPlaceholderNameMap, "new Map()");
187
- }
188
- }
189
- },
190
- api: {
191
- /**
192
- * Post-build hook that patches SSR chunks containing server island placeholders.
193
- *
194
- * During build, SSR can run before all server islands are discovered (e.g. islands
195
- * only used in prerendered pages). This hook runs after SSR + prerender builds and:
196
- * 1) replaces placeholders with the complete map of discovered islands
197
- * 2) copies island chunks emitted in prerender into the SSR output directory
198
- *
199
- * Two cases:
200
- * 1. Islands were discovered: Replace placeholders with real import maps.
201
- * 2. No islands found: Replace placeholders with empty maps.
202
- */
203
- async buildPostHook({
204
- chunks,
205
- mutate
206
- }) {
207
- const ssrChunkWithPlaceholder = chunks.find(
208
- (c) => !c.prerender && c.code.includes(serverIslandPlaceholderMap)
209
- );
210
- if (!ssrChunkWithPlaceholder) {
211
- return;
212
- }
213
- if (resolvedIslandImports.size > 0) {
214
- const isRelativeChunk = ssrChunkWithPlaceholder.fileName.includes("/");
215
- const dots = isRelativeChunk ? ".." : ".";
216
- const mapSource = createServerIslandImportMapSource(
217
- resolvedIslandImports,
218
- (fileName) => `${dots}/${fileName}`
219
- );
220
- const nameMapSource = createNameMapSource(serverIslandNameMap);
221
- const newCode = ssrChunkWithPlaceholder.code.replace(serverIslandPlaceholderMap, mapSource).replace(serverIslandPlaceholderNameMap, nameMapSource);
222
- mutate(ssrChunkWithPlaceholder.fileName, newCode, false);
223
- const serverOutputDir = getServerOutputDirectory(settings);
224
- const prerenderOutputDir = getPrerenderOutputDirectory(settings);
225
- for (const [, fileName] of resolvedIslandImports) {
226
- const srcPath = new URL(fileName, appendForwardSlash(prerenderOutputDir.toString()));
227
- const destPath = new URL(fileName, appendForwardSlash(serverOutputDir.toString()));
228
- if (!fs.existsSync(srcPath)) continue;
229
- const destDir = new URL("./", destPath);
230
- await fs.promises.mkdir(destDir, { recursive: true });
231
- await fs.promises.copyFile(srcPath, destPath);
232
- }
233
- } else {
234
- const newCode = ssrChunkWithPlaceholder.code.replace(serverIslandPlaceholderMap, "new Map()").replace(serverIslandPlaceholderNameMap, "new Map()");
235
- mutate(ssrChunkWithPlaceholder.fileName, newCode, false);
236
- }
237
- }
173
+ return {
174
+ code: code.replace(serverIslandMapReplaceExp, "new Map();").replace(serverIslandNameMapReplaceExp, "new Map()"),
175
+ map: null
176
+ };
238
177
  }
239
178
  };
240
179
  }
241
180
  export {
242
181
  SERVER_ISLAND_MANIFEST,
243
- serverIslandPlaceholderMap,
182
+ SERVER_ISLAND_MAP_MARKER,
244
183
  vitePluginServerIslands
245
184
  };
@@ -4,7 +4,13 @@ import type { ModuleLoader } from './module-loader/index.js';
4
4
  */
5
5
  export declare function normalizePath(id: string): string;
6
6
  /**
7
- * Resolve the hydration paths so that it can be imported in the client
7
+ * Resolve island component specifiers to stable paths for hydration metadata.
8
+ *
9
+ * Examples:
10
+ * - `./components/Button.jsx` from `/app/src/pages/index.astro`
11
+ * -> `/app/src/pages/components/Button.tsx` (when `.tsx` exists)
12
+ * - `#components/react/Counter.tsx`
13
+ * -> `/app/src/components/react/Counter.tsx` via package `imports`
8
14
  */
9
15
  export declare function resolvePath(specifier: string, importer: string): string;
10
16
  export declare function rootRelativePath(root: URL, idOrUrl: URL | string, shouldPrependForwardSlash?: boolean): string;
@@ -1,5 +1,6 @@
1
+ import { createRequire } from "node:module";
1
2
  import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
3
4
  import { prependForwardSlash, slash } from "../core/path.js";
4
5
  import { resolveJsToTs, unwrapId, VALID_ID_PREFIX, viteID } from "./util.js";
5
6
  const isWindows = typeof process !== "undefined" && process.platform === "win32";
@@ -10,6 +11,22 @@ function resolvePath(specifier, importer) {
10
11
  if (specifier.startsWith(".")) {
11
12
  const absoluteSpecifier = path.resolve(path.dirname(importer), specifier);
12
13
  return resolveJsToTs(normalizePath(absoluteSpecifier));
14
+ } else if (specifier.startsWith("#")) {
15
+ try {
16
+ const resolved = createRequire(pathToFileURL(importer)).resolve(specifier);
17
+ return resolveJsToTs(normalizePath(resolved));
18
+ } catch {
19
+ try {
20
+ const importerURL = pathToFileURL(importer).toString();
21
+ const resolved = import.meta.resolve(specifier, importerURL);
22
+ const resolvedUrl = new URL(resolved);
23
+ if (resolvedUrl.protocol === "file:") {
24
+ return resolveJsToTs(normalizePath(fileURLToPath(resolvedUrl)));
25
+ }
26
+ } catch {
27
+ }
28
+ }
29
+ return specifier;
13
30
  } else {
14
31
  return specifier;
15
32
  }
@@ -1,4 +1,4 @@
1
- import type { Plugin } from 'vite';
1
+ import { type Plugin } from 'vite';
2
2
  import type { AstroSettings } from '../types/astro.js';
3
3
  export declare const SERIALIZED_MANIFEST_ID = "virtual:astro:manifest";
4
4
  export declare const SERIALIZED_MANIFEST_RESOLVED_ID: string;
@@ -1,3 +1,5 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { normalizePath } from "vite";
1
3
  import { ACTIONS_ENTRYPOINT_VIRTUAL_MODULE_ID } from "../actions/consts.js";
2
4
  import { toFallbackType } from "../core/app/common.js";
3
5
  import { toRoutingStrategy } from "../core/app/entrypoints/index.js";
@@ -31,8 +33,9 @@ function serializedManifestPlugin({
31
33
  command,
32
34
  sync
33
35
  }) {
36
+ const normalizedSrcDir = normalizePath(fileURLToPath(settings.config.srcDir));
34
37
  function reloadManifest(path, server) {
35
- if (path != null && path.startsWith(settings.config.srcDir.pathname)) {
38
+ if (path != null && normalizePath(path).startsWith(normalizedSrcDir)) {
36
39
  const environment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
37
40
  const virtualMod = environment.moduleGraph.getModuleById(SERIALIZED_MANIFEST_RESOLVED_ID);
38
41
  if (!virtualMod) return;
@@ -1,10 +1,12 @@
1
1
  import type { HmrContext } from 'vite';
2
2
  import type { Logger } from '../core/logger/core.js';
3
+ import type { CompileAstroResult } from './compile.js';
3
4
  import type { CompileMetadata } from './types.js';
4
5
  interface HandleHotUpdateOptions {
5
6
  logger: Logger;
7
+ compile: (code: string, filename: string) => Promise<CompileAstroResult>;
6
8
  astroFileToCompileMetadata: Map<string, CompileMetadata>;
7
9
  }
8
- export declare function handleHotUpdate(ctx: HmrContext, { logger, astroFileToCompileMetadata }: HandleHotUpdateOptions): Promise<import("vite").ModuleNode[] | undefined>;
10
+ export declare function handleHotUpdate(ctx: HmrContext, { logger, compile, astroFileToCompileMetadata }: HandleHotUpdateOptions): Promise<import("vite").ModuleNode[] | undefined>;
9
11
  export declare function isStyleOnlyChanged(oldCode: string, newCode: string): boolean;
10
12
  export {};
@@ -1,6 +1,6 @@
1
1
  import { parseAstroRequest } from "./query.js";
2
2
  import { frontmatterRE } from "./utils.js";
3
- async function handleHotUpdate(ctx, { logger, astroFileToCompileMetadata }) {
3
+ async function handleHotUpdate(ctx, { logger, compile, astroFileToCompileMetadata }) {
4
4
  for (const [astroFile, compileData] of astroFileToCompileMetadata) {
5
5
  const isUpdatedFileCssDep = compileData.css.some((css) => css.dependencies?.includes(ctx.file));
6
6
  if (isUpdatedFileCssDep) {
@@ -12,7 +12,11 @@ async function handleHotUpdate(ctx, { logger, astroFileToCompileMetadata }) {
12
12
  const newCode = await ctx.read();
13
13
  if (isStyleOnlyChanged(oldCode, newCode)) {
14
14
  logger.debug("watch", "style-only change");
15
- astroFileToCompileMetadata.delete(ctx.file);
15
+ try {
16
+ await compile(newCode, ctx.file);
17
+ } catch {
18
+ astroFileToCompileMetadata.delete(ctx.file);
19
+ }
16
20
  return ctx.modules.filter((mod) => {
17
21
  if (!mod.id) {
18
22
  return false;
@@ -246,7 +246,7 @@ File: ${id}`
246
246
  }
247
247
  },
248
248
  async handleHotUpdate(ctx) {
249
- return handleHotUpdate(ctx, { logger, astroFileToCompileMetadata });
249
+ return handleHotUpdate(ctx, { logger, compile, astroFileToCompileMetadata });
250
250
  }
251
251
  },
252
252
  {
@@ -19,7 +19,7 @@ import { getViteErrorPayload } from "../core/errors/dev/index.js";
19
19
  import { AstroError, AstroErrorData } from "../core/errors/index.js";
20
20
  import { NOOP_MIDDLEWARE_FN } from "../core/middleware/noop-middleware.js";
21
21
  import { createViteLoader } from "../core/module-loader/index.js";
22
- import { matchAllRoutes } from "../core/routing/match.js";
22
+ import { isRouteServerIsland, matchAllRoutes } from "../core/routing/match.js";
23
23
  import { resolveMiddlewareMode } from "../integrations/adapter-utils.js";
24
24
  import { SERIALIZED_MANIFEST_ID } from "../manifest/serialized.js";
25
25
  import { ASTRO_DEV_SERVER_APP_ID } from "../vite-plugin-app/index.js";
@@ -45,9 +45,6 @@ function createVitePluginAstroServer({
45
45
  const prerenderEnvironment = viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.prerender];
46
46
  const runnableSsrEnvironment = isRunnableDevEnvironment(ssrEnvironment) ? ssrEnvironment : void 0;
47
47
  const runnablePrerenderEnvironment = isRunnableDevEnvironment(prerenderEnvironment) ? prerenderEnvironment : void 0;
48
- if (!runnableSsrEnvironment && !runnablePrerenderEnvironment) {
49
- return;
50
- }
51
48
  async function createHandler(environment) {
52
49
  const loader = createViteLoader(viteServer, environment);
53
50
  const { default: createAstroServerApp } = await environment.runner.import(ASTRO_DEV_SERVER_APP_ID);
@@ -84,17 +81,16 @@ function createVitePluginAstroServer({
84
81
  );
85
82
  }
86
83
  }
87
- process.on("unhandledRejection", handleUnhandledRejection);
88
- viteServer.httpServer?.on("close", () => {
89
- process.off("unhandledRejection", handleUnhandledRejection);
90
- });
84
+ if (ssrHandler || prerenderHandler) {
85
+ process.on("unhandledRejection", handleUnhandledRejection);
86
+ viteServer.httpServer?.on("close", () => {
87
+ process.off("unhandledRejection", handleUnhandledRejection);
88
+ });
89
+ }
91
90
  return () => {
92
91
  const shouldHandlePrerenderInCore = Boolean(
93
92
  viteServer[devPrerenderMiddlewareSymbol]
94
93
  );
95
- if (!ssrHandler && !(prerenderHandler && shouldHandlePrerenderInCore)) {
96
- return;
97
- }
98
94
  viteServer.middlewares.stack.unshift({
99
95
  route: "",
100
96
  handle: baseMiddleware(settings, logger)
@@ -130,7 +126,7 @@ function createVitePluginAstroServer({
130
126
  const { routes } = await prerenderHandler.environment.runner.import("virtual:astro:routes");
131
127
  const routesList = { routes: routes.map((r) => r.routeData) };
132
128
  const matches = matchAllRoutes(pathname, routesList);
133
- if (!matches.some((route) => route.prerender)) {
129
+ if (!matches.some((route) => route.prerender || isRouteServerIsland(route))) {
134
130
  return next();
135
131
  }
136
132
  localStorage.run(request, () => {
@@ -1,11 +1,7 @@
1
1
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js";
2
+ import { hasNonPrerenderedProjectRoute } from "../core/routing/helpers.js";
2
3
  const ASTRO_RENDERERS_MODULE_ID = "virtual:astro:renderers";
3
4
  const RESOLVED_ASTRO_RENDERERS_MODULE_ID = `\0${ASTRO_RENDERERS_MODULE_ID}`;
4
- function ssrBuildNeedsRenderers(routesList) {
5
- return routesList.routes.some(
6
- (route) => route.type === "page" && !route.prerender && route.origin !== "internal"
7
- );
8
- }
9
5
  function vitePluginRenderers(options) {
10
6
  const renderers = options.settings.renderers;
11
7
  return {
@@ -24,7 +20,9 @@ function vitePluginRenderers(options) {
24
20
  id: new RegExp(`^${RESOLVED_ASTRO_RENDERERS_MODULE_ID}$`)
25
21
  },
26
22
  handler() {
27
- if (options.command === "build" && this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr && renderers.length > 0 && !ssrBuildNeedsRenderers(options.routesList)) {
23
+ if (options.command === "build" && this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr && renderers.length > 0 && !hasNonPrerenderedProjectRoute(options.routesList.routes, {
24
+ includeEndpoints: false
25
+ })) {
28
26
  return { code: `export const renderers = [];` };
29
27
  }
30
28
  if (renderers.length > 0) {
@@ -52,11 +52,12 @@ async function astroPluginRoutes({
52
52
  };
53
53
  }
54
54
  );
55
+ const normalizedSrcDir = normalizePath(fileURLToPath(settings.config.srcDir));
55
56
  async function rebuildRoutes(path = null, server) {
56
- if (path != null && path.startsWith(settings.config.srcDir.pathname)) {
57
+ if (path != null && normalizePath(path).startsWith(normalizedSrcDir)) {
57
58
  logger.debug(
58
59
  "update",
59
- `Re-calculating routes for ${path.slice(settings.config.srcDir.pathname.length)}`
60
+ `Re-calculating routes for ${normalizePath(path).slice(normalizedSrcDir.length)}`
60
61
  );
61
62
  const file = pathToFileURL(normalizePath(path));
62
63
  const newRoutesList = await createRoutesList(
@@ -38,7 +38,7 @@ function astroScriptsPlugin({ settings }) {
38
38
  },
39
39
  buildStart() {
40
40
  const hasHydrationScripts = settings.scripts.some((s) => s.stage === "before-hydration");
41
- if (hasHydrationScripts && (this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender || this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr)) {
41
+ if (hasHydrationScripts && (this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client || this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender || this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr)) {
42
42
  this.emitFile({
43
43
  type: "chunk",
44
44
  id: BEFORE_HYDRATION_SCRIPT_ID,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "6.0.4",
3
+ "version": "6.0.6",
4
4
  "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
5
5
  "type": "module",
6
6
  "author": "withastro",
@@ -154,7 +154,7 @@
154
154
  "yargs-parser": "^22.0.0",
155
155
  "zod": "^4.3.6",
156
156
  "@astrojs/internal-helpers": "0.8.0",
157
- "@astrojs/markdown-remark": "7.0.0",
157
+ "@astrojs/markdown-remark": "7.0.1",
158
158
  "@astrojs/telemetry": "3.3.0"
159
159
  },
160
160
  "optionalDependencies": {
@@ -190,11 +190,11 @@
190
190
  "undici": "^7.22.0",
191
191
  "unified": "^11.0.5",
192
192
  "vitest": "^3.2.4",
193
- "@astrojs/check": "0.9.7",
193
+ "@astrojs/check": "0.9.8",
194
194
  "astro-scripts": "0.0.14"
195
195
  },
196
196
  "engines": {
197
- "node": "^20.19.1 || >=22.12.0",
197
+ "node": ">=22.12.0",
198
198
  "npm": ">=9.6.5",
199
199
  "pnpm": ">=7.1.0"
200
200
  },
@@ -111,11 +111,12 @@ declare module 'astro:content' {
111
111
  type InferEntrySchema<C extends keyof DataEntryMap> = import('astro/zod').infer<
112
112
  ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
113
113
  >;
114
+ type ExtractLoaderConfig<T> = T extends { loader: infer L } ? L : never;
114
115
  type InferLoaderSchema<
115
116
  C extends keyof DataEntryMap,
116
- L = Required<ContentConfig['collections'][C]>['loader'],
117
+ L = ExtractLoaderConfig<ContentConfig['collections'][C]>,
117
118
  > = L extends { schema: import('astro/zod').ZodSchema }
118
- ? import('astro/zod').infer<Required<ContentConfig['collections'][C]>['loader']['schema']>
119
+ ? import('astro/zod').infer<L['schema']>
119
120
  : any;
120
121
 
121
122
  type DataEntryMap = {