astro 4.6.2 → 4.6.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 (37) hide show
  1. package/astro-jsx.d.ts +0 -2
  2. package/components/Image.astro +1 -1
  3. package/components/Picture.astro +1 -1
  4. package/dist/@types/astro.d.ts +38 -37
  5. package/dist/assets/endpoint/generic.js +1 -1
  6. package/dist/assets/endpoint/node.js +1 -1
  7. package/dist/assets/vite-plugin-assets.js +3 -1
  8. package/dist/cli/add/index.js +3 -3
  9. package/dist/cli/install-package.js +3 -1
  10. package/dist/core/build/consts.d.ts +1 -0
  11. package/dist/core/build/consts.js +4 -0
  12. package/dist/core/build/index.js +2 -1
  13. package/dist/core/build/internal.d.ts +1 -0
  14. package/dist/core/build/internal.js +2 -1
  15. package/dist/core/build/plugin.d.ts +1 -1
  16. package/dist/core/build/plugins/plugin-content.js +135 -30
  17. package/dist/core/build/plugins/util.d.ts +1 -1
  18. package/dist/core/build/static-build.d.ts +1 -1
  19. package/dist/core/build/static-build.js +3 -2
  20. package/dist/core/config/config.d.ts +1 -0
  21. package/dist/core/config/config.js +10 -8
  22. package/dist/core/config/index.d.ts +1 -1
  23. package/dist/core/config/index.js +8 -1
  24. package/dist/core/config/schema.d.ts +1 -1
  25. package/dist/core/constants.js +1 -1
  26. package/dist/core/dev/dev.js +1 -1
  27. package/dist/core/messages.js +2 -2
  28. package/dist/core/render-context.d.ts +10 -1
  29. package/dist/core/render-context.js +38 -8
  30. package/dist/integrations/index.d.ts +2 -1
  31. package/dist/integrations/index.js +9 -2
  32. package/dist/runtime/client/dev-toolbar/apps/settings.js +3 -1
  33. package/dist/runtime/client/dev-toolbar/entrypoint.js +1 -1
  34. package/dist/runtime/client/dev-toolbar/toolbar.js +3 -1
  35. package/dist/transitions/router.js +4 -2
  36. package/dist/vite-plugin-astro/index.js +11 -4
  37. package/package.json +1 -1
package/astro-jsx.d.ts CHANGED
@@ -9,8 +9,6 @@
9
9
  * Adapted from React’s TypeScript definition from DefinitelyTyped.
10
10
  * @see https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts
11
11
  */
