astro 3.0.12 → 3.0.13

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.
@@ -15,6 +15,7 @@ const { fallback = 'animate' } = Astro.props as Props;
15
15
  type Direction = 'forward' | 'back';
16
16
  type State = {
17
17
  index: number;
18
+ scrollX: number;
18
19
  scrollY: number;
19
20
  };
20
21
  type Events = 'astro:page-load' | 'astro:after-swap';
@@ -37,9 +38,9 @@ const { fallback = 'animate' } = Astro.props as Props;
37
38
  // we reloaded a page with history state
38
39
  // (e.g. history navigation from non-transition page or browser reload)
39
40
  currentHistoryIndex = history.state.index;
40
- scrollTo({ left: 0, top: history.state.scrollY });
41
+ scrollTo({ left: history.state.scrollX, top: history.state.scrollY });
41
42
  } else if (transitionEnabledOnThisPage()) {
42
- history.replaceState({ index: currentHistoryIndex, scrollY }, '');
43
+ history.replaceState({ index: currentHistoryIndex, scrollX, scrollY }, '');
43
44
  }
44
45
  const throttle = (cb: (...args: any[]) => any, delay: number) => {
45
46
  let wait = false;
@@ -64,9 +65,19 @@ const { fallback = 'animate' } = Astro.props as Props;
64
65
  };
65
66
 
66
67
  async function getHTML(href: string) {
67
- const res = await fetch(href);
68
+ let res;
69
+ try {
70
+ res = await fetch(href);
71
+ } catch (err) {
72
+ return { ok: false };
73
+ }
68
74
  const html = await res.text();
69
- return { ok: res.ok, html };
75
+ return {
76
+ ok: res.ok,
77
+ html,
78
+ redirected: res.redirected ? res.url : undefined,
79
+ contentType: res.headers.get('content-type'),
80
+ };
70
81
  }
71
82
 
72
83
  function getFallback(): Fallback {
@@ -113,6 +124,11 @@ const { fallback = 'animate' } = Astro.props as Props;
113
124
 
114
125
  const parser = new DOMParser();
115
126
 
127
+ // A noop element used to prevent styles from being removed
128
+ if (import.meta.env.DEV) {
129
+ var noopEl: string | undefined = document.createElement('div');
130
+ }
131
+
116
132
  async function updateDOM(doc: Document, loc: URL, state?: State, fallback?: Fallback) {
117
133
  // Check for a head element that should persist, either because it has the data
118
134
  // attribute or is a link el.
@@ -139,6 +155,18 @@ const { fallback = 'animate' } = Astro.props as Props;
139
155
  }
140
156
  }
141
157
  }
158
+ // Only run this in dev. This will get stripped from production builds and is not needed.
159
+ if (import.meta.env.DEV) {
160
+ if (el.tagName === 'STYLE' && el.dataset.viteDevId) {
161
+ const devId = el.dataset.viteDevId;
162
+ // If this same style tag exists, remove it from the new page
163
+ return (
164
+ doc.querySelector(`style[data-astro-dev-id="${devId}"]`) ||
165
+ // Otherwise, keep it anyways. This is client:only styles.
166
+ noopEl
167
+ );
168
+ }
169
+ }
142
170
  return null;
143
171
  };
144
172
 
@@ -191,17 +219,29 @@ const { fallback = 'animate' } = Astro.props as Props;
191
219
  // Chromium based browsers (Chrome, Edge, Opera, ...)
192
220
  scrollTo({ left: 0, top: 0, behavior: 'instant' });
193
221
 
222
+ let initialScrollX = 0;
194
223
  let initialScrollY = 0;
195
224
  if (!state && loc.hash) {
196
225
  const id = decodeURIComponent(loc.hash.slice(1));
197
226
  const elem = document.getElementById(id);
198
227
  // prefer scrollIntoView() over scrollTo() because it takes scroll-padding into account
199
- elem && (initialScrollY = elem.offsetTop) && elem.scrollIntoView();
200
- } else if (state && state.scrollY !== 0) {
201
- scrollTo(0, state.scrollY); // usings default scrollBehavior
228
+ if (elem) {
229
+ elem.scrollIntoView();
230
+ initialScrollX = Math.max(
231
+ 0,
232
+ elem.offsetLeft + elem.offsetWidth - document.documentElement.clientWidth
233
+ );
234
+ initialScrollY = elem.offsetTop;
235
+ }
236
+ } else if (state) {
237
+ scrollTo(state.scrollX, state.scrollY); // usings default scrollBehavior
202
238
  }
203
239
  !state &&
204
- history.pushState({ index: ++currentHistoryIndex, scrollY: initialScrollY }, '', loc.href);
240
+ history.pushState(
241
+ { index: ++currentHistoryIndex, scrollX: initialScrollX, scrollY: initialScrollY },
242
+ '',
243
+ loc.href
244
+ );
205
245
  triggerEvent('astro:after-swap');
