astro 2.5.7 → 2.6.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 (61) hide show
  1. package/dist/@types/astro.d.ts +109 -90
  2. package/dist/assets/generate.js +2 -2
  3. package/dist/core/app/index.js +27 -18
  4. package/dist/core/app/types.d.ts +1 -2
  5. package/dist/core/build/common.js +2 -0
  6. package/dist/core/build/generate.js +76 -13
  7. package/dist/core/build/internal.d.ts +2 -0
  8. package/dist/core/build/internal.js +18 -1
  9. package/dist/core/build/plugins/plugin-css.js +1 -1
  10. package/dist/core/build/plugins/plugin-middleware.js +1 -7
  11. package/dist/core/build/plugins/plugin-pages.d.ts +1 -0
  12. package/dist/core/build/plugins/plugin-pages.js +14 -4
  13. package/dist/core/build/plugins/plugin-ssr.js +10 -14
  14. package/dist/core/build/static-build.js +9 -10
  15. package/dist/core/config/config.js +1 -10
  16. package/dist/core/config/schema.d.ts +48 -64
  17. package/dist/core/config/schema.js +13 -11
  18. package/dist/core/config/settings.js +2 -2
  19. package/dist/core/constants.js +1 -1
  20. package/dist/core/dev/dev.js +1 -1
  21. package/dist/core/endpoint/index.js +2 -2
  22. package/dist/core/errors/errors-data.d.ts +13 -21
  23. package/dist/core/errors/errors-data.js +14 -2
  24. package/dist/core/messages.js +2 -2
  25. package/dist/core/path.d.ts +1 -15
  26. package/dist/core/path.js +1 -80
  27. package/dist/core/redirects/component.d.ts +4 -0
  28. package/dist/core/redirects/component.js +19 -0
  29. package/dist/core/redirects/helpers.d.ts +4 -0
  30. package/dist/core/redirects/helpers.js +29 -0
  31. package/dist/core/redirects/index.d.ts +3 -0
  32. package/dist/core/redirects/index.js +11 -0
  33. package/dist/core/redirects/validate.d.ts +1 -0
  34. package/dist/core/redirects/validate.js +13 -0
  35. package/dist/core/render/context.d.ts +2 -1
  36. package/dist/core/render/core.d.ts +2 -1
  37. package/dist/core/render/core.js +18 -1
  38. package/dist/core/render/dev/environment.js +2 -2
  39. package/dist/core/render/result.d.ts +2 -0
  40. package/dist/core/render/result.js +3 -3
  41. package/dist/core/routing/manifest/create.js +50 -4
  42. package/dist/core/util.js +10 -3
  43. package/dist/integrations/index.js +3 -8
  44. package/dist/prerender/utils.d.ts +2 -2
  45. package/dist/prerender/utils.js +6 -6
  46. package/dist/runtime/server/astro-island.js +7 -4
  47. package/dist/runtime/server/astro-island.prebuilt.d.ts +1 -1
  48. package/dist/runtime/server/astro-island.prebuilt.js +1 -1
  49. package/dist/runtime/server/render/astro/instance.js +0 -3
  50. package/dist/runtime/server/render/common.js +6 -0
  51. package/dist/runtime/server/render/component.js +2 -2
  52. package/dist/runtime/server/render/page.d.ts +1 -1
  53. package/dist/runtime/server/render/page.js +10 -6
  54. package/dist/vite-plugin-astro-server/request.js +2 -2
  55. package/dist/vite-plugin-astro-server/route.js +16 -9
  56. package/dist/vite-plugin-html/transform/index.js +3 -7
  57. package/dist/vite-plugin-scanner/index.js +4 -4
  58. package/env.d.ts +2 -2
  59. package/package.json +2 -1
  60. package/tsconfigs/base.json +3 -1
  61. package/tsconfigs/strictest.json +3 -1
@@ -73,7 +73,7 @@ export interface CLIFlags {
73
73
  drafts?: boolean;
74
74
  open?: boolean;
75
75
  experimentalAssets?: boolean;
76
- experimentalMiddleware?: boolean;
76
+ experimentalRedirects?: boolean;
77
77
  }
