astro 6.0.8 → 6.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/client.d.ts +30 -40
  2. package/dist/assets/build/generate.js +17 -19
  3. package/dist/assets/build/remote.d.ts +4 -4
  4. package/dist/assets/build/remote.js +12 -10
  5. package/dist/assets/fonts/infra/dev-font-file-id-generator.js +4 -1
  6. package/dist/assets/fonts/vite-plugin-fonts.js +8 -0
  7. package/dist/assets/services/sharp.d.ts +21 -2
  8. package/dist/assets/services/sharp.js +54 -12
  9. package/dist/assets/vite-plugin-assets.js +4 -1
  10. package/dist/cli/add/index.js +55 -1
  11. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  12. package/dist/content/content-layer.js +3 -3
  13. package/dist/content/index.d.ts +1 -1
  14. package/dist/content/index.js +2 -3
  15. package/dist/content/runtime.d.ts +2 -0
  16. package/dist/content/runtime.js +2 -1
  17. package/dist/content/types-generator.js +4 -0
  18. package/dist/content/utils.d.ts +0 -1
  19. package/dist/content/utils.js +1 -10
  20. package/dist/core/app/dev/app.d.ts +5 -0
  21. package/dist/core/app/dev/app.js +7 -0
  22. package/dist/core/app/entrypoints/virtual/dev.js +4 -0
  23. package/dist/core/app/node.js +5 -4
  24. package/dist/core/app/validate-headers.d.ts +6 -0
  25. package/dist/core/app/validate-headers.js +4 -0
  26. package/dist/core/base-pipeline.d.ts +5 -0
  27. package/dist/core/base-pipeline.js +7 -0
  28. package/dist/core/build/generate.d.ts +47 -0
  29. package/dist/core/build/generate.js +43 -25
  30. package/dist/core/build/plugins/plugin-css.js +8 -4
  31. package/dist/core/config/schemas/base.d.ts +4 -2
  32. package/dist/core/config/schemas/base.js +22 -1
  33. package/dist/core/config/schemas/relative.d.ts +3 -3
  34. package/dist/core/constants.js +1 -1
  35. package/dist/core/create-vite.js +1 -1
  36. package/dist/core/dev/dev.js +13 -1
  37. package/dist/core/errors/errors-data.d.ts +2 -2
  38. package/dist/core/head-propagation/boundary.d.ts +8 -0
  39. package/dist/core/head-propagation/boundary.js +11 -0
  40. package/dist/core/head-propagation/buffer.d.ts +21 -0
  41. package/dist/core/head-propagation/buffer.js +18 -0
  42. package/dist/core/head-propagation/comment.d.ts +7 -0
  43. package/dist/core/head-propagation/comment.js +7 -0
  44. package/dist/core/head-propagation/graph.d.ts +18 -0
  45. package/dist/core/head-propagation/graph.js +32 -0
  46. package/dist/core/head-propagation/policy.d.ts +22 -0
  47. package/dist/core/head-propagation/policy.js +14 -0
  48. package/dist/core/head-propagation/resolver.d.ts +28 -0
  49. package/dist/core/head-propagation/resolver.js +25 -0
  50. package/dist/core/messages/runtime.d.ts +3 -0
  51. package/dist/core/messages/runtime.js +9 -1
  52. package/dist/core/middleware/vite-plugin.d.ts +1 -1
  53. package/dist/core/middleware/vite-plugin.js +25 -0
  54. package/dist/core/redirects/render.d.ts +17 -0
  55. package/dist/core/redirects/render.js +33 -24
  56. package/dist/core/routing/create-manifest.d.ts +15 -0
  57. package/dist/core/routing/create-manifest.js +131 -130
  58. package/dist/core/routing/prerender.d.ts +5 -0
  59. package/dist/core/routing/prerender.js +7 -1
  60. package/dist/core/server-islands/vite-plugin-server-islands.js +18 -6
  61. package/dist/integrations/hooks.js +4 -1
  62. package/dist/jsx/rehype.js +1 -1
  63. package/dist/manifest/serialized.js +5 -0
  64. package/dist/manifest/virtual-module.d.ts +4 -1
  65. package/dist/manifest/virtual-module.js +37 -35
  66. package/dist/runtime/client/dev-toolbar/apps/audit/rules/a11y.js +15 -5
  67. package/dist/runtime/server/render/astro/factory.js +6 -7
  68. package/dist/runtime/server/render/astro/instance.js +2 -4
  69. package/dist/runtime/server/render/astro/render.js +2 -11
  70. package/dist/runtime/server/render/common.js +3 -2
  71. package/dist/runtime/server/render/head-propagation/runtime.d.ts +20 -0
  72. package/dist/runtime/server/render/head-propagation/runtime.js +53 -0
  73. package/dist/runtime/server/render/page.js +5 -1
  74. package/dist/runtime/server/transition.d.ts +19 -1
  75. package/dist/runtime/server/transition.js +6 -1
  76. package/dist/transitions/events.d.ts +1 -1
  77. package/dist/transitions/events.js +5 -5
  78. package/dist/transitions/router.js +23 -19
  79. package/dist/transitions/swap-functions.js +6 -0
  80. package/dist/types/public/config.d.ts +71 -12
  81. package/dist/types/public/integrations.d.ts +9 -2
  82. package/dist/vite-plugin-app/app.d.ts +5 -0
  83. package/dist/vite-plugin-app/app.js +17 -1
  84. package/dist/vite-plugin-app/createAstroServerApp.js +4 -0
  85. package/dist/vite-plugin-astro-server/plugin.js +2 -1
  86. package/dist/vite-plugin-astro-server/vite.js +2 -2
  87. package/dist/vite-plugin-head/index.js +63 -25
  88. package/dist/vite-plugin-scripts/index.js +5 -0
  89. package/package.json +11 -11