206
246
  };
207
247
 
@@ -250,18 +290,24 @@ const { fallback = 'animate' } = Astro.props as Props;
250
290
  async function navigate(dir: Direction, loc: URL, state?: State) {
251
291
  let finished: Promise<void>;
252
292
  const href = loc.href;
253
- const { html, ok } = await getHTML(href);
293
+ const { html, ok, contentType, redirected } = await getHTML(href);
294
+ // if there was a redirection, show the final URL in the browser's address bar
295
+ redirected && (loc = new URL(redirected));
254
296
  // If there is a problem fetching the new page, just do an MPA navigation to it.
255
- if (!ok) {
297
+ if (!ok || contentType !== 'text/html') {
256
298
  location.href = href;
257
299
  return;
258
300
  }
259
- const doc = parser.parseFromString(html, 'text/html');
301
+
302
+ const doc = parser.parseFromString(html, contentType);
260
303
  if (!doc.querySelector('[name="astro-view-transitions-enabled"]')) {
261
304
  location.href = href;
262
305
  return;
263
306
  }
264
307
 
308
+ // Now we are sure that we will push state, and it is time to create a state if it is still missing.
309
+ !state && history.replaceState({ index: currentHistoryIndex, scrollX, scrollY }, '');
310
+
265
311
  document.documentElement.dataset.astroTransition = dir;
266
312
  if (supportsViewTransitions) {
267
313
  finished = document.startViewTransition(() => updateDOM(doc, loc, state)).finished;
@@ -335,28 +381,31 @@ const { fallback = 'animate' } = Astro.props as Props;
335
381
  // But we want to handle it like any other same page navigation
336
382
  // So we scroll to the top of the page but do not start page transitions
337
383
  ev.preventDefault();
338
- persistState({ ...history.state, scrollY });
339
- scrollTo({ left: 0, top: 0, behavior: 'instant' });
384
+ // push state on the first navigation but not if we were here already
340
385
  if (location.hash) {
341
- // last target was different
342
- const newState: State = { index: ++currentHistoryIndex, scrollY: 0 };
386
+ history.replaceState(
387
+ { index: currentHistoryIndex, scrollX, scrollY: -(scrollY + 1) },
388
+ ''
389
+ );
390
+ const newState: State = { index: ++currentHistoryIndex, scrollX: 0, scrollY: 0 };
343
391
  history.pushState(newState, '', link.href);
344
392
  }
393
+ scrollTo({ left: 0, top: 0, behavior: 'instant' });
345
394
  return;
346
395
  }
347
396
  }
348
397
 
349
398
  // these are the cases we will handle: same origin, different page
350
399
  ev.preventDefault();
351
- persistState({ index: currentHistoryIndex, scrollY });
352
400
  navigate('forward', new URL(link.href));
353
401
  });
354
402
 