78
78
  export interface BuildConfig {
79
79
  /**
@@ -396,6 +396,50 @@ export interface AstroUserConfig {
396
396
  * ```
397
397
  */
398
398
  cacheDir?: string;
399
+ /**
400
+ * @docs
401
+ * @name redirects (Experimental)
402
+ * @type {RedirectConfig}
403
+ * @default `{}`
404
+ * @version 2.6.0
405
+ * @description Specify a mapping of redirects where the key is the route to match
406
+ * and the value is the path to redirect to.
407
+ *
408
+ * You can redirect both static and dynamic routes, but only to the same kind of route.
409
+ * For example you cannot have a `'/article': '/blog/[...slug]'` redirect.
410
+ *
411
+ *
412
+ * ```js
413
+ * {
414
+ * redirects: {
415
+ * '/old': '/new',
416
+ * '/blog/[...slug]': '/articles/[...slug]',
417
+ * }
418
+ * }
419
+ * ```
420
+ *
421
+ *
422
+ * For statically-generated sites with no adapter installed, this will produce a client redirect using a [`<meta http-equiv="refresh">` tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#http-equiv) and does not support status codes.
423
+ *
424
+ * When using SSR or with a static adapter in `output: static`
425
+ * mode, status codes are supported.
426
+ * Astro will serve redirected GET requests with a status of `301`
427
+ * and use a status of `308` for any other request method.
428
+ *
429
+ * You can customize the [redirection status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages) using an object in the redirect config:
430
+ *
431
+ * ```js
432
+ * {
433
+ * redirects: {
434
+ * '/other': {
435
+ * status: 302,
436
+ * destination: '/place',
437
+ * },
438
+ * }
439
+ * }
440
+ * ```
441
+ */
442
+ redirects?: RedirectConfig;
399
443
  /**
400
444
  * @docs
401
445
  * @name site
@@ -670,6 +714,50 @@ export interface AstroUserConfig {
670
714
  * ```
671
715
  */
672
716
  serverEntry?: string;
717
+ /**
718
+ * @docs
719
+ * @name build.redirects
720
+ * @type {boolean}
721
+ * @default `true`
722
+ * @version 2.6.0
723
+ * @description
724
+ * Specifies whether redirects will be output to HTML during the build.
725
+ * This option only applies to `output: 'static'` mode; in SSR redirects
726
+ * are treated the same as all responses.
727
+ *
728
+ * This option is mostly meant to be used by adapters that have special
729
+ * configuration files for redirects and do not need/want HTML based redirects.
730
+ *
731
+ * ```js
732
+ * {
733
+ * build: {
734
+ * redirects: false
735
+ * }
736
+ * }
737
+ * ```
738
+ */
739
+ redirects?: boolean;
740
+ /**
741
+ * @docs
742
+ * @name build.inlineStylesheets
743
+ * @type {('always' | 'auto' | 'never')}
744
+ * @default `never`
745
+ * @version 2.6.0
746
+ * @description
747
+ * Control whether styles are sent to the browser in a separate css file or inlined into `<style>` tags. Choose from the following options:
748
+ * - `'always'` - all styles are inlined into `<style>` tags
749
+ * - `'auto'` - only stylesheets smaller than `ViteConfig.build.assetsInlineLimit` (default: 4kb) are inlined. Otherwise, styles are sent in external stylesheets.
750
+ * - `'never'` - all styles are sent in external stylesheets
751
+ *
752
+ * ```js
753
+ * {
754
+ * build: {
755
+ * inlineStylesheets: `auto`,
756
+ * },
757
+ * }
758
+ * ```
759
+ */
760
+ inlineStylesheets?: 'always' | 'auto' | 'never';
673
761
  };
674
762
  /**
675
763
  * @docs
@@ -1001,103 +1089,24 @@ export interface AstroUserConfig {
1001
1089
  assets?: boolean;
1002
1090
  /**
1003
1091
  * @docs
1004
- * @name experimental.inlineStylesheets
1005
- * @type {('always' | 'auto' | 'never')}
1006
- * @default `never`
1007
- * @version 2.4.0
1008
- * @description
1009
- * Control whether styles are sent to the browser in a separate css file or inlined into `<style>` tags. Choose from the following options:
1010
- * - `'always'` - all styles are inlined into `<style>` tags
1011
- * - `'auto'` - only stylesheets smaller than `ViteConfig.build.assetsInlineLimit` (default: 4kb) are inlined. Otherwise, styles are sent in external stylesheets.
1012
- * - `'never'` - all styles are sent in external stylesheets
1013
- *
1014
- * ```js
1015
- * {
1016
- * experimental: {
1017
- * inlineStylesheets: `auto`,
1018
- * },
1019
- * }
1020
- * ```
1021
- */
1022
- inlineStylesheets?: 'always' | 'auto' | 'never';
1023
- /**
1024
- * @docs
1025
- * @name experimental.customClientDirectives
1092
+ * @name experimental.redirects
1026
1093
  * @type {boolean}
1027
1094
  * @default `false`
1028
- * @version 2.5.0
1095
+ * @version 2.6.0
1029
1096
  * @description
1030
- * Allow integrations to use the [experimental `addClientDirective` API](/en/reference/integrations-reference/#addclientdirective-option) in the `astro:config:setup` hook
1031
- * to add custom client directives in Astro files.
1032
- *
1033
- * To enable this feature, set `experimental.customClientDirectives` to `true` in your Astro config:
1097
+ * Enable experimental support for redirect configuration. With this enabled
1098
+ * you can set redirects via the top-level `redirects` property. To enable
1099
+ * this feature, set `experimental.redirects` to `true`.
1034
1100
  *
1035
1101
  * ```js
1036
1102
  * {
1037
1103
  * experimental: {
1038
- * customClientDirectives: true,
1104
+ * redirects: true,
1039
1105
  * },
1040
1106
  * }
1041
1107
  * ```
1042
1108
  */