12
- // BUG! Prettier 3.0 removes `declare`: https://github.com/prettier/prettier/issues/15207
13
- // prettier-ignore
14
12
  declare namespace astroHTML.JSX {
15
13
  export type Child = Node | Node[] | string | number | boolean | null | undefined | unknown;
16
14
  export type Children = Child | Child[];
@@ -1,5 +1,5 @@
1
1
  ---
2
- import { getImage, type LocalImageProps, type RemoteImageProps } from 'astro:assets';
2
+ import { type LocalImageProps, type RemoteImageProps, getImage } from 'astro:assets';
3
3
  import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
4
4
  import type { HTMLAttributes } from '../types';
5
5
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- import { getImage, type LocalImageProps, type RemoteImageProps } from 'astro:assets';
2
+ import { type LocalImageProps, type RemoteImageProps, getImage } from 'astro:assets';
3
3
  import type { GetImageResult, ImageOutputFormat } from '../dist/@types/astro';
4
4
  import { isESMImportedImage, resolveSrc } from '../dist/assets/utils/imageKind';
5
5
  import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
@@ -1394,18 +1394,18 @@ export interface AstroUserConfig {
1394
1394
  * When `true`, all URLs will display a language prefix.
1395
1395
  * URLs will be of the form `example.com/[locale]/content/` for every route, including the default language.
1396
1396
  * Localized folders are used for every language, including the default.
1397
- *
1398
- * ```js
1399
- * export default defineConfig({
1400
- * i18n: {
1401
- * defaultLocale: "en",
1402
- * locales: ["en", "fr", "pt-br", "es"],
1403
- * routing: {
1404
- * prefixDefaultLocale: true,
1405
- * }
1406
- * }
1407
- * })
1408
- * ```
1397
+ *
1398
+ * ```js
1399
+ * export default defineConfig({
1400
+ * i18n: {
1401
+ * defaultLocale: "en",
1402
+ * locales: ["en", "fr", "pt-br", "es"],
1403
+ * routing: {
1404
+ * prefixDefaultLocale: true,
1405
+ * }
1406
+ * }
1407
+ * })
1408
+ * ```
1409
1409
  */
1410
1410
  prefixDefaultLocale?: boolean;
1411
1411
  /**
@@ -1446,32 +1446,32 @@ export interface AstroUserConfig {
1446
1446
  * - `"pathname": The strategy is applied to the pathname of the URLs
1447
1447
  */
1448
1448
  strategy?: 'pathname';
1449
- } |
1449
+ }
1450
1450
  /**
1451
- *
1452
- * @docs
1453
- * @name i18n.routing.manual
1454
- * @kind h4
1455
- * @type {string}
1456
- * @version 4.6.0
1457
- * @description
1458
- * When this option is enabled, Astro will **disable** its i18n middleware so that you can implement your own custom logic. No other `routing` options (e.g. `prefixDefaultLocale`) may be configured with `routing: "manual"`.
1459
- *
1460
- * You will be responsible for writing your own routing logic, or executing Astro's i18n middleware manually alongside your own.
1461
- *
1462
- * ```js
1463
- * export default defineConfig({
1464
- * i18n: {
1465
- * defaultLocale: "en",
1466
- * locales: ["en", "fr", "pt-br", "es"],
1467
- * routing: {
1468
- * prefixDefaultLocale: true,
1469
- * }
1470
- * }
1471
- * })
1472
- * ```
1473
- */
1474
- 'manual';
1451
+ *
1452
+ * @docs
1453
+ * @name i18n.routing.manual
1454
+ * @kind h4
1455
+ * @type {string}
1456
+ * @version 4.6.0
1457
+ * @description
1458
+ * When this option is enabled, Astro will **disable** its i18n middleware so that you can implement your own custom logic. No other `routing` options (e.g. `prefixDefaultLocale`) may be configured with `routing: "manual"`.
1459
+ *
1460
+ * You will be responsible for writing your own routing logic, or executing Astro's i18n middleware manually alongside your own.
1461
+ *
1462
+ * ```js
1463
+ * export default defineConfig({
1464
+ * i18n: {
1465
+ * defaultLocale: "en",
1466
+ * locales: ["en", "fr", "pt-br", "es"],
1467
+ * routing: {
1468
+ * prefixDefaultLocale: true,
1469
+ * }
1470
+ * }
1471
+ * })
1472
+ * ```
1473
+ */
1474
+ | 'manual';
1475
1475
  /**
1476
1476
  * @name i18n.domains
1477
1477
  * @type {Record<string, string> }
@@ -2524,6 +2524,7 @@ export interface AstroIntegration {
2524
2524
  dir: URL;
2525
2525
  routes: RouteData[];
2526
2526
  logger: AstroIntegrationLogger;
2527
+ cacheManifest: boolean;
2527
2528
  }) => void | Promise<void>;
2528
2529
  };
2529
2530
  }
@@ -1,9 +1,9 @@
1
+ import { imageConfig } from "astro:assets";
1
2
  import { isRemotePath } from "@astrojs/internal-helpers/path";
2
3
  import mime from "mime/lite.js";
3
4
  import { getConfiguredImageService } from "../internal.js";
4
5
  import { etag } from "../utils/etag.js";
5
6
  import { isRemoteAllowed } from "../utils/remotePattern.js";
6
- import { imageConfig } from "astro:assets";
7
7
  async function loadRemoteImage(src, headers) {
8
8
  try {
9
9
  const res = await fetch(src, {
@@ -1,13 +1,13 @@
1
1
  import os from "node:os";
2
2
  import { isAbsolute } from "node:path";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
+ import { assetsDir, imageConfig, outDir } from "astro:assets";
4
5
  import { isRemotePath, removeQueryString } from "@astrojs/internal-helpers/path";
5
6
  import { readFile } from "fs/promises";
6
7
  import mime from "mime/lite.js";
7
8
  import { getConfiguredImageService } from "../internal.js";
8
9
  import { etag } from "../utils/etag.js";
9
10
  import { isRemoteAllowed } from "../utils/remotePattern.js";
10
- import { assetsDir, imageConfig, outDir } from "astro:assets";
11
11
  function replaceFileSystemReferences(src) {
12
12
  return os.platform().includes("win32") ? src.replace(/^\/@fs\//, "") : src.replace(/^\/@fs/, "");
13
13
  }
@@ -113,7 +113,9 @@ function assets({
113
113
  isServerLikeOutput(settings.config) ? settings.config.build.client : settings.config.outDir
114
114
  )
115
115
  )});
116
- export const assetsDir = /* #__PURE__ */ new URL(${JSON.stringify(settings.config.build.assets)}, outDir);
116
+ export const assetsDir = /* #__PURE__ */ new URL(${JSON.stringify(
117
+ settings.config.build.assets
118
+ )}, outDir);
117
119
  export const getImage = async (options) => await getImageInternal(options, imageConfig);
