astro 6.0.5 → 6.0.7

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 (43) hide show
  1. package/bin/astro.mjs +2 -5
  2. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  3. package/dist/content/content-layer.js +7 -4
  4. package/dist/core/app/base.js +2 -1
  5. package/dist/core/app/entrypoints/node.d.ts +1 -1
  6. package/dist/core/app/entrypoints/node.js +9 -1
  7. package/dist/core/app/middlewares.d.ts +1 -0
  8. package/dist/core/app/middlewares.js +2 -1
  9. package/dist/core/app/node.d.ts +17 -0
  10. package/dist/core/app/node.js +1 -0
  11. package/dist/core/build/plugins/plugin-manifest.js +11 -5
  12. package/dist/core/constants.js +1 -1
  13. package/dist/core/create-vite.js +4 -1
  14. package/dist/core/dev/dev.js +1 -1
  15. package/dist/core/messages/runtime.js +1 -1
  16. package/dist/core/render/params-and-props.js +4 -6
  17. package/dist/core/render/ssr-element.js +26 -4
  18. package/dist/core/render-context.js +7 -0
  19. package/dist/core/routing/helpers.d.ts +6 -0
  20. package/dist/core/routing/helpers.js +6 -0
  21. package/dist/core/routing/match.js +8 -0
  22. package/dist/core/routing/rewrite.js +13 -0
  23. package/dist/core/server-islands/shared-state.d.ts +44 -0
  24. package/dist/core/server-islands/shared-state.js +96 -0
  25. package/dist/core/server-islands/vite-plugin-server-islands.d.ts +4 -1
  26. package/dist/core/server-islands/vite-plugin-server-islands.js +34 -60
  27. package/dist/core/viteUtils.d.ts +7 -1
  28. package/dist/core/viteUtils.js +18 -1
  29. package/dist/i18n/middleware.js +14 -7
  30. package/dist/i18n/utils.d.ts +6 -0
  31. package/dist/i18n/utils.js +22 -0
  32. package/dist/manifest/serialized.d.ts +1 -1
  33. package/dist/manifest/serialized.js +15 -4
  34. package/dist/vite-plugin-astro/hmr.d.ts +3 -1
  35. package/dist/vite-plugin-astro/hmr.js +6 -2
  36. package/dist/vite-plugin-astro/index.js +1 -1
  37. package/dist/vite-plugin-astro-server/plugin.js +8 -12
  38. package/dist/vite-plugin-css/index.js +20 -0
  39. package/dist/vite-plugin-renderers/index.d.ts +2 -0
  40. package/dist/vite-plugin-renderers/index.js +1 -1
  41. package/dist/vite-plugin-routes/index.js +3 -2
  42. package/package.json +3 -3
  43. package/templates/content/types.d.ts +3 -2
package/bin/astro.mjs CHANGED
@@ -8,12 +8,9 @@ const CI_INSTRUCTIONS = {
8
8
  VERCEL: 'https://vercel.com/docs/runtimes#official-runtimes/node-js/node-js-version',
9
9
  };
10
10
 
11
- // TODO: remove once Stackblitz supports Node 22
12
- const IS_STACKBLITZ = !!process.versions.webcontainer;
13
-
14
11
  // Hardcode supported Node.js version so we don't have to read differently in CJS & ESM.
15
- const engines = IS_STACKBLITZ ? '>=20.19.1' : '>=22.12.0';
16
- const skipSemverCheckIfAbove = IS_STACKBLITZ ? 21 : 23;
12
+ const engines = '>=22.12.0';
13
+ const skipSemverCheckIfAbove = 23;
17
14
 
18
15
  /** `astro *` */
19
16
  async function main() {
@@ -1,6 +1,6 @@
1
1
  class BuildTimeAstroVersionProvider {
2
2
  // Injected during the build through esbuild define
3
- version = "6.0.5";
3
+ version = "6.0.7";
4
4
  }
5
5
  export {
6
6
  BuildTimeAstroVersionProvider
@@ -117,7 +117,10 @@ class ContentLayer {
117
117
  });
118
118
  return {
119
119
  html: code,
120
- metadata
120
+ metadata: {
121
+ ...metadata,
122
+ imagePaths: (metadata.localImagePaths ?? []).concat(metadata.remoteImagePaths ?? [])
123
+ }
121
124
  };
122
125
  }
123
126
  /**
@@ -189,7 +192,7 @@ ${contentConfig.error.message}`
189
192
  logger.info("Content config changed");
190
193
  shouldClear = true;
191
194
  }
192
- if (previousAstroVersion && previousAstroVersion !== "6.0.5") {
195
+ if (previousAstroVersion && previousAstroVersion !== "6.0.7") {
193
196
  logger.info("Astro version changed");
194
197
  shouldClear = true;
195
198
  }
@@ -197,8 +200,8 @@ ${contentConfig.error.message}`
197
200
  logger.info("Clearing content store");
198
201
  this.#store.clearAll();
199
202
  }
200
- if ("6.0.5") {
201
- this.#store.metaStore().set("astro-version", "6.0.5");
203
+ if ("6.0.7") {
204
+ this.#store.metaStore().set("astro-version", "6.0.7");
202
205
  }
203
206
  if (currentConfigDigest) {
204
207
  this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -32,6 +32,7 @@ import { AstroIntegrationLogger, Logger } from "../logger/core.js";
32
32
  import { RenderContext } from "../render-context.js";
33
33
  import { redirectTemplate } from "../routing/3xx.js";
34
34
  import { ensure404Route } from "../routing/astro-designed-error-pages.js";
35
+ import { routeHasHtmlExtension } from "../routing/helpers.js";
35
36
  import { matchRoute } from "../routing/match.js";
36
37
  import { applyCacheHeaders } from "../cache/runtime/cache.js";
37
38
  import { Router } from "../routing/router.js";
@@ -303,7 +304,7 @@ class BaseApp {
303
304
  });
304
305
  }
305
306
  let pathname = this.getPathnameFromRequest(request);
306
- if (this.isDev()) {
307
+ if (this.isDev() && !routeHasHtmlExtension(routeData)) {
307
308
  pathname = pathname.replace(/\/index\.html$/, "/").replace(/\.html$/, "");
308
309
  }
309
310
  const defaultStatus = this.getDefaultStatusCode(routeData, pathname);
@@ -1 +1 @@
1
- export { NodeApp, loadApp, loadManifest, createRequest, writeResponse } from '../node.js';
1
+ export { NodeApp, loadApp, loadManifest, createRequest, writeResponse, getAbortControllerCleanup, } from '../node.js';
@@ -1,7 +1,15 @@
1
- import { NodeApp, loadApp, loadManifest, createRequest, writeResponse } from "../node.js";
1
+ import {
2
+ NodeApp,
3
+ loadApp,
4
+ loadManifest,
5
+ createRequest,
6
+ writeResponse,
7
+ getAbortControllerCleanup
8
+ } from "../node.js";
2
9
  export {
3
10
  NodeApp,
4
11
  createRequest,
12
+ getAbortControllerCleanup,
5
13
  loadApp,
6
14
  loadManifest,
7
15
  writeResponse
@@ -5,3 +5,4 @@ import type { MiddlewareHandler } from '../../types/public/common.js';
5
5
  * @private
6
6
  */