355
403
  addEventListener('popstate', (ev) => {
356
404
  if (!transitionEnabledOnThisPage() && ev.state) {
357
- // The current page doesn't haven't View Transitions,
358
- // respect that with a full page reload
359
- // -- but only for transition managed by us (ev.state is set)
405
+ // The current page doesn't have View Transitions enabled
406
+ // but the page we navigate to does (because it set the state).
407
+ // Do a full page refresh to reload the client-side router from the new page.
408
+ // Scroll restauration will then happen during the reload when the router's code is re-executed
360
409
  history.scrollRestoration && (history.scrollRestoration = 'manual');
361
410
  location.reload();
362
411
  return;
@@ -383,7 +432,11 @@ const { fallback = 'animate' } = Astro.props as Props;
383
432
  const nextIndex = state.index;
384
433
  const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
385
434
  currentHistoryIndex = nextIndex;
386
- navigate(direction, new URL(location.href), state);
435
+ if (state.scrollY < 0) {
436
+ scrollTo(state.scrollX, -(state.scrollY + 1));
437
+ } else {
438
+ navigate(direction, new URL(location.href), state);
439
+ }
387
440
  });
388
441
 
389
442
  ['mouseenter', 'touchstart', 'focus'].forEach((evName) => {
@@ -408,7 +461,7 @@ const { fallback = 'animate' } = Astro.props as Props;
408
461
  // There's not a good way to record scroll position before a back button.
409
462
  // So the way we do it is by listening to scrollend if supported, and if not continuously record the scroll position.
410
463
  const updateState = () => {
411
- persistState({ ...history.state, scrollY });
464
+ persistState({ ...history.state, scrollX, scrollY });
412
465
  };
413
466
 
414
467
  if ('onscrollend' in window) addEventListener('scrollend', updateState);
@@ -543,12 +543,12 @@ export interface AstroUserConfig {
543
543
  * @description
544
544
  *
545
545
  * Specify the strategy used for scoping styles within Astro components. Choose from:
546
- * - `'where'` - Use `:where` selectors, causing no specifity increase.
547
- * - `'class'` - Use class-based selectors, causing a +1 specifity increase.
548
- * - `'attribute'` - Use `data-` attributes, causing no specifity increase.
546
+ * - `'where'` - Use `:where` selectors, causing no specificity increase.
547
+ * - `'class'` - Use class-based selectors, causing a +1 specificity increase.
548
+ * - `'attribute'` - Use `data-` attributes, causing a +1 specificity increase.
549
549
  *
550
550
  * Using `'class'` is helpful when you want to ensure that element selectors within an Astro component override global style defaults (e.g. from a global stylesheet).
551
- * Using `'where'` gives you more control over specifity, but requires that you use higher-specifity selectors, layers, and other tools to control which selectors are applied.
551
+ * Using `'where'` gives you more control over specificity, but requires that you use higher-specificity selectors, layers, and other tools to control which selectors are applied.
552
552
  * Using `'attribute'` is useful when you are manipulating the `class` attribute of elements and need to avoid conflicts between your own styling logic and Astro's application of styles.
553
553
  */
554
554
  scopedStyleStrategy?: 'where' | 'class' | 'attribute';
@@ -911,7 +911,7 @@ export interface AstroUserConfig {
911
911
  * }
912
912
  * ```
913
913
  */
914
- service: ImageServiceConfig;
914
+ service?: ImageServiceConfig;
915
915
  /**
916
916
  * @docs
917
917
  * @name image.domains
@@ -296,7 +296,7 @@ function getContentPaths({ srcDir, root }, fs = fsMod) {
296
296
  };
297
297
  }
298
298
  function search(fs, srcDir) {
299
- const paths = ["config.mjs", "config.js", "config.ts"].map(
299
+ const paths = ["config.mjs", "config.js", "config.mts", "config.ts"].map(
300
300
  (p) => new URL(`./content/${p}`, srcDir)
301
301
  );
302
302
  for (const file of paths) {
@@ -429,6 +429,9 @@ async function generatePath(pathname, gopts, pipeline) {
429
429
  <body>
430
430
  <a href="${location}">Redirecting from <code>${fromPath}</code> to <code>${location}</code></a>
431
431
  </body>`;
432
+ if (pipeline.getConfig().compressHTML === true) {
433
+ body = body.replaceAll("\n", "");
434
+ }
432
435
  if (pageData.route.type !== "redirect") {
433
436
  pageData.route.redirect = location;
434
437
  }
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "3.0.12";
1
+ const ASTRO_VERSION = "3.0.13";
2
2
  const SUPPORTED_MARKDOWN_FILE_EXTENSIONS = [
3
3
  ".markdown",
4
4
  ".mdown",
@@ -20,7 +20,7 @@ async function dev(inlineConfig) {
20
20
  base: restart.container.settings.config.base
21
21
  })
22
22
  );
23
- const currentVersion = "3.0.12";
23
+ const currentVersion = "3.0.13";
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.0.12";
53
+ const version = "3.0.13";
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.0.12"}`
238
+ `v${"3.0.13"}`
239
239
  )} ${headline}`
240
240
  );
241
241
  }
@@ -11,9 +11,7 @@ function createAssetLink(href, base, assetsPrefix) {
11
11
  function createStylesheetElement(stylesheet, base, assetsPrefix) {
12
12
  if (stylesheet.type === "inline") {
13
13
  return {
14
- props: {
15
- type: "text/css"
16
- },
14
+ props: {},
17
15
  children: stylesheet.content
18
16
  };
19
17
  } else {
@@ -32,5 +32,5 @@ function isStyle(node) {
32
32
  return node.nodeType === node.ELEMENT_NODE && node.tagName.toLowerCase() === "style";
33
33
  }
34
34
  function isViteInjectedStyle(node) {
35
- return isStyle(node) && node.getAttribute("type") === "text/css" && !!node.getAttribute("data-vite-dev-id");
35
+ return isStyle(node) && !!node.getAttribute("data-vite-dev-id");
36
36
  }
@@ -14,7 +14,7 @@ function renderUniqueStylesheet(result, sheet) {
14
14
  if (sheet.type === "inline") {
15
15
  if (Array.from(result.styles).some((s) => s.children.includes(sheet.content)))
16
16
  return "";
17
- return renderElement("style", { props: { type: "text/css" }, children: sheet.content });
17
+ return renderElement("style", { props: {}, children: sheet.content });
18
18
  }
19
19
  }
20
20
  export {
@@ -241,7 +241,6 @@ async function getScriptsAndStyles({ pipeline, filePath }) {
241
241
  });
242
242
  styles.add({
243
243
  props: {
244
- type: "text/css",
245
244
  // Track the ID so we can match it to Vite's injected style later
246
245
  "data-astro-dev-id": viteID(new URL(`.${url}`, settings.config.root))
247
246
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "3.0.12",
3
+ "version": "3.0.13",
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",