1043
- customClientDirectives?: boolean;
1044
- /**
1045
- * @docs
1046
- * @name experimental.middleware
1047
- * @type {boolean}
1048
- * @default `false`
1049
- * @version 2.4.0
1050
- * @description
1051
- * Enable experimental support for Astro middleware.
1052
- *
1053
- * To enable this feature, set `experimental.middleware` to `true` in your Astro config:
1054
- *
1055
- * ```js
1056
- * {
1057
- * experimental: {
1058
- * middleware: true,
1059
- * },
1060
- * }
1061
- * ```
1062
- */
1063
- middleware?: boolean;
1064
- /**
1065
- * @docs
1066
- * @name experimental.hybridOutput
1067
- * @type {boolean}
1068
- * @default `false`
1069
- * @version 2.5.0
1070
- * @description
1071
- * Enable experimental support for hybrid SSR with pre-rendering enabled by default.
1072
- *
1073
- * To enable this feature, first set `experimental.hybridOutput` to `true` in your Astro config, and set `output` to `hybrid`.
1074
- *
1075
- * ```js
1076
- * {
1077
- * output: 'hybrid',
1078
- * experimental: {
1079
- * hybridOutput: true,
1080
- * },
1081
- * }
1082
- * ```
1083
- * Then add `export const prerender = false` to any page or endpoint you want to opt-out of pre-rendering.
1084
- * ```astro
1085
- * ---
1086
- * // pages/contact.astro
1087
- * export const prerender = false
1088
- *
1089
- * if (Astro.request.method === 'POST') {
1090
- * // handle form submission
1091
- * }
1092
- * ---
1093
- * <form method="POST">
1094
- * <input type="text" name="name" />
1095
- * <input type="email" name="email" />
1096
- * <button type="submit">Submit</button>
1097
- * </form>
1098
- * ```
1099
- */
1100
- hybridOutput?: boolean;
1109
+ redirects?: boolean;
1101
1110
  };
1102
1111
  /** @deprecated - Use "integrations" instead. Run Astro to learn more about migrating. */
1103
1112
  renderers?: never;
@@ -1420,6 +1429,7 @@ export interface AstroAdapter {
1420
1429
  args?: any;
1421
1430
  }
1422
1431
  type Body = string;
1432
+ export type ValidRedirectStatus = 300 | 301 | 302 | 303 | 304 | 307 | 308;
1423
1433
  interface AstroSharedContext<Props extends Record<string, any> = Record<string, any>> {
1424
1434
  /**
1425
1435
  * The address (usually IP address) of the user. Used with SSR only.
@@ -1448,7 +1458,7 @@ interface AstroSharedContext<Props extends Record<string, any> = Record<string,
1448
1458
  /**
1449
1459
  * Redirect to another page (**SSR Only**).
1450
1460
  */
1451
- redirect(path: string, status?: 301 | 302 | 303 | 307 | 308): Response;
1461
+ redirect(path: string, status?: ValidRedirectStatus): Response;
1452
1462
  /**
1453
1463
  * Object accessed via Astro middleware
1454
1464
  */
@@ -1631,7 +1641,7 @@ export interface AstroIntegration {
1631
1641
  };
1632
1642
  }
1633
1643
  export type MiddlewareNext<R> = () => Promise<R>;
1634
- export type MiddlewareHandler<R> = (context: APIContext, next: MiddlewareNext<R>) => Promise<R> | Promise<void> | void;
1644
+ export type MiddlewareHandler<R> = (context: APIContext, next: MiddlewareNext<R>) => Promise<R> | R | Promise<void> | void;
1635
1645
  export type MiddlewareResponseHandler = MiddlewareHandler<Response>;
