astro 3.3.1 → 3.3.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.
@@ -506,7 +506,28 @@ export interface AstroUserConfig {
506
506
  *
507
507
  * When using this option, all of your static asset imports and URLs should add the base as a prefix. You can access this value via `import.meta.env.BASE_URL`.
508
508
  *
509
- * The value of `import.meta.env.BASE_URL` respects your `trailingSlash` config and will include a trailing slash if you explicitly include one or if `trailingSlash: "always"` is set. If `trailingSlash: "never"` is set, `BASE_URL` will not include a trailing slash, even if `base` includes one.
509
+ * The value of `import.meta.env.BASE_URL` will be determined by your `trailingSlash` config, no matter what value you have set for `base`.
510
+ *
511
+ * A trailing slash is always included if `trailingSlash: "always"` is set. If `trailingSlash: "never"` is set, `BASE_URL` will not include a trailing slash, even if `base` includes one.
512
+ *
513
+ * Additionally, Astro will internally manipulate the configured value of `config.base` before making it available to integrations. The value of `config.base` as read by integrations will also be determined by your `trailingSlash` configuration in the same way.
514
+ *
515
+ * In the example below, the values of `import.meta.env.BASE_URL` and `config.base` when processed will both be `/docs`:
516
+ * ```js
517
+ * {
518
+ * base: '/docs/',
519
+ * trailingSlash: "never"
520
+ * }
521
+ * ```
522
+ *
523
+ * In the example below, the values of `import.meta.env.BASE_URL` and `config.base` when processed will both be `/docs/`:
524
+ *
525
+ * ```js
526
+ * {
527
+ * base: '/docs',
528
+ * trailingSlash: "always"
529
+ * }
530
+ * ```
510
531
  */
511
532
  base?: string;
512
533
  /**
@@ -1,11 +1,15 @@
1
1
  import { isRemotePath, removeQueryString } from "@astrojs/internal-helpers/path";
2
2
  import { readFile } from "fs/promises";
3
3
  import mime from "mime/lite.js";
4
+ import os from "os";
4
5
  import { getConfiguredImageService, isRemoteAllowed } from "../internal.js";
5
6
  import { etag } from "../utils/etag.js";
6
7
  import { assetsDir, imageConfig } from "astro:assets";
8
+ function replaceFileSystemReferences(src) {
9
+ return os.platform().includes("win32") ? src.replace(/^\/@fs\//, "") : src.replace(/^\/@fs/, "");
10
+ }
7
11
  async function loadLocalImage(src, url) {
8
- const filePath = import.meta.env.DEV ? removeQueryString(src.slice("/@fs".length)) : new URL("." + src, assetsDir);
12
+ const filePath = import.meta.env.DEV ? removeQueryString(replaceFileSystemReferences(src)) : new URL("." + src, assetsDir);
9
13
  let buffer = void 0;
10
14
  try {
11
15
  buffer = await readFile(filePath);
@@ -86,7 +86,7 @@ function assets({
86
86
  },
87
87
  // In build, rewrite paths to ESM imported images in code to their final location
88
88
  async renderChunk(code) {
89
- const assetUrlRE = /__ASTRO_ASSET_IMAGE__([a-z\d]{8})__(?:_(.*?)__)?/g;
89
+ const assetUrlRE = /__ASTRO_ASSET_IMAGE__([\w$]{8})__(?:_(.*?)__)?/g;
90
90
  let match;
91
91
  let s;
92
92
  while (match = assetUrlRE.exec(code)) {
@@ -583,7 +583,11 @@ ${message}`
583
583
  ...inheritedFlags,
584
584
  ...installCommand.dependencies
585
585
  ],
586
- { cwd }
586
+ {
587
+ cwd,
588
+ // reset NODE_ENV to ensure install command run in dev mode
589
+ env: { NODE_ENV: void 0 }
590
+ }
587
591
  );
588
592
  spinner.succeed();
589
593
  return 1 /* updated */;
@@ -8,6 +8,10 @@ export interface RenderErrorOptions {
8
8
  routeData?: RouteData;
9
9
  response?: Response;
10
10
  status: 404 | 500;
11
+ /**
12
+ * Whether to skip onRequest() while rendering the error page. Defaults to false.
13
+ */
14
+ skipMiddleware?: boolean;
11
15
  }
12
16
  export declare class App {
13
17
  #private;
@@ -208,7 +208,7 @@ class App {
208
208
  * If it is a known error code, try sending the according page (e.g. 404.astro / 500.astro).
209
209
  * This also handles pre-rendered /404 or /500 routes
210
210
  */
211
- async #renderError(request, { status, response: originalResponse }) {
211
+ async #renderError(request, { status, response: originalResponse, skipMiddleware = false }) {
212
212
  const errorRouteData = matchRoute("/" + status, this.#manifestData);
213
213
  const url = new URL(request.url);
214
214
  if (errorRouteData) {
@@ -232,12 +232,22 @@ class App {
232
232
  status
233
233
  );
234
234
  const page = await mod.page();
235
- if (mod.onRequest) {
235
+ if (skipMiddleware === false && mod.onRequest) {
236
236
  this.#pipeline.setMiddlewareFunction(mod.onRequest);
237
237
  }
238
+ if (skipMiddleware) {
239
+ this.#pipeline.unsetMiddlewareFunction();
240
+ }
238
241
  const response2 = await this.#pipeline.renderRoute(newRenderContext, page);
239
242
  return this.#mergeResponses(response2, originalResponse);
240
243
  } catch {
244
+ if (skipMiddleware === false && mod.onRequest) {
245
+ return this.#renderError(request, {
246
+ status,
247
+ response: originalResponse,
248
+ skipMiddleware: true
249
+ });
250
+ }
241
251
  }
242
252
  }
243
253
  const response = this.#mergeResponses(new Response(null, { status }), originalResponse);
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "3.3.1";
1
+ const ASTRO_VERSION = "3.3.3";
2
2
  const SUPPORTED_MARKDOWN_FILE_EXTENSIONS = [
3
3
  ".markdown",
4
4
  ".mdown",
@@ -69,6 +69,8 @@ async function createVite(commandConfig, { settings, logger, mode, command, fs =
69
69
  }
70
70
  });
71
71
  const commonConfig = {
72
+ // Tell Vite not to combine config from vite.config.js with our provided inline config
73
+ configFile: false,
72
74
  cacheDir: fileURLToPath(new URL("./node_modules/.vite/", settings.config.root)),
73
75
  // using local caches allows Astro to be used in monorepos, etc.
74
76
  clearScreen: false,
@@ -20,7 +20,7 @@ async function dev(inlineConfig) {
20
20
  base: restart.container.settings.config.base
21
21
  })
22
22
  );
23
- const currentVersion = "3.3.1";
23
+ const currentVersion = "3.3.3";
24
24
  if (currentVersion.includes("-")) {
25
25
  logger.warn(null, msg.prerelease({ currentVersion }));
26
26
  }
@@ -50,7 +50,7 @@ function serverStart({
50
50
  base,
51
51
  isRestart = false
52
52
  }) {
53
- const version = "3.3.1";
53
+ const version = "3.3.3";
54
54
  const localPrefix = `${dim("\u2503")} Local `;
55
55
  const networkPrefix = `${dim("\u2503")} Network `;
56
56
  const emptyPrefix = " ".repeat(11);
@@ -235,7 +235,7 @@ function printHelp({
235
235
  message.push(
236
236
  linebreak(),
237
237
  ` ${bgGreen(black(` ${commandName} `))} ${green(
238
- `v${"3.3.1"}`
238
+ `v${"3.3.3"}`
239
239
  )} ${headline}`
240
240
  );
241
241
  }
@@ -25,6 +25,10 @@ export declare class Pipeline {
25
25
  * A middleware function that will be called before each request.
26
26
  */
27
27
  setMiddlewareFunction(onRequest: MiddlewareEndpointHandler): void;
28
+ /**
29
+ * Removes the current middleware function. Subsequent requests won't trigger any middleware.
30
+ */
31
+ unsetMiddlewareFunction(): void;
28
32
  /**
29
33
  * Returns the current environment
30
34
  */
@@ -33,6 +33,12 @@ class Pipeline {
33
33
  setMiddlewareFunction(onRequest) {
34
34
  this.#onRequest = onRequest;
35
35
  }
36
+ /**
37
+ * Removes the current middleware function. Subsequent requests won't trigger any middleware.
38
+ */
39
+ unsetMiddlewareFunction() {
40
+ this.#onRequest = void 0;
41
+ }
36
42
  /**
37
43
  * Returns the current environment
38
44
  */
@@ -26,6 +26,7 @@ const announce = () => {
26
26
  );
27
27
  };
28
28
  const PERSIST_ATTR = "data-astro-transition-persist";
29
+ const VITE_ID = "data-vite-dev-id";
29
30
  let parser;
30
31
  let currentHistoryIndex = 0;
31
32
  if (inBrowser) {
@@ -142,6 +143,8 @@ const updateHistoryAndScrollPosition = (toLocation, replace, intraPage) => {
142
143
  async function updateDOM(newDocument, toLocation, options, popState, fallback) {
143
144
  const persistedHeadElement = (el) => {
144
145
  const id = el.getAttribute(PERSIST_ATTR);
146
+ if (id === "")
147
+ return void 0;
145
148
  const newEl = id && newDocument.head.querySelector(`[${PERSIST_ATTR}="${id}"]`);
146
149
  if (newEl) {
147
150
  return newEl;
@@ -154,7 +157,7 @@ async function updateDOM(newDocument, toLocation, options, popState, fallback) {
154
157
  };
155
158
  const saveFocus = () => {
156
159
  const activeElement = document.activeElement;
157
- if (activeElement?.closest("[data-astro-transition-persist]")) {
160
+ if (activeElement?.closest(`[${PERSIST_ATTR}]`)) {
158
161
  if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {
159
162
  const start = activeElement.selectionStart;
160
163
  const end = activeElement.selectionEnd;
@@ -198,7 +201,7 @@ async function updateDOM(newDocument, toLocation, options, popState, fallback) {
198
201
  const newEl = persistedHeadElement(el);
199
202
  if (newEl) {
200
203
  newEl.remove();
201
- } else {
204
+ } else if (newEl === null) {
202
205
  el.remove();
203
206
  }
204
207
  }
@@ -274,6 +277,8 @@ async function transition(direction, toLocation, options, popState) {
274
277
  location.href = href;
275
278
  return;
276
279
  }
280
+ if (import.meta.env.DEV)
281
+ await prepareForClientOnlyComponents(newDocument, toLocation);
277
282
  if (!popState) {
278
283
  history.replaceState({ ...history.state, scrollX, scrollY }, "");
279
284
  }
@@ -359,6 +364,41 @@ if (inBrowser) {
359
364
  markScriptsExec();
360
365
  }
361
366
  }
367
+ async function prepareForClientOnlyComponents(newDocument, toLocation) {
368
+ if (newDocument.body.querySelector(`astro-island[client='only']`)) {
369
+ const nextPage = document.createElement("iframe");
370
+ nextPage.setAttribute("src", toLocation.href);
371
+ nextPage.style.display = "none";
372
+ document.body.append(nextPage);
373
+ await hydrationDone(nextPage);
374
+ const nextHead = nextPage.contentDocument?.head;
375
+ if (nextHead) {
376
+ document.head.querySelectorAll(`style[${PERSIST_ATTR}=""]`).forEach((s) => s.removeAttribute(PERSIST_ATTR));
377
+ const viteIds = [...nextHead.querySelectorAll(`style[${VITE_ID}]`)].map(
378
+ (style) => style.getAttribute(VITE_ID)
379
+ );
380
+ viteIds.forEach((id) => {
381
+ const style = document.head.querySelector(`style[${VITE_ID}="${id}"]`);
382
+ if (style && !newDocument.head.querySelector(`style[${VITE_ID}="${id}"]`)) {
383
+ style.setAttribute(PERSIST_ATTR, "");
384
+ }
385
+ });
386
+ }
387
+ async function hydrationDone(loadingPage) {
388
+ await new Promise(
389
+ (r) => loadingPage.contentWindow?.addEventListener("load", r, { once: true })
390
+ );
391
+ return new Promise(async (r) => {
392
+ for (let count = 0; count <= 20; ++count) {
393
+ if (!loadingPage.contentDocument.body.querySelector("astro-island[ssr]"))
394
+ break;
395
+ await new Promise((r2) => setTimeout(r2, 50));
396
+ }
397
+ r();
398
+ });
399
+ }
400
+ }
401
+ }
362
402
  export {
363
403
  navigate,
364
404
  supportsViewTransitions,
@@ -1,3 +1,4 @@
1
+ import { slash } from "@astrojs/internal-helpers/path";
1
2
  import { fileURLToPath } from "node:url";
2
3
  import {
3
4
  cachedCompilation,
@@ -55,7 +56,7 @@ async function handleHotUpdate(ctx, { config, logger, compile, source }) {
55
56
  }
56
57
  }
57
58
  const mods = [...filtered].filter((m) => !m.url.endsWith("="));
58
- const file = ctx.file.replace(config.root.pathname, "/");
59
+ const file = ctx.file.replace(slash(fileURLToPath(config.root)), "/");
59
60
  if (isStyleOnlyChange) {
60
61
  logger.info("astro", msg.hmr({ file, style: true }));
61
62
  return mods.filter((mod) => mod.id !== ctx.file && !mod.id?.endsWith(".ts"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "3.3.1",
3
+ "version": "3.3.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",