@@ -0,0 +1,28 @@
1
+ import type { PropagationHint, SSRResult } from '../../types/public/internal.js';
2
+ /**
3
+ * Resolves the effective propagation hint for a component.
4
+ *
5
+ * Priority: explicit factory hint -> component metadata -> `none`.
6
+ *
7
+ * @example
8
+ * A runtime-created head entry uses `propagation: 'self'`, so it propagates
9
+ * even when metadata says `none`.
10
+ */
11
+ export declare function resolvePropagationHint(input: {
12
+ factoryHint: PropagationHint | undefined;
13
+ moduleId: string | undefined;
14
+ metadataLookup: (moduleId: string) => PropagationHint | undefined;
15
+ }): PropagationHint;
16
+ /** Returns true when a hint should register a component as a propagator. */
17
+ export declare function isPropagatingHint(hint: PropagationHint): boolean;
18
+ /**
19
+ * Reads propagation metadata from an `SSRResult` + component factory.
20
+ *
21
+ * @example
22
+ * A compiled `.astro` module with metadata `in-tree` is treated as propagating
23
+ * when the factory does not set a stronger explicit hint.
24
+ */
25
+ export declare function getPropagationHint(result: SSRResult, factory: {
26
+ propagation?: PropagationHint;
27
+ moduleId?: string | undefined;
28
+ }): PropagationHint;
@@ -0,0 +1,25 @@
1
+ function resolvePropagationHint(input) {
2
+ const explicitHint = input.factoryHint ?? "none";
3
+ if (explicitHint !== "none") {
4
+ return explicitHint;
5
+ }
6
+ if (!input.moduleId) {
7
+ return "none";
8
+ }
9
+ return input.metadataLookup(input.moduleId) ?? "none";
10
+ }
11
+ function isPropagatingHint(hint) {
12
+ return hint === "self" || hint === "in-tree";
13
+ }
14
+ function getPropagationHint(result, factory) {
15
+ return resolvePropagationHint({
16
+ factoryHint: factory.propagation,
17
+ moduleId: factory.moduleId,
18
+ metadataLookup: (moduleId) => result.componentMetadata.get(moduleId)?.propagation
19
+ });
20
+ }
21
+ export {
22
+ getPropagationHint,
23
+ isPropagatingHint,
24
+ resolvePropagationHint
25
+ };
@@ -41,6 +41,9 @@ export declare function preferenceReset(name: string): string;
41
41
  export declare function telemetryDisabled(): string;
42
42
  export declare function telemetryReset(): string;
43
43
  export declare function fsStrictWarning(): string;
44
+ export declare function vite8Warning({ viteVersion }: {
45
+ viteVersion: string;
46
+ }): string;
44
47
  export declare function prerelease({ currentVersion }: {
45
48
  currentVersion: string;
46
49
  }): string;
