astro 2.9.7 → 2.10.1

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.
@@ -34,6 +34,7 @@ const { fallback = 'animate' } = Astro.props as Props;
34
34
  !!document.querySelector('[name="astro-view-transitions-enabled"]');
35
35
  const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name));
36
36
  const onload = () => triggerEvent('astro:load');
37
+ const PERSIST_ATTR = 'data-astro-transition-persist';
37
38
 
38
39
  const throttle = (cb: (...args: any[]) => any, delay: number) => {
39
40
  let wait = false;
@@ -86,8 +87,50 @@ const { fallback = 'animate' } = Astro.props as Props;
86
87
  async function updateDOM(dir: Direction, html: string, state?: State, fallback?: Fallback) {
87
88
  const doc = parser.parseFromString(html, 'text/html');
88
89
  doc.documentElement.dataset.astroTransition = dir;
90
+
91
+ // Check for a head element that should persist, either because it has the data
92
+ // attribute or is a link el.
93
+ const persistedHeadElement = (el: Element): Element | null => {
94
+ const id = el.getAttribute(PERSIST_ATTR);
95
+ const newEl = id && doc.head.querySelector(`[${PERSIST_ATTR}="${id}"]`);
96
+ if (newEl) {
97
+ return newEl;
98
+ }
99
+ if (el.matches('link[rel=stylesheet]')) {
100
+ const href = el.getAttribute('href');
101
+ return doc.head.querySelector(`link[rel=stylesheet][href="${href}"]`);
102
+ }
103
+ return null;
104
+ };
105
+
89
106
  const swap = () => {
90
- document.documentElement.replaceWith(doc.documentElement);
107
+ // Swap head
108
+ for (const el of Array.from(document.head.children)) {
109
+ const newEl = persistedHeadElement(el);
110
+ // If the element exists in the document already, remove it
111
+ // from the new document and leave the current node alone
112
+ if (newEl) {
113
+ newEl.remove();
114
+ } else {
115
+ // Otherwise remove the element in the head. It doesn't exist in the new page.
116
+ el.remove();
117
+ }
118
+ }
119
+ // Everything left in the new head is new, append it all.
120
+ document.head.append(...doc.head.children);
121
+
122
+ // Move over persist stuff in the body
123
+ const oldBody = document.body;
124
+ document.body.replaceWith(doc.body);
125
+ for (const el of oldBody.querySelectorAll(`[${PERSIST_ATTR}]`)) {
126
+ const id = el.getAttribute(PERSIST_ATTR);
127
+ const newEl = document.querySelector(`[${PERSIST_ATTR}="${id}"]`);
128
+ if (newEl) {
129
+ // The element exists in the new page, replace it with the element
130
+ // from the old page so that state is preserved.
131
+ newEl.replaceWith(el);
132
+ }
133
+ }
91
134
 
92
135
  if (state?.scrollY != null) {
93
136
  scrollTo(0, state.scrollY);
@@ -97,17 +140,27 @@ const { fallback = 'animate' } = Astro.props as Props;
97
140
  };
98
141
 
99
142
  // Wait on links to finish, to prevent FOUC
100
- const links = Array.from(doc.querySelectorAll('head link[rel=stylesheet]')).map(
101
- (link) =>
102
- new Promise((resolve) => {
103
- const c = link.cloneNode();
104
- ['load', 'error'].forEach((evName) => c.addEventListener(evName, resolve));
105
- document.head.append(c);
106
- })
107
- );
108
- if (links.length) {
109
- await Promise.all(links);
143
+ const links: Promise<any>[] = [];
144
+ for (const el of doc.querySelectorAll('head link[rel=stylesheet]')) {
145
+ // Do not preload links that are already on the page.
146
+ if (
147
+ !document.querySelector(
148
+ `[${PERSIST_ATTR}="${el.getAttribute(PERSIST_ATTR)}"], link[rel=stylesheet]`
149
+ )
150
+ ) {
151
+ const c = document.createElement('link');
152
+ c.setAttribute('rel', 'preload');
153
+ c.setAttribute('as', 'style');
154
+ c.setAttribute('href', el.getAttribute('href')!);
155
+ links.push(
156
+ new Promise<any>((resolve) => {
157
+ ['load', 'error'].forEach((evName) => c.addEventListener(evName, resolve));
158
+ document.head.append(c);
159
+ })
160
+ );
161
+ }
110
162
  }
163
+ links.length && (await Promise.all(links));
111
164
 
112
165
  if (fallback === 'animate') {
113
166
  let isAnimating = false;
@@ -187,7 +240,7 @@ const { fallback = 'animate' } = Astro.props as Props;
187
240
  transitionEnabledOnThisPage()
188
241
  ) {
189
242
  ev.preventDefault();
190
- navigate('forward', link.href);
243
+ navigate('forward', link.href, { index: currentHistoryIndex, scrollY: 0 });
191
244
  currentHistoryIndex++;
192
245
  const newState: State = { index: currentHistoryIndex, scrollY };
193
246
  persistState({ index: currentHistoryIndex - 1, scrollY });
@@ -55,6 +55,7 @@ export interface AstroBuiltinAttributes {
55
55
  'is:raw'?: boolean;
56
56
  'transition:animate'?: 'morph' | 'slide' | 'fade' | TransitionDirectionalAnimations;
57
57
  'transition:name'?: string;
58
+ 'transition:persist'?: boolean | string;
58
59
  }
59
60
  export interface AstroDefineVarsAttribute {
60
61
  'define:vars'?: any;
@@ -122,7 +122,7 @@ function assets({
122
122
  if (s) {
123
123
  return {
124
124
  code: s.toString(),
125
- map: resolvedConfig.build.sourcemap ? s.generateMap({ hires: true }) : null
125
+ map: resolvedConfig.build.sourcemap ? s.generateMap({ hires: "boundary" }) : null
126
126
  };
127
127
  } else {
128
128
  return null;
@@ -168,7 +168,7 @@ function globWithUnderscoresIgnored(relContentDir, exts) {
168
168
  const contentDir = appendForwardSlash(relContentDir);
169
169
  return [
170
170
  `${contentDir}**/*${extGlob}`,
171
- `!${contentDir}**/_*/**${extGlob}`,
171
+ `!${contentDir}**/_*/**/*${extGlob}`,
172
172
  `!${contentDir}**/_*${extGlob}`
173
173
  ];
174
174
  }
@@ -2,9 +2,13 @@ import mime from "mime";
2
2
  import { attachToResponse, getSetCookiesFromResponse } from "../cookies/index.js";
3
3
  import { consoleLogDestination } from "../logger/console.js";
4
4
  import { error } from "../logger/core.js";
5
- import { prependForwardSlash, removeTrailingForwardSlash } from "../path.js";
5
+ import {
6
+ collapseDuplicateSlashes,
7
+ prependForwardSlash,
8
+ removeTrailingForwardSlash
9
+ } from "../path.js";
6
10
  import { RedirectSinglePageBuiltModule } from "../redirects/index.js";
7
- import { isResponse } from "../render/core";
11
+ import { isResponse } from "../render/core.js";
8
12
  import {
9
13
  createEnvironment,
10
14
  createRenderContext,
@@ -98,13 +102,16 @@ class App {
98
102
  const url = new URL(request.url);
99
103
  if (this.#manifest.assets.has(url.pathname))
100
104
  return void 0;
101
- let pathname = prependForwardSlash(this.removeBase(url.pathname));
102
- let routeData = matchRoute(pathname, this.#manifestData);
105
+ const pathname = prependForwardSlash(this.removeBase(url.pathname));
106
+ const routeData = matchRoute(pathname, this.#manifestData);
103
107
  if (!routeData || routeData.prerender)
104
108
  return void 0;
105
109
  return routeData;
106
110
  }
107
111
  async render(request, routeData, locals) {
112
+ if (request.url !== collapseDuplicateSlashes(request.url)) {
113
+ request = new Request(collapseDuplicateSlashes(request.url), request);
114
+ }
108
115
  if (!routeData) {
109
116
  routeData = this.match(request);
110
117
  }
@@ -229,7 +236,7 @@ class App {
229
236
  }
230
237
  }
231
238
  /**
232
- * If is a known error code, try sending the according page (e.g. 404.astro / 500.astro).
239
+ * If it is a known error code, try sending the according page (e.g. 404.astro / 500.astro).
233
240
  * This also handles pre-rendered /404 or /500 routes
234
241
  */
235
242
  async #renderError(request, { routeData, status, response: originalResponse }) {
@@ -272,8 +279,10 @@ class App {
272
279
  return newResponse;
273
280
  const { status, statusText, headers } = oldResponse;
274
281
  return new Response(newResponse.body, {
282
+ // If the original status was 200 (default), override it with the new status (probably 404 or 500)
283
+ // Otherwise, the user set a specific status while rendering and we should respect that one
275
284
  status: status === 200 ? newResponse.status : status,
276
- statusText,
285
+ statusText: status === 200 ? newResponse.statusText : statusText,
277
286
  headers: new Headers(Array.from(headers))
278
287
  });
279
288
  }
@@ -25,6 +25,7 @@ async function compile({
25
25
  scopedStyleStrategy: astroConfig.scopedStyleStrategy,
26
26
  resultScopedSlot: true,
27
27
  experimentalTransitions: astroConfig.experimental.viewTransitions,
28
+ experimentalPersistence: astroConfig.experimental.viewTransitions,
28
29
  transitionsAnimationURL: "astro/components/viewtransitions.css",
29
30
  preprocessStyle: createStylePreprocessor({
30
31
  filename,
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "2.9.7";
1
+ const ASTRO_VERSION = "2.10.1";
2
2
  const SUPPORTED_MARKDOWN_FILE_EXTENSIONS = [
3
3
  ".markdown",
4
4
  ".mdown",
@@ -26,4 +26,3 @@ export interface CreateContainerParams {
26
26
  }
27
27
  export declare function createContainer({ isRestart, logging, inlineConfig, settings, fs, }: CreateContainerParams): Promise<Container>;
28
28
  export declare function startContainer({ settings, viteServer, logging, }: Container): Promise<AddressInfo>;
29
- export declare function isStarted(container: Container): boolean;
@@ -79,12 +79,7 @@ async function startContainer({
79
79
  });
80
80
  return devServerAddressInfo;
81
81
  }
82
- function isStarted(container) {
83
- var _a;
84
- return !!((_a = container.viteServer.httpServer) == null ? void 0 : _a.listening);
85
- }
86
82
  export {
87
83
  createContainer,
88
- isStarted,
89
84
  startContainer
90
85
  };
@@ -23,7 +23,7 @@ async function dev(inlineConfig) {
23
23
  base: restart.container.settings.config.base
24
24
  })
25
25
  );
26
- const currentVersion = "2.9.7";
26
+ const currentVersion = "2.10.1";
27
27
  if (currentVersion.includes("-")) {
28
28
  warn(logging, null, msg.prerelease({ currentVersion }));
29
29
  }
@@ -1,3 +1,3 @@
1
- export { createContainer, isStarted, startContainer } from './container.js';
1
+ export { createContainer, startContainer } from './container.js';
2
2
  export { default } from './dev.js';
3
3
  export { createContainerWithAutomaticRestart } from './restart.js';
@@ -1,10 +1,9 @@
1
- import { createContainer, isStarted, startContainer } from "./container.js";
1
+ import { createContainer, startContainer } from "./container.js";
2
2
  import { default as default2 } from "./dev.js";
3
3
  import { createContainerWithAutomaticRestart } from "./restart.js";
4
4
  export {
5
5
  createContainer,
6
6
  createContainerWithAutomaticRestart,
7
7
  default2 as default,
8
- isStarted,
9
8
  startContainer
10
9
  };
@@ -3,10 +3,7 @@ import nodeFs from 'node:fs';
3
3
  import type { AstroInlineConfig } from '../../@types/astro';
4
4
  import type { Container } from './container';
5
5
  export declare function shouldRestartContainer({ settings, inlineConfig, restartInFlight }: Container, changedFile: string): boolean;
6
- export declare function restartContainer(container: Container): Promise<{
7
- container: Container;
8
- error: Error | null;
9
- }>;
6
+ export declare function restartContainer(container: Container): Promise<Container | Error>;
10
7
  export interface CreateContainerWithAutomaticRestart {
11
8
  inlineConfig?: AstroInlineConfig;
12
9
  fs: typeof nodeFs;
@@ -7,8 +7,8 @@ import { isAstroConfigZodError } from "../errors/errors.js";
7
7
  import { createSafeError } from "../errors/index.js";
8
8
  import { info, error as logError } from "../logger/core.js";
9
9
  import { formatErrorMessage } from "../messages.js";
10
- import { createContainer, isStarted, startContainer } from "./container.js";
11
- async function createRestartedContainer(container, settings, needsStart) {
10
+ import { createContainer, startContainer } from "./container.js";
11
+ async function createRestartedContainer(container, settings) {
12
12
  const { logging, fs, inlineConfig } = container;
13
13
  const newContainer = await createContainer({
14
14
  isRestart: true,
@@ -17,9 +17,7 @@ async function createRestartedContainer(container, settings, needsStart) {
17
17
  inlineConfig,
18
18
  fs
19
19
  });
20
- if (needsStart) {
21
- await startContainer(newContainer);
22
- }
20
+ await startContainer(newContainer);
23
21
  return newContainer;
24
22
  }
25
23
  function shouldRestartContainer({ settings, inlineConfig, restartInFlight }, changedFile) {
@@ -43,15 +41,11 @@ function shouldRestartContainer({ settings, inlineConfig, restartInFlight }, cha
43
41
  async function restartContainer(container) {
44
42
  const { logging, close, settings: existingSettings } = container;
45
43
  container.restartInFlight = true;
46
- const needsStart = isStarted(container);
47
44
  try {
48
45
  const { astroConfig } = await resolveConfig(container.inlineConfig, "dev", container.fs);
49
46
  const settings = createSettings(astroConfig, fileURLToPath(existingSettings.config.root));
50
47
  await close();
51
- return {
52
- container: await createRestartedContainer(container, settings, needsStart),
53
- error: null
54
- };
48
+ return await createRestartedContainer(container, settings);
55
49
  } catch (_err) {
56
50
  const error = createSafeError(_err);
57
51
  if (!isAstroConfigZodError(_err)) {
@@ -64,12 +58,9 @@ async function restartContainer(container) {
64
58
  stack: error.stack || ""
65
59
  }
66
60
  });
67
- await close();
61
+ container.restartInFlight = false;
68
62
  info(logging, "astro", "Continuing with previous valid configuration\n");
69
- return {
70
- container: await createRestartedContainer(container, existingSettings, needsStart),
71
- error
72
- };
63
+ return error;
73
64
  }
74
65
  }
75
66
  async function createContainerWithAutomaticRestart({
@@ -94,10 +85,14 @@ async function createContainerWithAutomaticRestart({
94
85
  async function handleServerRestart(logMsg) {
95
86
  info(logging, "astro", logMsg + "\n");
96
87
  const container = restart.container;
97
- const { container: newContainer, error } = await restartContainer(container);
98
- restart.container = newContainer;
99
- addWatches();
100
- resolveRestart(error);
88
+ const result = await restartContainer(container);
89
+ if (result instanceof Error) {
90
+ resolveRestart(result);
91
+ } else {
92
+ restart.container = result;
93
+ addWatches();
94
+ resolveRestart(null);
95
+ }
101
96
  restartComplete = new Promise((resolve) => {
102
97
  resolveRestart = resolve;
103
98
  });
@@ -47,7 +47,7 @@ function serverStart({
47
47
  base,
48
48
  isRestart = false
49
49
  }) {
50
- const version = "2.9.7";
50
+ const version = "2.10.1";
51
51
  const localPrefix = `${dim("\u2503")} Local `;
52
52
  const networkPrefix = `${dim("\u2503")} Network `;
53
53
  const emptyPrefix = " ".repeat(11);
@@ -233,7 +233,7 @@ function printHelp({
233
233
  message.push(
234
234
  linebreak(),
235
235
  ` ${bgGreen(black(` ${commandName} `))} ${green(
236
- `v${"2.9.7"}`
236
+ `v${"2.10.1"}`
237
237
  )} ${headline}`
238
238
  );
239
239
  }
@@ -2,6 +2,10 @@ import { AstroError, AstroErrorData } from "../../core/errors/index.js";
2
2
  import { escapeHTML } from "./escape.js";
3
3
  import { serializeProps } from "./serialize.js";
4
4
  import { serializeListValue } from "./util.js";
5
+ const transitionDirectivesToCopyOnIsland = Object.freeze([
6
+ "data-astro-transition-scope",
7
+ "data-astro-transition-persist"
8
+ ]);
5
9
  function extractDirectives(inputProps, clientDirectives) {
6
10
  let extracted = {
7
11
  isPage: false,
@@ -104,6 +108,11 @@ async function generateHydrateScript(scriptOptions, metadata) {
104
108
  value: metadata.hydrateArgs || ""
105
109
  })
106
110
  );
111
+ transitionDirectivesToCopyOnIsland.forEach((name) => {
112
+ if (props[name]) {
113
+ island.props[name] = props[name];
114
+ }
115
+ });
107
116
  return island;
108
117
  }
109
118
  export {
@@ -5,7 +5,7 @@ export { escapeHTML, HTMLBytes, HTMLString, isHTMLString, markHTMLString, unesca
5
5
  export { renderJSX } from './jsx.js';
6
6
  export { addAttribute, createHeadAndContent, defineScriptVars, Fragment, maybeRenderHead, renderTemplate as render, renderComponent, Renderer as Renderer, renderHead, renderHTMLElement, renderPage, renderScriptElement, renderSlot, renderSlotToString, renderTemplate, renderToString, renderUniqueStylesheet, voidElementNames, } from './render/index.js';
7
7
  export type { AstroComponentFactory, AstroComponentInstance, ComponentSlots, RenderInstruction, } from './render/index.js';
8
- export { renderTransition } from './transition.js';
8
+ export { createTransitionScope, renderTransition } from './transition.js';
9
9
  export declare function mergeSlots(...slotted: unknown[]): Record<string, () => any>;
10
10
  /** @internal Associate JSX components with a specific renderer (see /src/vite-plugin-jsx/tag.ts) */
11
11
  export declare function __astro_tag_component__(Component: unknown, rendererName: string): void;
@@ -30,7 +30,7 @@ import {
30
30
  renderUniqueStylesheet,
31
31
  voidElementNames
32
32
  } from "./render/index.js";
33
- import { renderTransition } from "./transition.js";
33
+ import { createTransitionScope, renderTransition } from "./transition.js";
34
34
  import { markHTMLString as markHTMLString2 } from "./escape.js";
35
35
  import { addAttribute as addAttribute2, Renderer as Renderer2 } from "./render/index.js";
36
36
  function mergeSlots(...slotted) {
@@ -95,6 +95,7 @@ export {
95
95
  createAstro,
96
96
  createComponent,
97
97
  createHeadAndContent,
98
+ createTransitionScope,
98
99
  defineScriptVars,
99
100
  defineStyleVars,
100
101
  escapeHTML,
@@ -1,2 +1,3 @@
1
1
  import type { SSRResult, TransitionAnimationValue } from '../../@types/astro';
2
+ export declare function createTransitionScope(result: SSRResult, hash: string): string;
2
3
  export declare function renderTransition(result: SSRResult, hash: string, animationName: TransitionAnimationValue | undefined, transitionName: string): string;
@@ -129,5 +129,6 @@ function toTimeValue(num) {
129
129
  return typeof num === "number" ? num + "ms" : num;
130
130
  }
131
131
  export {
132
+ createTransitionScope,
132
133
  renderTransition
133
134
  };
@@ -37,7 +37,7 @@ function astro() {
37
37
  if (s) {
38
38
  return {
39
39
  code: s.toString(),
40
- map: s.generateMap({ hires: true })
40
+ map: s.generateMap({ hires: "boundary" })
41
41
  };
42
42
  }
43
43
  }
@@ -2,7 +2,7 @@ import { collectErrorMetadata } from "../core/errors/dev/index.js";
2
2
  import { createSafeError } from "../core/errors/index.js";
3
3
  import { error } from "../core/logger/core.js";
4
4
  import * as msg from "../core/messages.js";
5
- import { removeTrailingForwardSlash } from "../core/path.js";
5
+ import { collapseDuplicateSlashes, removeTrailingForwardSlash } from "../core/path.js";
6
6
  import { eventError, telemetry } from "../events/index.js";
7
7
  import { isServerLikeOutput } from "../prerender/utils.js";
8
8
  import { runWithErrorHandling } from "./controller.js";
@@ -20,7 +20,7 @@ async function handleRequest({
20
20
  const { config } = settings;
21
21
  const origin = `${moduleLoader.isHttps() ? "https" : "http"}://${incomingRequest.headers.host}`;
22
22
  const buildingToSSR = isServerLikeOutput(config);
23
- const url = new URL(origin + incomingRequest.url);
23
+ const url = new URL(collapseDuplicateSlashes(origin + incomingRequest.url));
24
24
  let pathname;
25
25
  if (config.trailingSlash === "never" && !incomingRequest.url) {
26
26
  pathname = "";
@@ -98,7 +98,7 @@ function envVitePlugin({ settings }) {
98
98
  if (s) {
99
99
  return {
100
100
  code: s.toString(),
101
- map: s.generateMap({ hires: true })
101
+ map: s.generateMap({ hires: "boundary" })
102
102
  };
103
103
  }
104
104
  }
@@ -13,7 +13,7 @@ async function transform(code, id) {
13
13
  s.append('`\n }\nrender["astro:html"] = true;\nexport default render;');
14
14
  return {
15
15
  code: s.toString(),
16
- map: s.generateMap()
16
+ map: s.generateMap({ hires: "boundary" })
17
17
  };
18
18
  }
19
19
  export {
@@ -29,7 +29,7 @@ function astroScriptsPostPlugin({
29
29
  `);
30
30
  return {
31
31
  code: s.toString(),
32
- map: s.generateMap({ hires: true })
32
+ map: s.generateMap({ hires: "boundary" })
33
33
  };
34
34
  }
35
35
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "2.9.7",
3
+ "version": "2.10.1",
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",
@@ -102,8 +102,8 @@
102
102
  "vendor"
103
103
  ],
104
104
  "dependencies": {
105
- "@astrojs/compiler": "^1.6.3",
106
- "@astrojs/internal-helpers": "^0.1.1",
105
+ "@astrojs/compiler": "^1.8.0",
106
+ "@astrojs/internal-helpers": "^0.1.2",
107
107
  "@astrojs/language-server": "^1.0.0",
108
108
  "@astrojs/markdown-remark": "^2.2.1",
109
109
  "@astrojs/telemetry": "^2.1.1",
@@ -137,7 +137,7 @@
137
137
  "html-escaper": "^3.0.3",
138
138
  "js-yaml": "^4.1.0",
139
139
  "kleur": "^4.1.4",
140
- "magic-string": "^0.27.0",
140
+ "magic-string": "^0.30.2",
141
141
  "mime": "^3.0.0",
142
142
  "network-information-types": "^0.1.1",
143
143
  "ora": "^6.3.1",