astro 2.9.7 → 2.10.0

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
  }
@@ -4,7 +4,7 @@ import { consoleLogDestination } from "../logger/console.js";
4
4
  import { error } from "../logger/core.js";
5
5
  import { prependForwardSlash, removeTrailingForwardSlash } from "../path.js";
6
6
  import { RedirectSinglePageBuiltModule } from "../redirects/index.js";
7
- import { isResponse } from "../render/core";
7
+ import { isResponse } from "../render/core.js";
8
8
  import {
9
9
  createEnvironment,
10
10
  createRenderContext,
@@ -229,7 +229,7 @@ class App {
229
229
  }
230
230
  }
231
231
  /**
232
- * If is a known error code, try sending the according page (e.g. 404.astro / 500.astro).
232
+ * If it is a known error code, try sending the according page (e.g. 404.astro / 500.astro).
233
233
  * This also handles pre-rendered /404 or /500 routes
234
234
  */
235
235
  async #renderError(request, { routeData, status, response: originalResponse }) {
@@ -272,8 +272,10 @@ class App {
272
272
  return newResponse;
273
273
  const { status, statusText, headers } = oldResponse;
274
274
  return new Response(newResponse.body, {
275
+ // If the original status was 200 (default), override it with the new status (probably 404 or 500)
276
+ // Otherwise, the user set a specific status while rendering and we should respect that one
275
277
  status: status === 200 ? newResponse.status : status,
276
- statusText,
278
+ statusText: status === 200 ? newResponse.statusText : statusText,
277
279
  headers: new Headers(Array.from(headers))
278
280
  });
279
281
  }
@@ -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.0";
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.0";
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.0";
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.0"}`
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 {
@@ -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
  }
@@ -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.0",
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,7 +102,7 @@
102
102
  "vendor"
103
103
  ],
104
104
  "dependencies": {
105
- "@astrojs/compiler": "^1.6.3",
105
+ "@astrojs/compiler": "^1.8.0",
106
106
  "@astrojs/internal-helpers": "^0.1.1",
107
107
  "@astrojs/language-server": "^1.0.0",
108
108
  "@astrojs/markdown-remark": "^2.2.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",