astro 6.1.1 → 6.1.3

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 (32) hide show
  1. package/dist/actions/integration.js +2 -2
  2. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  3. package/dist/content/content-layer.js +3 -3
  4. package/dist/content/loaders/glob.js +6 -2
  5. package/dist/content/mutable-data-store.js +26 -0
  6. package/dist/core/app/dev/pipeline.js +4 -0
  7. package/dist/core/build/plugins/index.js +3 -1
  8. package/dist/core/build/plugins/plugin-chunk-imports.d.ts +10 -0
  9. package/dist/core/build/plugins/plugin-chunk-imports.js +38 -0
  10. package/dist/core/build/plugins/plugin-css.js +7 -2
  11. package/dist/core/build/plugins/plugin-manifest.js +5 -0
  12. package/dist/core/constants.js +1 -1
  13. package/dist/core/cookies/cookies.d.ts +1 -1
  14. package/dist/core/dev/dev.js +1 -1
  15. package/dist/core/errors/errors-data.js +3 -3
  16. package/dist/core/messages/runtime.js +1 -1
  17. package/dist/core/middleware/index.d.ts +7 -0
  18. package/dist/core/middleware/index.js +17 -12
  19. package/dist/core/preview/static-preview-server.js +12 -5
  20. package/dist/core/routing/dev.js +0 -1
  21. package/dist/core/routing/helpers.d.ts +4 -2
  22. package/dist/core/routing/helpers.js +5 -3
  23. package/dist/prerender/routing.d.ts +1 -3
  24. package/dist/prerender/routing.js +1 -6
  25. package/dist/runtime/server/escape.d.ts +2 -1
  26. package/dist/runtime/server/escape.js +4 -5
  27. package/dist/vite-plugin-astro-server/plugin.d.ts +0 -8
  28. package/dist/vite-plugin-astro-server/plugin.js +0 -107
  29. package/dist/vite-plugin-head/index.js +40 -2
  30. package/dist/vite-plugin-hmr-reload/index.js +2 -2
  31. package/dist/vite-plugin-renderers/index.js +4 -3
  32. package/package.json +10 -5
@@ -1,6 +1,6 @@
1
1
  import { AstroError } from "../core/errors/errors.js";
2
2
  import { ActionsWithoutServerOutputError } from "../core/errors/errors-data.js";
3
- import { hasNonPrerenderedProjectRoute } from "../core/routing/helpers.js";
3
+ import { hasNonPrerenderedRoute } from "../core/routing/helpers.js";
4
4
  import { viteID } from "../core/util.js";
5
5
  import { ACTION_RPC_ROUTE_PATTERN, ACTIONS_TYPES_FILE, VIRTUAL_MODULE_ID } from "./consts.js";