1636
1646
  export type MiddlewareEndpointHandler = MiddlewareHandler<Response | EndpointOutput>;
1637
1647
  export type MiddlewareNextResponse = MiddlewareNext<Response>;
@@ -1642,12 +1652,16 @@ export interface AstroPluginOptions {
1642
1652
  settings: AstroSettings;
1643
1653
  logging: LogOptions;
1644
1654
  }
1645
- export type RouteType = 'page' | 'endpoint';
1655
+ export type RouteType = 'page' | 'endpoint' | 'redirect';
1646
1656
  export interface RoutePart {
1647
1657
  content: string;
1648
1658
  dynamic: boolean;
1649
1659
  spread: boolean;
1650
1660
  }
1661
+ type RedirectConfig = string | {
1662
+ status: ValidRedirectStatus;
1663
+ destination: string;
1664
+ };
1651
1665
  export interface RouteData {
1652
1666
  route: string;
1653
1667
  component: string;
@@ -1659,7 +1673,12 @@ export interface RouteData {
1659
1673
  segments: RoutePart[][];
1660
1674
  type: RouteType;
1661
1675
  prerender: boolean;
1676
+ redirect?: RedirectConfig;
1677
+ redirectRoute?: RouteData;
1662
1678
  }
1679
+ export type RedirectRouteData = RouteData & {
1680
+ redirect: string;
1681
+ };
1663
1682
  export type SerializedRouteData = Omit<RouteData, 'generate' | 'pattern'> & {
1664
1683
  generate: undefined;
1665
1684
  pattern: string;
@@ -2,7 +2,7 @@ import fs from "node:fs";
2
2
  import { basename, join } from "node:path/posix";
3
3
  import { warn } from "../core/logger/core.js";
4
4
  import { prependForwardSlash } from "../core/path.js";
5
- import { isHybridOutput } from "../prerender/utils.js";
5
+ import { isServerLikeOutput } from "../prerender/utils.js";
6
6
  import { getConfiguredImageService, isESMImportedImage } from "./internal.js";
7
7
  async function generateImage(buildOpts, options, filepath) {
8
8
  if (!isESMImportedImage(options.src)) {
@@ -21,7 +21,7 @@ async function generateImage(buildOpts, options, filepath) {
21
21
  useCache = false;
22
22
  }
23
23
  let serverRoot, clientRoot;
24
- if (buildOpts.settings.config.output === "server" || isHybridOutput(buildOpts.settings.config)) {
24
+ if (isServerLikeOutput(buildOpts.settings.config)) {
25
25
  serverRoot = buildOpts.settings.config.build.server;
26
26
  clientRoot = buildOpts.settings.config.build.client;
27
27
  } else {
@@ -5,6 +5,7 @@ import { consoleLogDestination } from "../logger/console.js";
5
5
  import { error } from "../logger/core.js";
6
6
  import { callMiddleware } from "../middleware/callMiddleware.js";
7
7
  import { prependForwardSlash, removeTrailingForwardSlash } from "../path.js";
8
+ import { RedirectSinglePageBuiltModule } from "../redirects/index.js";
8
9
  import {
9
10
  createEnvironment,
10
11
  createRenderContext,
@@ -113,19 +114,17 @@ class App {
113
114
  if (routeData.route === "/404") {
114
115
  defaultStatus = 404;
115
116
  }
116
- let page = await this.#manifest.pageMap.get(routeData.component)();
117
- let mod = await page.page();
118
- if (routeData.type === "page") {
117
+ let mod = await this.#getModuleForRoute(routeData);
118
+ if (routeData.type === "page" || routeData.type === "redirect") {
119
119
  let response = await this.#renderPage(request, routeData, mod, defaultStatus);
120
120
  if (response.status === 500 || response.status === 404) {
121
- const errorPageData = matchRoute("/" + response.status, this.#manifestData);
122
- if (errorPageData && errorPageData.route !== routeData.route) {
123
- page = await this.#manifest.pageMap.get(errorPageData.component)();
124
- mod = await page.page();
121
+ const errorRouteData = matchRoute("/" + response.status, this.#manifestData);
122
+ if (errorRouteData && errorRouteData.route !== routeData.route) {
123
+ mod = await this.#getModuleForRoute(errorRouteData);
125
124
  try {
126
125
  let errorResponse = await this.#renderPage(
127
126
  request,
128
- errorPageData,
127
+ errorRouteData,
129
128
  mod,
130
129
  response.status
131
130
  );
@@ -144,7 +143,21 @@ class App {
144
143
  setCookieHeaders(response) {
145
144
  return getSetCookiesFromResponse(response);
146
145
  }
147
- async #renderPage(request, routeData, mod, status = 200) {
146
+ async #getModuleForRoute(route) {
147
+ if (route.type === "redirect") {
148
+ return RedirectSinglePageBuiltModule;
149
+ } else {
150
+ const importComponentInstance = this.#manifest.pageMap.get(route.component);
151
+ if (!importComponentInstance) {
152
+ throw new Error(
153
+ `Unexpectedly unable to find a component instance for route ${route.route}`
154
+ );
155
+ }
156
+ const built = await importComponentInstance();
157
+ return built;
158
+ }
159
+ }
160
+ async #renderPage(request, routeData, page, status = 200) {
148
161
  var _a;
149
162
  const url = new URL(request.url);
150
163
  const pathname = prependForwardSlash(this.removeBase(url.pathname));
@@ -165,6 +178,7 @@ class App {
165
178
  }
166
179
  }
167
180
  try {
181
+ const mod = await page.page();
168
182
  const renderContext = await createRenderContext({
169
183
  request,
170
184
  origin: url.origin,
@@ -185,7 +199,7 @@ class App {
185
199
  site: this.#env.site,
186
200
  adapterName: this.#env.adapterName
187
201
  });
188
- const onRequest = (_a = this.#manifest.middleware) == null ? void 0 : _a.onRequest;
202
+ const onRequest = (_a = page.middleware) == null ? void 0 : _a.onRequest;
189
203
  let response;
190
204
  if (onRequest) {
191
205
  response = await callMiddleware(
@@ -214,9 +228,10 @@ class App {
214
228
  });
215
229
  }
216
230
  }
217
- async #callEndpoint(request, routeData, mod, status = 200) {
231
+ async #callEndpoint(request, routeData, page, status = 200) {
218
232
  const url = new URL(request.url);
219
233
  const pathname = "/" + this.removeBase(url.pathname);
234
+ const mod = await page.page();
220
235
  const handler = mod;
221
236
  const ctx = await createRenderContext({
222
237
  request,
@@ -227,13 +242,7 @@ class App {
227
242
  env: this.#env,
228
243
  mod: handler
229
244
  });
230
- const result = await callEndpoint(
231
- handler,
232
- this.#env,
233
- ctx,
234
- this.#logging,
235
- this.#manifest.middleware
236
- );
245
+ const result = await callEndpoint(handler, this.#env, ctx, this.#logging, page.middleware);
237
246
  if (result.type === "response") {
238
247
  if (result.response.headers.get("X-Astro-Response") === "Not-Found") {
239
248
  const fourOhFourRequest = new Request(new URL("/404", request.url));
@@ -1,5 +1,5 @@
1
1
  import type { MarkdownRenderingOptions } from '@astrojs/markdown-remark';
2
- import type { AstroMiddlewareInstance, RouteData, SerializedRouteData, SSRComponentMetadata, SSRLoadedRenderer, SSRResult } from '../../@types/astro';
2
+ import type { RouteData, SerializedRouteData, SSRComponentMetadata, SSRLoadedRenderer, SSRResult } from '../../@types/astro';
3
3
  import type { SinglePageBuiltModule } from '../build/types';
4
4
  export type ComponentPath = string;
5
5
  export type StylesheetAsset = {
@@ -42,7 +42,6 @@ export interface SSRManifest {
42
42
  entryModules: Record<string, string>;
43
43
  assets: Set<string>;
44
44
  componentMetadata: SSRResult['componentMetadata'];
45
- middleware?: AstroMiddlewareInstance<unknown>;
46
45
  }
47
46
  export type SerializedSSRManifest = Omit<SSRManifest, 'routes' | 'assets' | 'componentMetadata' | 'clientDirectives'> & {
48
47
  routes: SerializedRouteInfo[];
@@ -16,6 +16,7 @@ function getOutFolder(astroConfig, pathname, routeType) {
16
16
  case "endpoint":
17
17
  return new URL("." + appendForwardSlash(npath.dirname(pathname)), outRoot);
18
18
  case "page":
19
+ case "redirect":
19
20
  switch (astroConfig.build.format) {
20
21
  case "directory": {
21
22
  if (STATUS_CODE_PAGES.has(pathname)) {
@@ -35,6 +36,7 @@ function getOutFile(astroConfig, outFolder, pathname, routeType) {
35
36
  case "endpoint":
36
37
  return new URL(npath.basename(pathname), outFolder);
37
38
  case "page":
39
+ case "redirect":
38
40
  switch (astroConfig.build.format) {
39
41
  case "directory": {
40
42
  if (STATUS_CODE_PAGES.has(pathname)) {
@@ -8,6 +8,7 @@ import {
8
8
  } from "../../assets/generate.js";
9
9
  import {
10
10
  eachPageDataFromEntryPoint,
11
+ eachRedirectPageData,
11
12
  hasPrerenderedPages
12
13
  } from "../../core/build/internal.js";
13
14
  import {
@@ -16,12 +17,17 @@ import {
16
17
  removeTrailingForwardSlash
17
18
  } from "../../core/path.js";
18
19
  import { runHookBuildGenerated } from "../../integrations/index.js";
19
- import { isHybridOutput } from "../../prerender/utils.js";
20
+ import { isServerLikeOutput } from "../../prerender/utils.js";
20
21
  import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from "../../vite-plugin-scripts/index.js";
21
22
  import { callEndpoint, createAPIContext, throwIfRedirectNotAllowed } from "../endpoint/index.js";
22
23
  import { AstroError } from "../errors/index.js";
23
24
  import { debug, info } from "../logger/core.js";
24
25
  import { callMiddleware } from "../middleware/callMiddleware.js";
26
+ import {
27
+ getRedirectLocationOrThrow,
28
+ RedirectSinglePageBuiltModule,
29
+ routeIsRedirect
30
+ } from "../redirects/index.js";
25
31
  import { createEnvironment, createRenderContext, renderPage } from "../render/index.js";
26
32
  import { callGetStaticPaths } from "../render/route-cache.js";
27
33
  import {
@@ -33,8 +39,30 @@ import { createRequest } from "../request.js";
33
39
  import { matchRoute } from "../routing/match.js";
34
40
  import { getOutputFilename } from "../util.js";
35
41
  import { getOutDirWithinCwd, getOutFile, getOutFolder } from "./common.js";
36
- import { cssOrder, getPageDataByComponent, mergeInlineCss } from "./internal.js";
42
+ import {
43
+ cssOrder,
44
+ getEntryFilePathFromComponentPath,
45
+ getPageDataByComponent,
46
+ mergeInlineCss
47
+ } from "./internal.js";
37
48
  import { getTimeStat } from "./util.js";
49
+ function createEntryURL(filePath, outFolder) {
50
+ return new URL("./" + filePath + `?time=${Date.now()}`, outFolder);
51
+ }
52
+ async function getEntryForRedirectRoute(route, internals, outFolder) {
53
+ if (route.type !== "redirect") {
54
+ throw new Error(`Expected a redirect route.`);
55
+ }
56
+ if (route.redirectRoute) {
57
+ const filePath = getEntryFilePathFromComponentPath(internals, route.redirectRoute.component);
58
+ if (filePath) {
59
+ const url = createEntryURL(filePath, outFolder);
60
+ const ssrEntryPage = await import(url.toString());
61
+ return ssrEntryPage;
62
+ }
63
+ }
64
+ return RedirectSinglePageBuiltModule;
65
+ }
38
66
  function shouldSkipDraft(pageModule, settings) {
39
67
  var _a;
40
68
  return (
@@ -61,7 +89,7 @@ function chunkIsPage(settings, output, internals) {
61
89
  }
62
90
  async function generatePages(opts, internals) {
63
91
  const timer = performance.now();
64
- const ssr = opts.settings.config.output === "server" || isHybridOutput(opts.settings.config);
92
+ const ssr = isServerLikeOutput(opts.settings.config);
65
93
  const serverEntry = opts.buildConfig.serverEntry;
66
94
  const outFolder = ssr ? opts.buildConfig.server : getOutDirWithinCwd(opts.settings.config.outDir);
67
95
  if (ssr && !hasPrerenderedPages(internals))
@@ -73,17 +101,25 @@ ${bgGreen(black(` ${verb} static routes `))}`);
73
101
  if (ssr) {
74
102
  for (const [pageData, filePath] of eachPageDataFromEntryPoint(internals)) {
75
103
  if (pageData.route.prerender) {
76
- const ssrEntryURLPage = new URL("./" + filePath + `?time=${Date.now()}`, outFolder);
104
+ const ssrEntryURLPage = createEntryURL(filePath, outFolder);
77
105
  const ssrEntryPage = await import(ssrEntryURLPage.toString());
78
106
  await generatePage(opts, internals, pageData, ssrEntryPage, builtPaths);
79
107
  }
80
108
  }
109
+ for (const pageData of eachRedirectPageData(internals)) {
110
+ const entry = await getEntryForRedirectRoute(pageData.route, internals, outFolder);
111
+ await generatePage(opts, internals, pageData, entry, builtPaths);
112
+ }
81
113
  } else {
82
114
  for (const [pageData, filePath] of eachPageDataFromEntryPoint(internals)) {
83
- const ssrEntryURLPage = new URL("./" + filePath + `?time=${Date.now()}`, outFolder);
115
+ const ssrEntryURLPage = createEntryURL(filePath, outFolder);
84
116
  const ssrEntryPage = await import(ssrEntryURLPage.toString());
85
117
  await generatePage(opts, internals, pageData, ssrEntryPage, builtPaths);
86
118
  }
119
+ for (const pageData of eachRedirectPageData(internals)) {
120
+ const entry = await getEntryForRedirectRoute(pageData.route, internals, outFolder);
121
+ await generatePage(opts, internals, pageData, entry, builtPaths);
122
+ }
87
123
  }
88
124
  if (opts.settings.config.experimental.assets) {
89
125
  info(opts.logging, null, `
@@ -114,6 +150,9 @@ async function generateImage(opts, transform, path) {
114
150
  info(opts.logging, null, ` ${green("\u25B6")} ${path} ${dim(statsText)} ${dim(timeIncrease)}`);
115
151
  }
116
152
  async function generatePage(opts, internals, pageData, ssrEntry, builtPaths) {
153
+ if (routeIsRedirect(pageData.route) && !opts.settings.config.experimental.redirects) {
154
+ throw new Error(`To use redirects first set experimental.redirects to \`true\``);
155
+ }
117
156
  let timeStart = performance.now();
118
157
  const renderers = ssrEntry.renderers;
119
158
  const pageInfo = getPageDataByComponent(internals, pageData.route.component);
@@ -167,7 +206,7 @@ async function getPathsForRoute(pageData, mod, opts, builtPaths) {
167
206
  route: pageData.route,
168
207
  isValidate: false,
169
208
  logging: opts.logging,
170
- ssr: opts.settings.config.output === "server" || isHybridOutput(opts.settings.config)
209
+ ssr: isServerLikeOutput(opts.settings.config)
171
210
  }).then((_result) => {
172
211
  const label = _result.staticPaths.length === 1 ? "page" : "pages";
173
212
  debug(
@@ -281,7 +320,7 @@ async function generatePath(pathname, opts, gopts, middleware) {
281
320
  });
282
321
  }
283
322
  }
284
- const ssr = settings.config.output === "server" || isHybridOutput(settings.config);
323
+ const ssr = isServerLikeOutput(settings.config);
285
324
  const url = getUrlForPath(
286
325
  pathname,
287
326
  opts.settings.config.base,
@@ -365,11 +404,23 @@ async function generatePath(pathname, opts, gopts, middleware) {
365
404
  onRequest,
366
405
  apiContext,
367
406
  () => {
368
- return renderPage({ mod, renderContext, env, apiContext });
407
+ return renderPage({
408
+ mod,
409
+ renderContext,
410
+ env,
411
+ apiContext,
412
+ isCompressHTML: settings.config.compressHTML
413
+ });
369
414
  }
370
415
  );
371
416
  } else {
372
- response = await renderPage({ mod, renderContext, env, apiContext });
417
+ response = await renderPage({
418
+ mod,
419
+ renderContext,
420
+ env,
421
+ apiContext,
422
+ isCompressHTML: settings.config.compressHTML
423
+ });
373
424
  }
374
425
  } catch (err) {
375
426
  if (!AstroError.is(err) && !err.id && typeof err === "object") {
@@ -377,10 +428,22 @@ async function generatePath(pathname, opts, gopts, middleware) {
377
428
  }
378
429
  throw err;
379
430
  }
380
- throwIfRedirectNotAllowed(response, opts.settings.config);
381
- if (!response.body)
382
- return;
383
- body = await response.text();
431
+ if (response.status >= 300 && response.status < 400) {
432
+ if (!opts.settings.config.build.redirects) {
433
+ return;
434
+ }
435
+ const location = getRedirectLocationOrThrow(response.headers);
436
+ body = `<!doctype html>
437
+ <title>Redirecting to: ${location}</title>
438
+ <meta http-equiv="refresh" content="0;url=${location}" />`;
439
+ if (pageData.route.type !== "redirect") {
440
+ pageData.route.redirect = location;
441
+ }
442
+ } else {
443
+ if (!response.body)
444
+ return;
445
+ body = await response.text();
446
+ }
384
447
  }
385
448
  const outFolder = getOutFolder(settings.config, pathname, pageData.route.type);
386
449
  const outFile = getOutFile(settings.config, outFolder, pathname, pageData.route.type);
@@ -79,6 +79,7 @@ export declare function getPageDataByComponent(internals: BuildInternals, compon
79
79
  export declare function getPageDataByViteID(internals: BuildInternals, viteid: ViteID): PageBuildData | undefined;
80
80
  export declare function hasPageDataByViteID(internals: BuildInternals, viteid: ViteID): boolean;
81
81
  export declare function eachPageData(internals: BuildInternals): Generator<PageBuildData, void, undefined>;
82
+ export declare function eachRedirectPageData(internals: BuildInternals): Generator<PageBuildData, void, unknown>;
82
83
  export declare function eachPageDataFromEntryPoint(internals: BuildInternals): Generator<[PageBuildData, string]>;
83
84
  export declare function hasPrerenderedPages(internals: BuildInternals): boolean;
84
85
  interface OrderInfo {
@@ -95,4 +96,5 @@ export declare function cssOrder(a: OrderInfo, b: OrderInfo): 1 | -1;
95
96
  export declare function mergeInlineCss(acc: Array<StylesheetAsset>, current: StylesheetAsset): Array<StylesheetAsset>;
96
97
  export declare function isHoistedScript(internals: BuildInternals, id: string): boolean;
97
98
  export declare function getPageDatasByHoistedScriptId(internals: BuildInternals, id: string): Generator<PageBuildData, void, unknown>;
99
+ export declare function getEntryFilePathFromComponentPath(internals: BuildInternals, path: string): string | undefined;
98
100
  export {};
@@ -1,6 +1,10 @@
1
1
  import { prependForwardSlash, removeFileExtension } from "../path.js";
2
2
  import { viteID } from "../util.js";
3
- import { ASTRO_PAGE_EXTENSION_POST_PATTERN, ASTRO_PAGE_MODULE_ID } from "./plugins/plugin-pages.js";
3
+ import {
4
+ ASTRO_PAGE_EXTENSION_POST_PATTERN,
5
+ ASTRO_PAGE_MODULE_ID,
6
+ getVirtualModulePageIdFromPath
7
+ } from "./plugins/plugin-pages.js";
4
8
  function createBuildInternals() {
5
9
  const hoistedScriptIdToHoistedMap = /* @__PURE__ */ new Map();
6
10
  const hoistedScriptIdToPagesMap = /* @__PURE__ */ new Map();
@@ -83,6 +87,13 @@ function hasPageDataByViteID(internals, viteid) {
83
87
  function* eachPageData(internals) {
84
88
  yield* internals.pagesByComponent.values();
85
89
  }
90
+ function* eachRedirectPageData(internals) {
91
+ for (const pageData of eachPageData(internals)) {
92
+ if (pageData.route.type === "redirect") {
93
+ yield pageData;
94
+ }
95
+ }
96
+ }
86
97
  function* eachPageDataFromEntryPoint(internals) {
87
98
  for (const [entryPoint, filePath] of internals.entrySpecifierToBundleMap) {
88
99
  if (entryPoint.includes(ASTRO_PAGE_MODULE_ID)) {
@@ -153,11 +164,17 @@ function* getPageDatasByHoistedScriptId(internals, id) {
153
164
  }
154
165
  }
155
166
  }
167
+ function getEntryFilePathFromComponentPath(internals, path) {
168
+ const id = getVirtualModulePageIdFromPath(path);
169
+ return internals.entrySpecifierToBundleMap.get(id);
170
+ }
156
171
  export {
157
172
  createBuildInternals,
158
173
  cssOrder,
159
174
  eachPageData,
160
175
  eachPageDataFromEntryPoint,
176
+ eachRedirectPageData,
177
+ getEntryFilePathFromComponentPath,
161
178
  getPageDataByComponent,
162
179
  getPageDataByViteID,
163
180
  getPageDatasByChunk,
@@ -146,7 +146,7 @@ function rollupPluginAstroBuildCSS(options) {
146
146
  enforce: "post",
147
147
  async generateBundle(_outputOptions, bundle) {
148
148
  var _a;
149
- const inlineConfig = settings.config.experimental.inlineStylesheets;
149
+ const inlineConfig = settings.config.build.inlineStylesheets;
150
150
  const { assetsInlineLimit = 4096 } = ((_a = settings.config.vite) == null ? void 0 : _a.build) ?? {};
151
151
  Object.entries(bundle).forEach(([id, stylesheet]) => {
152
152
  var _a2;