astro 6.0.4 → 6.0.5

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.
@@ -1,5 +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
4
  import { viteID } from "../core/util.js";
4
5
  import { ACTION_RPC_ROUTE_PATTERN, ACTIONS_TYPES_FILE, VIRTUAL_MODULE_ID } from "./consts.js";
5
6
  function astroIntegrationActionsRouteHandler({
@@ -18,20 +19,22 @@ function astroIntegrationActionsRouteHandler({
18
19
  });
19
20
  },
20
21
  "astro:config:done": async (params) => {
21
- if (params.buildOutput === "static") {
22
- const error = new AstroError(ActionsWithoutServerOutputError);
23
- error.stack = void 0;
24
- throw error;
25
- }
26
22
  const stringifiedActionsImport = JSON.stringify(
27
23
  viteID(new URL(`./${filename}`, params.config.srcDir))
28
24
  );
29
25
  settings.injectedTypes.push({
30
26
  filename: ACTIONS_TYPES_FILE,
31
27
  content: `declare module "astro:actions" {
32
- export const actions: typeof import(${stringifiedActionsImport})["server"];
28
+ export const actions: typeof import(${stringifiedActionsImport})["server"];
33
29
  }`
34
30
  });
31
+ },
32
+ "astro:routes:resolved": ({ routes }) => {
33
+ if (!hasNonPrerenderedProjectRoute(routes)) {
34
+ const error = new AstroError(ActionsWithoutServerOutputError);
35
+ error.stack = void 0;
36
+ throw error;
37
+ }
35
38
  }
36
39
  }
37
40
  };
@@ -3,8 +3,9 @@ export interface SvgComponentProps {
3
3
  meta: ImageMetadata;
4
4
  attributes: Record<string, string>;
5
5
  children: string;
6
+ styles: string[];
6
7
  }
7
- export declare function createSvgComponent({ meta, attributes, children }: SvgComponentProps): import("../runtime/server/index.js").AstroComponentFactory & ImageMetadata;
8
+ export declare function createSvgComponent({ meta, attributes, children, styles }: SvgComponentProps): import("../runtime/server/index.js").AstroComponentFactory & ImageMetadata;
8
9
  type SvgAttributes = Record<string, any>;
9
10
  export declare function dropAttributes(attributes: SvgAttributes): SvgAttributes;
10
11
  export {};
@@ -1,13 +1,24 @@
1
+ import { generateCspDigest } from "../core/encryption.js";
1
2
  import {
2
3
  createComponent,
3
4
  render,
4
5
  spreadAttributes,
5
6
  unescapeHTML
6
7
  } from "../runtime/server/index.js";