6
6
  function astroIntegrationActionsRouteHandler({
@@ -30,7 +30,7 @@ function astroIntegrationActionsRouteHandler({
30
30
  });
31
31
  },
32
32
  "astro:routes:resolved": ({ routes }) => {
33
- if (!hasNonPrerenderedProjectRoute(routes)) {
33
+ if (!hasNonPrerenderedRoute(routes)) {
34
34
  const error = new AstroError(ActionsWithoutServerOutputError);
35
35
  error.stack = void 0;
36
36
  throw error;
@@ -1,6 +1,6 @@
1
1
  class BuildTimeAstroVersionProvider {
2
2
  // Injected during the build through esbuild define
3
- version = "6.1.1";
3
+ version = "6.1.3";
4
4
  }
5
5
  export {
6
6
  BuildTimeAstroVersionProvider
@@ -192,7 +192,7 @@ ${contentConfig.error.message}`
192
192
  logger.info("Content config changed");
193
193
  shouldClear = true;
194
194
  }
195
- if (previousAstroVersion && previousAstroVersion !== "6.1.1") {
195
+ if (previousAstroVersion && previousAstroVersion !== "6.1.3") {
196
196
  logger.info("Astro version changed");
197
197
  shouldClear = true;
198
198
  }
@@ -200,8 +200,8 @@ ${contentConfig.error.message}`
200
200
  logger.info("Clearing content store");
201
201
  this.#store.clearAll();
202
202
  }
203
- if ("6.1.1") {
204
- this.#store.metaStore().set("astro-version", "6.1.1");
203
+ if ("6.1.3") {
204
+ this.#store.metaStore().set("astro-version", "6.1.3");
205
205
  }
206
206
  if (currentConfigDigest) {
207
207
  this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -242,8 +242,12 @@ function glob(globOptions) {
242
242
  const entryType = configForFile(changedPath);
243
243
  const baseUrl = pathToFileURL(basePath);
244
244
  const oldId = fileToIdMap.get(changedPath);
245
- await syncData(entry, baseUrl, entryType, oldId);
246
- logger.info(`Reloaded data from ${colors.green(entry)}`);
245
+ try {
246
+ await syncData(entry, baseUrl, entryType, oldId);
247
+ logger.info(`Reloaded data from ${colors.green(entry)}`);
248
+ } catch (e) {
249
+ logger.error(`Failed to reload ${entry}: ${e.message}`);
250
+ }
247
251
  }
248
252
  watcher.on("change", onChange);
249
253
  watcher.on("add", onChange);
@@ -35,15 +35,18 @@ class MutableDataStore extends ImmutableDataStore {
35
35
  if (collection) {
36
36
  collection.delete(String(key));
37
37
  this.#saveToDiskDebounced();
38
+ this.#writeAssetsImportsDebounced();
38
39
  }
39
40
  }
40
41
  clear(collectionName) {
41
42
  this._collections.delete(collectionName);
42
43
  this.#saveToDiskDebounced();
44
+ this.#writeAssetsImportsDebounced();
43
45
  }
44
46
  clearAll() {
45
47
  this._collections.clear();
46
48
  this.#saveToDiskDebounced();
49
+ this.#writeAssetsImportsDebounced();
47
50
  }
48
51
  addAssetImport(assetImport, filePath) {
49
52
  const id = imageSrcToImportId(assetImport, filePath);
@@ -62,8 +65,31 @@ class MutableDataStore extends ImmutableDataStore {
62
65
  this.#writeModulesImportsDebounced();
63
66
  }
64
67
  }
68
+ /**
69
+ * Rebuilds #assetImports from the current entries in _collections.
70
+ * This ensures stale import IDs are removed when entries are updated or deleted,
71
+ * preventing unrecoverable ImageNotFound errors in astro dev after a content entry's
72
+ * image path is temporarily set to an invalid value and then restored.
73
+ */
74
+ #rebuildAssetImports() {
75
+ this.#assetImports.clear();
76
+ for (const collection of this._collections.values()) {
77
+ for (const entry of collection.values()) {
78
+ const typedEntry = entry;
79
+ if (typedEntry.assetImports?.length) {
80
+ for (const assetImport of typedEntry.assetImports) {
81
+ const id = imageSrcToImportId(assetImport, typedEntry.filePath);
82
+ if (id) {
83
+ this.#assetImports.add(id);
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
65
90
  async writeAssetImports(filePath) {
66
91
  this.#assetsFile = filePath;
92
+ this.#rebuildAssetImports();
67
93
  if (this.#assetImports.size === 0) {
68
94
  try {
69
95
  await this.#writeFileAtomic(filePath, "export default new Map();");
@@ -42,6 +42,10 @@ class NonRunnablePipeline extends Pipeline {
42
42
  return pipeline;
43
43
  }
44
44
  async headElements(routeData) {
45
+ const { componentMetadataEntries } = await import("virtual:astro:component-metadata");
46
+ for (const [id, entry] of componentMetadataEntries) {
47
+ this.manifest.componentMetadata.set(id, entry);
48
+ }
45
49
  const { assetsPrefix, base } = this.manifest;
46
50
  const routeInfo = this.manifest.routes.find((route) => route.routeData === routeData);
47
51
  const links = /* @__PURE__ */ new Set();
@@ -8,6 +8,7 @@ import { pluginMiddleware } from "./plugin-middleware.js";
8
8
  import { pluginPrerender } from "./plugin-prerender.js";
9
9
  import { pluginScripts } from "./plugin-scripts.js";
10
10
  import { pluginSSR } from "./plugin-ssr.js";
11
+ import { pluginChunkImports } from "./plugin-chunk-imports.js";
11
12
  import { pluginNoop } from "./plugin-noop.js";
12
13
  import { vitePluginSSRAssets } from "../vite-plugin-ssr-assets.js";
13
14
  function getAllBuildPlugins(internals, options) {
@@ -23,7 +24,8 @@ function getAllBuildPlugins(internals, options) {
23
24
  pluginScripts(internals),
24
25
  ...pluginSSR(options, internals),
25
26
  pluginNoop(),
26
- vitePluginSSRAssets(internals)
27
+ vitePluginSSRAssets(internals),
28
+ pluginChunkImports(options)
27
29
  ].filter(Boolean);
28
30
  }
29
31
  export {
@@ -0,0 +1,10 @@
1
+ import type { Plugin as VitePlugin } from 'vite';
2
+ import type { StaticBuildOptions } from '../types.js';
3
+ /**
4
+ * Appends assetQueryParams (e.g., ?dpl=<VERCEL_DEPLOYMENT_ID>) to relative
5
+ * JS import paths inside client chunks. Without this, inter-chunk imports
6
+ * bypass the HTML rendering pipeline and miss skew protection query params.
7
+ *
8
+ * Uses es-module-lexer to reliably parse both static and dynamic imports.
9
+ */
10
+ export declare function pluginChunkImports(options: StaticBuildOptions): VitePlugin | undefined;
@@ -0,0 +1,38 @@
1
+ import { init, parse } from "es-module-lexer";
2
+ import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../../constants.js";
3
+ function pluginChunkImports(options) {
4
+ const assetQueryParams = options.settings.adapter?.client?.assetQueryParams;
5
+ if (!assetQueryParams || assetQueryParams.toString() === "") {
6
+ return void 0;
7
+ }
8
+ const queryString = assetQueryParams.toString();
9
+ return {
10
+ name: "@astro/plugin-chunk-imports",
11
+ enforce: "post",
12
+ applyToEnvironment(environment) {
13
+ return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client;
14
+ },
15
+ async renderChunk(code, _chunk) {
16
+ if (!code.includes("./")) {
17
+ return null;
18
+ }
19
+ await init;
20
+ const [imports] = parse(code);
21
+ const relativeImports = imports.filter(
22
+ (imp) => imp.n && /^\.\.?\//.test(imp.n) && /\.(?:js|mjs)$/.test(imp.n)
23
+ );
24
+ if (relativeImports.length === 0) {
25
+ return null;
26
+ }
27
+ let rewritten = code;
28
+ for (let i = relativeImports.length - 1; i >= 0; i--) {
29
+ const imp = relativeImports[i];
30
+ rewritten = rewritten.slice(0, imp.e) + "?" + queryString + rewritten.slice(imp.e);
31
+ }
32
+ return { code: rewritten, map: null };
33
+ }
34
+ };
35
+ }
36
+ export {
37
+ pluginChunkImports
38
+ };
@@ -15,6 +15,11 @@ function pluginCSS(options, internals) {
15
15
  internals
16
16
  });
17
17
  }
18
+ function isBuildCssBoundary(id, ctx) {
19
+ if (isPropagatedAssetBoundary(id)) return true;
20
+ const info = ctx.getModuleInfo(id);
21
+ return info ? moduleIsTopLevelPage(info) : false;
22
+ }
18
23
  function rollupPluginAstroBuildCSS(options) {
19
24
  const { internals, buildOptions } = options;
20
25
  const { settings } = buildOptions;
@@ -95,7 +100,7 @@ function rollupPluginAstroBuildCSS(options) {
95
100
  const parentModuleInfos = getParentExtendedModuleInfos(
96
101
  scopedToModule,
97
102
  this,
98
- isPropagatedAssetBoundary
103
+ (moduleId) => isBuildCssBoundary(moduleId, this)
99
104
  );
100
105
  for (const { info: pageInfo, depth, order } of parentModuleInfos) {
101
106
  if (moduleIsTopLevelPage(pageInfo)) {
@@ -155,7 +160,7 @@ function rollupPluginAstroBuildCSS(options) {
155
160
  const parentModuleInfos = getParentExtendedModuleInfos(
156
161
  id,
157
162
  this,
158
- isPropagatedAssetBoundary
163
+ (importer) => isBuildCssBoundary(importer, this)
159
164
  );
160
165
  for (const { info: pageInfo, depth, order } of parentModuleInfos) {
161
166
  if (isPropagatedAssetBoundary(pageInfo.id)) {
@@ -67,6 +67,11 @@ async function createManifest(buildOpts, internals) {
67
67
  for (const file of clientStatics) {
68
68
  internals.staticFiles.add(file);
69
69
  }
70
+ for (const [, ssrAssets] of internals.ssrAssetsPerEnvironment) {
71
+ for (const asset of ssrAssets) {
72
+ internals.staticFiles.add(asset);
73
+ }
74
+ }
70
75
  const staticFiles = internals.staticFiles;
71
76
  const encodedKey = await encodeKey(await buildOpts.key);
72
77
  const manifest = await buildManifest(buildOpts, internals, Array.from(staticFiles), encodedKey);
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "6.1.1";
1
+ const ASTRO_VERSION = "6.1.3";
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";
@@ -3,7 +3,7 @@ export type AstroCookieSetOptions = Pick<SerializeOptions, 'domain' | 'path' | '
3
3
  export interface AstroCookieGetOptions {
4
4
  decode?: (value: string) => string;
5
5
  }
6
- type AstroCookieDeleteOptions = Omit<AstroCookieSetOptions, 'expires' | 'maxAge' | 'encode'>;
6
+ export type AstroCookieDeleteOptions = Omit<AstroCookieSetOptions, 'expires' | 'maxAge' | 'encode'>;
7
7
  interface AstroCookieInterface {
8
8
  value: string;
9
9
  json(): Record<string, any>;
@@ -37,7 +37,7 @@ async function dev(inlineConfig) {
37
37
  await telemetry.record([]);
38
38
  const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
39
39
  const logger = restart.container.logger;
40
- const currentVersion = "6.1.1";
40
+ const currentVersion = "6.1.3";
41
41
  const isPrerelease = currentVersion.includes("-");
42
42
  if (!isPrerelease) {
43
43
  try {
@@ -577,7 +577,7 @@ const InvalidContentEntryFrontmatterError = {
577
577
  `**${String(collection)} \u2192 ${String(
578
578
  entryId
579
579
  )}** frontmatter does not match collection schema.`,
580
- error.message
580
+ ...error.issues.map((issue) => ` **${issue.path.join(".")}**: ${issue.message}`)
581
581
  ].join("\n");
582
582
  },
583
583
  hint: "See https://docs.astro.build/en/guides/content-collections/ for more information on content schemas."
@@ -589,7 +589,7 @@ const InvalidContentEntryDataError = {
589
589
  return [
590
590
  `**${String(collection)} \u2192 ${String(entryId)}** data does not match collection schema.
591
591
  `,
592
- ` **: ${error.message}`,
592
+ ...error.issues.map((issue) => ` **${issue.path.join(".")}**: ${issue.message}`),
593
593
  ""
594
594
  ].join("\n");
595
595
  },
@@ -631,7 +631,7 @@ const ContentEntryDataError = {
631
631
  return [
632
632
  `**${String(collection)} \u2192 ${String(entryId)}** data does not match collection schema.
633
633
  `,
634
- ` **: ${error.message}`,
634
+ ...error.issues.map((issue) => ` **${issue.path.join(".")}**: ${issue.message}`),
635
635
  ""
636
636
  ].join("\n");
637
637
  },
@@ -276,7 +276,7 @@ function printHelp({
276
276
  message.push(
277
277
  linebreak(),
278
278
  ` ${bgGreen(black(` ${commandName} `))} ${green(
279
- `v${"6.1.1"}`
279
+ `v${"6.1.3"}`
280
280
  )} ${headline}`
281
281
  );
282
282
  }
@@ -37,6 +37,13 @@ export type CreateContext = {
37
37
  * Creates a context to be passed to Astro middleware `onRequest` function.
38
38
  */
39
39
  declare function createContext({ request, params, userDefinedLocales, defaultLocale, locals, clientAddress, }: CreateContext): APIContext;
40
+ /**
41
+ * Checks whether the passed `value` is serializable.
42
+ *
43
+ * A serializable value contains plain values. For example, `Proxy`, `Set`, `Map`, functions, etc.
44
+ * are not accepted because they can't be serialized.
45
+ */
46
+ export declare function isLocalsSerializable(value: unknown): boolean;
40
47
  /**
41
48
  * It attempts to serialize `value` and return it as a string.
42
49
  *
@@ -82,20 +82,24 @@ function createContext({
82
82
  });
83
83
  }
84
84
  function isLocalsSerializable(value) {
85
- let type = typeof value;
86
- let plainObject = true;
87
- if (type === "object" && isPlainObject(value)) {
88
- for (const [, nestedValue] of Object.entries(value)) {
89
- if (!isLocalsSerializable(nestedValue)) {
90
- plainObject = false;
91
- break;
92
- }
85
+ const stack = [value];
86
+ while (stack.length > 0) {
87
+ const current = stack.pop();
88
+ const type = typeof current;
89
+ if (current === null || type === "string" || type === "number" || type === "boolean") {
90
+ continue;
93
91
  }
94
- } else {
95
- plainObject = false;
92
+ if (Array.isArray(current)) {
93
+ stack.push(...current);
94
+ continue;
95
+ }
96
+ if (type === "object" && isPlainObject(current)) {
97
+ stack.push(...Object.values(current));
98
+ continue;
99
+ }
100
+ return false;
96
101
  }
97
- let result = value === null || type === "string" || type === "number" || type === "boolean" || Array.isArray(value) || plainObject;
98
- return result;
102
+ return true;
99
103
  }
100
104
  function isPlainObject(value) {
101
105
  if (typeof value !== "object" || value === null) return false;
@@ -118,6 +122,7 @@ import { defineMiddleware } from "./defineMiddleware.js";
118
122
  export {
119
123
  createContext,
120
124
  defineMiddleware,
125
+ isLocalsSerializable,
121
126
  sequence,
122
127
  trySerializeLocals
123
128
  };
@@ -1,6 +1,6 @@
1
1
  import { performance } from "node:perf_hooks";
2
2
  import { fileURLToPath } from "node:url";
3
- import { preview } from "vite";
3
+ import { mergeConfig, preview } from "vite";
4
4
  import * as msg from "../messages/runtime.js";
5
5
  import { getResolvedHostForHttpServer } from "./util.js";
6
6
  import { vitePluginAstroPreview } from "./vite-plugin-astro-preview.js";
@@ -10,7 +10,7 @@ async function createStaticPreviewServer(settings, logger) {
10
10
  const startServerTime = performance.now();
11
11
  let previewServer;
12
12
  try {
13
- previewServer = await preview({
13
+ const astroPreviewConfig = {
14
14
  configFile: false,
15
15
  base: settings.config.base,
16
16
  appType: "mpa",
@@ -22,11 +22,18 @@ async function createStaticPreviewServer(settings, logger) {
22
22
  host: settings.config.server.host,
23
23
  port: settings.config.server.port,
24
24
  headers: settings.config.server.headers,
25
- open: settings.config.server.open,
26
- allowedHosts: settings.config.server.allowedHosts
25
+ open: settings.config.server.open
27
26
  },
28
27
  plugins: [vitePluginAstroPreview(settings)]
29
- });
28
+ };
29
+ const { plugins: _plugins, ...userViteConfig } = settings.config.vite ?? {};
30
+ const mergedViteConfig = mergeConfig(userViteConfig, astroPreviewConfig);
31
+ const { allowedHosts } = settings.config.server;
32
+ if (typeof allowedHosts === "boolean" || Array.isArray(allowedHosts) && allowedHosts.length > 0) {
33
+ mergedViteConfig.preview ??= {};
34
+ mergedViteConfig.preview.allowedHosts = allowedHosts;
35
+ }
36
+ previewServer = await preview(mergedViteConfig);
30
37
  } catch (err) {
31
38
  if (err instanceof Error) {
32
39
  logger.error(null, err.stack || err.message);
@@ -8,7 +8,6 @@ async function matchRoute(pathname, routesList, pipeline, manifest) {
8
8
  const { logger, routeCache } = pipeline;
9
9
  const matches = matchAllRoutes(pathname, routesList);
10
10
  const preloadedMatches = getSortedPreloadedMatches({
11
- pipeline,
12
11
  matches,
13
12
  manifest
14
13
  });
@@ -38,10 +38,12 @@ export declare function getCustom500Route(manifestData: RoutesList): RouteData |
38
38
  * `.html` suffix from pathnames that intentionally include it.
39
39
  */
40
40
  export declare function routeHasHtmlExtension(route: RouteData): boolean;
41
- export declare function hasNonPrerenderedProjectRoute(routes: Array<Pick<RouteData, 'type' | 'origin' | 'prerender'>>, options?: {
41
+ export declare function hasNonPrerenderedRoute(routes: Array<Pick<RouteData, 'type' | 'origin' | 'prerender'>>, options?: {
42
42
  includeEndpoints?: boolean;
43
+ includeExternal?: boolean;
43
44
  }): boolean;
44
- export declare function hasNonPrerenderedProjectRoute(routes: Array<Pick<IntegrationResolvedRoute, 'type' | 'origin' | 'isPrerendered'>>, options?: {
45
+ export declare function hasNonPrerenderedRoute(routes: Array<Pick<IntegrationResolvedRoute, 'type' | 'origin' | 'isPrerendered'>>, options?: {
45
46
  includeEndpoints?: boolean;
47
+ includeExternal?: boolean;
46
48
  }): boolean;
47
49
  export {};
@@ -30,19 +30,21 @@ function routeHasHtmlExtension(route) {
30
30
  (segment) => segment.some((part) => !part.dynamic && part.content.includes(".html"))
31
31
  );
32
32
  }
33
- function hasNonPrerenderedProjectRoute(routes, options) {
33
+ function hasNonPrerenderedRoute(routes, options) {
34
34
  const includeEndpoints = options?.includeEndpoints ?? true;
35
+ const includeExternal = options?.includeExternal ?? false;
35
36
  const routeTypes = includeEndpoints ? ["page", "endpoint"] : ["page"];
37
+ const origins = includeExternal ? ["project", "external"] : ["project"];
36
38
  return routes.some((route) => {
37
39
  const isPrerendered = "isPrerendered" in route ? route.isPrerendered : route.prerender;
38
- return routeTypes.includes(route.type) && route.origin === "project" && !isPrerendered;
40
+ return routeTypes.includes(route.type) && origins.includes(route.origin) && !isPrerendered;
39
41
  });
40
42
  }
41
43
  export {
42
44
  getCustom404Route,
43
45
  getCustom500Route,
44
46
  getFallbackRoute,
45
- hasNonPrerenderedProjectRoute,
47
+ hasNonPrerenderedRoute,
46
48
  routeHasHtmlExtension,
47
49
  routeIsFallback,
48
50
  routeIsRedirect
@@ -1,11 +1,9 @@
1
1
  import type { RouteData, SSRManifest } from '../types/public/internal.js';
2
- import type { RunnablePipeline } from '../vite-plugin-app/pipeline.js';
3
2
  type GetSortedPreloadedMatchesParams = {
4
- pipeline: RunnablePipeline;
5
3
  matches: RouteData[];
6
4
  manifest: SSRManifest;
7
5
  };
8
- export declare function getSortedPreloadedMatches({ pipeline, matches, manifest, }: GetSortedPreloadedMatchesParams): PreloadAndSetPrerenderStatusResult[];
6
+ export declare function getSortedPreloadedMatches({ matches, manifest }: GetSortedPreloadedMatchesParams): PreloadAndSetPrerenderStatusResult[];
9
7
  type PreloadAndSetPrerenderStatusResult = {
10
8
  filePath: URL;
11
9
  route: RouteData;
@@ -1,12 +1,7 @@
1
1
  import { routeIsRedirect } from "../core/routing/helpers.js";
2
2
  import { routeComparator } from "../core/routing/priority.js";
3
- function getSortedPreloadedMatches({
4
- pipeline,
5
- matches,
6
- manifest
7
- }) {
3
+ function getSortedPreloadedMatches({ matches, manifest }) {
8
4
  return preloadAndSetPrerenderStatus({
9
- pipeline,
10
5
  matches,
11
6
  manifest
12
7
  }).sort((a, b) => routeComparator(a.route, b.route)).sort((a, b) => prioritizePrerenderedMatchesComparator(a.route, b.route));
@@ -1,12 +1,13 @@
1
1
  export declare const escapeHTML: (str: string) => string;
2
2
  export declare class HTMLBytes extends Uint8Array {
3
3
  }
4
+ declare const htmlStringSymbol: unique symbol;
4
5
  /**
5
6
  * A "blessed" extension of String that tells Astro that the string
6
7
  * has already been escaped. This helps prevent double-escaping of HTML.
7
8
  */
8
9
  export declare class HTMLString extends String {
9
- get [Symbol.toStringTag](): string;
10
+ [htmlStringSymbol]: boolean;
10
11
  }
11
12
  type BlessedType = string | HTMLBytes;
12
13
  /**
@@ -8,13 +8,12 @@ Object.defineProperty(HTMLBytes.prototype, Symbol.toStringTag, {
8
8
  return "HTMLBytes";
9
9
  }
10
10
  });
11
+ const htmlStringSymbol = /* @__PURE__ */ Symbol.for("astro:html-string");
11
12
  class HTMLString extends String {
12
- get [Symbol.toStringTag]() {
13
- return "HTMLString";
14
- }
13
+ [htmlStringSymbol] = true;
15
14
  }
16
15
  const markHTMLString = (value) => {
17
- if (value instanceof HTMLString) {
16
+ if (isHTMLString(value)) {
18
17
  return value;
19
18
  }
20
19
  if (typeof value === "string") {
@@ -23,7 +22,7 @@ const markHTMLString = (value) => {
23
22
  return value;
24
23
  };
25
24
  function isHTMLString(value) {
26
- return value instanceof HTMLString;
25
+ return !!value?.[htmlStringSymbol];
27
26
  }
28
27
  function markHTMLBytes(bytes) {
29
28
  return new HTMLBytes(bytes);
@@ -1,5 +1,4 @@
1
1
  import type * as vite from 'vite';
2
- import type { SSRManifest } from '../core/app/types.js';
3
2
  import type { Logger } from '../core/logger/core.js';
4
3
  import type { AstroSettings } from '../types/astro.js';
5
4
  interface AstroPluginOptions {
@@ -7,11 +6,4 @@ interface AstroPluginOptions {
7
6
  logger: Logger;
8
7
  }
9
8
  export default function createVitePluginAstroServer({ settings, logger, }: AstroPluginOptions): vite.Plugin;
10
- /**
11
- * It creates a `SSRManifest` from the `AstroSettings`.
12
- *
13
- * Renderers needs to be pulled out from the page module emitted during the build.
14
- * @param settings
15
- */
16
- export declare function createDevelopmentManifest(settings: AstroSettings): Promise<SSRManifest>;
17
9
  export {};
@@ -1,26 +1,11 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { IncomingMessage } from "node:http";
3
3
  import { isRunnableDevEnvironment } from "vite";
4
- import { toFallbackType } from "../core/app/common.js";
5
- import { toRoutingStrategy } from "../core/app/entrypoints/index.js";
6
4
  import { ASTRO_VITE_ENVIRONMENT_NAMES, devPrerenderMiddlewareSymbol } from "../core/constants.js";
7
- import {
8
- getAlgorithm,
9
- getDirectives,
10
- getScriptHashes,
11
- getScriptResources,
12
- getStrictDynamic,
13
- getStyleHashes,
14
- getStyleResources,
15
- shouldTrackCspHashes
16
- } from "../core/csp/common.js";
17
- import { createKey, getEnvironmentKey, hasEnvironmentKey } from "../core/encryption.js";
18
5
  import { getViteErrorPayload } from "../core/errors/dev/index.js";
19
6
  import { AstroError, AstroErrorData } from "../core/errors/index.js";
20
- import { NOOP_MIDDLEWARE_FN } from "../core/middleware/noop-middleware.js";
21
7
  import { createViteLoader } from "../core/module-loader/index.js";
22
8
  import { matchAllRoutes } from "../core/routing/match.js";
23
- import { resolveMiddlewareMode } from "../integrations/adapter-utils.js";
24
9
  import { SERIALIZED_MANIFEST_ID } from "../manifest/serialized.js";
25
10
  import { ASTRO_DEV_SERVER_APP_ID } from "../vite-plugin-app/index.js";
26
11
  import { baseMiddleware } from "./base.js";
@@ -30,7 +15,6 @@ import { setRouteError } from "./server-state.js";
30
15
  import { routeGuardMiddleware } from "./route-guard.js";
31
16
  import { secFetchMiddleware } from "./sec-fetch.js";
32
17
  import { trailingSlashMiddleware } from "./trailing-slash.js";
33
- import { sessionConfigToManifest } from "../core/session/utils.js";
34
18
  function createVitePluginAstroServer({
35
19
  settings,
36
20
  logger
@@ -154,97 +138,6 @@ function createVitePluginAstroServer({
154
138
  }
155
139
  };
156
140
  }
157
- async function createDevelopmentManifest(settings) {
158
- let i18nManifest;
159
- let csp;
160
- if (settings.config.i18n) {
161
- i18nManifest = {
162
- fallback: settings.config.i18n.fallback,
163
- strategy: toRoutingStrategy(settings.config.i18n.routing, settings.config.i18n.domains),
164
- defaultLocale: settings.config.i18n.defaultLocale,
165
- locales: settings.config.i18n.locales,
166
- domainLookupTable: {},
167
- fallbackType: toFallbackType(settings.config.i18n.routing),
168
- domains: settings.config.i18n.domains
169
- };
170
- }
171
- if (shouldTrackCspHashes(settings.config.security.csp)) {
172
- const styleHashes = [
173
- ...getStyleHashes(settings.config.security.csp),
174
- ...settings.injectedCsp.styleHashes
175
- ];
176
- csp = {
177
- cspDestination: settings.adapter?.adapterFeatures?.staticHeaders ? "adapter" : void 0,
178
- scriptHashes: getScriptHashes(settings.config.security.csp),
179
- scriptResources: getScriptResources(settings.config.security.csp),
180
- styleHashes,
181
- styleResources: getStyleResources(settings.config.security.csp),
182
- algorithm: getAlgorithm(settings.config.security.csp),
183
- directives: getDirectives(settings),
184
- isStrictDynamic: getStrictDynamic(settings.config.security.csp)
185
- };
186
- }
187
- return {
188
- rootDir: settings.config.root,
189
- srcDir: settings.config.srcDir,
190
- cacheDir: settings.config.cacheDir,
191
- outDir: settings.config.outDir,
192
- buildServerDir: settings.config.build.server,
193
- buildClientDir: settings.config.build.client,
194
- publicDir: settings.config.publicDir,
195
- trailingSlash: settings.config.trailingSlash,
196
- buildFormat: settings.config.build.format,
197
- compressHTML: settings.config.compressHTML,
198
- assetsDir: settings.config.build.assets,
199
- serverLike: settings.buildOutput === "server",
200
- middlewareMode: resolveMiddlewareMode(settings.adapter?.adapterFeatures),
201
- assets: /* @__PURE__ */ new Set(),
202
- entryModules: {},
203
- routes: [],
204
- adapterName: settings?.adapter?.name ?? "",
205
- clientDirectives: settings.clientDirectives,
206
- renderers: [],
207
- base: settings.config.base,
208
- userAssetsBase: settings.config?.vite?.base,
209
- assetsPrefix: settings.config.build.assetsPrefix,
210
- site: settings.config.site,
211
- componentMetadata: /* @__PURE__ */ new Map(),
212
- inlinedScripts: /* @__PURE__ */ new Map(),
213
- i18n: i18nManifest,
214
- checkOrigin: settings.config.security?.checkOrigin ?? false,
215
- allowedDomains: settings.config.security?.allowedDomains,
216
- actionBodySizeLimit: settings.config.security?.actionBodySizeLimit ? settings.config.security.actionBodySizeLimit : 1024 * 1024,
217
- // 1mb default
218
- serverIslandBodySizeLimit: settings.config.security?.serverIslandBodySizeLimit ? settings.config.security.serverIslandBodySizeLimit : 1024 * 1024,
219
- // 1mb default
220
- key: hasEnvironmentKey() ? getEnvironmentKey() : createKey(),
221
- middleware() {
222
- return {
223
- onRequest: NOOP_MIDDLEWARE_FN
224
- };
225
- },
226
- sessionConfig: sessionConfigToManifest(settings.config.session),
227
- csp,
228
- image: {
229
- objectFit: settings.config.image.objectFit,
230
- objectPosition: settings.config.image.objectPosition,
231
- layout: settings.config.image.layout
232
- },
233
- devToolbar: {
234
- enabled: settings.config.devToolbar.enabled && await settings.preferences.get("devToolbar.enabled"),
235
- latestAstroVersion: settings.latestAstroVersion,
236
- debugInfoOutput: "",
237
- placement: settings.config.devToolbar.placement
238
- },
239
- logLevel: settings.logLevel,
240
- shouldInjectCspMetaTags: false,
241
- experimentalQueuedRendering: {
242
- enabled: !!settings.config.experimental?.queuedRendering,
243
- poolSize: settings.config.experimental?.queuedRendering?.poolSize ?? 1e3
244
- }
245
- };
246
- }
247
141
  export {
248
- createDevelopmentManifest,
249
142
  createVitePluginAstroServer as default
250
143
  };
@@ -6,8 +6,16 @@ import {
6
6
  import { getTopLevelPageModuleInfos } from "../core/build/graph.js";
7
7
  import { getAstroMetadata } from "../vite-plugin-astro/index.js";
8
8
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js";
9
+ const VIRTUAL_COMPONENT_METADATA = "virtual:astro:component-metadata";
10
+ const RESOLVED_VIRTUAL_COMPONENT_METADATA = `\0${VIRTUAL_COMPONENT_METADATA}`;
9
11
  function configHeadVitePlugin() {
10
12
  let environment;
13
+ function invalidateComponentMetadataModule() {
14
+ const virtualMod = environment.moduleGraph.getModuleById(RESOLVED_VIRTUAL_COMPONENT_METADATA);
15
+ if (virtualMod) {
16
+ environment.moduleGraph.invalidateModule(virtualMod);
17
+ }
18
+ }
11
19
  function buildImporterGraphFromEnvironment(seed) {
12
20
  const queue = [seed];
13
21
  const collected = /* @__PURE__ */ new Set();
@@ -46,15 +54,44 @@ function configHeadVitePlugin() {
46
54
  }
47
55
  }
48
56
  }
57
+ invalidateComponentMetadataModule();
49
58
  }
50
59
  return {
51
60
  name: "astro:head-metadata",
52
61
  enforce: "pre",
53
62
  apply: "serve",
54
- configureServer(server) {
55
- environment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
63
+ configureServer(devServer) {
64
+ environment = devServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
65
+ devServer.watcher.on("add", invalidateComponentMetadataModule);
66
+ devServer.watcher.on("unlink", invalidateComponentMetadataModule);
67
+ devServer.watcher.on("change", invalidateComponentMetadataModule);
68
+ },
69
+ load(id) {
70
+ if (id !== RESOLVED_VIRTUAL_COMPONENT_METADATA) {
71
+ return;
72
+ }
73
+ const componentMetadataEntries = [];
74
+ for (const [moduleId, mod] of environment.moduleGraph.idToModuleMap) {
75
+ const info = this.getModuleInfo(moduleId) ?? (mod.id ? this.getModuleInfo(mod.id) : null);
76
+ if (!info) continue;
77
+ const astro = getAstroMetadata(info);
78
+ if (!astro) continue;
79
+ componentMetadataEntries.push([
80
+ moduleId,
81
+ {
82
+ containsHead: astro.containsHead,
83
+ propagation: astro.propagation
84
+ }
85
+ ]);
86
+ }
87
+ return {
88
+ code: `export const componentMetadataEntries = ${JSON.stringify(componentMetadataEntries)};`
89
+ };
56
90
  },
57
91
  resolveId(source, importer) {
92
+ if (source === VIRTUAL_COMPONENT_METADATA) {
93
+ return RESOLVED_VIRTUAL_COMPONENT_METADATA;
94
+ }
58
95
  if (importer) {
59
96
  return this.resolve(source, importer, { skipSelf: true }).then((result) => {
60
97
  if (result) {
@@ -81,6 +118,7 @@ function configHeadVitePlugin() {
81
118
  if (hasHeadInjectComment(source)) {
82
119
  propagateMetadata.call(this, id, "propagation", "in-tree");
83
120
  }
121
+ invalidateComponentMetadataModule();
84
122
  }
85
123
  };
86
124
  }
@@ -1,6 +1,6 @@
1
- import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js";
2
1
  import { VIRTUAL_PAGE_RESOLVED_MODULE_ID } from "../vite-plugin-pages/const.js";
3
2
  import { getDevCssModuleNameFromPageVirtualModuleName } from "../vite-plugin-css/util.js";
3
+ import { isAstroServerEnvironment } from "../environments.js";
4
4
  function hmrReload() {
5
5
  return {
6
6
  name: "astro:hmr-reload",
@@ -8,7 +8,7 @@ function hmrReload() {
8
8
  hotUpdate: {
9
9
  order: "post",
10
10
  handler({ modules, server, timestamp }) {
11
- if (this.environment.name !== ASTRO_VITE_ENVIRONMENT_NAMES.ssr) return;
11
+ if (!isAstroServerEnvironment(this.environment)) return;
12
12
  let hasSsrOnlyModules = false;
13
13
  const invalidatedModules = /* @__PURE__ */ new Set();
14
14
  for (const mod of modules) {
@@ -1,5 +1,5 @@
1
1
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js";
2
- import { hasNonPrerenderedProjectRoute } from "../core/routing/helpers.js";
2
+ import { hasNonPrerenderedRoute } from "../core/routing/helpers.js";
3
3
  const ASTRO_RENDERERS_MODULE_ID = "virtual:astro:renderers";
4
4
  const RESOLVED_ASTRO_RENDERERS_MODULE_ID = `\0${ASTRO_RENDERERS_MODULE_ID}`;
5
5
  function vitePluginRenderers(options) {
@@ -20,8 +20,9 @@ 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 && !options.serverIslandsState.hasIslands() && !hasNonPrerenderedProjectRoute(options.routesList.routes, {
24
- includeEndpoints: false
23
+ if (options.command === "build" && this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr && renderers.length > 0 && !options.serverIslandsState.hasIslands() && !hasNonPrerenderedRoute(options.routesList.routes, {
24
+ includeEndpoints: false,
25
+ includeExternal: true
25
26
  })) {
26
27
  return { code: `export const renderers = [];` };
27
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "6.1.1",
3
+ "version": "6.1.3",
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.1.0",
157
156
  "@astrojs/internal-helpers": "0.8.0",
157
+ "@astrojs/markdown-remark": "7.1.0",
158
158
  "@astrojs/telemetry": "3.3.0"
159
159
  },
160
160
  "optionalDependencies": {
@@ -175,7 +175,7 @@
175
175
  "cheerio": "1.2.0",
176
176
  "eol": "^0.10.0",
177
177
  "expect-type": "^1.3.0",
178
- "fs-fixture": "^2.11.0",
178
+ "fs-fixture": "^2.13.0",
179
179
  "mdast-util-mdx": "^3.0.0",
180
180
  "mdast-util-mdx-jsx": "^3.2.0",
181
181
  "node-mocks-http": "^1.17.2",
@@ -218,7 +218,12 @@
218
218
  "test:e2e:chrome": "playwright test",
219
219
  "test:e2e:firefox": "playwright test --config playwright.firefox.config.js",
220
220
  "test:types": "tsc --project test/types/tsconfig.json",
221
- "test:unit": "astro-scripts test \"test/units/**/*.test.js\" --teardown ./test/units/teardown.js",
222
- "test:integration": "astro-scripts test \"test/*.test.js\""
221
+ "typecheck:tests": "tsc --project tsconfig.test.json",
222
+ "test:unit": "pnpm run test:unit:js && pnpm run test:unit:ts",
223
+ "test:unit:js": "astro-scripts test \"test/units/**/*.test.js\" --teardown ./test/units/teardown.js",
224
+ "test:unit:ts": "astro-scripts test \"test/units/**/*.test.ts\" --strip-types --teardown ./test/units/teardown.js",
225
+ "test:integration": "pnpm run test:integration:js && pnpm run test:integration:ts",
226
+ "test:integration:js": "astro-scripts test \"test/*.test.js\"",
227
+ "test:integration:ts": "astro-scripts test \"test/*.test.ts\" --strip-types"
223
228
  }
224
229
  }