118
120
  `;
119
121
  }
@@ -126,12 +126,12 @@ async function add(names, { flags }) {
126
126
  ["node", "astro add node"]
127
127
  ],
128
128
  Others: [
129
+ ["db", "astro add db"],
129
130
  ["tailwind", "astro add tailwind"],
130
- ["image", "astro add image"],
131
131
  ["mdx", "astro add mdx"],
132
+ ["markdoc", "astro add markdoc"],
132
133
  ["partytown", "astro add partytown"],
133
- ["sitemap", "astro add sitemap"],
134
- ["prefetch", "astro add prefetch"]
134
+ ["sitemap", "astro add sitemap"]
135
135
  ]
136
136
  },
137
137
  description: `For more integrations, check out: ${cyan("https://astro.build/integrations")}`
@@ -26,7 +26,9 @@ async function getPackage(packageName, logger, options, otherDeps = []) {
26
26
  } catch (e) {
27
27
  if (options.optional)
28
28
  return void 0;
29
- let message = `To continue, Astro requires the following dependency to be installed: ${bold(packageName)}.`;
29
+ let message = `To continue, Astro requires the following dependency to be installed: ${bold(
30
+ packageName
31
+ )}.`;
30
32
  if (ci.isCI) {
31
33
  message += ` Packages cannot be installed automatically in CI environments.`;
32
34
  }
@@ -0,0 +1 @@
1
+ export declare const CHUNKS_PATH = "chunks/";
@@ -0,0 +1,4 @@
1
+ const CHUNKS_PATH = "chunks/";
2
+ export {
3
+ CHUNKS_PATH
4
+ };
@@ -146,7 +146,8 @@ class AstroBuilder {
146
146
  config: this.settings.config,
147
147
  pages: pageNames,
148
148
  routes: Object.values(allPages).flat().map((pageData) => pageData.route),
149
- logging: this.logger
149
+ logging: this.logger,
150
+ cacheManifest: internals.cacheManifestUsed
150
151
  });
151
152
  if (this.logger.level && levels[this.logger.level()] <= levels["info"]) {
152
153
  await this.printStats({
@@ -66,6 +66,7 @@ export interface BuildInternals {
66
66
  */
67
67
  discoveredScripts: Set<string>;
68
68
  cachedClientEntries: string[];
69
+ cacheManifestUsed: boolean;
69
70
  propagatedStylesMap: Map<string, Set<StylesheetAsset>>;
70
71
  propagatedScriptsMap: Map<string, Set<string>>;
71
72
  staticFiles: Set<string>;
@@ -29,7 +29,8 @@ function createBuildInternals() {
29
29
  staticFiles: /* @__PURE__ */ new Set(),
30
30
  componentMetadata: /* @__PURE__ */ new Map(),
31
31
  ssrSplitEntryChunks: /* @__PURE__ */ new Map(),
32
- entryPoints: /* @__PURE__ */ new Map()
32
+ entryPoints: /* @__PURE__ */ new Map(),
33
+ cacheManifestUsed: false
33
34
  };
34
35
  }
35
36
  function trackPageData(internals, component, pageData, componentModuleId, componentURL) {
@@ -1,4 +1,4 @@
1
- import type { Plugin as VitePlugin, Rollup } from 'vite';
1
+ import type { Rollup, Plugin as VitePlugin } from 'vite';
2
2
  import type { BuildInternals } from './internal.js';
3
3
  import type { StaticBuildOptions, ViteBuildReturn } from './types.js';
4
4
  type RollupOutputArray = Extract<ViteBuildReturn, Array<any>>;
@@ -10,40 +10,53 @@ import {
10
10
  generateLookupMap
11
11
  } from "../../../content/vite-plugin-content-virtual-mod.js";
12
12
  import { isServerLikeOutput } from "../../../prerender/utils.js";
13
- import { joinPaths, removeFileExtension, removeLeadingForwardSlash } from "../../path.js";
13
+ import { configPaths } from "../../config/index.js";
14
+ import { emptyDir } from "../../fs/index.js";
15
+ import {
16
+ appendForwardSlash,
17
+ joinPaths,
18
+ removeFileExtension,
19
+ removeLeadingForwardSlash
20
+ } from "../../path.js";
14
21
  import { addRollupInput } from "../add-rollup-input.js";
22
+ import { CHUNKS_PATH } from "../consts.js";
15
23
  import {} from "../internal.js";
16
24
  import { copyFiles } from "../static-build.js";
17
25
  import { encodeName } from "../util.js";
18
26
  import { extendManualChunks } from "./util.js";
19
27
  const CONTENT_CACHE_DIR = "./content/";
20
28
  const CONTENT_MANIFEST_FILE = "./manifest.json";
21
- const CONTENT_MANIFEST_VERSION = 0;
29
+ const CONTENT_MANIFEST_VERSION = 1;
22
30
  const virtualEmptyModuleId = `virtual:empty-content`;
23
31
  const resolvedVirtualEmptyModuleId = `\0${virtualEmptyModuleId}`;
32
+ const NO_MANIFEST_VERSION = -1;
24
33
  function createContentManifest() {
25
- return { version: -1, entries: [], serverEntries: [], clientEntries: [] };
34
+ return {
35
+ version: NO_MANIFEST_VERSION,
36
+ entries: [],
37
+ serverEntries: [],
38
+ clientEntries: [],
39
+ lockfiles: "",
40
+ configs: ""
41
+ };
26
42
  }
27
- function vitePluginContent(opts, lookupMap, internals) {
43
+ function vitePluginContent(opts, lookupMap, internals, cachedBuildOutput) {
28
44
  const { config } = opts.settings;
29
45
  const { cacheDir } = config;
30
46
  const distRoot = config.outDir;
31
47
  const distContentRoot = new URL("./content/", distRoot);
32
- const cachedChunks = new URL("./chunks/", opts.settings.config.cacheDir);
33
- const distChunks = new URL("./chunks/", opts.settings.config.outDir);
34
48
  const contentCacheDir = new URL(CONTENT_CACHE_DIR, cacheDir);
35
49
  const contentManifestFile = new URL(CONTENT_MANIFEST_FILE, contentCacheDir);
36
- const cache = contentCacheDir;
37
- const cacheTmp = new URL("./.tmp/", cache);
50
+ const cacheTmp = new URL("./.tmp/", contentCacheDir);
38
51
  let oldManifest = createContentManifest();
39
52
  let newManifest = createContentManifest();
40
53
  let entries;
41
54
  let injectedEmptyFile = false;
55
+ let currentManifestState = "valid";
42
56
  if (fsMod.existsSync(contentManifestFile)) {
43
57
  try {
44
58
  const data = fsMod.readFileSync(contentManifestFile, { encoding: "utf8" });
45
59
  oldManifest = JSON.parse(data);
46
- internals.cachedClientEntries = oldManifest.clientEntries;
47
60
  } catch {
48
61
  }
49
62
  }
@@ -53,6 +66,30 @@ function vitePluginContent(opts, lookupMap, internals) {
53
66
  let newOptions = Object.assign({}, options);
54
67
  newManifest = await generateContentManifest(opts, lookupMap);
55
68
  entries = getEntriesFromManifests(oldManifest, newManifest);
69
+ currentManifestState = manifestState(oldManifest, newManifest);
70
+ if (currentManifestState === "valid") {
71
+ internals.cachedClientEntries = oldManifest.clientEntries;
72
+ } else {
73
+ let logReason = "";
74
+ switch (currentManifestState) {
75
+ case "config-mismatch":
76
+ logReason = "Astro config has changed";
77
+ break;
78
+ case "lockfile-mismatch":
79
+ logReason = "Lockfiles have changed";
80
+ break;
81
+ case "no-entries":
82
+ logReason = "No content collections entries cached";
83
+ break;
84
+ case "version-mismatch":
85
+ logReason = "The cache manifest version has changed";
86
+ break;
87
+ case "no-manifest":
88
+ logReason = "No content manifest was found in the cache";
89
+ break;
90
+ }
91
+ opts.logger.info("build", `Cache invalid, rebuilding from source. Reason: ${logReason}.`);
92
+ }
56
93
  for (const { type, entry } of entries.buildFromSource) {
57
94
  const fileURL = encodeURI(joinPaths(opts.settings.config.root.toString(), entry));
58
95
  const input = fileURLToPath(fileURL);
@@ -62,8 +99,12 @@ function vitePluginContent(opts, lookupMap, internals) {
62
99
  }
63
100
  newOptions = addRollupInput(newOptions, inputs);
64
101
  }
65
- if (fsMod.existsSync(cachedChunks)) {
66
- await copyFiles(cachedChunks, distChunks, true);
102
+ if (currentManifestState === "valid") {
103
+ for (const { cached, dist } of cachedBuildOutput) {
104
+ if (fsMod.existsSync(cached)) {
105
+ await copyFiles(cached, dist, true);
106
+ }
107
+ }
67
108
  }
68
109
  if (entries.buildFromSource.length === 0) {
69
110
  newOptions = addRollupInput(newOptions, [virtualEmptyModuleId]);
@@ -152,15 +193,17 @@ export default {}`
152
193
  ]);