7
- function createSvgComponent({ meta, attributes, children }) {
8
- const Component = createComponent((_, props) => {
9
- const normalizedProps = normalizeProps(attributes, props);
10
- return render`<svg${spreadAttributes(normalizedProps)}>${unescapeHTML(children)}</svg>`;
8
+ function createSvgComponent({ meta, attributes, children, styles }) {
9
+ const hasStyles = styles.length > 0;
10
+ const Component = createComponent({
11
+ async factory(result, props) {
12
+ const normalizedProps = normalizeProps(attributes, props);
13
+ if (hasStyles && result.cspDestination) {
14
+ for (const style of styles) {
15
+ const hash = await generateCspDigest(style, result.cspAlgorithm);
16
+ result._metadata.extraStyleHashes.push(hash);
17
+ }
18
+ }
19
+ return render`<svg${spreadAttributes(normalizedProps)}>${unescapeHTML(children)}</svg>`;
20
+ },
21
+ propagation: hasStyles ? "self" : "none"
11
22
  });
12
23
  if (import.meta.env.DEV) {
13
24
  makeNonEnumerable(Component);
@@ -1,5 +1,5 @@
1
1
  import { optimize } from "svgo";
2
- import { parse, renderSync } from "ultrahtml";
2
+ import { ELEMENT_NODE, TEXT_NODE, parse, renderSync } from "ultrahtml";
3
3
  import { AstroError, AstroErrorData } from "../../core/errors/index.js";
4
4
  import { dropAttributes } from "../runtime.js";
5
5
  function parseSvg({
@@ -25,18 +25,31 @@ function parseSvg({
25
25
  }
26
26
  const root = parse(processedContents);
27
27
  const svgNode = root.children.find(
28
- ({ name, type }) => type === 1 && name === "svg"
28
+ ({ name, type }) => type === ELEMENT_NODE && name === "svg"
29
29
  );
30
30
  if (!svgNode) {
31
31
  throw new Error("SVG file does not contain an <svg> element");
32
32
  }
33
33
  const { attributes, children } = svgNode;
34
34
  const body = renderSync({ ...root, children });
35
- return { attributes, body };
35
+ const styles = [];
36
+ for (const child of children) {
37
+ if (child.type === ELEMENT_NODE && child.name === "style") {
38
+ const textContent = child.children?.filter((c) => c.type === TEXT_NODE).map((c) => c.value).join("");
39
+ if (textContent) {
40
+ styles.push(textContent);
41
+ }
42
+ }
43
+ }
44
+ return { attributes, body, styles };
36
45
  }
37
46
  function makeSvgComponent(meta, contents, svgoConfig) {
38
47
  const file = typeof contents === "string" ? contents : contents.toString("utf-8");
39
- const { attributes, body: children } = parseSvg({
48
+ const {
49
+ attributes,
50
+ body: children,
51
+ styles
52
+ } = parseSvg({
40
53
  path: meta.fsPath,
41
54
  contents: file,
42
55
  svgoConfig
@@ -44,19 +57,24 @@ function makeSvgComponent(meta, contents, svgoConfig) {
44
57
  const props = {
45
58
  meta,
46
59
  attributes: dropAttributes(attributes),
47
- children
60
+ children,
61
+ styles
48
62
  };
49
63
  return `import { createSvgComponent } from 'astro/assets/runtime';
50
64
  export default createSvgComponent(${JSON.stringify(props)})`;
51
65
  }
52
66
  function parseSvgComponentData(meta, contents, svgoConfig) {
53
67
  const file = typeof contents === "string" ? contents : contents.toString("utf-8");
54
- const { attributes, body: children } = parseSvg({
68
+ const {
69
+ attributes,
70
+ body: children,
71
+ styles
72
+ } = parseSvg({
55
73
  path: meta.fsPath,
56
74
  contents: file,
57
75
  svgoConfig
58
76
  });
59
- return { attributes: dropAttributes(attributes), children };
77
+ return { attributes: dropAttributes(attributes), children, styles };
60
78
  }
61
79
  export {
62
80
  makeSvgComponent,
@@ -1,6 +1,6 @@
1
1
  class BuildTimeAstroVersionProvider {
2
2
  // Injected during the build through esbuild define
3
- version = "6.0.4";
3
+ version = "6.0.5";
4
4
  }
5
5
  export {
6
6
  BuildTimeAstroVersionProvider
@@ -189,7 +189,7 @@ ${contentConfig.error.message}`
189
189
  logger.info("Content config changed");
190
190
  shouldClear = true;
191
191
  }
192
- if (previousAstroVersion && previousAstroVersion !== "6.0.4") {
192
+ if (previousAstroVersion && previousAstroVersion !== "6.0.5") {
193
193
  logger.info("Astro version changed");
194
194
  shouldClear = true;
195
195
  }
@@ -197,8 +197,8 @@ ${contentConfig.error.message}`
197
197
  logger.info("Clearing content store");
198
198
  this.#store.clearAll();
199
199
  }
200
- if ("6.0.4") {
201
- this.#store.metaStore().set("astro-version", "6.0.4");
200
+ if ("6.0.5") {
201
+ this.#store.metaStore().set("astro-version", "6.0.5");
202
202
  }
203
203
  if (currentConfigDigest) {
204
204
  this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -6,6 +6,10 @@ async function attachContentServerListeners({
6
6
  logger,
7
7
  settings
8
8
  }) {
9
+ const maxListeners = viteServer.watcher.getMaxListeners();
10
+ if (maxListeners !== 0 && maxListeners < 50) {
11
+ viteServer.watcher.setMaxListeners(50);
12
+ }
9
13
  const contentGenerator = await createContentTypesGenerator({
10
14
  fs,
11
15
  settings,
@@ -463,7 +463,10 @@ async function generateJSONSchema(fsMod, collectionConfig, collectionKey, collec
463
463
  ctx.jsonSchema.type = "string";
464
464
  ctx.jsonSchema.format = "date-time";
465
465
  }
466
- }
466
+ },
467
+ // Collection schemas are used for parsing collection input, so we need to tell Zod to use the
468
+ // input shape when generating a JSON schema.
469
+ io: "input"
467
470
  });
468
471
  const schemaStr = JSON.stringify(schema, null, 2);
469
472
  const schemaJsonPath = new URL(
@@ -61,102 +61,106 @@ async function generatePages(options, internals, prerenderOutputDir) {
61
61
  const verb = ssr ? "prerendering" : "generating";
62
62
  logger.info("SKIP_FORMAT", `
63
63
  ${colors.bgGreen(colors.black(` ${verb} static routes `))}`);
64
- const pathsWithRoutes = await prerenderer.getStaticPaths();
65
64
  const routeToHeaders = /* @__PURE__ */ new Map();
66
- const hasI18nDomains = ssr && options.settings.config.i18n?.domains && Object.keys(options.settings.config.i18n.domains).length > 0;
67
- const { config } = options.settings;
68
- const builtPaths = /* @__PURE__ */ new Set();
69
- const filteredPaths = pathsWithRoutes.filter(({ pathname, route }) => {
70
- if (hasI18nDomains && route.prerender) {
71
- throw new AstroError({
72
- ...AstroErrorData.NoPrerenderedRoutesWithDomains,
73
- message: AstroErrorData.NoPrerenderedRoutesWithDomains.message(route.component)
74
- });
75
- }
76
- const normalized = removeTrailingForwardSlash(pathname);
77
- if (!builtPaths.has(normalized)) {
78
- builtPaths.add(normalized);
79
- return true;
80
- }
81
- const matchedRoute = matchRoute(decodeURI(pathname), options.routesList);
82
- if (!matchedRoute) {
83
- return false;
84
- }
85
- if (matchedRoute === route) {
86
- return true;
87
- }
88
- if (config.prerenderConflictBehavior === "error") {
89
- throw new AstroError({
90
- ...AstroErrorData.PrerenderRouteConflict,
91
- message: AstroErrorData.PrerenderRouteConflict.message(
65
+ let staticImageList = getStaticImageList();
66
+ try {
67
+ const pathsWithRoutes = await prerenderer.getStaticPaths();
68
+ const hasI18nDomains = ssr && options.settings.config.i18n?.domains && Object.keys(options.settings.config.i18n.domains).length > 0;
69
+ const { config } = options.settings;
70
+ const builtPaths = /* @__PURE__ */ new Set();
71
+ const filteredPaths = pathsWithRoutes.filter(({ pathname, route }) => {
72
+ if (hasI18nDomains && route.prerender) {
73
+ throw new AstroError({
74
+ ...AstroErrorData.NoPrerenderedRoutesWithDomains,
75
+ message: AstroErrorData.NoPrerenderedRoutesWithDomains.message(route.component)
76
+ });
77
+ }
78
+ const normalized = removeTrailingForwardSlash(pathname);
79
+ if (!builtPaths.has(normalized)) {
80
+ builtPaths.add(normalized);
81
+ return true;
82
+ }
83
+ const matchedRoute = matchRoute(decodeURI(pathname), options.routesList);
84
+ if (!matchedRoute) {
85
+ return false;
86
+ }
87
+ if (matchedRoute === route) {
88
+ return true;
89
+ }
90
+ if (config.prerenderConflictBehavior === "error") {
91
+ throw new AstroError({
92
+ ...AstroErrorData.PrerenderRouteConflict,
93
+ message: AstroErrorData.PrerenderRouteConflict.message(
94
+ matchedRoute.route,
95
+ route.route,
96
+ normalized
97
+ ),
98
+ hint: AstroErrorData.PrerenderRouteConflict.hint(matchedRoute.route, route.route)
99
+ });
100
+ } else if (config.prerenderConflictBehavior === "warn") {
101
+ const msg = AstroErrorData.PrerenderRouteConflict.message(
92
102
  matchedRoute.route,
93
103
  route.route,
94
104
  normalized
95
- ),
96
- hint: AstroErrorData.PrerenderRouteConflict.hint(matchedRoute.route, route.route)
97
- });
98
- } else if (config.prerenderConflictBehavior === "warn") {
99
- const msg = AstroErrorData.PrerenderRouteConflict.message(
100
- matchedRoute.route,
101
- route.route,
102
- normalized
103
- );
104
- logger.warn("build", msg);
105
- }
106
- return false;
107
- });
108
- if (config.build.concurrency > 1) {
109
- const limit = PLimit(config.build.concurrency);
110
- const BATCH_SIZE = 1e5;
111
- for (let i = 0; i < filteredPaths.length; i += BATCH_SIZE) {
112
- const batch = filteredPaths.slice(i, i + BATCH_SIZE);
113
- const promises = [];
114
- for (const { pathname, route } of batch) {
115
- promises.push(
116
- limit(
117
- () => generatePathWithPrerenderer(
118
- prerenderer,
119
- pathname,
120
- route,
121
- options,
122
- routeToHeaders,
123
- logger
105
+ );
106
+ logger.warn("build", msg);
107
+ }
108
+ return false;
109
+ });
110
+ if (config.build.concurrency > 1) {
111
+ const limit = PLimit(config.build.concurrency);
112
+ const BATCH_SIZE = 1e5;
113
+ for (let i = 0; i < filteredPaths.length; i += BATCH_SIZE) {
114
+ const batch = filteredPaths.slice(i, i + BATCH_SIZE);
115
+ const promises = [];
116
+ for (const { pathname, route } of batch) {
117
+ promises.push(
118
+ limit(
119
+ () => generatePathWithPrerenderer(
120
+ prerenderer,
121
+ pathname,
122
+ route,
123
+ options,
124
+ routeToHeaders,
125
+ logger
126
+ )
124
127
  )
125
- )
128
+ );
129
+ }
130
+ await Promise.all(promises);
131
+ }
132
+ } else {
133
+ for (const { pathname, route } of filteredPaths) {
134
+ await generatePathWithPrerenderer(
135
+ prerenderer,
136
+ pathname,
137
+ route,
138
+ options,
139
+ routeToHeaders,
140
+ logger
126
141
  );
127
142
  }
128
- await Promise.all(promises);
129
- }
130
- } else {
131
- for (const { pathname, route } of filteredPaths) {
132
- await generatePathWithPrerenderer(
133
- prerenderer,
134
- pathname,
135
- route,
136
- options,
137
- routeToHeaders,
138
- logger
139
- );
140
143
  }
141
- }
142
- for (const { route: generatedRoute } of filteredPaths) {
143
- if (generatedRoute.distURL && generatedRoute.distURL.length > 0) {
144
- for (const pageData of Object.values(options.allPages)) {
145
- if (pageData.route.route === generatedRoute.route && pageData.route.component === generatedRoute.component) {
146
- pageData.route.distURL = generatedRoute.distURL;
147
- break;
144
+ for (const { route: generatedRoute } of filteredPaths) {
145
+ if (generatedRoute.distURL && generatedRoute.distURL.length > 0) {
146
+ for (const pageData of Object.values(options.allPages)) {
147
+ if (pageData.route.route === generatedRoute.route && pageData.route.component === generatedRoute.component) {
148
+ pageData.route.distURL = generatedRoute.distURL;
149
+ break;
150
+ }
148
151
  }
149
152
  }
150
153
  }
151
- }
152
- const staticImageList = getStaticImageList();
153
- if (prerenderer.collectStaticImages) {
154
- const adapterImages = await prerenderer.collectStaticImages();
155
- for (const [path, entry] of adapterImages) {
156
- staticImageList.set(path, entry);
154
+ staticImageList = getStaticImageList();
155
+ if (prerenderer.collectStaticImages) {
156
+ const adapterImages = await prerenderer.collectStaticImages();
157
+ for (const [path, entry] of adapterImages) {
158
+ staticImageList.set(path, entry);
159
+ }
157
160
  }
161
+ } finally {
162
+ await prerenderer.teardown?.();
158
163
  }
159
- await prerenderer.teardown?.();
160
164
  logger.info(
161
165
  null,
162
166
  colors.green(`\u2713 Completed in ${getTimeStat(generatePagesTimer, performance.now())}.
@@ -328,7 +332,11 @@ function getUrlForPath(pathname, base, origin, format, trailingSlash, routeType)
328
332
  }
329
333
  let buildPathname;
330
334
  if (pathname === "/" || pathname === "") {
331
- buildPathname = collapseDuplicateTrailingSlashes(base + ending, trailingSlash !== "never");
335
+ if (format === "file") {
336
+ buildPathname = joinPaths(base, "index.html");
337
+ } else {
338
+ buildPathname = collapseDuplicateTrailingSlashes(base + ending, trailingSlash !== "never");
339
+ }
332
340
  } else if (routeType === "endpoint") {
333
341
  const buildPathRelative = removeLeadingForwardSlash(pathname);
334
342
  buildPathname = joinPaths(base, buildPathRelative);
@@ -1,7 +1,17 @@
1
1
  import { normalizeEntryId } from "./plugin-component-entry.js";
2
2
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../../constants.js";
3
+ function getRollupInputAsSet(rollupInput) {
4
+ if (Array.isArray(rollupInput)) {
5
+ return new Set(rollupInput);
6
+ } else if (typeof rollupInput === "string") {
7
+ return /* @__PURE__ */ new Set([rollupInput]);
8
+ } else if (rollupInput && typeof rollupInput === "object") {
9
+ return new Set(Object.values(rollupInput));
10
+ } else {
11
+ return /* @__PURE__ */ new Set();
12
+ }
13
+ }
3
14
  function pluginInternals(options, internals) {
4
- let input;
5
15
  return {
6
16
  name: "@astro/plugin-build-internals",
7
17
  applyToEnvironment(environment) {
@@ -27,19 +37,8 @@ function pluginInternals(options, internals) {
27
37
  };
28
38
  }
29
39
  },
30
- configResolved(config) {
31
- const rollupInput = config.build?.rollupOptions?.input;
32
- if (Array.isArray(rollupInput)) {
33
- input = new Set(rollupInput);
34
- } else if (typeof rollupInput === "string") {
35
- input = /* @__PURE__ */ new Set([rollupInput]);
36
- } else if (rollupInput && typeof rollupInput === "object") {
37
- input = new Set(Object.values(rollupInput));
38
- } else {
39
- input = /* @__PURE__ */ new Set();
40
- }
41
- },
42
40
  async generateBundle(_options, bundle) {
41
+ const input = getRollupInputAsSet(this.environment?.config.build.rollupOptions.input);
43
42
  const promises = [];
44
43
  const mapping = /* @__PURE__ */ new Map();
45
44
  const allInput = /* @__PURE__ */ new Set([...input, ...internals.clientInput]);
@@ -34,7 +34,7 @@ import { encodeName, getTimeStat, viteBuildReturnToRollupOutputs } from "./util.
34
34
  import { NOOP_MODULE_ID } from "./plugins/plugin-noop.js";
35
35
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../constants.js";
36
36
  import { getSSRAssets } from "./internal.js";
37
- import { serverIslandPlaceholderMap } from "../server-islands/vite-plugin-server-islands.js";
37
+ import { SERVER_ISLAND_MAP_MARKER } from "../server-islands/vite-plugin-server-islands.js";
38
38
  const PRERENDER_ENTRY_FILENAME_PREFIX = "prerender-entry";
39
39
  function extractRelevantChunks(outputs, prerender) {
40
40
  const extracted = [];
@@ -43,7 +43,7 @@ function extractRelevantChunks(outputs, prerender) {
43
43
  if (chunk.type === "asset") continue;
44
44
  const needsContentInjection = chunk.code.includes(LINKS_PLACEHOLDER);
45
45
  const needsManifestInjection = chunk.moduleIds.includes(SERIALIZED_MANIFEST_RESOLVED_ID);
46
- const needsServerIslandInjection = chunk.code.includes(serverIslandPlaceholderMap);
46
+ const needsServerIslandInjection = chunk.code.includes(SERVER_ISLAND_MAP_MARKER);
47
47
  if (needsContentInjection || needsManifestInjection || needsServerIslandInjection) {
48
48
  extracted.push({
49
49
  fileName: chunk.fileName,
@@ -218,6 +218,13 @@ async function buildEnvironments(opts, internals) {
218
218
  // This takes precedence over platform plugin fallbacks (e.g., Cloudflare)
219
219
  builder: {
220
220
  async buildApp(builder2) {
221
+ settings.timer.start("Prerender build");
222
+ let prerenderOutput = await builder2.build(builder2.environments.prerender);
223
+ settings.timer.end("Prerender build");
224
+ extractPrerenderEntryFileName(internals, prerenderOutput);
225
+ const prerenderOutputs = viteBuildReturnToRollupOutputs(prerenderOutput);
226
+ const prerenderChunks = extractRelevantChunks(prerenderOutputs, true);
227
+ prerenderOutput = void 0;
221
228
  let ssrChunks = [];
222
229
  if (settings.buildOutput !== "static") {
223
230
  settings.timer.start("SSR build");
@@ -229,13 +236,6 @@ async function buildEnvironments(opts, internals) {
229
236
  ssrChunks = extractRelevantChunks(ssrOutputs, false);
230
237
  ssrOutput = void 0;
231
238
  }
232
- settings.timer.start("Prerender build");
233
- let prerenderOutput = await builder2.build(builder2.environments.prerender);
234
- settings.timer.end("Prerender build");
235
- extractPrerenderEntryFileName(internals, prerenderOutput);
236
- const prerenderOutputs = viteBuildReturnToRollupOutputs(prerenderOutput);
237
- const prerenderChunks = extractRelevantChunks(prerenderOutputs, true);
238
- prerenderOutput = void 0;
239
239
  const ssrPlugins = builder2.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr]?.config.plugins ?? [];
240
240
  buildPostHooks = ssrPlugins.map(
241
241
  (plugin) => typeof plugin.api?.buildPostHook === "function" ? plugin.api.buildPostHook : void 0
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "6.0.4";
1
+ const ASTRO_VERSION = "6.0.5";
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";
@@ -26,7 +26,7 @@ async function dev(inlineConfig) {
26
26
  await telemetry.record([]);
27
27
  const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
28
28
  const logger = restart.container.logger;
29
- const currentVersion = "6.0.4";
29
+ const currentVersion = "6.0.5";
30
30
  const isPrerelease = currentVersion.includes("-");
31
31
  if (!isPrerelease) {
32
32
  try {
@@ -269,7 +269,7 @@ function printHelp({
269
269
  message.push(
270
270
  linebreak(),
271
271
  ` ${bgGreen(black(` ${commandName} `))} ${green(
272
- `v${"6.0.4"}`
272
+ `v${"6.0.5"}`
273
273
  )} ${headline}`
274
274
  );
275
275
  }
@@ -1,4 +1,5 @@
1
1
  import type { RouteData } from '../../types/public/internal.js';
2
+ import type { IntegrationResolvedRoute } from '../../types/public/integrations.js';
2
3
  import type { RouteInfo } from '../app/types.js';
3
4
  import type { RoutesList } from '../../types/astro.js';
4
5
  type RedirectRouteData = RouteData & {
@@ -31,4 +32,10 @@ export declare function getCustom404Route(manifestData: RoutesList): RouteData |
31
32
  * Return a user-provided 500 route if one exists.
32
33
  */
33
34
  export declare function getCustom500Route(manifestData: RoutesList): RouteData | undefined;
35
+ export declare function hasNonPrerenderedProjectRoute(routes: Array<Pick<RouteData, 'type' | 'origin' | 'prerender'>>, options?: {
36
+ includeEndpoints?: boolean;
37
+ }): boolean;
38
+ export declare function hasNonPrerenderedProjectRoute(routes: Array<Pick<IntegrationResolvedRoute, 'type' | 'origin' | 'isPrerendered'>>, options?: {
39
+ includeEndpoints?: boolean;
40
+ }): boolean;
34
41
  export {};
@@ -25,10 +25,19 @@ function getCustom404Route(manifestData) {
25
25
  function getCustom500Route(manifestData) {
26
26
  return manifestData.routes.find((r) => isRoute500(r.route));
27
27
  }
28
+ function hasNonPrerenderedProjectRoute(routes, options) {
29
+ const includeEndpoints = options?.includeEndpoints ?? true;
30
+ const routeTypes = includeEndpoints ? ["page", "endpoint"] : ["page"];
31
+ return routes.some((route) => {
32
+ const isPrerendered = "isPrerendered" in route ? route.isPrerendered : route.prerender;
33
+ return routeTypes.includes(route.type) && route.origin === "project" && !isPrerendered;
34
+ });
35
+ }
28
36
  export {
29
37
  getCustom404Route,
30
38
  getCustom500Route,
31
39
  getFallbackRoute,
40
+ hasNonPrerenderedProjectRoute,
32
41
  routeIsFallback,
33
42
  routeIsRedirect
34
43
  };
@@ -1,5 +1,5 @@
1
1
  import type { Plugin as VitePlugin } from 'vite';
2
2
  import type { AstroPluginOptions } from '../../types/astro.js';
3
3
  export declare const SERVER_ISLAND_MANIFEST = "virtual:astro:server-island-manifest";
4
- export declare const serverIslandPlaceholderMap = "'$$server-islands-map$$'";
4
+ export declare const SERVER_ISLAND_MAP_MARKER = "$$server-islands-map$$";
5
5
  export declare function vitePluginServerIslands({ settings }: AstroPluginOptions): VitePlugin;
@@ -1,12 +1,12 @@
1
- import fs from "node:fs";
2
- import { getPrerenderOutputDirectory, getServerOutputDirectory } from "../../prerender/utils.js";
3
- import { AstroError, AstroErrorData } from "../errors/index.js";
4
- import { appendForwardSlash } from "../path.js";
5
1
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../constants.js";
2
+ import { AstroError, AstroErrorData } from "../errors/index.js";
6
3
  const SERVER_ISLAND_MANIFEST = "virtual:astro:server-island-manifest";
7
4
  const RESOLVED_SERVER_ISLAND_MANIFEST = "\0" + SERVER_ISLAND_MANIFEST;
8
5
  const serverIslandPlaceholderMap = "'$$server-islands-map$$'";
9
6
  const serverIslandPlaceholderNameMap = "'$$server-islands-name-map$$'";
7
+ const SERVER_ISLAND_MAP_MARKER = "$$server-islands-map$$";
8
+ const serverIslandMapReplaceExp = /['"]\$\$server-islands-map\$\$['"]/g;
9
+ const serverIslandNameMapReplaceExp = /['"]\$\$server-islands-name-map\$\$['"]/g;
10
10
  function createServerIslandImportMapSource(entries, toImportPath) {
11
11
  const mappings = Array.from(entries, ([islandName, fileName]) => {
12
12
  const importPath = toImportPath(fileName);
@@ -22,17 +22,35 @@ function createNameMapSource(entries) {
22
22
  function vitePluginServerIslands({ settings }) {
23
23
  let command = "serve";
24
24
  let ssrEnvironment = null;
25
- const referenceIdMap = /* @__PURE__ */ new Map();
26
25
  const serverIslandMap = /* @__PURE__ */ new Map();
27
26
  const serverIslandNameMap = /* @__PURE__ */ new Map();
28
- const resolvedIslandImports = /* @__PURE__ */ new Map();
29
- let ssrManifestChunk = null;
27
+ const serverIslandSourceMap = /* @__PURE__ */ new Map();
28
+ const referenceIdMap = /* @__PURE__ */ new Map();
29
+ function ensureServerIslandReferenceIds(ctx) {
30
+ for (const [resolvedPath, islandName] of serverIslandNameMap) {
31
+ if (referenceIdMap.has(resolvedPath)) continue;
32
+ const source = serverIslandSourceMap.get(resolvedPath);
33
+ const referenceId = ctx.emitFile({
34
+ type: "chunk",
35
+ id: source?.id ?? resolvedPath,
36
+ importer: source?.importer,
37
+ name: islandName
38
+ });
39
+ referenceIdMap.set(resolvedPath, referenceId);
40
+ }
41
+ }
30
42
  return {
31
43
  name: "astro:server-islands",
32
44
  enforce: "post",
33
45
  config(_config, { command: _command }) {
34
46
  command = _command;
35
47
  },
48
+ buildStart() {
49
+ if (command !== "build" || this.environment?.name !== ASTRO_VITE_ENVIRONMENT_NAMES.ssr) {
50
+ return;
51
+ }
52
+ ensureServerIslandReferenceIds(this);
53
+ },
36
54
  configureServer(server) {
37
55
  ssrEnvironment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
38
56
  },
@@ -59,16 +77,13 @@ export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
59
77
  transform: {
60
78
  filter: {
61
79
  id: {
62
- include: [
63
- // Allows server islands in astro and mdx files
64
- /\.(astro|mdx)$/,
65
- new RegExp(`^${RESOLVED_SERVER_ISLAND_MANIFEST}$`)
66
- ]
80
+ include: [/\.(astro|mdx)$/, new RegExp(`^${RESOLVED_SERVER_ISLAND_MANIFEST}$`)]
67
81
  }
68
82
  },
69
83
  async handler(_code, id) {
70
84
  const info = this.getModuleInfo(id);
71
85
  const astro = info ? info.meta.astro : void 0;
86
+ const isBuildSsr = command === "build" && this.environment?.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr;
72
87
  if (astro) {
73
88
  for (const comp of astro.serverComponents) {
74
89
  if (!serverIslandNameMap.has(comp.resolvedPath)) {
@@ -77,23 +92,23 @@ export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
77
92
  }
78
93
  let name = comp.localName;
79
94
  let idx = 1;
80
- while (true) {
81
- if (!serverIslandMap.has(name)) {
82
- break;
83
- }
95
+ while (serverIslandMap.has(name)) {
84
96
  name += idx++;
85
97
  }
86
98
  serverIslandNameMap.set(comp.resolvedPath, name);
87
99
  serverIslandMap.set(name, comp.resolvedPath);
88
- if (command === "build") {
89
- const referenceId = this.emitFile({
90
- type: "chunk",
91
- id: comp.specifier,
92
- importer: id,
93
- name: comp.localName
94
- });
95
- referenceIdMap.set(comp.resolvedPath, referenceId);
96
- }
100
+ serverIslandSourceMap.set(comp.resolvedPath, { id: comp.specifier, importer: id });
101
+ }
102
+ if (isBuildSsr && !referenceIdMap.has(comp.resolvedPath)) {
103
+ const islandName = serverIslandNameMap.get(comp.resolvedPath);
104
+ const source = serverIslandSourceMap.get(comp.resolvedPath);
105
+ const referenceId = this.emitFile({
106
+ type: "chunk",
107
+ id: source?.id ?? comp.resolvedPath,
108
+ importer: source?.importer,
109
+ name: islandName
110
+ });
111
+ referenceIdMap.set(comp.resolvedPath, referenceId);
97
112
  }
98
113
  }
99
114
  }
@@ -110,7 +125,7 @@ export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
110
125
  throw new AstroError(AstroErrorData.NoAdapterInstalledServerIslands);
111
126
  }
112
127
  }
113
- if (serverIslandNameMap.size > 0 && serverIslandMap.size > 0) {
128
+ if (command !== "build" && serverIslandNameMap.size > 0 && serverIslandMap.size > 0) {
114
129
  const mapSource = createServerIslandImportMapSource(
115
130
  serverIslandMap,
116
131
  (fileName) => fileName
@@ -118,22 +133,21 @@ export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
118
133
  const nameMapSource = createNameMapSource(serverIslandNameMap);
119
134
  return {
120
135
  code: `
121
- export const serverIslandMap = ${mapSource};
122
-
136
+ export const serverIslandMap = ${mapSource};
123
137
 
124
- export const serverIslandNameMap = ${nameMapSource};
125
- `
138
+ export const serverIslandNameMap = ${nameMapSource};
139
+ `
126
140
  };
127
141
  }
128
142
  }
129
143
  }
130
144
  },
131
145
  renderChunk(code, chunk) {
132
- if (code.includes(serverIslandPlaceholderMap)) {
133
- if (command === "build") {
134
- if (referenceIdMap.size === 0) {
135
- return;
136
- }
146
+ if (!code.includes(SERVER_ISLAND_MAP_MARKER)) return;
147
+ if (command === "build") {
148
+ const envName = this.environment?.name;
149
+ let mapSource;
150
+ if (envName === ASTRO_VITE_ENVIRONMENT_NAMES.ssr) {
137
151
  const isRelativeChunk = !chunk.isEntry;
138
152
  const dots = isRelativeChunk ? ".." : ".";
139
153
  const mapEntries = [];
@@ -141,105 +155,30 @@ export const serverIslandNameMap = ${nameMapSource};
141
155
  const fileName = this.getFileName(referenceId);
142
156
  const islandName = serverIslandNameMap.get(resolvedPath);
143
157
  if (!islandName) continue;
144
- if (!resolvedIslandImports.has(islandName)) {
145
- resolvedIslandImports.set(islandName, fileName);
146
- }
147
158
  mapEntries.push([islandName, fileName]);
148
159
  }
149
- const mapSource = createServerIslandImportMapSource(
160
+ mapSource = createServerIslandImportMapSource(
150
161
  mapEntries,
151
162
  (fileName) => `${dots}/${fileName}`
152
163
  );
153
- const nameMapSource = createNameMapSource(serverIslandNameMap);
154
- return {
155
- code: code.replace(serverIslandPlaceholderMap, mapSource).replace(serverIslandPlaceholderNameMap, nameMapSource),
156
- map: null
157
- };
164
+ } else {
165
+ mapSource = createServerIslandImportMapSource(serverIslandMap, (fileName) => fileName);
158
166
  }
167
+ const nameMapSource = createNameMapSource(serverIslandNameMap);
159
168
  return {
160
- code: code.replace(serverIslandPlaceholderMap, "new Map();").replace(serverIslandPlaceholderNameMap, "new Map()"),
169
+ code: code.replace(serverIslandMapReplaceExp, mapSource).replace(serverIslandNameMapReplaceExp, nameMapSource),
161
170
  map: null
162
171
  };
163
172
  }
164
- },
165
- generateBundle(_options, bundle) {
166
- const envName = this.environment?.name;
167
- if (envName === ASTRO_VITE_ENVIRONMENT_NAMES.ssr) {
168
- for (const chunk of Object.values(bundle)) {
169
- if (chunk.type === "chunk" && chunk.code.includes(serverIslandPlaceholderMap)) {
170
- ssrManifestChunk = chunk;
171
- break;
172
- }
173
- }
174
- }
175
- if (envName === ASTRO_VITE_ENVIRONMENT_NAMES.prerender && ssrManifestChunk) {
176
- if (resolvedIslandImports.size > 0) {
177
- const isRelativeChunk = ssrManifestChunk.fileName.includes("/");
178
- const dots = isRelativeChunk ? ".." : ".";
179
- const mapSource = createServerIslandImportMapSource(
180
- resolvedIslandImports,
181
- (fileName) => `${dots}/${fileName}`
182
- );
183
- const nameMapSource = createNameMapSource(serverIslandNameMap);
184
- ssrManifestChunk.code = ssrManifestChunk.code.replace(serverIslandPlaceholderMap, mapSource).replace(serverIslandPlaceholderNameMap, nameMapSource);
185
- } else {
186
- ssrManifestChunk.code = ssrManifestChunk.code.replace(serverIslandPlaceholderMap, "new Map()").replace(serverIslandPlaceholderNameMap, "new Map()");
187
- }
188
- }
189
- },
190
- api: {
191
- /**
192
- * Post-build hook that patches SSR chunks containing server island placeholders.
193
- *
194
- * During build, SSR can run before all server islands are discovered (e.g. islands
195
- * only used in prerendered pages). This hook runs after SSR + prerender builds and:
196
- * 1) replaces placeholders with the complete map of discovered islands
197
- * 2) copies island chunks emitted in prerender into the SSR output directory
198
- *
199
- * Two cases:
200
- * 1. Islands were discovered: Replace placeholders with real import maps.
201
- * 2. No islands found: Replace placeholders with empty maps.
202
- */
203
- async buildPostHook({
204
- chunks,
205
- mutate
206
- }) {
207
- const ssrChunkWithPlaceholder = chunks.find(
208
- (c) => !c.prerender && c.code.includes(serverIslandPlaceholderMap)
209
- );
210
- if (!ssrChunkWithPlaceholder) {
211
- return;
212
- }
213
- if (resolvedIslandImports.size > 0) {
214
- const isRelativeChunk = ssrChunkWithPlaceholder.fileName.includes("/");
215
- const dots = isRelativeChunk ? ".." : ".";
216
- const mapSource = createServerIslandImportMapSource(
217
- resolvedIslandImports,
218
- (fileName) => `${dots}/${fileName}`
219
- );
220
- const nameMapSource = createNameMapSource(serverIslandNameMap);
221
- const newCode = ssrChunkWithPlaceholder.code.replace(serverIslandPlaceholderMap, mapSource).replace(serverIslandPlaceholderNameMap, nameMapSource);
222
- mutate(ssrChunkWithPlaceholder.fileName, newCode, false);
223
- const serverOutputDir = getServerOutputDirectory(settings);
224
- const prerenderOutputDir = getPrerenderOutputDirectory(settings);
225
- for (const [, fileName] of resolvedIslandImports) {
226
- const srcPath = new URL(fileName, appendForwardSlash(prerenderOutputDir.toString()));
227
- const destPath = new URL(fileName, appendForwardSlash(serverOutputDir.toString()));
228
- if (!fs.existsSync(srcPath)) continue;
229
- const destDir = new URL("./", destPath);
230
- await fs.promises.mkdir(destDir, { recursive: true });
231
- await fs.promises.copyFile(srcPath, destPath);
232
- }
233
- } else {
234
- const newCode = ssrChunkWithPlaceholder.code.replace(serverIslandPlaceholderMap, "new Map()").replace(serverIslandPlaceholderNameMap, "new Map()");
235
- mutate(ssrChunkWithPlaceholder.fileName, newCode, false);
236
- }
237
- }
173
+ return {
174
+ code: code.replace(serverIslandMapReplaceExp, "new Map();").replace(serverIslandNameMapReplaceExp, "new Map()"),
175
+ map: null
176
+ };
238
177
  }
239
178
  };
240
179
  }
241
180
  export {
242
181
  SERVER_ISLAND_MANIFEST,
243
- serverIslandPlaceholderMap,
182
+ SERVER_ISLAND_MAP_MARKER,
244
183
  vitePluginServerIslands
245
184
  };
@@ -19,7 +19,7 @@ import { getViteErrorPayload } from "../core/errors/dev/index.js";
19
19
  import { AstroError, AstroErrorData } from "../core/errors/index.js";
20
20
  import { NOOP_MIDDLEWARE_FN } from "../core/middleware/noop-middleware.js";
21
21
  import { createViteLoader } from "../core/module-loader/index.js";
22
- import { matchAllRoutes } from "../core/routing/match.js";
22
+ import { isRouteServerIsland, matchAllRoutes } from "../core/routing/match.js";
23
23
  import { resolveMiddlewareMode } from "../integrations/adapter-utils.js";
24
24
  import { SERIALIZED_MANIFEST_ID } from "../manifest/serialized.js";
25
25
  import { ASTRO_DEV_SERVER_APP_ID } from "../vite-plugin-app/index.js";
@@ -130,7 +130,7 @@ function createVitePluginAstroServer({
130
130
  const { routes } = await prerenderHandler.environment.runner.import("virtual:astro:routes");
131
131
  const routesList = { routes: routes.map((r) => r.routeData) };
132
132
  const matches = matchAllRoutes(pathname, routesList);
133
- if (!matches.some((route) => route.prerender)) {
133
+ if (!matches.some((route) => route.prerender || isRouteServerIsland(route))) {
134
134
  return next();
135
135
  }
136
136
  localStorage.run(request, () => {
@@ -1,11 +1,7 @@
1
1
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js";
2
+ import { hasNonPrerenderedProjectRoute } from "../core/routing/helpers.js";
2
3
  const ASTRO_RENDERERS_MODULE_ID = "virtual:astro:renderers";
3
4
  const RESOLVED_ASTRO_RENDERERS_MODULE_ID = `\0${ASTRO_RENDERERS_MODULE_ID}`;
4
- function ssrBuildNeedsRenderers(routesList) {
5
- return routesList.routes.some(
6
- (route) => route.type === "page" && !route.prerender && route.origin !== "internal"
7
- );
8
- }
9
5
  function vitePluginRenderers(options) {
10
6
  const renderers = options.settings.renderers;
11
7
  return {
@@ -24,7 +20,9 @@ function vitePluginRenderers(options) {
24
20
  id: new RegExp(`^${RESOLVED_ASTRO_RENDERERS_MODULE_ID}$`)
25
21
  },
26
22
  handler() {
27
- if (options.command === "build" && this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr && renderers.length > 0 && !ssrBuildNeedsRenderers(options.routesList)) {
23
+ if (options.command === "build" && this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr && renderers.length > 0 && !hasNonPrerenderedProjectRoute(options.routesList.routes, {
24
+ includeEndpoints: false
25
+ })) {
28
26
  return { code: `export const renderers = [];` };
29
27
  }
30
28
  if (renderers.length > 0) {
@@ -38,7 +38,7 @@ function astroScriptsPlugin({ settings }) {
38
38
  },
39
39
  buildStart() {
40
40
  const hasHydrationScripts = settings.scripts.some((s) => s.stage === "before-hydration");
41
- if (hasHydrationScripts && (this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender || this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr)) {
41
+ if (hasHydrationScripts && (this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client || this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender || this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr)) {
42
42
  this.emitFile({
43
43
  type: "chunk",
44
44
  id: BEFORE_HYDRATION_SCRIPT_ID,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "6.0.4",
3
+ "version": "6.0.5",
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/internal-helpers": "0.8.0",
157
156
  "@astrojs/markdown-remark": "7.0.0",
157
+ "@astrojs/internal-helpers": "0.8.0",
158
158
  "@astrojs/telemetry": "3.3.0"
159
159
  },
160
160
  "optionalDependencies": {
@@ -190,7 +190,7 @@
190
190
  "undici": "^7.22.0",
191
191
  "unified": "^11.0.5",
192
192
  "vitest": "^3.2.4",
193
- "@astrojs/check": "0.9.7",
193
+ "@astrojs/check": "0.9.8",
194
194
  "astro-scripts": "0.0.14"
195
195
  },
196
196
  "engines": {