7
7
  export declare function createOriginCheckMiddleware(): MiddlewareHandler;
8
+ export declare function hasFormLikeHeader(contentType: string | null): boolean;
@@ -44,5 +44,6 @@ function hasFormLikeHeader(contentType) {
44
44
  return false;
45
45
  }
46
46
  export {
47
- createOriginCheckMiddleware
47
+ createOriginCheckMiddleware,
48
+ hasFormLikeHeader
48
49
  };
@@ -90,6 +90,23 @@ export declare class NodeApp extends App {
90
90
  */
91
91
  static writeResponse: typeof writeResponse;
92
92
  }
93
+ /**
94
+ * Returns the cleanup function for the AbortController and socket listeners created by `createRequest()`
95
+ * for the NodeJS IncomingMessage. This should only be called directly if the request is not
96
+ * being handled by Astro, i.e. if not calling `writeResponse()` after `createRequest()`.
97
+ * ```js
98
+ * import { createRequest, getAbortControllerCleanup } from 'astro/app/node';
99
+ * import { createServer } from 'node:http';
100
+ *
101
+ * const server = createServer(async (req, res) => {
102
+ * const request = createRequest(req);
103
+ * const cleanup = getAbortControllerCleanup(req);
104
+ * if (cleanup) cleanup();
105
+ * // can now safely call another handler
106
+ * })
107
+ * ```
108
+ */
109
+ export declare function getAbortControllerCleanup(req?: NodeRequest): (() => void) | undefined;
93
110
  /** @deprecated This will be removed in a future major version. */
94
111
  export declare function loadManifest(rootFolder: URL): Promise<SSRManifest>;
95
112
  /** @deprecated This will be removed in a future major version. */