153
194
  newManifest.serverEntries = Array.from(serverComponents);
154
195
  newManifest.clientEntries = Array.from(clientComponents);
196
+ const cacheExists = fsMod.existsSync(contentCacheDir);
197
+ if (cacheExists && currentManifestState !== "valid") {
198
+ emptyDir(contentCacheDir);
199
+ }
155
200
  await fsMod.promises.mkdir(contentCacheDir, { recursive: true });
156
201
  await fsMod.promises.writeFile(contentManifestFile, JSON.stringify(newManifest), {
157
202
  encoding: "utf8"
158
203
  });
159
- const cacheExists = fsMod.existsSync(cache);
160
- fsMod.mkdirSync(cache, { recursive: true });
161
204
  await fsMod.promises.mkdir(cacheTmp, { recursive: true });
162
205
  await copyFiles(distContentRoot, cacheTmp, true);
163
- if (cacheExists) {
206
+ if (cacheExists && currentManifestState === "valid") {
164
207
  await copyFiles(contentCacheDir, distContentRoot, false);
165
208
  }
166
209
  await copyFiles(cacheTmp, contentCacheDir);
@@ -186,11 +229,11 @@ function findEntryFromSrcRelativePath(lookupMap, srcRelativePath) {
186
229
  }
187
230
  }
188
231
  function getEntriesFromManifests(oldManifest, newManifest) {
189
- const { version: oldVersion, entries: oldEntries } = oldManifest;
190
- const { version: newVersion, entries: newEntries } = newManifest;
232
+ const { entries: oldEntries } = oldManifest;
233
+ const { entries: newEntries } = newManifest;
191
234
  let entries = { restoreFromCache: [], buildFromSource: [] };
192
235
  const newEntryMap = new Map(newEntries);
193
- if (oldVersion !== newVersion || oldEntries.length === 0) {
236
+ if (manifestState(oldManifest, newManifest) !== "valid") {
194
237
  entries.buildFromSource = Array.from(newEntryMap.keys());
195
238
  return entries;
196
239
  }
@@ -206,13 +249,27 @@ function getEntriesFromManifests(oldManifest, newManifest) {
206
249
  }
207
250
  return entries;
208
251
  }
252
+ function manifestState(oldManifest, newManifest) {
253
+ if (oldManifest.version === NO_MANIFEST_VERSION) {
254
+ return "no-manifest";
255
+ }
256
+ if (oldManifest.version !== newManifest.version) {
257
+ return "version-mismatch";
258
+ }
259
+ if (oldManifest.entries.length === 0) {
260
+ return "no-entries";
261
+ }
262
+ if (oldManifest.lockfiles !== newManifest.lockfiles || newManifest.lockfiles === "") {
263
+ return "lockfile-mismatch";
264
+ }
265
+ if (oldManifest.configs !== newManifest.configs) {
266
+ return "config-mismatch";
267
+ }
268
+ return "valid";
269
+ }
209
270
  async function generateContentManifest(opts, lookupMap) {
210
- let manifest = {
211
- version: CONTENT_MANIFEST_VERSION,
212
- entries: [],
213
- serverEntries: [],
214
- clientEntries: []
215
- };
271
+ let manifest = createContentManifest();
272
+ manifest.version = CONTENT_MANIFEST_VERSION;
216
273
  const limit = pLimit(10);
217
274
  const promises = [];
218
275
  for (const [collection, { type, entries }] of Object.entries(lookupMap)) {
@@ -227,19 +284,65 @@ async function generateContentManifest(opts, lookupMap) {
227
284
  );
228
285
  }
229
286
  }
287
+ const [lockfiles, configs] = await Promise.all([
288
+ lockfilesHash(opts.settings.config.root),
289
+ configHash(opts.settings.config.root)
290
+ ]);
291
+ manifest.lockfiles = lockfiles;
292
+ manifest.configs = configs;
230
293
  await Promise.all(promises);
231
294
  return manifest;
232
295
  }