@@ -134,6 +134,13 @@ function fsStrictWarning() {
134
134
  ${subtitle}
135
135
  `;
136
136
  }
137
+ function vite8Warning({ viteVersion }) {
138
+ const title = yellow(`\u25B6 Vite ${bold(viteVersion)} detected in your project.`);
139
+ const subtitle = ` Astro requires Vite 7. Add ${bold('"overrides": { "vite": "^7" }')} to your ${bold("package.json")} to avoid issues.`;
140
+ return `${title}
141
+ ${subtitle}
142
+ `;
143
+ }
137
144
  function prerelease({ currentVersion }) {
138
145
  const tag = currentVersion.split("-").slice(1).join("-").replace(/\..*$/, "") || "unknown";
139
146
  const badge = bgYellow(black(` ${tag} `));
@@ -269,7 +276,7 @@ function printHelp({
269
276
  message.push(
270
277
  linebreak(),
271
278
  ` ${bgGreen(black(` ${commandName} `))} ${green(
272
- `v${"6.0.8"}`
279
+ `v${"6.1.1"}`
273
280
  )} ${headline}`
274
281
  );
275
282
  }
@@ -327,5 +334,6 @@ export {
327
334
  telemetryEnabled,
328
335
  telemetryNotice,
329
336
  telemetryReset,
337
+ vite8Warning,
330
338
  warnIfCspWithShiki
331
339
  };
@@ -1,4 +1,4 @@
1
- import type { Plugin as VitePlugin } from 'vite';
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,3 +1,7 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import {
3
+ normalizePath as viteNormalizePath
4
+ } from "vite";
1
5
  import { getServerOutputDirectory } from "../../prerender/utils.js";
2
6
  import { addRollupInput } from "../build/add-rollup-input.js";
3
7
  import { ASTRO_VITE_ENVIRONMENT_NAMES, MIDDLEWARE_PATH_SEGMENT_NAME } from "../constants.js";
@@ -11,11 +15,32 @@ function vitePluginMiddleware({ settings }) {
11
15
  let resolvedMiddlewareId = void 0;
12
16
  const hasIntegrationMiddleware = settings.middlewares.pre.length > 0 || settings.middlewares.post.length > 0;
13
17
  let userMiddlewareIsPresent = false;
18
+ const normalizedSrcDir = viteNormalizePath(fileURLToPath(settings.config.srcDir));
14
19
  return {
15
20
  name: "@astro/plugin-middleware",
16
21
  applyToEnvironment(environment) {
17
22
  return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.astro || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender;
18
23
  },
24
+ configureServer(server) {
25
+ server.watcher.on("change", (path) => {
26
+ const normalizedPath = viteNormalizePath(path);
27
+ if (!normalizedPath.startsWith(normalizedSrcDir)) return;
28
+ const relativePath = normalizedPath.slice(normalizedSrcDir.length);
29
+ if (!relativePath.startsWith(`${MIDDLEWARE_PATH_SEGMENT_NAME}.`)) return;
30
+ for (const name of [
31
+ ASTRO_VITE_ENVIRONMENT_NAMES.ssr,
32
+ ASTRO_VITE_ENVIRONMENT_NAMES.astro
33
+ ]) {
34
+ const environment = server.environments[name];
35
+ if (!environment) continue;
36
+ const virtualMod = environment.moduleGraph.getModuleById(MIDDLEWARE_RESOLVED_MODULE_ID);
37
+ if (virtualMod) {
38
+ environment.moduleGraph.invalidateModule(virtualMod);
39
+ }
40
+ environment.hot.send("astro:middleware-updated", {});
41
+ }
42
+ });
43
+ },
19
44
  resolveId: {
20
45
  filter: {
21
46
  id: new RegExp(`^${MIDDLEWARE_MODULE_ID}$`)
@@ -1,4 +1,21 @@
1
+ import type { Params } from '../../types/public/common.js';
1
2
  import type { RedirectConfig } from '../../types/public/index.js';
3
+ import type { RouteData } from '../../types/public/internal.js';
2
4
  import type { RenderContext } from '../render-context.js';
3
5
  export declare function redirectIsExternal(redirect: RedirectConfig): boolean;
6
+ /**
7
+ * Computes the HTTP status code for a redirect response.
8
+ *
9
+ * - If the route has a `redirectRoute` and an explicit numeric status, that status is used.
10
+ * - Otherwise: GET → 301, non-GET (e.g. POST) → 308.
11
+ */
12
+ export declare function computeRedirectStatus(method: string, redirect: RedirectConfig | undefined, redirectRoute: RouteData | undefined): number;
13
+ /**
14
+ * Resolves the final redirect target URL by substituting dynamic params into
15
+ * the redirect string (e.g. `/[slug]/page` → `/hello/page`).
16
+ *
17
+ * When `redirectRoute` is provided its route generator is used; otherwise params
18
+ * are substituted manually into the string redirect target.
19
+ */
20
+ export declare function resolveRedirectTarget(params: Params, redirect: RedirectConfig | undefined, redirectRoute: RouteData | undefined, trailingSlash: 'always' | 'never' | 'ignore'): string;
4
21
  export declare function renderRedirect(renderContext: RenderContext): Promise<Response>;
@@ -9,31 +9,12 @@ function redirectIsExternal(redirect) {
9
9
  return isExternalURL(redirect.destination);
10
10
  }
11
11
  }
12
- async function renderRedirect(renderContext) {
13
- const {
14
- request: { method },
15
- routeData
16
- } = renderContext;
17
- const { redirect, redirectRoute } = routeData;
18
- const status = redirectRoute && typeof redirect === "object" ? redirect.status : method === "GET" ? 301 : 308;
19
- const headers = { location: encodeURI(redirectRouteGenerate(renderContext)) };
20
- if (redirect && redirectIsExternal(redirect)) {
21
- if (typeof redirect === "string") {
22
- return Response.redirect(redirect, status);
23
- } else {
24
- return Response.redirect(redirect.destination, status);
25
- }
26
- }
27
- return new Response(null, { status, headers });
12
+ function computeRedirectStatus(method, redirect, redirectRoute) {
13
+ return redirectRoute && typeof redirect === "object" ? redirect.status : method === "GET" ? 301 : 308;
28
14
  }
29
- function redirectRouteGenerate(renderContext) {
30
- const {
31
- params,
32
- routeData: { redirect, redirectRoute },
33
- pipeline
34
- } = renderContext;
15
+ function resolveRedirectTarget(params, redirect, redirectRoute, trailingSlash) {
35
16
  if (typeof redirectRoute !== "undefined") {
36
- const generate = getRouteGenerator(redirectRoute.segments, pipeline.manifest.trailingSlash);
17
+ const generate = getRouteGenerator(redirectRoute.segments, trailingSlash);
37
18
  return generate(params) || redirectRoute?.pathname || "/";
38
19
  } else if (typeof redirect === "string") {
39
20
  if (redirectIsExternal(redirect)) {
@@ -51,7 +32,35 @@ function redirectRouteGenerate(renderContext) {
51
32
  }
52
33
  return redirect.destination;
53
34
  }
35
+ async function renderRedirect(renderContext) {
36
+ const {
37
+ request: { method },
38
+ routeData
39
+ } = renderContext;
40
+ const { redirect, redirectRoute } = routeData;
41
+ const status = computeRedirectStatus(method, redirect, redirectRoute);
42
+ const headers = {
43
+ location: encodeURI(
44
+ resolveRedirectTarget(
45
+ renderContext.params,
46
+ redirect,
47
+ redirectRoute,
48
+ renderContext.pipeline.manifest.trailingSlash
49
+ )
50
+ )
51
+ };
52
+ if (redirect && redirectIsExternal(redirect)) {
53
+ if (typeof redirect === "string") {
54
+ return Response.redirect(redirect, status);
55
+ } else {
56
+ return Response.redirect(redirect.destination, status);
57
+ }
58
+ }
59
+ return new Response(null, { status, headers });
60
+ }
54
61
  export {
62
+ computeRedirectStatus,
55
63
  redirectIsExternal,
56
- renderRedirect
64
+ renderRedirect,
65
+ resolveRedirectTarget
57
66
  };
@@ -1,5 +1,6 @@
1
1
  import nodeFs from 'node:fs';
2
2
  import type { AstroSettings, RoutesList } from '../../types/astro.js';
3
+ import type { AstroConfig } from '../../types/public/config.js';
3
4
  import type { RouteData } from '../../types/public/internal.js';
4
5
  import type { Logger } from '../logger/core.js';
5
6
  export interface RouteEntry {
@@ -22,6 +23,20 @@ export declare function createRoutesFromEntries(entries: RouteEntry[], settings:
22
23
  export declare function createRoutesList(params: CreateRouteManifestParams, logger: Logger, { dev, }?: {
23
24
  dev?: boolean;
24
25
  }): Promise<RoutesList>;
26
+ /**
27
+ * Generates i18n fallback routes and attaches them to their source routes.
28
+ *
29
+ * For each locale that has a fallback configured (e.g. `{ es: 'en' }`), this
30
+ * function inspects the existing route list and creates `type: 'fallback'`
31
+ * entries for any paths that the source locale does not already have. The
32
+ * fallback routes are pushed onto `route.fallbackRoutes` of their source route
33
+ * so that the build pipeline can serve the fallback content.
34
+ *
35
+ * @param routes The full route list — mutated in-place.
36
+ * @param i18n The resolved `config.i18n` object.
37
+ * @param config The resolved Astro config (needs `base` and `trailingSlash`).
38
+ */
39
+ export declare function createI18nFallbackRoutes(routes: RouteData[], i18n: NonNullable<AstroConfig['i18n']>, config: Pick<AstroConfig, 'base' | 'trailingSlash'>): void;
25
40
  /**
26
41
  * Resolve a route entrypoint to an absolute component path.
27
42
  */
@@ -63,6 +63,7 @@ function createFileBasedRoutes({ settings, cwd, fsMod }, logger) {
63
63
  const pages = resolvePages(config);
64
64
  const localFs = fsMod ?? nodeFs;
65
65
  const rootPath = fileURLToPath(config.root);
66
+ const pagesPath = fileURLToPath(pages);
66
67
  if (!localFs.existsSync(pages)) {
67
68
  if (settings.injectedRoutes.length === 0) {
68
69
  const pagesDirRootRelative = pages.href.slice(settings.config.root.href.length);
@@ -177,7 +178,7 @@ function createFileBasedRoutes({ settings, cwd, fsMod }, logger) {
177
178
  }
178
179
  }
179
180
  }
180
- walk(localFs, fileURLToPath(pages), [], []);
181
+ walk(localFs, pagesPath, [], []);
181
182
  return routes;
182
183
  }
183
184
  function createRoutesFromEntries(entries, settings, logger, pagesDirRelative = "src/pages") {
@@ -467,16 +468,12 @@ async function createRoutesList(params, logger, {
467
468
  if (route.type !== "page" && route.type !== "endpoint" && route.type !== "redirect") return;
468
469
  if (route.type === "redirect" && !route.redirectRoute) return;
469
470
  const localFs = params.fsMod ?? nodeFs;
470
- const content = await localFs.promises.readFile(
471
- fileURLToPath(
472
- new URL(
473
- // The destination redirect might be a prerendered
474
- route.type === "redirect" && route.redirectRoute ? route.redirectRoute.component : route.component,
475
- settings.config.root
476
- )
477
- ),
478
- "utf-8"
471
+ const componentUrl = new URL(
472
+ // The destination redirect might be a prerendered
473
+ route.type === "redirect" && route.redirectRoute ? route.redirectRoute.component : route.component,
474
+ settings.config.root
479
475
  );
476
+ const content = await localFs.promises.readFile(componentUrl, "utf-8");
480
477
  await getRoutePrerenderOption(content, route, settings, logger);
481
478
  })
482
479
  );
@@ -504,144 +501,147 @@ async function createRoutesList(params, logger, {
504
501
  });
505
502
  }
506
503
  }
507
- const routesByLocale = /* @__PURE__ */ new Map();
508
- const setRoutes = new Set(routes.filter((route) => route.type === "page"));
509
- const filteredLocales = i18n.locales.filter((loc) => {
510
- if (typeof loc === "string") {
511
- return loc !== i18n.defaultLocale;
512
- }
513
- return loc.path !== i18n.defaultLocale;
514
- }).map((locale) => {
515
- if (typeof locale === "string") {
516
- return locale;
517
- }
518
- return locale.path;
519
- });
520
- for (const locale of filteredLocales) {
521
- for (const route of setRoutes) {
522
- const hasLocaleInRoute = route.route.split("/").includes(locale);
523
- if (!hasLocaleInRoute) {
524
- continue;
525
- }
526
- const currentRoutes = routesByLocale.get(locale);
527
- if (currentRoutes) {
528
- currentRoutes.push(route);
529
- routesByLocale.set(locale, currentRoutes);
530
- } else {
531
- routesByLocale.set(locale, [route]);
532
- }
533
- setRoutes.delete(route);
534
- }
504
+ createI18nFallbackRoutes(routes, i18n, config);
505
+ }
506
+ if (dev) {
507
+ ensure404Route({ routes });
508
+ }
509
+ if (dev || settings.buildOutput === "server") {
510
+ injectImageEndpoint(settings, { routes }, dev ? "dev" : "build");
511
+ }
512
+ if (dev || settings.config.adapter) {
513
+ injectServerIslandRoute(settings.config, { routes });
514
+ }
515
+ await runHookRoutesResolved({ routes, settings, logger });
516
+ return {
517
+ routes
518
+ };
519
+ }
520
+ function createI18nFallbackRoutes(routes, i18n, config) {
521
+ const strategy = toRoutingStrategy(i18n.routing, i18n.domains);
522
+ const routesByLocale = /* @__PURE__ */ new Map();
523
+ const setRoutes = new Set(routes.filter((route) => route.type === "page"));
524
+ const filteredLocales = i18n.locales.filter((loc) => {
525
+ if (typeof loc === "string") {
526
+ return loc !== i18n.defaultLocale;
535
527
  }
528
+ return loc.path !== i18n.defaultLocale;
529
+ }).map((locale) => {
530
+ if (typeof locale === "string") {
531
+ return locale;
532
+ }
533
+ return locale.path;
534
+ });
535
+ for (const locale of filteredLocales) {
536
536
  for (const route of setRoutes) {
537
- const currentRoutes = routesByLocale.get(i18n.defaultLocale);
537
+ const hasLocaleInRoute = route.route.split("/").includes(locale);
538
+ if (!hasLocaleInRoute) {
539
+ continue;
540
+ }
541
+ const currentRoutes = routesByLocale.get(locale);
538
542
  if (currentRoutes) {
539
543
  currentRoutes.push(route);
540
- routesByLocale.set(i18n.defaultLocale, currentRoutes);
544
+ routesByLocale.set(locale, currentRoutes);
541
545
  } else {
542
- routesByLocale.set(i18n.defaultLocale, [route]);
546
+ routesByLocale.set(locale, [route]);
543
547
  }
544
548
  setRoutes.delete(route);
545
549
  }
546
- if (strategy === "pathname-prefix-always") {
547
- const defaultLocaleRoutes = routesByLocale.get(i18n.defaultLocale);
548
- if (defaultLocaleRoutes) {
549
- const indexDefaultRoute = defaultLocaleRoutes.find(({ route }) => route === "/") ?? defaultLocaleRoutes.find(({ route }) => route === `/${i18n.defaultLocale}`);
550
- if (indexDefaultRoute) {
551
- const pathname = "/";
552
- const route = "/";
553
- const segments = removeLeadingForwardSlash(route).split(path.posix.sep).filter(Boolean).map((s) => {
554
- validateSegment(s);
555
- return getParts(s, route);
556
- });
557
- routes.push({
558
- ...indexDefaultRoute,
559
- pathname,
560
- route,
561
- segments,
562
- pattern: getPattern(segments, config.base, config.trailingSlash),
563
- type: "fallback"
564
- });
565
- }
550
+ }
551
+ for (const route of setRoutes) {
552
+ const currentRoutes = routesByLocale.get(i18n.defaultLocale);
553
+ if (currentRoutes) {
554
+ currentRoutes.push(route);
555
+ routesByLocale.set(i18n.defaultLocale, currentRoutes);
556
+ } else {
557
+ routesByLocale.set(i18n.defaultLocale, [route]);
558
+ }
559
+ setRoutes.delete(route);
560
+ }
561
+ if (strategy === "pathname-prefix-always") {
562
+ const defaultLocaleRoutes = routesByLocale.get(i18n.defaultLocale);
563
+ if (defaultLocaleRoutes) {
564
+ const indexDefaultRoute = defaultLocaleRoutes.find(({ route }) => route === "/") ?? defaultLocaleRoutes.find(({ route }) => route === `/${i18n.defaultLocale}`);
565
+ if (indexDefaultRoute) {
566
+ const pathname = "/";
567
+ const route = "/";
568
+ const segments = removeLeadingForwardSlash(route).split(path.posix.sep).filter(Boolean).map((s) => {
569
+ validateSegment(s);
570
+ return getParts(s, route);
571
+ });
572
+ routes.push({
573
+ ...indexDefaultRoute,
574
+ pathname,
575
+ route,
576
+ segments,
577
+ pattern: getPattern(segments, config.base, config.trailingSlash),
578
+ type: "fallback"
579
+ });
566
580
  }
567
581
  }
568
- if (i18n.fallback) {
569
- let fallback = Object.entries(i18n.fallback);
570
- if (fallback.length > 0) {
571
- for (const [fallbackFromLocale, fallbackToLocale] of fallback) {
572
- let fallbackToRoutes;
573
- if (fallbackToLocale === i18n.defaultLocale) {
574
- fallbackToRoutes = routesByLocale.get(i18n.defaultLocale);
575
- } else {
576
- fallbackToRoutes = routesByLocale.get(fallbackToLocale);
577
- }
578
- const fallbackFromRoutes = routesByLocale.get(fallbackFromLocale);
579
- if (!fallbackToRoutes) {
580
- continue;
581
- }
582
- for (const fallbackToRoute of fallbackToRoutes) {
583
- const hasRoute = fallbackFromRoutes && // we check if the fallback from locale (the origin) has already this route
584
- fallbackFromRoutes.some((route) => {
585
- if (fallbackToLocale === i18n.defaultLocale) {
586
- return route.route === `/${fallbackFromLocale}${fallbackToRoute.route}` || route.route.replace(`/${fallbackFromLocale}`, "") === fallbackToRoute.route;
587
- } else {
588
- const expectedRoute = replaceOrKeep(
589
- fallbackToRoute.route,
590
- fallbackToLocale,
591
- fallbackFromLocale
592
- );
593
- return route.route === expectedRoute;
582
+ }
583
+ if (i18n.fallback) {
584
+ const fallback = Object.entries(i18n.fallback);
585
+ if (fallback.length > 0) {
586
+ for (const [fallbackFromLocale, fallbackToLocale] of fallback) {
587
+ let fallbackToRoutes;
588
+ if (fallbackToLocale === i18n.defaultLocale) {
589
+ fallbackToRoutes = routesByLocale.get(i18n.defaultLocale);
590
+ } else {
591
+ fallbackToRoutes = routesByLocale.get(fallbackToLocale);
592
+ }
593
+ const fallbackFromRoutes = routesByLocale.get(fallbackFromLocale);
594
+ if (!fallbackToRoutes) {
595
+ continue;
596
+ }
597
+ for (const fallbackToRoute of fallbackToRoutes) {
598
+ const hasRoute = fallbackFromRoutes && fallbackFromRoutes.some((route) => {
599
+ if (fallbackToLocale === i18n.defaultLocale) {
600
+ return route.route === `/${fallbackFromLocale}${fallbackToRoute.route}` || route.route.replace(`/${fallbackFromLocale}`, "") === fallbackToRoute.route;
601
+ } else {
602
+ const expectedRoute = replaceOrKeep(
603
+ fallbackToRoute.route,
604
+ fallbackToLocale,
605
+ fallbackFromLocale
606
+ );
607
+ return route.route === expectedRoute;
608
+ }
609
+ });
610
+ if (!hasRoute) {
611
+ let pathname;
612
+ let route;
613
+ if (fallbackToLocale === i18n.defaultLocale && strategy === "pathname-prefix-other-locales") {
614
+ if (fallbackToRoute.pathname) {
615
+ pathname = `/${fallbackFromLocale}${fallbackToRoute.pathname}`;
594
616
  }
617
+ route = `/${fallbackFromLocale}${fallbackToRoute.route}`;
618
+ } else {
619
+ pathname = fallbackToRoute.pathname ? replaceOrKeep(fallbackToRoute.pathname, fallbackToLocale, fallbackFromLocale) : void 0;
620
+ route = replaceOrKeep(fallbackToRoute.route, fallbackToLocale, fallbackFromLocale);
621
+ }
622
+ const segments = removeLeadingForwardSlash(route).split(path.posix.sep).filter(Boolean).map((s) => {
623
+ validateSegment(s);
624
+ return getParts(s, route);
595
625
  });
596
- if (!hasRoute) {
597
- let pathname;
598
- let route;
599
- if (fallbackToLocale === i18n.defaultLocale && strategy === "pathname-prefix-other-locales") {
600
- if (fallbackToRoute.pathname) {
601
- pathname = `/${fallbackFromLocale}${fallbackToRoute.pathname}`;
602
- }
603
- route = `/${fallbackFromLocale}${fallbackToRoute.route}`;
604
- } else {
605
- pathname = fallbackToRoute.pathname ? replaceOrKeep(fallbackToRoute.pathname, fallbackToLocale, fallbackFromLocale) : void 0;
606
- route = replaceOrKeep(fallbackToRoute.route, fallbackToLocale, fallbackFromLocale);
607
- }
608
- const segments = removeLeadingForwardSlash(route).split(path.posix.sep).filter(Boolean).map((s) => {
609
- validateSegment(s);
610
- return getParts(s, route);
611
- });
612
- const index = routes.findIndex((r) => r === fallbackToRoute);
613
- if (index >= 0) {
614
- const fallbackRoute = {
615
- ...fallbackToRoute,
616
- pathname,
617
- route,
618
- segments,
619
- pattern: getPattern(segments, config.base, config.trailingSlash),
620
- type: "fallback",
621
- fallbackRoutes: []
622
- };
623
- const routeData = routes[index];
624
- routeData.fallbackRoutes.push(fallbackRoute);
625
- }
626
+ const index = routes.findIndex((r) => r === fallbackToRoute);
627
+ if (index >= 0) {
628
+ const fallbackRoute = {
629
+ ...fallbackToRoute,
630
+ pathname,
631
+ route,
632
+ segments,
633
+ pattern: getPattern(segments, config.base, config.trailingSlash),
634
+ type: "fallback",
635
+ fallbackRoutes: []
636
+ };
637
+ const routeData = routes[index];
638
+ routeData.fallbackRoutes.push(fallbackRoute);
626
639
  }
627
640
  }
628
641
  }
629
642
  }
630
643
  }
631
644
  }
632
- if (dev) {
633
- ensure404Route({ routes });
634
- }
635
- if (dev || settings.buildOutput === "server") {
636
- injectImageEndpoint(settings, { routes }, dev ? "dev" : "build");
637
- }
638
- if (dev || settings.config.adapter) {
639
- injectServerIslandRoute(settings.config, { routes });
640
- }
641
- await runHookRoutesResolved({ routes, settings, logger });
642
- return {
643
- routes
644
- };
645
645
  }
646
646
  function resolveInjectedRoute(entrypoint, root, cwd) {
647
647
  let resolved;
@@ -666,6 +666,7 @@ function replaceOrKeep(original, from, to) {
666
666
  return original.replace(`/${from}/`, `/${to}/`).replace(`/${from}`, `/${to}`);
667
667
  }
668
668
  export {
669
+ createI18nFallbackRoutes,
669
670
  createRoutesFromEntries,
670
671
  createRoutesList,
671
672
  resolveInjectedRoute
@@ -1,4 +1,9 @@
1
1
  import type { AstroSettings } from '../../types/astro.js';
2
2
  import type { RouteData } from '../../types/public/internal.js';
3
3
  import type { Logger } from '../logger/core.js';
4
+ /**
5
+ * Parses the `export const prerender = true|false` declaration from a route's
6
+ * source content. Returns `true`, `false`, or `undefined` if not present.
7
+ */
8
+ export declare function parsePrerenderExport(content: string): boolean | undefined;
4
9
  export declare function getRoutePrerenderOption(content: string, route: RouteData, settings: AstroSettings, logger: Logger): Promise<void>;
@@ -1,6 +1,11 @@
1
1
  import { runHookRouteSetup } from "../../integrations/hooks.js";
2
2
  import { getPrerenderDefault } from "../../prerender/utils.js";
3
3
  const PRERENDER_REGEX = /^\s*export\s+const\s+prerender\s*=\s*(true|false);?/m;
4
+ function parsePrerenderExport(content) {
5
+ const match = PRERENDER_REGEX.exec(content);
6
+ if (!match) return void 0;
7
+ return match[1] === "true";
8
+ }
4
9
  async function getRoutePrerenderOption(content, route, settings, logger) {
5
10
  const match = PRERENDER_REGEX.exec(content);
6
11
  if (match) {
@@ -16,5 +21,6 @@ async function getRoutePrerenderOption(content, route, settings, logger) {
16
21
  if (!route.prerender) settings.buildOutput = "server";
17
22
  }
18
23
  export {
19
- getRoutePrerenderOption
24
+ getRoutePrerenderOption,
25
+ parsePrerenderExport
20
26
  };
@@ -12,7 +12,7 @@ function vitePluginServerIslands({
12
12
  serverIslandsState
13
13
  }) {
14
14
  let command = "serve";
15
- let ssrEnvironment = null;
15
+ let serverEnvironments = [];
16
16
  function ensureServerIslandReferenceIds(ctx) {
17
17
  for (const [resolvedPath, island] of serverIslandsState.getDiscoveredIslandEntries()) {
18
18
  if (serverIslandsState.hasReferenceId(resolvedPath)) continue;
@@ -38,7 +38,17 @@ function vitePluginServerIslands({
38
38
  ensureServerIslandReferenceIds(this);
39
39
  },
40
40
  configureServer(server) {
41
- ssrEnvironment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
41
+ serverEnvironments = [];
42
+ for (const name of [
43
+ ASTRO_VITE_ENVIRONMENT_NAMES.ssr,
44
+ ASTRO_VITE_ENVIRONMENT_NAMES.prerender,
45
+ ASTRO_VITE_ENVIRONMENT_NAMES.astro
46
+ ]) {
47
+ const env = server.environments[name];
48
+ if (env) {
49
+ serverEnvironments.push(env);
50
+ }
51
+ }
42
52
  },
43
53
  resolveId: {
44
54
  filter: {
@@ -92,10 +102,12 @@ export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
92
102
  }
93
103
  }
94
104
  }
95
- if (serverIslandsState.hasIslands() && ssrEnvironment) {
96
- const mod = ssrEnvironment.moduleGraph.getModuleById(RESOLVED_SERVER_ISLAND_MANIFEST);
97
- if (mod) {
98
- ssrEnvironment.moduleGraph.invalidateModule(mod);
105
+ if (serverIslandsState.hasIslands()) {
106
+ for (const env of serverEnvironments) {
107
+ const mod = env.moduleGraph.getModuleById(RESOLVED_SERVER_ISLAND_MANIFEST);
108
+ if (mod) {
109
+ env.moduleGraph.invalidateModule(mod);
110
+ }
99
111
  }
100
112
  }
101
113
  if (id === RESOLVED_SERVER_ISLAND_MANIFEST) {