@@ -278,6 +278,7 @@ async function loadApp(rootFolder) {
278
278
  export {
279
279
  NodeApp,
280
280
  createRequest,
281
+ getAbortControllerCleanup,
281
282
  loadApp,
282
283
  loadManifest,
283
284
  writeResponse
@@ -81,13 +81,19 @@ async function buildManifest(opts, internals, staticFiles, encodedKey) {
81
81
  const { settings } = opts;
82
82
  const routes = [];
83
83
  const domainLookupTable = {};
84
- const entryModules = Object.fromEntries(internals.entrySpecifierToBundleMap.entries());
85
- if (settings.scripts.some((script) => script.stage === "page")) {
86
- staticFiles.push(entryModules[PAGE_SCRIPT_ID]);
87
- }
84
+ const rawEntryModules = Object.fromEntries(internals.entrySpecifierToBundleMap.entries());
88
85
  const assetQueryParams = settings.adapter?.client?.assetQueryParams;
89
86
  const assetQueryString = assetQueryParams ? assetQueryParams.toString() : void 0;
90
87
  const appendAssetQuery = (pth) => assetQueryString ? `${pth}?${assetQueryString}` : pth;
88
+ const entryModules = Object.fromEntries(
89
+ Object.entries(rawEntryModules).map(([key, value]) => [
90
+ key,
91
+ value ? appendAssetQuery(value) : value
92
+ ])
93
+ );
94
+ if (settings.scripts.some((script) => script.stage === "page")) {
95
+ staticFiles.push(rawEntryModules[PAGE_SCRIPT_ID]);
96
+ }
91
97
  const prefixAssetPath = (pth) => {
92
98
  let result = "";
93
99
  if (settings.config.build.assetsPrefix) {
@@ -118,7 +124,7 @@ async function buildManifest(opts, internals, staticFiles, encodedKey) {
118
124
  if (!pageData) continue;
119
125
  const scripts = [];
120
126
  if (settings.scripts.some((script) => script.stage === "page")) {
121
- const src = entryModules[PAGE_SCRIPT_ID];
127
+ const src = rawEntryModules[PAGE_SCRIPT_ID];
122
128
  scripts.push({
123
129
  type: "external",
124
130
  value: appendAssetQuery(src)
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "6.0.5";
1
+ const ASTRO_VERSION = "6.0.7";
2
2
  const ASTRO_GENERATOR = `Astro v${ASTRO_VERSION}`;
3
3
  const REROUTE_DIRECTIVE_HEADER = "X-Astro-Reroute";
4
4
  const REWRITE_DIRECTIVE_HEADER_KEY = "X-Astro-Rewrite";
@@ -43,6 +43,7 @@ import astroScriptsPageSSRPlugin from "../vite-plugin-scripts/page-ssr.js";
43
43
  import { createViteLogger } from "./logger/vite.js";
44
44
  import { vitePluginMiddleware } from "./middleware/vite-plugin.js";
45
45
  import { joinPaths } from "./path.js";
46
+ import { ServerIslandsState } from "./server-islands/shared-state.js";
46
47
  import { vitePluginServerIslands } from "./server-islands/vite-plugin-server-islands.js";
47
48
  import { vitePluginCacheProvider } from "./cache/vite-plugin.js";
48
49
  import { vitePluginSessionDriver } from "./session/vite-plugin.js";
@@ -80,6 +81,7 @@ async function createVite(commandConfig, { settings, logger, mode, command, fs =
80
81
  mode,
81
82
  config: settings.config
82
83
  });
84
+ const serverIslandsState = new ServerIslandsState();
83
85
  validateEnvPrefixAgainstSchema(settings.config);
84
86
  const commonConfig = {
85
87
  // Tell Vite not to combine config from vite.config.js with our provided inline config
@@ -96,6 +98,7 @@ async function createVite(commandConfig, { settings, logger, mode, command, fs =
96
98
  vitePluginRenderers({
97
99
  settings,
98
100
  routesList,
101
+ serverIslandsState,
99
102
  command: command === "dev" ? "serve" : "build"
100
103
  }),
101
104
  vitePluginStaticPaths(),
@@ -133,7 +136,7 @@ async function createVite(commandConfig, { settings, logger, mode, command, fs =
133
136
  vitePluginFileURL(),
134
137
  astroInternationalization({ settings }),
135
138
  vitePluginActions({ fs, settings }),
136
- vitePluginServerIslands({ settings, logger }),
139
+ vitePluginServerIslands({ settings, logger, serverIslandsState }),
137
140
  vitePluginSessionDriver({ settings }),
138
141
  vitePluginCacheProvider({ settings }),
139
142
  astroContainer(),
@@ -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 = "6.0.5";
29
+ const currentVersion = "6.0.7";
30
30
  const isPrerelease = currentVersion.includes("-");
31
31
  if (!isPrerelease) {
32
32
  try {
@@ -269,7 +269,7 @@ function printHelp({
269
269
  message.push(
270
270
  linebreak(),
271
271
  ` ${bgGreen(black(` ${commandName} `))} ${green(
272
- `v${"6.0.5"}`
272
+ `v${"6.0.7"}`
273
273
  )} ${headline}`
274
274
  );
275
275
  }
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_404_COMPONENT } from "../constants.js";
2
2
  import { AstroError, AstroErrorData } from "../errors/index.js";
3
- import { routeIsFallback, routeIsRedirect } from "../routing/helpers.js";
3
+ import { routeHasHtmlExtension, routeIsFallback, routeIsRedirect } from "../routing/helpers.js";
4
4
  import { callGetStaticPaths, findPathItemByKey } from "./route-cache.js";
5
5
  async function getProps(opts) {
6
6
  const {
@@ -44,11 +44,9 @@ async function getProps(opts) {
44
44
  }
45
45
  function getParams(route, pathname) {
46
46
  if (!route.params.length) return {};
47
- let path = pathname;
48
- if (pathname.endsWith(".html")) {
49
- path = path.slice(0, -5);
50
- }
51
- const paramsMatch = route.pattern.exec(path) || route.fallbackRoutes.map((fallbackRoute) => fallbackRoute.pattern.exec(path)).find((x) => x);
47
+ const path = pathname.endsWith(".html") && !routeHasHtmlExtension(route) ? pathname.slice(0, -5) : pathname;
48
+ const allPatterns = [route, ...route.fallbackRoutes].map((r) => r.pattern);
49
+ const paramsMatch = allPatterns.map((pattern) => pattern.exec(path)).find((x) => x);
52
50
  if (!paramsMatch) return {};
53
51
  const params = {};
54
52
  route.params.forEach((key, i) => {
@@ -1,17 +1,39 @@
1
1
  import { getAssetsPrefix } from "../../assets/utils/getAssetsPrefix.js";
2
2
  import { fileExtension, joinPaths, prependForwardSlash, slash } from "../../core/path.js";
3
+ const URL_PARSE_BASE = "https://astro.build";
4
+ function splitAssetPath(path) {
5
+ const parsed = new URL(path, URL_PARSE_BASE);
6
+ const isAbsolute = URL.canParse(path);
7
+ const pathname = !isAbsolute && !path.startsWith("/") ? parsed.pathname.slice(1) : parsed.pathname;
8
+ return {
9
+ pathname,
10
+ suffix: `${parsed.search}${parsed.hash}`
11
+ };
12
+ }
13
+ function appendQueryParams(path, queryParams) {
14
+ const queryString = queryParams.toString();
15
+ if (!queryString) {
16
+ return path;
17
+ }
18
+ const hashIndex = path.indexOf("#");
19
+ const basePath = hashIndex === -1 ? path : path.slice(0, hashIndex);
20
+ const hash = hashIndex === -1 ? "" : path.slice(hashIndex);
21
+ const separator = basePath.includes("?") ? "&" : "?";
22
+ return `${basePath}${separator}${queryString}${hash}`;
23
+ }
3
24
  function createAssetLink(href, base, assetsPrefix, queryParams) {
25
+ const { pathname, suffix } = splitAssetPath(href);
4
26
  let url = "";
5
27
  if (assetsPrefix) {
6
- const pf = getAssetsPrefix(fileExtension(href), assetsPrefix);
7
- url = joinPaths(pf, slash(href));
28
+ const pf = getAssetsPrefix(fileExtension(pathname), assetsPrefix);
29
+ url = joinPaths(pf, slash(pathname)) + suffix;
8
30
  } else if (base) {
9
- url = prependForwardSlash(joinPaths(base, slash(href)));
31
+ url = prependForwardSlash(joinPaths(base, slash(pathname))) + suffix;
10
32
  } else {
11
33
  url = href;
12
34
  }
13
35
  if (queryParams) {
14
- url += "?" + queryParams.toString();
36
+ url = appendQueryParams(url, queryParams);
15
37
  }
16
38
  return url;
17
39
  }
@@ -4,6 +4,7 @@ import { getActionContext } from "../actions/runtime/server.js";
4
4
  import { createCallAction, createGetActionResult, hasActionPayload } from "../actions/utils.js";
5
5
  import {
6
6
  computeCurrentLocale,
7
+ computeCurrentLocaleFromParams,
7
8
  computePreferredLocale,
8
9
  computePreferredLocaleList
9
10
  } from "../i18n/utils.js";
@@ -737,6 +738,12 @@ class RenderContext {
737
738
  }
738
739
  pathname = pathname && !isRoute404or500(routeData) ? pathname : url.pathname;
739
740
  computedLocale = computeCurrentLocale(pathname, locales, defaultLocale);
741
+ if (routeData.params.length > 0) {
742
+ const localeFromParams = computeCurrentLocaleFromParams(this.params, locales);
743
+ if (localeFromParams) {
744
+ computedLocale = localeFromParams;
745
+ }
746
+ }
740
747
  }
741
748
  this.#currentLocale = computedLocale ?? fallbackTo;
742
749
  return this.#currentLocale;
@@ -32,6 +32,12 @@ export declare function getCustom404Route(manifestData: RoutesList): RouteData |
32
32
  * Return a user-provided 500 route if one exists.
33
33
  */
34
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;
35
41
  export declare function hasNonPrerenderedProjectRoute(routes: Array<Pick<RouteData, 'type' | 'origin' | 'prerender'>>, options?: {
36
42
  includeEndpoints?: boolean;
37
43
  }): boolean;
@@ -25,6 +25,11 @@ 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
+ }
28
33
  function hasNonPrerenderedProjectRoute(routes, options) {
29
34
  const includeEndpoints = options?.includeEndpoints ?? true;
30
35
  const routeTypes = includeEndpoints ? ["page", "endpoint"] : ["page"];
@@ -38,6 +43,7 @@ export {
38
43
  getCustom500Route,
39
44
  getFallbackRoute,
40
45
  hasNonPrerenderedProjectRoute,
46
+ routeHasHtmlExtension,
41
47
  routeIsFallback,
42
48
  routeIsRedirect
43
49
  };
@@ -2,6 +2,14 @@ import { redirectIsExternal } from "../redirects/render.js";
2
2
  import { SERVER_ISLAND_COMPONENT } from "../server-islands/endpoint.js";
3
3
  import { isRoute404, isRoute500 } from "./internal/route-errors.js";
4
4
  function matchRoute(pathname, manifest) {
5
+ if (isRoute404(pathname)) {
6
+ const errorRoute = manifest.routes.find((route) => isRoute404(route.route));
7
+ if (errorRoute) return errorRoute;
8
+ }
9
+ if (isRoute500(pathname)) {
10
+ const errorRoute = manifest.routes.find((route) => isRoute500(route.route));
11
+ if (errorRoute) return errorRoute;
12
+ }
5
13
  return manifest.routes.find((route) => {
6
14
  return route.pattern.test(pathname) || route.fallbackRoutes.some((fallbackRoute) => fallbackRoute.pattern.test(pathname));
7
15
  });
@@ -10,6 +10,7 @@ import {
10
10
  } from "../path.js";
11
11
  import { createRequest } from "../request.js";
12
12
  import { DEFAULT_404_ROUTE } from "./internal/astro-designed-error-pages.js";
13
+ import { isRoute404, isRoute500 } from "./internal/route-errors.js";
13
14
  function findRouteToRewrite({
14
15
  payload,
15
16
  routes,
@@ -53,6 +54,18 @@ function findRouteToRewrite({
53
54
  newUrl.pathname = joinPaths(...[base, pathname].filter(Boolean));
54
55
  }
55
56
  const decodedPathname = decodeURI(pathname);
57
+ if (isRoute404(decodedPathname)) {
58
+ const errorRoute = routes.find((route) => route.route === "/404");
59
+ if (errorRoute) {
60
+ return { routeData: errorRoute, newUrl, pathname: decodedPathname };
61
+ }
62
+ }
63
+ if (isRoute500(decodedPathname)) {
64
+ const errorRoute = routes.find((route) => route.route === "/500");
65
+ if (errorRoute) {
66
+ return { routeData: errorRoute, newUrl, pathname: decodedPathname };
67
+ }
68
+ }
56
69
  let foundRoute;
57
70
  for (const route of routes) {
58
71
  if (route.pattern.test(decodedPathname)) {
@@ -0,0 +1,44 @@
1
+ type ServerIslandDiscovery = {
2
+ resolvedPath: string;
3
+ localName: string;
4
+ specifier: string;
5
+ importer: string;
6
+ };
7
+ type ServerIslandRecord = Omit<ServerIslandDiscovery, 'resolvedPath'> & {
8
+ islandName: string;
9
+ };
10
+ export declare class ServerIslandsState {
11
+ private islandsByResolvedPath;
12
+ private resolvedPathByIslandName;
13
+ private referenceIdByResolvedPath;
14
+ hasIslands(): boolean;
15
+ /**
16
+ * Record a discovered server island.
17
+ *
18
+ * Dedupe is based on `resolvedPath`: if the same resolved path is discovered
19
+ * again from a different importer/specifier, the first record is preserved.
20
+ * This keeps island names stable across repeated scans.
21
+ */
22
+ discover(island: ServerIslandDiscovery): ServerIslandRecord;
23
+ getDiscoveredIslands(): Iterable<ServerIslandRecord>;
24
+ hasReferenceId(resolvedPath: string): boolean;
25
+ setReferenceId(resolvedPath: string, referenceId: string): void;
26
+ getDiscoveredIslandEntries(): Iterable<[string, ServerIslandRecord]>;
27
+ /**
28
+ * Build import-map source from discovered islands.
29
+ *
30
+ * Used by non-SSR build output and dev replacement paths where we can import
31
+ * directly from discovered component paths.
32
+ */
33
+ createImportMapSourceFromDiscovered(toImportPath: (fileName: string) => string): string;
34
+ /**
35
+ * Build import-map source from Rollup reference ids.
36
+ *
37
+ * Used by SSR build output: reference ids are resolved to final emitted chunk
38
+ * file names before generating import() mappings.
39
+ */
40
+ createImportMapSourceFromReferences(resolveFileName: (referenceId: string) => string, toImportPath: (fileName: string) => string): string;
41
+ createNameMapSource(): string;
42
+ private createImportMapSource;
43
+ }
44
+ export {};
@@ -0,0 +1,96 @@
1
+ class ServerIslandsState {
2
+ // Canonical source of discovered islands keyed by resolved component path.
3
+ islandsByResolvedPath = /* @__PURE__ */ new Map();
4
+ // Reverse lookup used to keep island names unique and stable.
5
+ resolvedPathByIslandName = /* @__PURE__ */ new Map();
6
+ // Rollup reference ids emitted for SSR chunks, keyed by resolved path.
7
+ referenceIdByResolvedPath = /* @__PURE__ */ new Map();
8
+ hasIslands() {
9
+ return this.islandsByResolvedPath.size > 0;
10
+ }
11
+ /**
12
+ * Record a discovered server island.
13
+ *
14
+ * Dedupe is based on `resolvedPath`: if the same resolved path is discovered
15
+ * again from a different importer/specifier, the first record is preserved.
16
+ * This keeps island names stable across repeated scans.
17
+ */
18
+ discover(island) {
19
+ const { resolvedPath, ...discovery } = island;
20
+ const existing = this.islandsByResolvedPath.get(resolvedPath);
21
+ if (existing) {
22
+ return existing;
23
+ }
24
+ let name = island.localName;
25
+ let idx = 1;
26
+ while (this.resolvedPathByIslandName.has(name)) {
27
+ name += idx++;
28
+ }
29
+ const record = {
30
+ ...discovery,
31
+ islandName: name
32
+ };
33
+ this.islandsByResolvedPath.set(resolvedPath, record);
34
+ this.resolvedPathByIslandName.set(name, resolvedPath);
35
+ return record;
36
+ }
37
+ getDiscoveredIslands() {
38
+ return this.islandsByResolvedPath.values();
39
+ }
40
+ hasReferenceId(resolvedPath) {
41
+ return this.referenceIdByResolvedPath.has(resolvedPath);
42
+ }
43
+ setReferenceId(resolvedPath, referenceId) {
44
+ this.referenceIdByResolvedPath.set(resolvedPath, referenceId);
45
+ }
46
+ getDiscoveredIslandEntries() {
47
+ return this.islandsByResolvedPath.entries();
48
+ }
49
+ /**
50
+ * Build import-map source from discovered islands.
51
+ *
52
+ * Used by non-SSR build output and dev replacement paths where we can import
53
+ * directly from discovered component paths.
54
+ */
55
+ createImportMapSourceFromDiscovered(toImportPath) {
56
+ const entries = Array.from(
57
+ this.islandsByResolvedPath,
58
+ ([resolvedPath, island]) => [island.islandName, resolvedPath]
59
+ );
60
+ return this.createImportMapSource(entries, toImportPath);
61
+ }
62
+ /**
63
+ * Build import-map source from Rollup reference ids.
64
+ *
65
+ * Used by SSR build output: reference ids are resolved to final emitted chunk
66
+ * file names before generating import() mappings.
67
+ */
68
+ createImportMapSourceFromReferences(resolveFileName, toImportPath) {
69
+ const entries = [];
70
+ for (const [resolvedPath, referenceId] of this.referenceIdByResolvedPath) {
71
+ const island = this.islandsByResolvedPath.get(resolvedPath);
72
+ if (!island) continue;
73
+ entries.push([island.islandName, resolveFileName(referenceId)]);
74
+ }
75
+ return this.createImportMapSource(entries, toImportPath);
76
+ }
77
+ createNameMapSource() {
78
+ const entries = Array.from(
79
+ this.islandsByResolvedPath,
80
+ ([resolvedPath, island]) => [resolvedPath, island.islandName]
81
+ );
82
+ return `new Map(${JSON.stringify(entries, null, 2)})`;
83
+ }
84
+ createImportMapSource(entries, toImportPath) {
85
+ const mappings = Array.from(entries, ([islandName, fileName]) => {
86
+ const importPath = toImportPath(fileName);
87
+ return ` [${JSON.stringify(islandName)}, () => import(${JSON.stringify(importPath)})],`;
88
+ });
89
+ return `new Map([
90
+ ${mappings.join("\n")}
91
+ ])`;
92
+ }
93
+ }
94
+ export {
95
+ ServerIslandsState
96
+ };
@@ -1,5 +1,8 @@
1
1
  import type { Plugin as VitePlugin } from 'vite';
2
2
  import type { AstroPluginOptions } from '../../types/astro.js';
3
+ import type { ServerIslandsState } from './shared-state.js';
3
4
  export declare const SERVER_ISLAND_MANIFEST = "virtual:astro:server-island-manifest";
4
5
  export declare const SERVER_ISLAND_MAP_MARKER = "$$server-islands-map$$";
5
- export declare function vitePluginServerIslands({ settings }: AstroPluginOptions): VitePlugin;
6
+ export declare function vitePluginServerIslands({ settings, serverIslandsState, }: AstroPluginOptions & {
7
+ serverIslandsState: ServerIslandsState;
8
+ }): VitePlugin;
@@ -7,36 +7,22 @@ const serverIslandPlaceholderNameMap = "'$$server-islands-name-map$$'";
7
7
  const SERVER_ISLAND_MAP_MARKER = "$$server-islands-map$$";
8
8
  const serverIslandMapReplaceExp = /['"]\$\$server-islands-map\$\$['"]/g;
9
9
  const serverIslandNameMapReplaceExp = /['"]\$\$server-islands-name-map\$\$['"]/g;
10
- function createServerIslandImportMapSource(entries, toImportPath) {
11
- const mappings = Array.from(entries, ([islandName, fileName]) => {
12
- const importPath = toImportPath(fileName);
13
- return ` [${JSON.stringify(islandName)}, () => import(${JSON.stringify(importPath)})],`;
14
- });
15
- return `new Map([
16
- ${mappings.join("\n")}
17
- ])`;
18
- }
19
- function createNameMapSource(entries) {
20
- return `new Map(${JSON.stringify(Array.from(entries), null, 2)})`;
21
- }
22
- function vitePluginServerIslands({ settings }) {
10
+ function vitePluginServerIslands({
11
+ settings,
12
+ serverIslandsState
13
+ }) {
23
14
  let command = "serve";
24
15
  let ssrEnvironment = null;
25
- const serverIslandMap = /* @__PURE__ */ new Map();
26
- const serverIslandNameMap = /* @__PURE__ */ new Map();
27
- const serverIslandSourceMap = /* @__PURE__ */ new Map();
28
- const referenceIdMap = /* @__PURE__ */ new Map();
29
16
  function ensureServerIslandReferenceIds(ctx) {
30
- for (const [resolvedPath, islandName] of serverIslandNameMap) {
31
- if (referenceIdMap.has(resolvedPath)) continue;
32
- const source = serverIslandSourceMap.get(resolvedPath);
17
+ for (const [resolvedPath, island] of serverIslandsState.getDiscoveredIslandEntries()) {
18
+ if (serverIslandsState.hasReferenceId(resolvedPath)) continue;
33
19
  const referenceId = ctx.emitFile({
34
20
  type: "chunk",
35
- id: source?.id ?? resolvedPath,
36
- importer: source?.importer,
37
- name: islandName
21
+ id: island.specifier,
22
+ importer: island.importer,
23
+ name: island.islandName
38
24
  });
39
- referenceIdMap.set(resolvedPath, referenceId);
25
+ serverIslandsState.setReferenceId(resolvedPath, referenceId);
40
26
  }
41
27
  }
42
28
  return {
@@ -86,33 +72,27 @@ export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
86
72
  const isBuildSsr = command === "build" && this.environment?.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr;
87
73
  if (astro) {
88
74
  for (const comp of astro.serverComponents) {
89
- if (!serverIslandNameMap.has(comp.resolvedPath)) {
90
- if (!settings.adapter) {
91
- throw new AstroError(AstroErrorData.NoAdapterInstalledServerIslands);
92
- }
93
- let name = comp.localName;
94
- let idx = 1;
95
- while (serverIslandMap.has(name)) {
96
- name += idx++;
97
- }
98
- serverIslandNameMap.set(comp.resolvedPath, name);
99
- serverIslandMap.set(name, comp.resolvedPath);
100
- serverIslandSourceMap.set(comp.resolvedPath, { id: comp.specifier, importer: id });
75
+ if (!settings.adapter) {
76
+ throw new AstroError(AstroErrorData.NoAdapterInstalledServerIslands);
101
77
  }
102
- if (isBuildSsr && !referenceIdMap.has(comp.resolvedPath)) {
103
- const islandName = serverIslandNameMap.get(comp.resolvedPath);
104
- const source = serverIslandSourceMap.get(comp.resolvedPath);
78
+ const island = serverIslandsState.discover({
79
+ resolvedPath: comp.resolvedPath,
80
+ localName: comp.localName,
81
+ specifier: comp.specifier ?? comp.resolvedPath,
82
+ importer: id
83
+ });
84
+ if (isBuildSsr && !serverIslandsState.hasReferenceId(comp.resolvedPath)) {
105
85
  const referenceId = this.emitFile({
106
86
  type: "chunk",
107
- id: source?.id ?? comp.resolvedPath,
108
- importer: source?.importer,
109
- name: islandName
87
+ id: island.specifier,
88
+ importer: island.importer,
89
+ name: island.islandName
110
90
  });
111
- referenceIdMap.set(comp.resolvedPath, referenceId);
91
+ serverIslandsState.setReferenceId(comp.resolvedPath, referenceId);
112
92
  }
113
93
  }
114
94
  }
115
- if (serverIslandNameMap.size > 0 && serverIslandMap.size > 0 && ssrEnvironment) {
95
+ if (serverIslandsState.hasIslands() && ssrEnvironment) {
116
96
  const mod = ssrEnvironment.moduleGraph.getModuleById(RESOLVED_SERVER_ISLAND_MANIFEST);
117
97
  if (mod) {
118
98
  ssrEnvironment.moduleGraph.invalidateModule(mod);
@@ -120,17 +100,16 @@ export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
120
100
  }
121
101
  if (id === RESOLVED_SERVER_ISLAND_MANIFEST) {
122
102
  if (command === "build" && settings.buildOutput) {
123
- const hasServerIslands = serverIslandNameMap.size > 0;
103
+ const hasServerIslands = serverIslandsState.hasIslands();
124
104
  if (hasServerIslands && settings.buildOutput !== "server") {
125
105
  throw new AstroError(AstroErrorData.NoAdapterInstalledServerIslands);
126
106
  }
127
107
  }
128
- if (command !== "build" && serverIslandNameMap.size > 0 && serverIslandMap.size > 0) {
129
- const mapSource = createServerIslandImportMapSource(
130
- serverIslandMap,
108
+ if (command !== "build" && serverIslandsState.hasIslands()) {
109
+ const mapSource = serverIslandsState.createImportMapSourceFromDiscovered(
131
110
  (fileName) => fileName
132
111
  );
133
- const nameMapSource = createNameMapSource(serverIslandNameMap);
112
+ const nameMapSource = serverIslandsState.createNameMapSource();
134
113
  return {
135
114
  code: `
136
115
  export const serverIslandMap = ${mapSource};
@@ -150,21 +129,16 @@ export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
150
129
  if (envName === ASTRO_VITE_ENVIRONMENT_NAMES.ssr) {
151
130
  const isRelativeChunk = !chunk.isEntry;
152
131
  const dots = isRelativeChunk ? ".." : ".";
153
- const mapEntries = [];
154
- for (const [resolvedPath, referenceId] of referenceIdMap) {
155
- const fileName = this.getFileName(referenceId);
156
- const islandName = serverIslandNameMap.get(resolvedPath);
157
- if (!islandName) continue;
158
- mapEntries.push([islandName, fileName]);
159
- }
160
- mapSource = createServerIslandImportMapSource(
161
- mapEntries,
132
+ mapSource = serverIslandsState.createImportMapSourceFromReferences(
133
+ (referenceId) => this.getFileName(referenceId),
162
134
  (fileName) => `${dots}/${fileName}`
163
135
  );
164
136
  } else {
165
- mapSource = createServerIslandImportMapSource(serverIslandMap, (fileName) => fileName);
137
+ mapSource = serverIslandsState.createImportMapSourceFromDiscovered(
138
+ (fileName) => fileName
139
+ );
166
140
  }
167
- const nameMapSource = createNameMapSource(serverIslandNameMap);
141
+ const nameMapSource = serverIslandsState.createNameMapSource();
168
142
  return {
169
143
  code: code.replace(serverIslandMapReplaceExp, mapSource).replace(serverIslandNameMapReplaceExp, nameMapSource),
170
144
  map: null
@@ -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
  }
@@ -48,15 +48,22 @@ function createI18nMiddleware(i18n, base, trailingSlash, format) {
48
48
  return context.redirect(location, routeDecision.status);
49
49
  }
50
50
  case "notFound": {
51
- const notFoundRes = new Response(response.body, {
52
- status: 404,
53
- headers: response.headers
54
- });
55
- notFoundRes.headers.set(REROUTE_DIRECTIVE_HEADER, "no");
51
+ if (context.isPrerendered) {
52
+ const prerenderedRes = new Response(response.body, {
53
+ status: 404,
54
+ headers: response.headers
55
+ });
56
+ prerenderedRes.headers.set(REROUTE_DIRECTIVE_HEADER, "no");
57
+ if (routeDecision.location) {
58
+ prerenderedRes.headers.set("Location", routeDecision.location);
59
+ }
60
+ return prerenderedRes;
61
+ }
62
+ const headers = new Headers();
56
63
  if (routeDecision.location) {
57
- notFoundRes.headers.set("Location", routeDecision.location);
64
+ headers.set("Location", routeDecision.location);
58
65
  }
59
- return notFoundRes;
66
+ return new Response(null, { status: 404, headers });
60
67
  }
61
68
  case "continue":
62
69
  break;
@@ -21,4 +21,10 @@ export declare function parseLocale(header: string): BrowserLocale[];
21
21
  export declare function computePreferredLocale(request: Request, locales: Locales): string | undefined;
22
22
  export declare function computePreferredLocaleList(request: Request, locales: Locales): string[];
23
23
  export declare function computeCurrentLocale(pathname: string, locales: Locales, defaultLocale: string): string | undefined;
24
+ /**
25
+ * Check if any of the route's resolved param values match a configured locale.
26
+ * This handles dynamic routes like `[locale]` or `[...path]` where the locale
27
+ * isn't in a static segment of the route pathname.
28
+ */
29
+ export declare function computeCurrentLocaleFromParams(params: Record<string, string | undefined>, locales: Locales): string | undefined;
24
30
  export {};
@@ -134,8 +134,30 @@ function computeCurrentLocale(pathname, locales, defaultLocale) {
134
134
  }
135
135
  }
136
136
  }
137
+ function computeCurrentLocaleFromParams(params, locales) {
138
+ const byNormalizedCode = /* @__PURE__ */ new Map();
139
+ const byPath = /* @__PURE__ */ new Map();
140
+ for (const locale of locales) {
141
+ if (typeof locale === "string") {
142
+ byNormalizedCode.set(normalizeTheLocale(locale), locale);
143
+ } else {
144
+ byPath.set(locale.path, locale.codes[0]);
145
+ for (const code of locale.codes) {
146
+ byNormalizedCode.set(normalizeTheLocale(code), code);
147
+ }
148
+ }
149
+ }
150
+ for (const value of Object.values(params)) {
151
+ if (!value) continue;
152
+ const pathMatch = byPath.get(value);
153
+ if (pathMatch) return pathMatch;
154
+ const codeMatch = byNormalizedCode.get(normalizeTheLocale(value));
155
+ if (codeMatch) return codeMatch;
156
+ }
157
+ }
137
158
  export {
138
159
  computeCurrentLocale,
160
+ computeCurrentLocaleFromParams,
139
161
  computePreferredLocale,
140
162
  computePreferredLocaleList,
141
163
  parseLocale
@@ -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,17 @@ function serializedManifestPlugin({
31
33
  command,
32
34
  sync
33
35
  }) {
36
+ const normalizedSrcDir = normalizePath(fileURLToPath(settings.config.srcDir));
37
+ let encodedKeyPromise;
38
+ function getEncodedKey() {
39
+ encodedKeyPromise ??= (async () => {
40
+ const key = hasEnvironmentKey() ? await getEnvironmentKey() : await createKey();
41
+ return encodeKey(key);
42
+ })();
43
+ return encodedKeyPromise;
44
+ }
34
45
  function reloadManifest(path, server) {
35
- if (path != null && path.startsWith(settings.config.srcDir.pathname)) {
46
+ if (path != null && normalizePath(path).startsWith(normalizedSrcDir)) {
36
47
  const environment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
37
48
  const virtualMod = environment.moduleGraph.getModuleById(SERIALIZED_MANIFEST_RESOLVED_ID);
38
49
  if (!virtualMod) return;
@@ -64,7 +75,7 @@ function serializedManifestPlugin({
64
75
  if (command === "build" && !sync) {
65
76
  manifestData = `'${MANIFEST_REPLACE}'`;
66
77
  } else {
67
- const serialized = await createSerializedManifest(settings);
78
+ const serialized = await createSerializedManifest(settings, await getEncodedKey());
68
79
  manifestData = JSON.stringify(serialized);
69
80
  }
70
81
  const hasCacheConfig = !!settings.config.experimental?.cache?.provider;
@@ -100,7 +111,7 @@ function serializedManifestPlugin({
100
111
  }
101
112
  };
102
113
  }
103
- async function createSerializedManifest(settings) {
114
+ async function createSerializedManifest(settings, encodedKey) {
104
115
  let i18nManifest;
105
116
  let csp;
106
117
  if (settings.config.i18n) {
@@ -158,7 +169,7 @@ async function createSerializedManifest(settings) {
158
169
  // 1mb default
159
170
  serverIslandBodySizeLimit: settings.config.security?.serverIslandBodySizeLimit ? settings.config.security.serverIslandBodySizeLimit : 1024 * 1024,
160
171
  // 1mb default
161
- key: await encodeKey(hasEnvironmentKey() ? await getEnvironmentKey() : await createKey()),
172
+ key: encodedKey ?? await encodeKey(hasEnvironmentKey() ? await getEnvironmentKey() : await createKey()),
162
173
  sessionConfig: sessionConfigToManifest(settings.config.session),
163
174
  cacheConfig: cacheConfigToManifest(
164
175
  settings.config.experimental?.cache,
@@ -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 { isRouteServerIsland, matchAllRoutes } from "../core/routing/match.js";
22
+ import { 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 || isRouteServerIsland(route))) {
129
+ if (!matches.some((route) => route.prerender)) {
134
130
  return next();
135
131
  }
136
132
  localStorage.run(request, () => {
@@ -18,6 +18,23 @@ import {
18
18
  function getComponentFromVirtualModuleCssName(virtualModulePrefix, id) {
19
19
  return id.slice(virtualModulePrefix.length).replace(new RegExp(ASTRO_CSS_EXTENSION_POST_PATTERN, "g"), ".");
20
20
  }
21
+ async function ensureModulesLoaded(env, mod, seen = /* @__PURE__ */ new Set()) {
22
+ const id = mod.id ?? mod.url;
23
+ if (seen.has(id)) return;
24
+ seen.add(id);
25
+ for (const imp of mod.importedModules) {
26
+ if (!imp.id) continue;
27
+ if (seen.has(imp.id)) continue;
28
+ if (imp.id.includes(PROPAGATED_ASSET_QUERY_PARAM)) continue;
29
+ if (!imp.transformResult) {
30
+ try {
31
+ await env.fetchModule(imp.id);
32
+ } catch {
33
+ }
34
+ }
35
+ await ensureModulesLoaded(env, imp, seen);
36
+ }
37
+ }
21
38
  function* collectCSSWithOrder(id, mod, seen = /* @__PURE__ */ new Set()) {
22
39
  seen.add(id);
23
40
  if (id.includes(PROPAGATED_ASSET_QUERY_PARAM)) {
@@ -98,6 +115,9 @@ function astroDevCssPlugin({ routesList, command }) {
98
115
  code: "export const css = new Set()"
99
116
  };
100
117
  }
118
+ if (env) {
119
+ await ensureModulesLoaded(env, mod);
120
+ }
101
121
  for (const collected of collectCSSWithOrder(componentPageId, mod)) {
102
122
  if (!cssWithOrder.has(collected.idKey)) {
103
123
  const content = cssContentCache.get(collected.id) || collected.content;
@@ -1,9 +1,11 @@
1
1
  import type { ConfigEnv, Plugin as VitePlugin } from 'vite';
2
+ import type { ServerIslandsState } from '../core/server-islands/shared-state.js';
2
3
  import type { AstroSettings, RoutesList } from '../types/astro.js';
3
4
  export declare const ASTRO_RENDERERS_MODULE_ID = "virtual:astro:renderers";
4
5
  interface PluginOptions {
5
6
  settings: AstroSettings;
6
7
  routesList: RoutesList;
8
+ serverIslandsState: ServerIslandsState;
7
9
  command: ConfigEnv['command'];
8
10
  }
9
11
  export default function vitePluginRenderers(options: PluginOptions): VitePlugin;
@@ -20,7 +20,7 @@ function vitePluginRenderers(options) {
20
20
  id: new RegExp(`^${RESOLVED_ASTRO_RENDERERS_MODULE_ID}$`)
21
21
  },
22
22
  handler() {
23
- if (options.command === "build" && this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr && renderers.length > 0 && !hasNonPrerenderedProjectRoute(options.routesList.routes, {
23
+ if (options.command === "build" && this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr && renderers.length > 0 && !options.serverIslandsState.hasIslands() && !hasNonPrerenderedProjectRoute(options.routesList.routes, {
24
24
  includeEndpoints: false
25
25
  })) {
26
26
  return { code: `export const renderers = [];` };
@@ -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(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "6.0.5",
3
+ "version": "6.0.7",
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",
@@ -153,8 +153,8 @@
153
153
  "xxhash-wasm": "^1.1.0",
154
154
  "yargs-parser": "^22.0.0",
155
155
  "zod": "^4.3.6",
156
- "@astrojs/markdown-remark": "7.0.0",
157
156
  "@astrojs/internal-helpers": "0.8.0",
157
+ "@astrojs/markdown-remark": "7.0.1",
158
158
  "@astrojs/telemetry": "3.3.0"
159
159
  },
160
160
  "optionalDependencies": {
@@ -194,7 +194,7 @@
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 = {