astro 6.0.3 → 6.0.4

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,6 +1,6 @@
1
1
  class BuildTimeAstroVersionProvider {
2
2
  // Injected during the build through esbuild define
3
- version = "6.0.3";
3
+ version = "6.0.4";
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.3") {
192
+ if (previousAstroVersion && previousAstroVersion !== "6.0.4") {
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.3") {
201
- this.#store.metaStore().set("astro-version", "6.0.3");
200
+ if ("6.0.4") {
201
+ this.#store.metaStore().set("astro-version", "6.0.4");
202
202
  }
203
203
  if (currentConfigDigest) {
204
204
  this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -1,4 +1,6 @@
1
- import type { AstroInlineConfig } from '../../types/public/config.js';
1
+ import type { AstroSettings, RoutesList } from '../../types/astro.js';
2
+ import type { AstroInlineConfig, RuntimeMode } from '../../types/public/config.js';
3
+ import type { Logger } from '../logger/core.js';
2
4
  interface BuildOptions {
3
5
  /**
4
6
  * Output a development-based build similar to code transformed in `astro dev`. This
@@ -25,4 +27,40 @@ interface BuildOptions {
25
27
  * @experimental The JavaScript API is experimental
26
28
  */
27
29
  export default function build(inlineConfig: AstroInlineConfig, options?: BuildOptions): Promise<void>;
30
+ interface AstroBuilderOptions extends BuildOptions {
31
+ logger: Logger;
32
+ mode: string;
33
+ runtimeMode: RuntimeMode;
34
+ /**
35
+ * Provide a pre-built routes list to skip filesystem route scanning.
36
+ * Useful for testing builds with in-memory virtual modules.
37
+ */
38
+ routesList?: RoutesList;
39
+ /**
40
+ * Whether to run `syncInternal` during setup. Defaults to true.
41
+ * Set to false for in-memory builds that don't need type generation.
42
+ */
43
+ sync?: boolean;
44
+ }
45
+ export declare class AstroBuilder {
46
+ private settings;
47
+ private logger;
48
+ private mode;
49
+ private runtimeMode;
50
+ private origin;
51
+ private routesList;
52
+ private timer;
53
+ private teardownCompiler;
54
+ private sync;
55
+ constructor(settings: AstroSettings, options: AstroBuilderOptions);
56
+ /** Setup Vite and run any async setup logic that couldn't run inside of the constructor. */
57
+ private setup;
58
+ /** Run the build logic. build() is marked private because usage should go through ".run()" */
59
+ private build;
60
+ /** Build the given Astro project. */
61
+ run(): Promise<void>;
62
+ private validateConfig;
63
+ /** Stats */
64
+ private printStats;
65
+ }
28
66
  export {};
@@ -56,14 +56,16 @@ class AstroBuilder {
56
56
  routesList;
57
57
  timer;
58
58
  teardownCompiler;
59
+ sync;
59
60
  constructor(settings, options) {
60
61
  this.mode = options.mode;
61
62
  this.runtimeMode = options.runtimeMode;
62
63
  this.settings = settings;
63
64
  this.logger = options.logger;
64
65
  this.teardownCompiler = options.teardownCompiler ?? true;
66
+ this.sync = options.sync ?? true;
65
67
  this.origin = settings.config.site ? new URL(settings.config.site).origin : `http://localhost:${settings.config.server.port}`;
66
- this.routesList = { routes: [] };
68
+ this.routesList = options.routesList ?? { routes: [] };
67
69
  this.timer = {};
68
70
  }
69
71
  /** Setup Vite and run any async setup logic that couldn't run inside of the constructor. */
@@ -77,7 +79,9 @@ class AstroBuilder {
77
79
  logger
78
80
  });
79
81
  this.settings.buildOutput = getPrerenderDefault(this.settings.config) ? "static" : "server";
80
- this.routesList = await createRoutesList({ settings: this.settings }, this.logger);
82
+ if (this.routesList.routes.length === 0) {
83
+ this.routesList = await createRoutesList({ settings: this.settings }, this.logger);
84
+ }
81
85
  await runHookConfigDone({ settings: this.settings, logger, command: "build" });
82
86
  if (!this.settings.config.adapter && this.settings.buildOutput === "server") {
83
87
  throw new AstroError(AstroErrorData.NoAdapterInstalled);
@@ -98,14 +102,16 @@ class AstroBuilder {
98
102
  sync: false
99
103
  }
100
104
  );
101
- const { syncInternal } = await import("../sync/index.js");
102
- await syncInternal({
103
- mode: this.mode,
104
- settings: this.settings,
105
- logger,
106
- fs,
107
- command: "build"
108
- });
105
+ if (this.sync) {
106
+ const { syncInternal } = await import("../sync/index.js");
107
+ await syncInternal({
108
+ mode: this.mode,
109
+ settings: this.settings,
110
+ logger,
111
+ fs,
112
+ command: "build"
113
+ });
114
+ }
109
115
  return { viteConfig };
110
116
  }
111
117
  /** Run the build logic. build() is marked private because usage should go through ".run()" */
@@ -215,5 +221,6 @@ class AstroBuilder {
215
221
  }
216
222
  }
217
223
  export {
224
+ AstroBuilder,
218
225
  build as default
219
226
  };
@@ -10,7 +10,11 @@ import { emptyDir, removeEmptyDirs } from "../../core/fs/index.js";
10
10
  import { appendForwardSlash, prependForwardSlash } from "../../core/path.js";
11
11
  import { runHookBuildSetup } from "../../integrations/hooks.js";
12
12
  import { SERIALIZED_MANIFEST_RESOLVED_ID } from "../../manifest/serialized.js";
13
- import { getClientOutputDirectory, getServerOutputDirectory } from "../../prerender/utils.js";
13
+ import {
14
+ getClientOutputDirectory,
15
+ getPrerenderOutputDirectory,
16
+ getServerOutputDirectory
17
+ } from "../../prerender/utils.js";
14
18
  import { VIRTUAL_PAGE_RESOLVED_MODULE_ID } from "../../vite-plugin-pages/const.js";
15
19
  import { PAGE_SCRIPT_ID } from "../../vite-plugin-scripts/index.js";
16
20
  import { routeIsRedirect } from "../routing/helpers.js";
@@ -30,6 +34,7 @@ import { encodeName, getTimeStat, viteBuildReturnToRollupOutputs } from "./util.
30
34
  import { NOOP_MODULE_ID } from "./plugins/plugin-noop.js";
31
35
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../constants.js";
32
36
  import { getSSRAssets } from "./internal.js";
37
+ import { serverIslandPlaceholderMap } from "../server-islands/vite-plugin-server-islands.js";
33
38
  const PRERENDER_ENTRY_FILENAME_PREFIX = "prerender-entry";
34
39
  function extractRelevantChunks(outputs, prerender) {
35
40
  const extracted = [];
@@ -38,7 +43,8 @@ function extractRelevantChunks(outputs, prerender) {
38
43
  if (chunk.type === "asset") continue;
39
44
  const needsContentInjection = chunk.code.includes(LINKS_PLACEHOLDER);
40
45
  const needsManifestInjection = chunk.moduleIds.includes(SERIALIZED_MANIFEST_RESOLVED_ID);
41
- if (needsContentInjection || needsManifestInjection) {
46
+ const needsServerIslandInjection = chunk.code.includes(serverIslandPlaceholderMap);
47
+ if (needsContentInjection || needsManifestInjection || needsServerIslandInjection) {
42
48
  extracted.push({
43
49
  fileName: chunk.fileName,
44
50
  code: chunk.code,
@@ -82,6 +88,7 @@ async function buildEnvironments(opts, internals) {
82
88
  const flatPlugins = buildPlugins.flat().filter(Boolean);
83
89
  const plugins = [...flatPlugins, ...viteConfig.plugins || []];
84
90
  let currentRollupInput = void 0;
91
+ let buildPostHooks = [];
85
92
  plugins.push({
86
93
  name: "astro:resolve-input",
87
94
  // When the rollup input is safe to update, we normalize it to always be an object
@@ -107,8 +114,13 @@ async function buildEnvironments(opts, internals) {
107
114
  buildApp: {
108
115
  order: "post",
109
116
  async handler() {
110
- await runManifestInjection(opts, internals, internals.extractedChunks ?? []);
111
- const prerenderOutputDir = new URL("./.prerender/", getServerOutputDirectory(settings));
117
+ await runManifestInjection(
118
+ opts,
119
+ internals,
120
+ internals.extractedChunks ?? [],
121
+ buildPostHooks
122
+ );
123
+ const prerenderOutputDir = getPrerenderOutputDirectory(settings);
112
124
  if (settings.buildOutput === "static") {
113
125
  settings.timer.start("Static generate");
114
126
  await ssrMoveAssets(opts, internals, prerenderOutputDir);
@@ -224,6 +236,10 @@ async function buildEnvironments(opts, internals) {
224
236
  const prerenderOutputs = viteBuildReturnToRollupOutputs(prerenderOutput);
225
237
  const prerenderChunks = extractRelevantChunks(prerenderOutputs, true);
226
238
  prerenderOutput = void 0;
239
+ const ssrPlugins = builder2.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr]?.config.plugins ?? [];
240
+ buildPostHooks = ssrPlugins.map(
241
+ (plugin) => typeof plugin.api?.buildPostHook === "function" ? plugin.api.buildPostHook : void 0
242
+ ).filter(Boolean);
227
243
  internals.clientInput = getClientInput(internals, settings);
228
244
  if (!internals.clientInput.size) {
229
245
  internals.clientInput.add(NOOP_MODULE_ID);
@@ -244,7 +260,7 @@ async function buildEnvironments(opts, internals) {
244
260
  [ASTRO_VITE_ENVIRONMENT_NAMES.prerender]: {
245
261
  build: {
246
262
  emitAssets: true,
247
- outDir: fileURLToPath(new URL("./.prerender/", getServerOutputDirectory(settings))),
263
+ outDir: fileURLToPath(getPrerenderOutputDirectory(settings)),
248
264
  rollupOptions: {
249
265
  // Only skip the default prerender entrypoint if an adapter with `entrypointResolution: 'self'` is used
250
266
  // AND provides a custom prerenderer. Otherwise, use the default.
@@ -318,7 +334,7 @@ function getPrerenderEntryFileName(prerenderOutput) {
318
334
  function extractPrerenderEntryFileName(internals, prerenderOutput) {
319
335
  internals.prerenderEntryFileName = getPrerenderEntryFileName(prerenderOutput);
320
336
  }
321
- async function runManifestInjection(opts, internals, chunks) {
337
+ async function runManifestInjection(opts, internals, chunks, buildPostHooks) {
322
338
  const mutations = /* @__PURE__ */ new Map();
323
339
  const mutate = (fileName, newCode, prerender) => {
324
340
  mutations.set(fileName, { code: newCode, prerender });
@@ -330,16 +346,18 @@ async function runManifestInjection(opts, internals, chunks) {
330
346
  internals,
331
347
  { chunks, mutate }
332
348
  );
349
+ for (const buildPostHook of buildPostHooks) {
350
+ await buildPostHook({ chunks, mutate });
351
+ }
333
352
  await writeMutatedChunks(opts, mutations);
334
353
  }
335
354
  async function writeMutatedChunks(opts, mutations) {
336
355
  const { settings } = opts;
337
356
  const config = settings.config;
338
- const serverOutputDir = getServerOutputDirectory(settings);
339
357
  for (const [fileName, mutation] of mutations) {
340
358
  let root;
341
359
  if (mutation.prerender) {
342
- root = new URL("./.prerender/", serverOutputDir);
360
+ root = getPrerenderOutputDirectory(settings);
343
361
  } else if (settings.buildOutput === "server") {
344
362
  root = config.build.server;
345
363
  } else {
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "6.0.3";
1
+ const ASTRO_VERSION = "6.0.4";
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.3";
29
+ const currentVersion = "6.0.4";
30
30
  const isPrerelease = currentVersion.includes("-");
31
31
  if (!isPrerelease) {
32
32
  try {
@@ -90,7 +90,7 @@ function collectErrorMetadata(e, rootFolder) {
90
90
  function generateHint(err) {
91
91
  const commonBrowserAPIs = ["document", "window"];
92
92
  if (/Unknown file extension "\.(?:jsx|vue|svelte|astro|css)" for /.test(err.message)) {
93
- return "You likely need to add this package to `vite.ssr.noExternal` in your astro config file.";
93
+ return "You likely need to add this package to `vite.resolve.noExternal` in your astro config file.";
94
94
  } else if (commonBrowserAPIs.some((api) => err.toString().includes(api))) {
95
95
  const hint = `Browser APIs are not available on the server.
96
96
 
@@ -269,7 +269,7 @@ function printHelp({
269
269
  message.push(
270
270
  linebreak(),
271
271
  ` ${bgGreen(black(` ${commandName} `))} ${green(
272
- `v${"6.0.3"}`
272
+ `v${"6.0.4"}`
273
273
  )} ${headline}`
274
274
  );
275
275
  }
@@ -1,4 +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
5
  export declare function vitePluginServerIslands({ settings }: AstroPluginOptions): VitePlugin;
@@ -1,16 +1,32 @@
1
- import MagicString from "magic-string";
1
+ import fs from "node:fs";
2
+ import { getPrerenderOutputDirectory, getServerOutputDirectory } from "../../prerender/utils.js";
2
3
  import { AstroError, AstroErrorData } from "../errors/index.js";
4
+ import { appendForwardSlash } from "../path.js";
3
5
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../constants.js";
4
6
  const SERVER_ISLAND_MANIFEST = "virtual:astro:server-island-manifest";
5
7
  const RESOLVED_SERVER_ISLAND_MANIFEST = "\0" + SERVER_ISLAND_MANIFEST;
6
8
  const serverIslandPlaceholderMap = "'$$server-islands-map$$'";
7
9
  const serverIslandPlaceholderNameMap = "'$$server-islands-name-map$$'";
10
+ function createServerIslandImportMapSource(entries, toImportPath) {
11
+ const mappings = Array.from(entries, ([islandName, fileName]) => {
12
+ const importPath = toImportPath(fileName);
13
+ return ` [${JSON.stringify(islandName)}, () => import(${JSON.stringify(importPath)})],`;
14
+ });
15
+ return `new Map([
16
+ ${mappings.join("\n")}
17
+ ])`;
18
+ }
19
+ function createNameMapSource(entries) {
20
+ return `new Map(${JSON.stringify(Array.from(entries), null, 2)})`;
21
+ }
8
22
  function vitePluginServerIslands({ settings }) {
9
23
  let command = "serve";
10
24
  let ssrEnvironment = null;
11
25
  const referenceIdMap = /* @__PURE__ */ new Map();
12
26
  const serverIslandMap = /* @__PURE__ */ new Map();
13
27
  const serverIslandNameMap = /* @__PURE__ */ new Map();
28
+ const resolvedIslandImports = /* @__PURE__ */ new Map();
29
+ let ssrManifestChunk = null;
14
30
  return {
15
31
  name: "astro:server-islands",
16
32
  enforce: "post",
@@ -70,7 +86,7 @@ export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
70
86
  serverIslandNameMap.set(comp.resolvedPath, name);
71
87
  serverIslandMap.set(name, comp.resolvedPath);
72
88
  if (command === "build") {
73
- let referenceId = this.emitFile({
89
+ const referenceId = this.emitFile({
74
90
  type: "chunk",
75
91
  id: comp.specifier,
76
92
  importer: id,
@@ -95,18 +111,17 @@ export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
95
111
  }
96
112
  }
97
113
  if (serverIslandNameMap.size > 0 && serverIslandMap.size > 0) {
98
- let mapSource = "new Map([\n ";
99
- for (let [name, path] of serverIslandMap) {
100
- mapSource += `
101
- ['${name}', () => import('${path}')],`;
102
- }
103
- mapSource += "]);";
114
+ const mapSource = createServerIslandImportMapSource(
115
+ serverIslandMap,
116
+ (fileName) => fileName
117
+ );
118
+ const nameMapSource = createNameMapSource(serverIslandNameMap);
104
119
  return {
105
120
  code: `
106
121
  export const serverIslandMap = ${mapSource};
107
122
 
108
123
 
109
- export const serverIslandNameMap = new Map(${JSON.stringify(Array.from(serverIslandNameMap.entries()), null, 2)});
124
+ export const serverIslandNameMap = ${nameMapSource};
110
125
  `
111
126
  };
112
127
  }
@@ -115,38 +130,116 @@ export const serverIslandNameMap = new Map(${JSON.stringify(Array.from(serverIsl
115
130
  },
116
131
  renderChunk(code, chunk) {
117
132
  if (code.includes(serverIslandPlaceholderMap)) {
118
- if (referenceIdMap.size === 0) {
133
+ if (command === "build") {
134
+ if (referenceIdMap.size === 0) {
135
+ return;
136
+ }
137
+ const isRelativeChunk = !chunk.isEntry;
138
+ const dots = isRelativeChunk ? ".." : ".";
139
+ const mapEntries = [];
140
+ for (const [resolvedPath, referenceId] of referenceIdMap) {
141
+ const fileName = this.getFileName(referenceId);
142
+ const islandName = serverIslandNameMap.get(resolvedPath);
143
+ if (!islandName) continue;
144
+ if (!resolvedIslandImports.has(islandName)) {
145
+ resolvedIslandImports.set(islandName, fileName);
146
+ }
147
+ mapEntries.push([islandName, fileName]);
148
+ }
149
+ const mapSource = createServerIslandImportMapSource(
150
+ mapEntries,
151
+ (fileName) => `${dots}/${fileName}`
152
+ );
153
+ const nameMapSource = createNameMapSource(serverIslandNameMap);
119
154
  return {
120
- code: code.replace(serverIslandPlaceholderMap, "new Map();").replace(serverIslandPlaceholderNameMap, "new Map()"),
155
+ code: code.replace(serverIslandPlaceholderMap, mapSource).replace(serverIslandPlaceholderNameMap, nameMapSource),
121
156
  map: null
122
157
  };
123
158
  }
124
- const isRelativeChunk = !chunk.isEntry;
125
- const dots = isRelativeChunk ? ".." : ".";
126
- let mapSource = "new Map([";
127
- let nameMapSource = "new Map(";
128
- for (let [resolvedPath, referenceId] of referenceIdMap) {
129
- const fileName = this.getFileName(referenceId);
130
- const islandName = serverIslandNameMap.get(resolvedPath);
131
- mapSource += `
132
- ['${islandName}', () => import('${dots}/${fileName}')],`;
133
- }
134
- nameMapSource += `${JSON.stringify(Array.from(serverIslandNameMap.entries()), null, 2)}`;
135
- mapSource += "\n])";
136
- nameMapSource += "\n)";
137
- referenceIdMap.clear();
138
- const ms = new MagicString(code);
139
- ms.replace(serverIslandPlaceholderMap, mapSource);
140
- ms.replace(serverIslandPlaceholderNameMap, nameMapSource);
141
159
  return {
142
- code: ms.toString(),
143
- map: ms.generateMap({ hires: "boundary" })
160
+ code: code.replace(serverIslandPlaceholderMap, "new Map();").replace(serverIslandPlaceholderNameMap, "new Map()"),
161
+ map: null
144
162
  };
145
163
  }
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
+ }
146
238
  }
147
239
  };
148
240
  }
149
241
  export {
150
242
  SERVER_ISLAND_MANIFEST,
243
+ serverIslandPlaceholderMap,
151
244
  vitePluginServerIslands
152
245
  };
@@ -74,9 +74,10 @@ class I18nRouter {
74
74
  matchPrefixAlways(pathname, _context) {
75
75
  const isRoot = pathname === this.#base + "/" || pathname === this.#base;
76
76
  if (isRoot) {
77
+ const basePrefix = this.#base === "/" ? "" : this.#base;
77
78
  return {
78
79
  type: "redirect",
79
- location: `${this.#base}/${this.#defaultLocale}`
80
+ location: `${basePrefix}/${this.#defaultLocale}`
80
81
  };
81
82
  }
82
83
  if (!pathHasLocale(pathname, this.#locales)) {
@@ -5,6 +5,10 @@ export declare function getPrerenderDefault(config: AstroConfig): boolean;
5
5
  * Returns the correct output directory of the SSR build based on the configuration
6
6
  */
7
7
  export declare function getServerOutputDirectory(settings: AstroSettings): URL;
8
+ /**
9
+ * Returns the output directory used by the prerender environment.
10
+ */
11
+ export declare function getPrerenderOutputDirectory(settings: AstroSettings): URL;
8
12
  /**
9
13
  * Returns the correct output directory of the client build based on the configuration
10
14
  */
@@ -5,6 +5,9 @@ function getPrerenderDefault(config) {
5
5
  function getServerOutputDirectory(settings) {
6
6
  return settings.buildOutput === "server" ? settings.config.build.server : getOutDirWithinCwd(settings.config.outDir);
7
7
  }
8
+ function getPrerenderOutputDirectory(settings) {
9
+ return new URL("./.prerender/", getServerOutputDirectory(settings));
10
+ }
8
11
  function getClientOutputDirectory(settings) {
9
12
  const preserveStructure = settings.adapter?.adapterFeatures?.preserveBuildClientDir;
10
13
  if (settings.buildOutput === "server" || preserveStructure) {
@@ -15,5 +18,6 @@ function getClientOutputDirectory(settings) {
15
18
  export {
16
19
  getClientOutputDirectory,
17
20
  getPrerenderDefault,
21
+ getPrerenderOutputDirectory,
18
22
  getServerOutputDirectory
19
23
  };
@@ -10,7 +10,11 @@ function astroDevToolbar({ settings, logger }) {
10
10
  return {
11
11
  optimizeDeps: {
12
12
  // Optimize CJS dependencies used by the dev toolbar
13
- include: ["astro > aria-query", "astro > axobject-query"]
13
+ include: [
14
+ "astro > aria-query",
15
+ "astro > axobject-query",
16
+ ...settings.devToolbarApps.length > 0 ? ["astro/toolbar"] : []
17
+ ]
14
18
  }
15
19
  };
16
20
  },
@@ -42,9 +42,13 @@ function routeGuardMiddleware(settings) {
42
42
  return next();
43
43
  }
44
44
  const rootFilePath = new URL("." + pathname, config.root);
45
- if (fs.existsSync(rootFilePath)) {
46
- const html = notFoundTemplate(pathname);
47
- return writeHtmlResponse(res, 404, html);
45
+ try {
46
+ const stat = fs.statSync(rootFilePath);
47
+ if (stat.isFile()) {
48
+ const html = notFoundTemplate(pathname);
49
+ return writeHtmlResponse(res, 404, html);
50
+ }
51
+ } catch {
48
52
  }
49
53
  return next();
50
54
  };
@@ -62,7 +62,7 @@ function vitePluginEnvironment({
62
62
  // For the dev toolbar
63
63
  "astro > html-escaper"
64
64
  ],
65
- exclude: ["astro:*", "virtual:astro:*"],
65
+ exclude: ["astro:*", "virtual:astro:*", "astro/virtual-modules/prefetch.js"],
66
66
  // Astro files can't be rendered on the client
67
67
  entries: [`${srcDirPattern}**/*.{jsx,tsx,vue,svelte,html}`]
68
68
  };
@@ -11,6 +11,7 @@ import { rootRelativePath } from "../core/viteUtils.js";
11
11
  import { createDefaultAstroMetadata } from "../vite-plugin-astro/metadata.js";
12
12
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js";
13
13
  import { isAstroServerEnvironment } from "../environments.js";
14
+ import { RESOLVED_MODULE_DEV_CSS_ALL } from "../vite-plugin-css/const.js";
14
15
  import { PAGE_SCRIPT_ID } from "../vite-plugin-scripts/index.js";
15
16
  const ASTRO_ROUTES_MODULE_ID = "virtual:astro:routes";
16
17
  const ASTRO_ROUTES_MODULE_ID_RESOLVED = "\0" + ASTRO_ROUTES_MODULE_ID;
@@ -91,6 +92,10 @@ async function astroPluginRoutes({
91
92
  const virtualMod = environment.moduleGraph.getModuleById(ASTRO_ROUTES_MODULE_ID_RESOLVED);
92
93
  if (!virtualMod) continue;
93
94
  environment.moduleGraph.invalidateModule(virtualMod);
95
+ const cssMod = environment.moduleGraph.getModuleById(RESOLVED_MODULE_DEV_CSS_ALL);
96
+ if (cssMod) {
97
+ environment.moduleGraph.invalidateModule(cssMod);
98
+ }
94
99
  environment.hot.send("astro:routes-updated", {});
95
100
  }
96
101
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "6.0.3",
3
+ "version": "6.0.4",
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,9 +153,9 @@
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",
156
157
  "@astrojs/markdown-remark": "7.0.0",
157
- "@astrojs/telemetry": "3.3.0",
158
- "@astrojs/internal-helpers": "0.8.0"
158
+ "@astrojs/telemetry": "3.3.0"
159
159
  },
160
160
  "optionalDependencies": {
161
161
  "sharp": "^0.34.0"
package/templates/env.mjs CHANGED
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
1
  // @ts-check
3
2
  import { schema } from 'virtual:astro:env/internal';
4
3
  import {