233
- function checksum(data) {
234
- return createHash("sha1").update(data).digest("base64");
296
+ async function pushBufferInto(fileURL, buffers) {
297
+ try {
298
+ const handle = await fsMod.promises.open(fileURL, "r");
299
+ const data = await handle.readFile();
300
+ buffers.push(data);
301
+ await handle.close();
302
+ } catch {
303
+ }
304
+ }
305
+ async function lockfilesHash(root) {
306
+ const lockfiles = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb"];
307
+ const datas = [];
308
+ const promises = [];
309
+ for (const lockfileName of lockfiles) {
310
+ const fileURL = new URL(`./${lockfileName}`, root);
311
+ promises.push(pushBufferInto(fileURL, datas));
312
+ }
313
+ await Promise.all(promises);
314
+ return checksum(...datas);
315
+ }
316
+ async function configHash(root) {
317
+ const configFileNames = configPaths;
318
+ for (const configPath of configFileNames) {
319
+ try {
320
+ const fileURL = new URL(`./${configPath}`, root);
321
+ const data = await fsMod.promises.readFile(fileURL);
322
+ const hash = checksum(data);
323
+ return hash;
324
+ } catch {
325
+ }
326
+ }
327
+ return checksum(`export default {}`);
328
+ }
329
+ function checksum(...datas) {
330
+ const hash = createHash("sha1");
331
+ datas.forEach((data) => hash.update(data));
332
+ return hash.digest("base64");
235
333
  }
236
334
  function collectionTypeToFlag(type) {
237
335
  const name = type[0].toUpperCase() + type.slice(1);
238
336
  return `astro${name}CollectionEntry`;
239
337
  }
240
338
  function pluginContent(opts, internals) {
241
- const cachedChunks = new URL("./chunks/", opts.settings.config.cacheDir);
242
- const distChunks = new URL("./chunks/", opts.settings.config.outDir);
339
+ const { cacheDir, outDir } = opts.settings.config;
340
+ const chunksFolder = "./" + CHUNKS_PATH;
341
+ const assetsFolder = "./" + appendForwardSlash(opts.settings.config.build.assets);
342
+ const cachedBuildOutput = [
343
+ { cached: new URL(chunksFolder, cacheDir), dist: new URL(chunksFolder, outDir) },
344
+ { cached: new URL(assetsFolder, cacheDir), dist: new URL(assetsFolder, outDir) }
345
+ ];
243
346
  return {
244
347
  targets: ["server"],
245
348
  hooks: {
@@ -252,7 +355,7 @@ function pluginContent(opts, internals) {
252
355
  }
253
356
  const lookupMap = await generateLookupMap({ settings: opts.settings, fs: fsMod });
254
357
  return {
255
- vitePlugin: vitePluginContent(opts, lookupMap, internals)
358
+ vitePlugin: vitePluginContent(opts, lookupMap, internals, cachedBuildOutput)
256
359
  };
257
360
  },
258
361
  async "build:post"() {
@@ -262,8 +365,10 @@ function pluginContent(opts, internals) {
262
365
  if (isServerLikeOutput(opts.settings.config)) {
263
366
  return;
264
367
  }
265
- if (fsMod.existsSync(distChunks)) {
266
- await copyFiles(distChunks, cachedChunks, true);
368
+ for (const { cached, dist } of cachedBuildOutput) {
369
+ if (fsMod.existsSync(dist)) {
370
+ await copyFiles(dist, cached, true);
371
+ }
267
372
  }
268
373
  }
269
374
  }
@@ -1,4 +1,4 @@
1
- import type { BuildOptions, Plugin as VitePlugin, Rollup } from 'vite';
1
+ import type { BuildOptions, Rollup, Plugin as VitePlugin } from 'vite';
2
2
  type OutputOptionsHook = Extract<VitePlugin['outputOptions'], Function>;
3
3
  type OutputOptions = Parameters<OutputOptionsHook>[0];
4
4
  type ExtendManualChunksHooks = {
@@ -6,7 +6,7 @@ export declare function viteBuild(opts: StaticBuildOptions): Promise<{
6
6
  ssrOutputChunkNames: string[];
7
7
  }>;
8
8
  export declare function staticBuild(opts: StaticBuildOptions, internals: BuildInternals, ssrOutputChunkNames: string[]): Promise<void>;
9
- export declare function copyFiles(fromFolder: URL, toFolder: URL, includeDotfiles?: boolean): Promise<void>;
9
+ export declare function copyFiles(fromFolder: URL, toFolder: URL, includeDotfiles?: boolean): Promise<void[] | undefined>;
10
10
  /**
11
11
  * This function takes the virtual module name of any page entrypoint and
12
12
  * transforms it to generate a final `.mjs` output file.
@@ -21,6 +21,7 @@ import { PAGE_SCRIPT_ID } from "../../vite-plugin-scripts/index.js";
21
21
  import { AstroError, AstroErrorData } from "../errors/index.js";
22
22
  import { routeIsRedirect } from "../redirects/index.js";
23
23
  import { getOutDirWithinCwd } from "./common.js";
24
+ import { CHUNKS_PATH } from "./consts.js";
24
25
  import { generatePages } from "./generate.js";
25
26
  import { trackPageData } from "./internal.js";
26
27
  import { createPluginContainer } from "./plugin.js";
@@ -146,7 +147,7 @@ async function ssrBuild(opts, internals, input, container) {
146
147
  // We need to keep these separate
147
148
  chunkFileNames(chunkInfo) {
148
149
  const { name } = chunkInfo;
149
- let prefix = "chunks/";
150
+ let prefix = CHUNKS_PATH;
150
151
  let suffix = "_[hash].mjs";
151
152
  if (isContentCache) {
152
153
  prefix += `${buildID}/`;
@@ -345,7 +346,7 @@ async function copyFiles(fromFolder, toFolder, includeDotfiles = false) {
345
346
  });
346
347
  if (files.length === 0)
347
348
  return;
348
- await Promise.all(
349
+ return await Promise.all(
349
350
  files.map(async function copyFile(filename) {
350
351
  const from = new URL(filename, fromFolder);
351
352
  const to = new URL(filename, toFolder);
@@ -7,6 +7,7 @@ export declare function validateConfig(userConfig: any, root: string, cmd: strin
7
7
  /** Convert the generic "yargs" flag object into our own, custom TypeScript object. */
8
8
  export declare function resolveFlags(flags: Partial<Flags>): CLIFlags;
9
9
  export declare function resolveRoot(cwd?: string | URL): string;
10
+ export declare const configPaths: readonly string[];
10
11
  interface ResolveConfigPathOptions {
11
12
  root: string;
12
13
  configFile?: string;
@@ -45,15 +45,16 @@ function resolveRoot(cwd) {
45
45
  }
46
46
  return cwd ? path.resolve(cwd) : process.cwd();
47
47
  }
48
+ const configPaths = Object.freeze([
49
+ "astro.config.mjs",
50
+ "astro.config.js",
51
+ "astro.config.ts",
52
+ "astro.config.mts",
53
+ "astro.config.cjs",
54
+ "astro.config.cts"
55
+ ]);
48
56
  async function search(fsMod, root) {
49
- const paths = [
50
- "astro.config.mjs",
51
- "astro.config.js",
52
- "astro.config.ts",
53
- "astro.config.mts",
54
- "astro.config.cjs",
55
- "astro.config.cts"
56
- ].map((p) => path.join(root, p));
57
+ const paths = configPaths.map((p) => path.join(root, p));
57
58
  for (const file of paths) {
58
59
  if (fsMod.existsSync(file)) {
59
60
  return file;
@@ -121,6 +122,7 @@ async function resolveConfig(inlineConfig, command, fsMod = fs) {
121
122
  return { userConfig, astroConfig };
122
123
  }
123
124
  export {
125
+ configPaths,
124
126
  resolveConfig,
125
127
  resolveConfigPath,
126
128
  resolveFlags,
@@ -1,4 +1,4 @@
1
- export { resolveConfig, resolveConfigPath, resolveFlags, resolveRoot } from './config.js';
1
+ export { configPaths, resolveConfig, resolveConfigPath, resolveFlags, resolveRoot, } from './config.js';
2
2
  export { createNodeLogger } from './logging.js';
3
3
  export { mergeConfig } from './merge.js';
4
4
  export type { AstroConfigType } from './schema.js';
@@ -1,9 +1,16 @@
1
- import { resolveConfig, resolveConfigPath, resolveFlags, resolveRoot } from "./config.js";
1
+ import {
2
+ configPaths,
3
+ resolveConfig,
4
+ resolveConfigPath,
5
+ resolveFlags,
6
+ resolveRoot
7
+ } from "./config.js";
2
8
  import { createNodeLogger } from "./logging.js";
3
9
  import { mergeConfig } from "./merge.js";
4
10
  import { createSettings } from "./settings.js";
5
11
  import { loadTSConfig, updateTSConfigForFramework } from "./tsconfig.js";
6
12
  export {
13
+ configPaths,
7
14
  createNodeLogger,
8
15
  createSettings,
9
16
  loadTSConfig,
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
2
  /// <reference types="node" resolution-mode="require"/>
3
- import type { RehypePlugin as _RehypePlugin, RemarkPlugin as _RemarkPlugin, RemarkRehype as _RemarkRehype, ShikiConfig } from '@astrojs/markdown-remark';
3
+ import type { ShikiConfig, RehypePlugin as _RehypePlugin, RemarkPlugin as _RemarkPlugin, RemarkRehype as _RemarkRehype } from '@astrojs/markdown-remark';
4
4
  import type { ViteUserConfig } from '../../@types/astro.js';
5
5
  import type { OutgoingHttpHeaders } from 'node:http';
6
6
  import { z } from 'zod';
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "4.6.2";
1
+ const ASTRO_VERSION = "4.6.3";
2
2
  const REROUTE_DIRECTIVE_HEADER = "X-Astro-Reroute";
3
3
  const ROUTE_TYPE_HEADER = "X-Astro-Route-Type";
4
4
  const DEFAULT_404_COMPONENT = "astro-default-404";
@@ -23,7 +23,7 @@ async function dev(inlineConfig) {
23
23
  base: restart.container.settings.config.base
24
24
  })
25
25
  );
26
- const currentVersion = "4.6.2";
26
+ const currentVersion = "4.6.3";
27
27
  if (currentVersion.includes("-")) {
28
28
  logger.warn("SKIP_FORMAT", msg.prerelease({ currentVersion }));
29
29
  }
@@ -36,7 +36,7 @@ function serverStart({
36
36
  host,
37
37
  base
38
38
  }) {
39
- const version = "4.6.2";
39
+ const version = "4.6.3";
40
40
  const localPrefix = `${dim("\u2503")} Local `;
41
41
  const networkPrefix = `${dim("\u2503")} Network `;
42
42
  const emptyPrefix = " ".repeat(11);
@@ -261,7 +261,7 @@ function printHelp({
261
261
  message.push(
262
262
  linebreak(),
263
263
  ` ${bgGreen(black(` ${commandName} `))} ${green(
264
- `v${"4.6.2"}`
264
+ `v${"4.6.3"}`
265
265
  )} ${headline}`
266
266
  );
267
267
  }
@@ -33,7 +33,16 @@ export declare class RenderContext {
33
33
  render(componentInstance: ComponentInstance | undefined): Promise<Response>;
34
34
  createAPIContext(props: APIContext['props']): APIContext;
35
35
  createResult(mod: ComponentInstance): Promise<SSRResult>;
36
- createAstro(result: SSRResult, astroGlobalPartial: AstroGlobalPartial, props: Record<string, any>, slotValues: Record<string, any> | null): AstroGlobal;
36
+ /**
37
+ * The Astro global is sourced in 3 different phases:
38
+ * - **Static**: `.generator` and `.glob` is printed by the compiler, instantiated once per process per astro file
39
+ * - **Page-level**: `.request`, `.cookies`, `.locals` etc. These remain the same for the duration of the request.
40
+ * - **Component-level**: `.props`, `.slots`, and `.self` are unique to each _use_ of each component.
41
+ *
42
+ * The page level partial is used as the prototype of the user-visible `Astro` global object, which is instantiated once per use of a component.
43
+ */
44
+ createAstro(result: SSRResult, astroStaticPartial: AstroGlobalPartial, props: Record<string, any>, slotValues: Record<string, any> | null): AstroGlobal;
45
+ createAstroPagePartial(result: SSRResult, astroStaticPartial: AstroGlobalPartial): Omit<AstroGlobal, 'props' | 'self' | 'slots'>;
37
46
  clientAddress(): string;
38
47
  computeCurrentLocale(): string | undefined;
39
48
  computePreferredLocale(): string | undefined;
@@ -202,7 +202,41 @@ class RenderContext {
202
202
  };
203
203
  return result;
204
204
  }
205
- createAstro(result, astroGlobalPartial, props, slotValues) {
205
+ #astroPagePartial;
206
+ /**
207
+ * The Astro global is sourced in 3 different phases:
208
+ * - **Static**: `.generator` and `.glob` is printed by the compiler, instantiated once per process per astro file
209
+ * - **Page-level**: `.request`, `.cookies`, `.locals` etc. These remain the same for the duration of the request.
210
+ * - **Component-level**: `.props`, `.slots`, and `.self` are unique to each _use_ of each component.
211
+ *
212
+ * The page level partial is used as the prototype of the user-visible `Astro` global object, which is instantiated once per use of a component.
213
+ */
214
+ createAstro(result, astroStaticPartial, props, slotValues) {
215
+ const astroPagePartial = this.#astroPagePartial ??= this.createAstroPagePartial(
216
+ result,
217
+ astroStaticPartial
218
+ );
219
+ const astroComponentPartial = { props, self: null };
220
+ const Astro = Object.assign(
221
+ Object.create(astroPagePartial),
222
+ astroComponentPartial
223
+ );
224
+ let _slots;
225
+ Object.defineProperty(Astro, "slots", {
226
+ get: () => {
227
+ if (!_slots) {
228
+ _slots = new Slots(
229
+ result,
230
+ slotValues,
231
+ this.pipeline.logger
232
+ );
233
+ }
234
+ return _slots;
235
+ }
236
+ });
237
+ return Astro;
238
+ }
239
+ createAstroPagePartial(result, astroStaticPartial) {
206
240
  const renderContext = this;
207
241
  const { cookies, locals, params, pipeline, request, url } = this;
208
242
  const { response } = result;
@@ -214,10 +248,9 @@ class RenderContext {
214
248
  }
215
249
  return new Response(null, { status, headers: { Location: path } });
216
250
  };
217
- const slots = new Slots(result, slotValues, pipeline.logger);
218
- const astroGlobalCombined = {
219
- generator: astroGlobalPartial.generator,
220
- glob: astroGlobalPartial.glob,
251
+ return {
252
+ generator: astroStaticPartial.generator,
253
+ glob: astroStaticPartial.glob,
221
254
  cookies,
222
255
  get clientAddress() {
223
256
  return renderContext.clientAddress();
@@ -232,16 +265,13 @@ class RenderContext {
232
265
  get preferredLocaleList() {
233
266
  return renderContext.computePreferredLocaleList();
234
267
  },
235
- props,
236
268
  locals,
237
269
  redirect,
238
270
  request,
239
271
  response,
240
- slots,
241
272
  site: pipeline.site,
242
273
  url
243
274
  };
244
- return astroGlobalCombined;
245
275
  }
246
276
  clientAddress() {
247
277
  const { pipeline, request } = this;
@@ -57,7 +57,8 @@ type RunHookBuildDone = {
57
57
  pages: string[];
58
58
  routes: RouteData[];
59
59
  logging: Logger;
60
+ cacheManifest: boolean;
60
61
  };
61
- export declare function runHookBuildDone({ config, pages, routes, logging }: RunHookBuildDone): Promise<void>;
62
+ export declare function runHookBuildDone({ config, pages, routes, logging, cacheManifest, }: RunHookBuildDone): Promise<void>;
62
63
  export declare function isFunctionPerRouteEnabled(adapter: AstroAdapter | undefined): boolean;
63
64
  export {};
@@ -349,7 +349,13 @@ async function runHookBuildGenerated({
349
349
  }
350
350
  }
351
351
  }
352
- async function runHookBuildDone({ config, pages, routes, logging }) {
352
+ async function runHookBuildDone({
353
+ config,
354
+ pages,
355
+ routes,
356
+ logging,
357
+ cacheManifest
358
+ }) {
353
359
  const dir = isServerLikeOutput(config) ? config.build.client : config.outDir;
354
360
  await fs.promises.mkdir(dir, { recursive: true });
355
361
  for (const integration of config.integrations) {
@@ -362,7 +368,8 @@ async function runHookBuildDone({ config, pages, routes, logging }) {
362
368
  pages: pages.map((p) => ({ pathname: p })),
363
369
  dir,
364
370
  routes,
365
- logger
371
+ logger,
372
+ cacheManifest
366
373
  }),
367
374
  logger: logging
368
375
  });
@@ -177,7 +177,9 @@ var settings_default = {
177
177
  if (placement === settings.config[setting.settingKey]) {
178
178
  option.selected = true;
179
179
  }
180
- option.textContent = `${placement.slice(0, 1).toUpperCase()}${placement.slice(1)}`.replace("-", " ");
180
+ option.textContent = `${placement.slice(0, 1).toUpperCase()}${placement.slice(
181
+ 1
182
+ )}`.replace("-", " ");
181
183
  astroSelect.append(option);
182
184
  });
183
185
  astroSelect.element.addEventListener("change", setting.changeEvent);
@@ -1,5 +1,5 @@
1
- import { settings } from "./settings.js";
2
1
  import { loadDevToolbarApps } from "astro:dev-toolbar";
2
+ import { settings } from "./settings.js";
3
3
  let overlay;
4
4
  document.addEventListener("DOMContentLoaded", async () => {
5
5
  const [
@@ -248,7 +248,9 @@ class AstroDevToolbar extends HTMLElement {
248
248
  this.apps.find((app) => app.builtIn && app.id === "astro:more")
249
249
  ) : ""}
250
250
  <div class="separator"></div>
251
- ${this.getAppTemplate(this.apps.find((app) => app.builtIn && app.id === "astro:settings"))}
251
+ ${this.getAppTemplate(
252
+ this.apps.find((app) => app.builtIn && app.id === "astro:settings")
253
+ )}
252
254
  </div>
253
255
  </div>
254
256
  <div id="dev-bar-hitbox-below"></div>
@@ -203,8 +203,10 @@ async function updateDOM(preparationEvent, options, historyState, fallback) {
203
203
  if (activeElement) {
204
204
  activeElement.focus();
205
205
  if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {
206
- activeElement.selectionStart = start;
207
- activeElement.selectionEnd = end;
206
+ if (typeof start === "number")
207
+ activeElement.selectionStart = start;
208
+ if (typeof end === "number")
209
+ activeElement.selectionEnd = end;
208
210
  }
209
211
  }
210
212
  };
@@ -54,10 +54,17 @@ function astro({ settings, logger }) {
54
54
  }
55
55
  const filename = normalizePath(normalizeFilename(parsedId.filename, config.root));
56
56
  let compileMetadata = astroFileToCompileMetadata.get(filename);
57
- if (!compileMetadata && server) {
58
- const code = await loadId(server.pluginContainer, filename);
59
- if (code != null)
60
- await compile(code, filename);
57
+ if (!compileMetadata) {
58
+ if (server) {
59
+ const code = await loadId(server.pluginContainer, filename);
60
+ if (code != null)
61
+ await compile(code, filename);
62
+ } else if (config.experimental.contentCollectionCache) {
63
+ await this.load({
64
+ id: filename,
65
+ resolveDependencies: false
66
+ });
67
+ }
61
68
  compileMetadata = astroFileToCompileMetadata.get(filename);
62
69
  }
63
70
  if (!compileMetadata) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "4.6.2",
3
+ "version": "4.6.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",