Merge branch 'main' into bare-standalone-paths

This commit is contained in:
Rishi Raj Jain 2023-09-28 00:21:32 +05:30 committed by GitHub
commit 0b232095bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
96 changed files with 1203 additions and 652 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fix logLevel passed to Vite build

View file

@ -0,0 +1,17 @@
---
'astro': minor
---
View transitions can now be triggered from JavaScript!
Import the client-side router from "astro:transitions/client" and enjoy your new remote control for navigation:
```js
import { navigate } from 'astro:transitions/client';
// Navigate to the selected option automatically.
document.querySelector('select').onchange = (ev) => {
let href = ev.target.value;
navigate(href);
};
```

View file

@ -0,0 +1,5 @@
---
"astro": patch
---
Fix NoImageMetadata image path error message

View file

@ -1,5 +0,0 @@
---
'astro': patch
---
Fix build not working when having multiple images in the same Markdown file

View file

@ -1,5 +0,0 @@
---
'create-astro': patch
---
Fix `--yes` behaviour to prevent it overriding `--template`

View file

@ -1,5 +0,0 @@
---
'astro': patch
---
fix: no deletion of scripts during view transition

View file

@ -1,5 +0,0 @@
---
'@astrojs/cloudflare': minor
---
Add support for loading wasm modules in the cloudflare adapter

View file

@ -1,5 +0,0 @@
---
'astro': patch
---
Fix getDataEntryById to lookup by basename

View file

@ -1,5 +0,0 @@
---
'astro': patch
---
Warn on empty content collections

View file

@ -1,5 +0,0 @@
---
'astro': patch
---
Improve the logging of assets for adapters that do not support image optimization

View file

@ -11,6 +11,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^3.1.2"
"astro": "^3.1.4"
}
}

View file

@ -14,6 +14,6 @@
"@astrojs/mdx": "^1.1.0",
"@astrojs/rss": "^3.0.0",
"@astrojs/sitemap": "^3.0.0",
"astro": "^3.1.2"
"astro": "^3.1.4"
}
}

View file

@ -15,7 +15,7 @@
],
"scripts": {},
"devDependencies": {
"astro": "^3.1.2"
"astro": "^3.1.4"
},
"peerDependencies": {
"astro": "^2.0.0-beta.0"

View file

@ -10,9 +10,9 @@
"astro": "astro"
},
"dependencies": {
"astro": "^3.1.2"
"astro": "^3.1.4"
},
"devDependencies": {
"@astrojs/deno": "^5.0.0"
"@astrojs/deno": "^5.0.1"
}
}

View file

@ -14,6 +14,6 @@
"@astrojs/alpinejs": "^0.3.0",
"@types/alpinejs": "^3.7.2",
"alpinejs": "^3.12.3",
"astro": "^3.1.2"
"astro": "^3.1.4"
}
}

View file

@ -13,7 +13,7 @@
"dependencies": {
"@astrojs/lit": "^3.0.0",
"@webcomponents/template-shadowroot": "^0.2.1",
"astro": "^3.1.2",
"astro": "^3.1.4",
"lit": "^2.8.0"
}
}

View file

@ -16,7 +16,7 @@
"@astrojs/solid-js": "^3.0.1",
"@astrojs/svelte": "^4.0.2",
"@astrojs/vue": "^3.0.0",
"astro": "^3.1.2",
"astro": "^3.1.4",
"preact": "^10.17.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View file

@ -13,7 +13,7 @@
"dependencies": {
"@astrojs/preact": "^3.0.0",
"@preact/signals": "^1.2.1",
"astro": "^3.1.2",
"astro": "^3.1.4",
"preact": "^10.17.1"
}
}

View file

@ -14,7 +14,7 @@
"@astrojs/react": "^3.0.2",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"astro": "^3.1.2",
"astro": "^3.1.4",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}

View file

@ -12,7 +12,7 @@
},
"dependencies": {
"@astrojs/solid-js": "^3.0.1",
"astro": "^3.1.2",
"astro": "^3.1.4",
"solid-js": "^1.7.11"
}
}

View file

@ -12,7 +12,7 @@
},
"dependencies": {
"@astrojs/svelte": "^4.0.2",
"astro": "^3.1.2",
"astro": "^3.1.4",
"svelte": "^4.2.0"
}
}

View file

@ -12,7 +12,7 @@
},
"dependencies": {
"@astrojs/vue": "^3.0.0",
"astro": "^3.1.2",
"astro": "^3.1.4",
"vue": "^3.3.4"
}
}

View file

@ -12,6 +12,6 @@
},
"dependencies": {
"@astrojs/node": "^6.0.1",
"astro": "^3.1.2"
"astro": "^3.1.4"
}
}

View file

@ -15,7 +15,7 @@
],
"scripts": {},
"devDependencies": {
"astro": "^3.1.2"
"astro": "^3.1.4"
},
"peerDependencies": {
"astro": "^2.0.0-beta.0"

View file

@ -13,7 +13,7 @@
},
"dependencies": {
"@astrojs/node": "^6.0.1",
"astro": "^3.1.2",
"astro": "^3.1.4",
"html-minifier": "^4.0.0"
}
}

View file

@ -11,6 +11,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^3.1.2"
"astro": "^3.1.4"
}
}

View file

@ -11,6 +11,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^3.1.2"
"astro": "^3.1.4"
}
}

View file

@ -11,6 +11,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^3.1.2"
"astro": "^3.1.4"
}
}

View file

@ -14,7 +14,7 @@
"dependencies": {
"@astrojs/node": "^6.0.1",
"@astrojs/svelte": "^4.0.2",
"astro": "^3.1.2",
"astro": "^3.1.4",
"svelte": "^4.2.0"
}
}

View file

@ -12,6 +12,6 @@
},
"dependencies": {
"@astrojs/markdoc": "^0.5.0",
"astro": "^3.1.2"
"astro": "^3.1.4"
}
}

View file

@ -12,7 +12,7 @@
},
"dependencies": {
"@astrojs/markdown-remark": "^3.2.0",
"astro": "^3.1.2",
"astro": "^3.1.4",
"hast-util-select": "^5.0.5",
"rehype-autolink-headings": "^6.1.1",
"rehype-slug": "^5.1.0",

View file

@ -11,6 +11,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^3.1.2"
"astro": "^3.1.4"
}
}

View file

@ -13,7 +13,7 @@
"dependencies": {
"@astrojs/mdx": "^1.1.0",
"@astrojs/preact": "^3.0.0",
"astro": "^3.1.2",
"astro": "^3.1.4",
"preact": "^10.17.1"
}
}

View file

@ -13,7 +13,7 @@
"dependencies": {
"@astrojs/preact": "^3.0.0",
"@nanostores/preact": "^0.5.0",
"astro": "^3.1.2",
"astro": "^3.1.4",
"nanostores": "^0.9.3",
"preact": "^10.17.1"
}

View file

@ -14,7 +14,7 @@
"@astrojs/mdx": "^1.1.0",
"@astrojs/tailwind": "^5.0.0",
"@types/canvas-confetti": "^1.6.0",
"astro": "^3.1.2",
"astro": "^3.1.4",
"autoprefixer": "^10.4.15",
"canvas-confetti": "^1.6.0",
"postcss": "^8.4.28",

View file

@ -11,7 +11,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^3.1.2",
"astro": "^3.1.4",
"vite-plugin-pwa": "0.16.4",
"workbox-window": "^7.0.0"
}

View file

@ -12,7 +12,7 @@
"test": "vitest"
},
"dependencies": {
"astro": "^3.1.2",
"astro": "^3.1.4",
"vitest": "^0.34.2"
}
}

View file

@ -1,5 +1,27 @@
# astro
## 3.1.4
### Patch Changes
- [#8646](https://github.com/withastro/astro/pull/8646) [`69fbf95b2`](https://github.com/withastro/astro/commit/69fbf95b22c0fb0d8e7e5fef9ec61e26cac9767f) Thanks [@matthewp](https://github.com/matthewp)! - Fix cases of head propagation not occuring in dev server
## 3.1.3
### Patch Changes
- [#8591](https://github.com/withastro/astro/pull/8591) [`863f5171e`](https://github.com/withastro/astro/commit/863f5171e8e7516c9d72f2e48ea7db1dea71c4f5) Thanks [@rishi-raj-jain](https://github.com/rishi-raj-jain)! - add site url to the location of redirect
- [#8633](https://github.com/withastro/astro/pull/8633) [`63141f3f3`](https://github.com/withastro/astro/commit/63141f3f3e4a57d2f55ccfebd7e506ea1033a1ab) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Fix build not working when having multiple images in the same Markdown file
- [#8636](https://github.com/withastro/astro/pull/8636) [`974d5117a`](https://github.com/withastro/astro/commit/974d5117abc8b47f8225e455b9285c88e305272f) Thanks [@martrapp](https://github.com/martrapp)! - fix: no deletion of scripts during view transition
- [#8645](https://github.com/withastro/astro/pull/8645) [`cb838b84b`](https://github.com/withastro/astro/commit/cb838b84b457041b0442996f7611b04aa940a620) Thanks [@matthewp](https://github.com/matthewp)! - Fix getDataEntryById to lookup by basename
- [#8640](https://github.com/withastro/astro/pull/8640) [`f36c4295b`](https://github.com/withastro/astro/commit/f36c4295be1ef2bcfa4aecb3c59551388419c53d) Thanks [@matthewp](https://github.com/matthewp)! - Warn on empty content collections
- [#8615](https://github.com/withastro/astro/pull/8615) [`4c4ad9d16`](https://github.com/withastro/astro/commit/4c4ad9d167e8d15ff2c15e3336ede8ca22f646b2) Thanks [@alexanderniebuhr](https://github.com/alexanderniebuhr)! - Improve the logging of assets for adapters that do not support image optimization
## 3.1.2
### Patch Changes

View file

@ -120,6 +120,13 @@ declare module 'astro:transitions' {
export const ViewTransitions: ViewTransitionsModule['default'];
}
declare module 'astro:transitions/client' {
type TransitionRouterModule = typeof import('./dist/transitions/router.js');
export const supportsViewTransitions: TransitionRouterModule['supportsViewTransitions'];
export const transitionEnabledOnThisPage: TransitionRouterModule['transitionEnabledOnThisPage'];
export const navigate: TransitionRouterModule['navigate'];
}
declare module 'astro:middleware' {
export * from 'astro/middleware/namespace';
}

View file

@ -11,91 +11,12 @@ const { fallback = 'animate' } = Astro.props as Props;
<meta name="astro-view-transitions-enabled" content="true" />
<meta name="astro-view-transitions-fallback" content={fallback} />
<script>
type Fallback = 'none' | 'animate' | 'swap';
type Direction = 'forward' | 'back';
type State = {
index: number;
scrollX: number;
scrollY: number;
intraPage?: boolean;
};
type Events = 'astro:page-load' | 'astro:after-swap';
// only update history entries that are managed by us
// leave other entries alone and do not accidently add state.
const persistState = (state: State) => history.state && history.replaceState(state, '');
// @ts-expect-error: startViewTransition might exist
const supportsViewTransitions = !!document.startViewTransition;
const transitionEnabledOnThisPage = () =>
!!document.querySelector('[name="astro-view-transitions-enabled"]');
const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name));
const onPageLoad = () => triggerEvent('astro:page-load');
const PERSIST_ATTR = 'data-astro-transition-persist';
const parser = new DOMParser();
// explained at its usage
let noopEl: HTMLDivElement;
if (import.meta.env.DEV) {
noopEl = document.createElement('div');
}
// The History API does not tell you if navigation is forward or back, so
// you can figure it using an index. On pushState the index is incremented so you
// can use that to determine popstate if going forward or back.
let currentHistoryIndex = 0;
if (history.state) {
// we reloaded a page with history state
// (e.g. history navigation from non-transition page or browser reload)
currentHistoryIndex = history.state.index;
scrollTo({ left: history.state.scrollX, top: history.state.scrollY });
} else if (transitionEnabledOnThisPage()) {
history.replaceState({ index: currentHistoryIndex, scrollX, scrollY, intraPage: false }, '');
}
const throttle = (cb: (...args: any[]) => any, delay: number) => {
let wait = false;
// During the waiting time additional events are lost.
// So repeat the callback at the end if we have swallowed events.
let onceMore = false;
return (...args: any[]) => {
if (wait) {
onceMore = true;
return;
}
cb(...args);
wait = true;
setTimeout(() => {
if (onceMore) {
onceMore = false;
cb(...args);
}
wait = false;
}, delay);
};
};
// returns the contents of the page or null if the router can't deal with it.
async function fetchHTML(
href: string
): Promise<null | { html: string; redirected?: string; mediaType: DOMParserSupportedType }> {
try {
const res = await fetch(href);
// drop potential charset (+ other name/value pairs) as parser needs the mediaType
const mediaType = res.headers.get('content-type')?.replace(/;.*$/, '');
// the DOMParser can handle two types of HTML
if (mediaType !== 'text/html' && mediaType !== 'application/xhtml+xml') {
// everything else (e.g. audio/mp3) will be handled by the browser but not by us
return null;
}
const html = await res.text();
return {
html,
redirected: res.redirected ? res.url : undefined,
mediaType,
};
} catch (err) {
// can't fetch, let someone else deal with it.
return null;
}
}
import {
supportsViewTransitions,
transitionEnabledOnThisPage,
navigate,
} from 'astro:transitions/client';
export type Fallback = 'none' | 'animate' | 'swap';
function getFallback(): Fallback {
const el = document.querySelector('[name="astro-view-transitions-fallback"]');
@ -105,263 +26,6 @@ const { fallback = 'animate' } = Astro.props as Props;
return 'animate';
}
function markScriptsExec() {
for (const script of document.scripts) {
script.dataset.astroExec = '';
}
}
function runScripts() {
let wait = Promise.resolve();
for (const script of Array.from(document.scripts)) {
if (script.dataset.astroExec === '') continue;
const newScript = document.createElement('script');
newScript.innerHTML = script.innerHTML;
for (const attr of script.attributes) {
if (attr.name === 'src') {
const p = new Promise((r) => {
newScript.onload = r;
});
wait = wait.then(() => p as any);
}
newScript.setAttribute(attr.name, attr.value);
}
newScript.dataset.astroExec = '';
script.replaceWith(newScript);
}
return wait;
}
function isInfinite(animation: Animation) {
const effect = animation.effect;
if (!effect || !(effect instanceof KeyframeEffect) || !effect.target) return false;
const style = window.getComputedStyle(effect.target, effect.pseudoElement);
return style.animationIterationCount === 'infinite';
}
const updateHistoryAndScrollPosition = (toLocation) => {
if (toLocation.href !== location.href) {
history.pushState(
{ index: ++currentHistoryIndex, scrollX: 0, scrollY: 0 },
'',
toLocation.href
);
// now we are on the new page for non-history navigations!
// (with history navigation page change happens before popstate is fired)
}
// freshly loaded pages start from the top
scrollTo({ left: 0, top: 0, behavior: 'instant' });
if (toLocation.hash) {
// because we are already on the target page ...
// ... what comes next is a intra-page navigation
// that won't reload the page but instead scroll to the fragment
location.href = toLocation.href;
}
};
// replace head and body of the windows document with contents from newDocument
// if !popstate, update the history entry and scroll position according to toLocation
// if popState is given, this holds the scroll position for history navigation
// if fallback === "animate" then simulate view transitions
async function updateDOM(
newDocument: Document,
toLocation: URL,
popState?: State,
fallback?: Fallback
) {
// Check for a head element that should persist and returns it,
// either because it has the data attribute or is a link el.
const persistedHeadElement = (el: HTMLElement): Element | null => {
const id = el.getAttribute(PERSIST_ATTR);
const newEl = id && newDocument.head.querySelector(`[${PERSIST_ATTR}="${id}"]`);
if (newEl) {
return newEl;
}
if (el.matches('link[rel=stylesheet]')) {
const href = el.getAttribute('href');
return newDocument.head.querySelector(`link[rel=stylesheet][href="${href}"]`);
}
// What follows is a fix for an issue (#8472) with missing client:only styles after transition.
// That problem exists only in dev mode where styles are injected into the page by Vite.
// Returning a noop element ensures that the styles are not removed from the old document.
// Guarding the code below with the dev mode check
// allows tree shaking to remove this code in production.
if (import.meta.env.DEV) {
if (el.tagName === 'STYLE' && el.dataset.viteDevId) {
const devId = el.dataset.viteDevId;
// If this same style tag exists, remove it from the new page
return (
newDocument.querySelector(`style[data-astro-dev-id="${devId}"]`) ||
// Otherwise, keep it anyways. This is client:only styles.
noopEl
);
}
}
return null;
};
const swap = () => {
// swap attributes of the html element
// - delete all attributes from the current document
// - insert all attributes from doc
// - reinsert all original attributes that are named 'data-astro-*'
const html = document.documentElement;
const astro = [...html.attributes].filter(
({ name }) => (html.removeAttribute(name), name.startsWith('data-astro-'))
);
[...newDocument.documentElement.attributes, ...astro].forEach(({ name, value }) =>
html.setAttribute(name, value)
);
// Replace scripts in both the head and body.
for (const s1 of document.scripts) {
for (const s2 of newDocument.scripts) {
if (
// Inline
(!s1.src && s1.textContent === s2.textContent) ||
// External
(s1.src && s1.type === s2.type && s1.src === s2.src)
) {
// the old script is in the new document: we mark it as executed to prevent re-execution
s2.dataset.astroExec = '';
break;
}
}
}
// Swap head
for (const el of Array.from(document.head.children)) {
const newEl = persistedHeadElement(el as HTMLElement);
// If the element exists in the document already, remove it
// from the new document and leave the current node alone
if (newEl) {
newEl.remove();
} else {
// Otherwise remove the element in the head. It doesn't exist in the new page.
el.remove();
}
}
// Everything left in the new head is new, append it all.
document.head.append(...newDocument.head.children);
// Persist elements in the existing body
const oldBody = document.body;
// this will reset scroll Position
document.body.replaceWith(newDocument.body);
for (const el of oldBody.querySelectorAll(`[${PERSIST_ATTR}]`)) {
const id = el.getAttribute(PERSIST_ATTR);
const newEl = document.querySelector(`[${PERSIST_ATTR}="${id}"]`);
if (newEl) {
// The element exists in the new page, replace it with the element
// from the old page so that state is preserved.
newEl.replaceWith(el);
}
}
if (popState) {
scrollTo(popState.scrollX, popState.scrollY); // usings 'auto' scrollBehavior
} else {
updateHistoryAndScrollPosition(toLocation);
}
triggerEvent('astro:after-swap');
};
// Wait on links to finish, to prevent FOUC
const links: Promise<any>[] = [];
for (const el of newDocument.querySelectorAll('head link[rel=stylesheet]')) {
// Do not preload links that are already on the page.
if (
!document.querySelector(
`[${PERSIST_ATTR}="${el.getAttribute(PERSIST_ATTR)}"], link[rel=stylesheet]`
)
) {
const c = document.createElement('link');
c.setAttribute('rel', 'preload');
c.setAttribute('as', 'style');
c.setAttribute('href', el.getAttribute('href')!);
links.push(
new Promise<any>((resolve) => {
['load', 'error'].forEach((evName) => c.addEventListener(evName, resolve));
document.head.append(c);
})
);
}
}
links.length && (await Promise.all(links));
if (fallback === 'animate') {
// Trigger the animations
const currentAnimations = document.getAnimations();
document.documentElement.dataset.astroTransitionFallback = 'old';
const newAnimations = document
.getAnimations()
.filter((a) => !currentAnimations.includes(a) && !isInfinite(a));
const finished = Promise.all(newAnimations.map((a) => a.finished));
const fallbackSwap = () => {
swap();
document.documentElement.dataset.astroTransitionFallback = 'new';
};
await finished;
fallbackSwap();
} else {
swap();
}
}
async function transition(direction: Direction, toLocation: URL, popState?: State) {
let finished: Promise<void>;
const href = toLocation.href;
const response = await fetchHTML(href);
// If there is a problem fetching the new page, just do an MPA navigation to it.
if (response === null) {
location.href = href;
return;
}
// if there was a redirection, show the final URL in the browser's address bar
if (response.redirected) {
toLocation = new URL(response.redirected);
}
const newDocument = parser.parseFromString(response.html, response.mediaType);
// The next line might look like a hack,
// but it is actually necessary as noscript elements
// and their contents are returned as markup by the parser,
// see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString
newDocument.querySelectorAll('noscript').forEach((el) => el.remove());
if (!newDocument.querySelector('[name="astro-view-transitions-enabled"]')) {
location.href = href;
return;
}
if (!popState) {
// save the current scroll position before we change the DOM and transition to the new page
history.replaceState({ ...history.state, scrollX, scrollY }, '');
}
document.documentElement.dataset.astroTransition = direction;
if (supportsViewTransitions) {
// @ts-expect-error: startViewTransition exist
finished = document.startViewTransition(() =>
updateDOM(newDocument, toLocation, popState)
).finished;
} else {
finished = updateDOM(newDocument, toLocation, popState, getFallback());
}
try {
await finished;
} finally {
// skip this for the moment as it tends to stop fallback animations
// document.documentElement.removeAttribute('data-astro-transition');
await runScripts();
markScriptsExec();
onPageLoad();
}
}
// Prefetching
function maybePrefetch(pathname: string) {
if (document.querySelector(`link[rel=prefetch][href="${pathname}"]`)) return;
@ -406,83 +70,9 @@ const { fallback = 'animate' } = Astro.props as Props;
return;
}
ev.preventDefault();
navigate(link.href);
});
function navigate(href) {
// not ours
if (!transitionEnabledOnThisPage()) {
location.href = href;
return;
}
const toLocation = new URL(href, location.href);
// We do not have page transitions on navigations to the same page (intra-page navigation)
// but we want to handle prevent reload on navigation to the same page
// Same page means same origin, path and query params (but maybe different hash)
if (
location.origin === toLocation.origin &&
location.pathname === toLocation.pathname &&
location.search === toLocation.search
) {
// mark current position as non transition intra-page scrolling
if (location.href !== toLocation.href) {
history.replaceState({ ...history.state, intraPage: true }, '');
history.pushState(
{ index: ++currentHistoryIndex, scrollX: 0, scrollY: 0 },
'',
toLocation.href
);
}
if (toLocation.hash) {
location.href = toLocation.href;
} else {
scrollTo({ left: 0, top: 0, behavior: 'instant' });
}
} else {
transition('forward', toLocation);
}
}
addEventListener('popstate', (ev) => {
if (!transitionEnabledOnThisPage() && ev.state) {
// The current page doesn't have View Transitions enabled
// but the page we navigate to does (because it set the state).
// Do a full page refresh to reload the client-side router from the new page.
// Scroll restauration will then happen during the reload when the router's code is re-executed
if (history.scrollRestoration) {
history.scrollRestoration = 'manual';
}
location.reload();
return;
}
// History entries without state are created by the browser (e.g. for hash links)
// Our view transition entries always have state.
// Just ignore stateless entries.
// The browser will handle navigation fine without our help
if (ev.state === null) {
if (history.scrollRestoration) {
history.scrollRestoration = 'auto';
}
return;
}
// With the default "auto", the browser will jump to the old scroll position
// before the ViewTransition is complete.
if (history.scrollRestoration) {
history.scrollRestoration = 'manual';
}
const state: State = history.state;
if (state.intraPage) {
// this is non transition intra-page scrolling
scrollTo(state.scrollX, state.scrollY);
} else {
const nextIndex = state.index;
const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
currentHistoryIndex = nextIndex;
transition(direction, new URL(location.href), state);
}
navigate(link.href, {
history: link.dataset.astroHistory === 'replace' ? 'replace' : 'auto',
});
});
['mouseenter', 'touchstart', 'focus'].forEach((evName) => {
@ -503,17 +93,5 @@ const { fallback = 'animate' } = Astro.props as Props;
{ passive: true, capture: true }
);
});
addEventListener('load', onPageLoad);
// There's not a good way to record scroll position before a back button.
// So the way we do it is by listening to scrollend if supported, and if not continuously record the scroll position.
const updateState = () => {
persistState({ ...history.state, scrollX, scrollY });
};
if ('onscrollend' in window) addEventListener('scrollend', updateState);
else addEventListener('scroll', throttle(updateState, 300));
markScriptsExec();
}
</script>

View file

@ -0,0 +1,20 @@
---
import Layout from '../components/Layout.astro';
---
<Layout>
<p id="six">Page 6</p>
<a id="click-one" href="/one">test</a>
<div id="test">test content</div>
<script>
import { navigate } from "astro:transitions/client";
// this is to simulate client side use, will be triggered from test
window.addEventListener('jumpToTwo', ()=>navigate('/two'));
// this is a holder to pick up the router in additional tests
window.clientSideRouterForTestsParkedHere = navigate
// this is the direct use of the router in this page, redirecting to page one
navigate('/one');
</script>
</Layout>

View file

@ -4,6 +4,7 @@ import Layout from '../components/Layout.astro';
<Layout link="/two.css">
<p id="two">Page 2</p>
<article id="twoarticle"></article>
<a id="click-longpage" data-astro-history="replace" href="/long-page">go to long page</a>
</Layout>
<script>
document.addEventListener('astro:page-load', () => {

View file

@ -680,6 +680,39 @@ test.describe('View Transitions', () => {
await expect(locator).not.toBeInViewport();
});
test('Use the client side router', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/six'));
// page six loads the router and automatically uses the router to navigate to page 1
let p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// nudge to jump to page 2
await page.evaluate(() => {
window.dispatchEvent(new Event('jumpToTwo'));
});
p = page.locator('#two');
await expect(p, 'should have content').toHaveText('Page 2');
// jump to page 3
await page.evaluate(() => {
// get the router from its fixture park position
const navigate = window.clientSideRouterForTestsParkedHere;
navigate('/three');
});
p = page.locator('#three');
await expect(p, 'should have content').toHaveText('Page 3');
// go back
await page.goBack();
p = page.locator('#two');
await expect(p, 'should have content').toHaveText('Page 2');
// no bad things happen when we revisit redirecting to page 6
await page.goto(astro.resolveUrl('/six'));
p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
});
test('body inline scripts do not re-execute on navigation', async ({ page, astro }) => {
const errors = [];
page.addListener('pageerror', (err) => {
@ -697,4 +730,52 @@ test.describe('View Transitions', () => {
expect(errors).toHaveLength(0);
});
test('replace history', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/one'));
// page six loads the router and automatically uses the router to navigate to page 1
let p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// go to page 2
await page.click('#click-two');
p = page.locator('#two');
await expect(p, 'should have content').toHaveText('Page 2');
// replace with long page
await page.click('#click-longpage');
let article = page.locator('#longpage');
await expect(article, 'should have script content').toBeVisible('exists');
// one step back == #1
await page.goBack();
p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
});
test('CSR replace history', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/six'));
// page six loads the router and automatically uses the router to navigate to page 1
let p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// goto #2
await page.evaluate(() => {
window.clientSideRouterForTestsParkedHere('/two', { history: 'auto' });
});
p = page.locator('#two');
await expect(p, 'should have content').toHaveText('Page 2');
// replace with long page
await page.evaluate(() => {
window.clientSideRouterForTestsParkedHere('/long-page', { history: 'replace' });
});
let article = page.locator('#longpage');
await expect(article, 'should have script content').toBeVisible('exists');
// one step back == #1
await page.goBack();
p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
});
});

View file

@ -1,6 +1,6 @@
{
"name": "astro",
"version": "3.1.2",
"version": "3.1.4",
"description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
"type": "module",
"author": "withastro",
@ -77,7 +77,8 @@
"types": "./dist/core/middleware/namespace.d.ts",
"default": "./dist/core/middleware/namespace.js"
},
"./transitions": "./dist/transitions/index.js"
"./transitions": "./dist/transitions/index.js",
"./transitions/router": "./dist/transitions/router.js"
},
"imports": {
"#astro/*": "./dist/*.js"

View file

@ -574,7 +574,9 @@ async function generatePath(pathname: string, gopts: GeneratePathOptions, pipeli
if (!pipeline.getConfig().build.redirects) {
return;
}
const location = getRedirectLocationOrThrow(response.headers);
const locationSite = getRedirectLocationOrThrow(response.headers);
const siteURL = pipeline.getConfig().site;
const location = siteURL ? new URL(locationSite, siteURL) : locationSite;
const fromPath = new URL(renderContext.request.url).pathname;
// A short delay causes Google to interpret the redirect as temporary.
// https://developers.google.com/search/docs/crawling-indexing/301-redirects#metarefresh
@ -592,7 +594,7 @@ async function generatePath(pathname: string, gopts: GeneratePathOptions, pipeli
}
// A dynamic redirect, set the location so that integrations know about it.
if (pageData.route.type !== 'redirect') {
pageData.route.redirect = location;
pageData.route.redirect = location.toString();
}
} else {
// If there's no body, do nothing

View file

@ -154,7 +154,8 @@ async function ssrBuild(
const viteBuildConfig: vite.InlineConfig = {
...viteConfig,
mode: viteConfig.mode || 'production',
logLevel: opts.viteConfig.logLevel ?? 'error',
// Check using `settings...` as `viteConfig` always defaults to `warn` by Astro
logLevel: settings.config.vite.logLevel ?? 'error',
build: {
target: 'esnext',
// Vite defaults cssMinify to false in SSR by default, but we want to minify it
@ -260,7 +261,8 @@ async function clientBuild(
const viteBuildConfig: vite.InlineConfig = {
...viteConfig,
mode: viteConfig.mode || 'production',
logLevel: 'info',
// Check using `settings...` as `viteConfig` always defaults to `warn` by Astro
logLevel: settings.config.vite.logLevel ?? 'info',
build: {
target: 'esnext',
...viteConfig.build,

View file

@ -243,8 +243,6 @@ export async function createVite(
}
result = vite.mergeConfig(result, commandConfig);
result.customLogger = vite.createLogger(result.logLevel ?? 'warn');
return result;
}

View file

@ -649,7 +649,7 @@ export const NoImageMetadata = {
name: 'NoImageMetadata',
title: 'Could not process image metadata.',
message: (imagePath: string | undefined) =>
`Could not process image metadata${imagePath ? ' for `${imagePath}`' : ''}.`,
`Could not process image metadata${imagePath ? ` for \`${imagePath}\`` : ''}.`,
hint: 'This is often caused by a corrupted or malformed image. Re-exporting the image from your image editor may fix this issue.',
} satisfies ErrorData;

View file

@ -0,0 +1,437 @@
export type Fallback = 'none' | 'animate' | 'swap';
export type Direction = 'forward' | 'back';
export type Options = { history?: 'auto' | 'push' | 'replace' };
type State = {
index: number;
scrollX: number;
scrollY: number;
intraPage?: boolean;
};
type Events = 'astro:page-load' | 'astro:after-swap';
// only update history entries that are managed by us
// leave other entries alone and do not accidently add state.
const persistState = (state: State) => history.state && history.replaceState(state, '');
export const supportsViewTransitions = !!document.startViewTransition;
export const transitionEnabledOnThisPage = () =>
!!document.querySelector('[name="astro-view-transitions-enabled"]');
const samePage = (otherLocation: URL) =>
location.pathname === otherLocation.pathname && location.search === otherLocation.search;
const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name));
const onPageLoad = () => triggerEvent('astro:page-load');
const PERSIST_ATTR = 'data-astro-transition-persist';
const parser = new DOMParser();
// explained at its usage
let noopEl: HTMLDivElement;
if (import.meta.env.DEV) {
noopEl = document.createElement('div');
}
// The History API does not tell you if navigation is forward or back, so
// you can figure it using an index. On pushState the index is incremented so you
// can use that to determine popstate if going forward or back.
let currentHistoryIndex = 0;
if (history.state) {
// we reloaded a page with history state
// (e.g. history navigation from non-transition page or browser reload)
currentHistoryIndex = history.state.index;
scrollTo({ left: history.state.scrollX, top: history.state.scrollY });
} else if (transitionEnabledOnThisPage()) {
history.replaceState({ index: currentHistoryIndex, scrollX, scrollY, intraPage: false }, '');
}
const throttle = (cb: (...args: any[]) => any, delay: number) => {
let wait = false;
// During the waiting time additional events are lost.
// So repeat the callback at the end if we have swallowed events.
let onceMore = false;
return (...args: any[]) => {
if (wait) {
onceMore = true;
return;
}
cb(...args);
wait = true;
setTimeout(() => {
if (onceMore) {
onceMore = false;
cb(...args);
}
wait = false;
}, delay);
};
};
// returns the contents of the page or null if the router can't deal with it.
async function fetchHTML(
href: string
): Promise<null | { html: string; redirected?: string; mediaType: DOMParserSupportedType }> {
try {
const res = await fetch(href);
// drop potential charset (+ other name/value pairs) as parser needs the mediaType
const mediaType = res.headers.get('content-type')?.replace(/;.*$/, '');
// the DOMParser can handle two types of HTML
if (mediaType !== 'text/html' && mediaType !== 'application/xhtml+xml') {
// everything else (e.g. audio/mp3) will be handled by the browser but not by us
return null;
}
const html = await res.text();
return {
html,
redirected: res.redirected ? res.url : undefined,
mediaType,
};
} catch (err) {
// can't fetch, let someone else deal with it.
return null;
}
}
function getFallback(): Fallback {
const el = document.querySelector('[name="astro-view-transitions-fallback"]');
if (el) {
return el.getAttribute('content') as Fallback;
}
return 'animate';
}
function markScriptsExec() {
for (const script of document.scripts) {
script.dataset.astroExec = '';
}
}
function runScripts() {
let wait = Promise.resolve();
for (const script of Array.from(document.scripts)) {
if (script.dataset.astroExec === '') continue;
const newScript = document.createElement('script');
newScript.innerHTML = script.innerHTML;
for (const attr of script.attributes) {
if (attr.name === 'src') {
const p = new Promise((r) => {
newScript.onload = r;
});
wait = wait.then(() => p as any);
}
newScript.setAttribute(attr.name, attr.value);
}
newScript.dataset.astroExec = '';
script.replaceWith(newScript);
}
return wait;
}
function isInfinite(animation: Animation) {
const effect = animation.effect;
if (!effect || !(effect instanceof KeyframeEffect) || !effect.target) return false;
const style = window.getComputedStyle(effect.target, effect.pseudoElement);
return style.animationIterationCount === 'infinite';
}
const updateHistoryAndScrollPosition = (toLocation: URL, replace: boolean, intraPage: boolean) => {
const fresh = !samePage(toLocation);
if (toLocation.href !== location.href) {
if (replace) {
history.replaceState({ ...history.state }, '', toLocation.href);
} else {
history.replaceState({ ...history.state, intraPage }, '');
history.pushState({ index: ++currentHistoryIndex, scrollX, scrollY }, '', toLocation.href);
}
// now we are on the new page for non-history navigations!
// (with history navigation page change happens before popstate is fired)
// freshly loaded pages start from the top
if (fresh) {
scrollTo({ left: 0, top: 0, behavior: 'instant' });
}
}
if (toLocation.hash) {
// because we are already on the target page ...
// ... what comes next is a intra-page navigation
// that won't reload the page but instead scroll to the fragment
location.href = toLocation.href;
} else {
scrollTo({ left: 0, top: 0, behavior: 'instant' });
}
};
// replace head and body of the windows document with contents from newDocument
// if !popstate, update the history entry and scroll position according to toLocation
// if popState is given, this holds the scroll position for history navigation
// if fallback === "animate" then simulate view transitions
async function updateDOM(
newDocument: Document,
toLocation: URL,
options: Options,
popState?: State,
fallback?: Fallback
) {
// Check for a head element that should persist and returns it,
// either because it has the data attribute or is a link el.
const persistedHeadElement = (el: HTMLElement): Element | null => {
const id = el.getAttribute(PERSIST_ATTR);
const newEl = id && newDocument.head.querySelector(`[${PERSIST_ATTR}="${id}"]`);
if (newEl) {
return newEl;
}
if (el.matches('link[rel=stylesheet]')) {
const href = el.getAttribute('href');
return newDocument.head.querySelector(`link[rel=stylesheet][href="${href}"]`);
}
// What follows is a fix for an issue (#8472) with missing client:only styles after transition.
// That problem exists only in dev mode where styles are injected into the page by Vite.
// Returning a noop element ensures that the styles are not removed from the old document.
// Guarding the code below with the dev mode check
// allows tree shaking to remove this code in production.
if (import.meta.env.DEV) {
if (el.tagName === 'STYLE' && el.dataset.viteDevId) {
const devId = el.dataset.viteDevId;
// If this same style tag exists, remove it from the new page
return (
newDocument.querySelector(`style[data-astro-dev-id="${devId}"]`) ||
// Otherwise, keep it anyways. This is client:only styles.
noopEl
);
}
}
return null;
};
const swap = () => {
// swap attributes of the html element
// - delete all attributes from the current document
// - insert all attributes from doc
// - reinsert all original attributes that are named 'data-astro-*'
const html = document.documentElement;
const astro = [...html.attributes].filter(
({ name }) => (html.removeAttribute(name), name.startsWith('data-astro-'))
);
[...newDocument.documentElement.attributes, ...astro].forEach(({ name, value }) =>
html.setAttribute(name, value)
);
// Replace scripts in both the head and body.
for (const s1 of document.scripts) {
for (const s2 of newDocument.scripts) {
if (
// Inline
(!s1.src && s1.textContent === s2.textContent) ||
// External
(s1.src && s1.type === s2.type && s1.src === s2.src)
) {
// the old script is in the new document: we mark it as executed to prevent re-execution
s2.dataset.astroExec = '';
break;
}
}
}
// Swap head
for (const el of Array.from(document.head.children)) {
const newEl = persistedHeadElement(el as HTMLElement);
// If the element exists in the document already, remove it
// from the new document and leave the current node alone
if (newEl) {
newEl.remove();
} else {
// Otherwise remove the element in the head. It doesn't exist in the new page.
el.remove();
}
}
// Everything left in the new head is new, append it all.
document.head.append(...newDocument.head.children);
// Persist elements in the existing body
const oldBody = document.body;
// this will reset scroll Position
document.body.replaceWith(newDocument.body);
for (const el of oldBody.querySelectorAll(`[${PERSIST_ATTR}]`)) {
const id = el.getAttribute(PERSIST_ATTR);
const newEl = document.querySelector(`[${PERSIST_ATTR}="${id}"]`);
if (newEl) {
// The element exists in the new page, replace it with the element
// from the old page so that state is preserved.
newEl.replaceWith(el);
}
}
if (popState) {
scrollTo(popState.scrollX, popState.scrollY); // usings 'auto' scrollBehavior
} else {
updateHistoryAndScrollPosition(toLocation, options.history === 'replace', false);
}
triggerEvent('astro:after-swap');
};
// Wait on links to finish, to prevent FOUC
const links: Promise<any>[] = [];
for (const el of newDocument.querySelectorAll('head link[rel=stylesheet]')) {
// Do not preload links that are already on the page.
if (
!document.querySelector(
`[${PERSIST_ATTR}="${el.getAttribute(PERSIST_ATTR)}"], link[rel=stylesheet]`
)
) {
const c = document.createElement('link');
c.setAttribute('rel', 'preload');
c.setAttribute('as', 'style');
c.setAttribute('href', el.getAttribute('href')!);
links.push(
new Promise<any>((resolve) => {
['load', 'error'].forEach((evName) => c.addEventListener(evName, resolve));
document.head.append(c);
})
);
}
}
links.length && (await Promise.all(links));
if (fallback === 'animate') {
// Trigger the animations
const currentAnimations = document.getAnimations();
document.documentElement.dataset.astroTransitionFallback = 'old';
const newAnimations = document
.getAnimations()
.filter((a) => !currentAnimations.includes(a) && !isInfinite(a));
const finished = Promise.all(newAnimations.map((a) => a.finished));
const fallbackSwap = () => {
swap();
document.documentElement.dataset.astroTransitionFallback = 'new';
};
await finished;
fallbackSwap();
} else {
swap();
}
}
async function transition(
direction: Direction,
toLocation: URL,
options: Options,
popState?: State
) {
let finished: Promise<void>;
const href = toLocation.href;
const response = await fetchHTML(href);
// If there is a problem fetching the new page, just do an MPA navigation to it.
if (response === null) {
location.href = href;
return;
}
// if there was a redirection, show the final URL in the browser's address bar
if (response.redirected) {
toLocation = new URL(response.redirected);
}
const newDocument = parser.parseFromString(response.html, response.mediaType);
// The next line might look like a hack,
// but it is actually necessary as noscript elements
// and their contents are returned as markup by the parser,
// see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString
newDocument.querySelectorAll('noscript').forEach((el) => el.remove());
if (!newDocument.querySelector('[name="astro-view-transitions-enabled"]')) {
location.href = href;
return;
}
if (!popState) {
// save the current scroll position before we change the DOM and transition to the new page
history.replaceState({ ...history.state, scrollX, scrollY }, '');
}
document.documentElement.dataset.astroTransition = direction;
if (supportsViewTransitions) {
finished = document.startViewTransition(() =>
updateDOM(newDocument, toLocation, options, popState)
).finished;
} else {
finished = updateDOM(newDocument, toLocation, options, popState, getFallback());
}
try {
await finished;
} finally {
// skip this for the moment as it tends to stop fallback animations
// document.documentElement.removeAttribute('data-astro-transition');
await runScripts();
markScriptsExec();
onPageLoad();
}
}
export function navigate(href: string, options?: Options) {
// not ours
if (!transitionEnabledOnThisPage()) {
location.href = href;
return;
}
const toLocation = new URL(href, location.href);
// We do not have page transitions on navigations to the same page (intra-page navigation)
// but we want to handle prevent reload on navigation to the same page
// Same page means same origin, path and query params (but maybe different hash)
if (location.origin === toLocation.origin && samePage(toLocation)) {
updateHistoryAndScrollPosition(toLocation, options?.history === 'replace', true);
} else {
// different origin will be detected by fetch
transition('forward', toLocation, options ?? {});
}
}
if (supportsViewTransitions || getFallback() !== 'none') {
addEventListener('popstate', (ev) => {
if (!transitionEnabledOnThisPage() && ev.state) {
// The current page doesn't have View Transitions enabled
// but the page we navigate to does (because it set the state).
// Do a full page refresh to reload the client-side router from the new page.
// Scroll restauration will then happen during the reload when the router's code is re-executed
if (history.scrollRestoration) {
history.scrollRestoration = 'manual';
}
location.reload();
return;
}
// History entries without state are created by the browser (e.g. for hash links)
// Our view transition entries always have state.
// Just ignore stateless entries.
// The browser will handle navigation fine without our help
if (ev.state === null) {
if (history.scrollRestoration) {
history.scrollRestoration = 'auto';
}
return;
}
// With the default "auto", the browser will jump to the old scroll position
// before the ViewTransition is complete.
if (history.scrollRestoration) {
history.scrollRestoration = 'manual';
}
const state: State = history.state;
if (state.intraPage) {
// this is non transition intra-page scrolling
scrollTo(state.scrollX, state.scrollY);
} else {
const nextIndex = state.index;
const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
currentHistoryIndex = nextIndex;
transition(direction, new URL(location.href), {}, state);
}
});
addEventListener('load', onPageLoad);
// There's not a good way to record scroll position before a back button.
// So the way we do it is by listening to scrollend if supported, and if not continuously record the scroll position.
const updateState = () => {
persistState({ ...history.state, scrollX, scrollY });
};
if ('onscrollend' in window) addEventListener('scrollend', updateState);
else addEventListener('scroll', throttle(updateState, 300));
markScriptsExec();
}

View file

@ -2,6 +2,8 @@ import * as vite from 'vite';
const virtualModuleId = 'astro:transitions';
const resolvedVirtualModuleId = '\0' + virtualModuleId;
const virtualClientModuleId = 'astro:transitions/client';
const resolvedVirtualClientModuleId = '\0' + virtualClientModuleId;
// The virtual module for the astro:transitions namespace
export default function astroTransitions(): vite.Plugin {
@ -11,6 +13,9 @@ export default function astroTransitions(): vite.Plugin {
if (id === virtualModuleId) {
return resolvedVirtualModuleId;
}
if (id === virtualClientModuleId) {
return resolvedVirtualClientModuleId;
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
@ -19,6 +24,11 @@ export default function astroTransitions(): vite.Plugin {
export { default as ViewTransitions } from "astro/components/ViewTransitions.astro";
`;
}
if (id === resolvedVirtualClientModuleId) {
return `
export * from "astro/transitions/router";
`;
}
},
};
}

View file

@ -45,19 +45,46 @@ export default function configHeadVitePlugin(): vite.Plugin {
return {
name: 'astro:head-metadata',
enforce: 'pre',
apply: 'serve',
configureServer(_server) {
server = _server;
},
resolveId(source, importer) {
if (importer) {
// Do propagation any time a new module is imported. This is because
// A module with propagation might be loaded before one of its parent pages
// is loaded, in which case that parent page won't have the in-tree and containsHead
// values. Walking up the tree in resolveId ensures that they do
return this.resolve(source, importer, { skipSelf: true }).then((result) => {
if (result) {
let info = this.getModuleInfo(result.id);
const astro = info && getAstroMetadata(info);
if (astro) {
if (astro.propagation === 'self' || astro.propagation === 'in-tree') {
propagateMetadata.call(this, importer, 'propagation', 'in-tree');
}
if (astro.containsHead) {
propagateMetadata.call(this, importer, 'containsHead', true);
}
}
}
return result;
});
}
},
transform(source, id) {
if (!server) {
return;
}
// TODO This could probably be removed now that this is handled in resolveId
let info = this.getModuleInfo(id);
if (info && getAstroMetadata(info)?.containsHead) {
propagateMetadata.call(this, id, 'containsHead', true);
}
// TODO This could probably be removed now that this is handled in resolveId
if (info && getAstroMetadata(info)?.propagation === 'self') {
const mod = server.moduleGraph.getModuleById(id);
for (const parent of mod?.importers ?? []) {

View file

@ -9,4 +9,7 @@ export default defineConfig({
ssr: {
noExternal: ['@test/static-build-pkg'],
},
redirects: {
'/old': '/new',
},
});

View file

@ -0,0 +1 @@
<p></p>

View file

@ -0,0 +1,8 @@
{
"name": "@test/view-transitions",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}

View file

@ -0,0 +1 @@
<pre transition:name="animate"></pre>

View file

@ -0,0 +1 @@
<slot/>

View file

@ -0,0 +1,4 @@
---
import Animate from './Animate.astro';
---
<Animate />

View file

@ -0,0 +1,11 @@
---
import Animate from '../components/Animate.astro';
---
<html>
<head>
<title>Testing</title>
</head>
<body>
<Animate />
</body>
</html>

View file

@ -0,0 +1,15 @@
---
import AnimateContainer from '../components/AnimateContainer.astro';
import Animations from '../components/Animations.astro';
---
<html>
<head>
<title>Testing</title>
</head>
<body>
<AnimateContainer>
<Animations />
</AnimateContainer>
</body>
</html>

View file

@ -41,6 +41,14 @@ describe('Static build', () => {
await fixture.build({ logger });
});
it('generates canonical redirect page with site prefix', async () => {
const html = await fixture.readFile('/old/index.html');
const $ = cheerioLoad(html);
const link = $('link[rel="canonical"]');
const href = link.attr('href');
expect(href).to.contain('http');
});
it('Builds out .astro pages', async () => {
const html = await fixture.readFile('/index.html');
expect(html).to.be.a('string');

View file

@ -1,5 +1,11 @@
# create-astro
## 4.2.1
### Patch Changes
- [#8634](https://github.com/withastro/astro/pull/8634) [`b64dd45c0`](https://github.com/withastro/astro/commit/b64dd45c0d641f9f2ed997e2cbdf8a6b0193195f) Thanks [@TheOtterlord](https://github.com/TheOtterlord)! - Fix `--yes` behaviour to prevent it overriding `--template`
## 4.2.0
### Minor Changes

View file

@ -1,6 +1,6 @@
{
"name": "create-astro",
"version": "4.2.0",
"version": "4.2.1",
"type": "module",
"author": "withastro",
"license": "MIT",

View file

@ -1,5 +1,19 @@
# @astrojs/cloudflare
## 7.3.0
### Minor Changes
- [#8459](https://github.com/withastro/astro/pull/8459) [`2365c1246`](https://github.com/withastro/astro/commit/2365c124645d5067a12987f205cee23a45d1d13d) Thanks [@schummar](https://github.com/schummar)! - Adds three new config options for `_routes.json` generation: `routes.strategy`, `routes.include`, and `routes.exclude`.
- [#8542](https://github.com/withastro/astro/pull/8542) [`faeead423`](https://github.com/withastro/astro/commit/faeead42325f378f9edac4e081eb7d6d50905136) Thanks [@adrianlyjak](https://github.com/adrianlyjak)! - Add support for loading wasm modules in the cloudflare adapter
### Patch Changes
- Updated dependencies [[`863f5171e`](https://github.com/withastro/astro/commit/863f5171e8e7516c9d72f2e48ea7db1dea71c4f5), [`63141f3f3`](https://github.com/withastro/astro/commit/63141f3f3e4a57d2f55ccfebd7e506ea1033a1ab), [`974d5117a`](https://github.com/withastro/astro/commit/974d5117abc8b47f8225e455b9285c88e305272f), [`cb838b84b`](https://github.com/withastro/astro/commit/cb838b84b457041b0442996f7611b04aa940a620), [`f36c4295b`](https://github.com/withastro/astro/commit/f36c4295be1ef2bcfa4aecb3c59551388419c53d), [`4c4ad9d16`](https://github.com/withastro/astro/commit/4c4ad9d167e8d15ff2c15e3336ede8ca22f646b2)]:
- astro@3.1.3
- @astrojs/underscore-redirects@0.3.0
## 7.2.0
### Minor Changes

View file

@ -75,6 +75,92 @@ export default defineConfig({
Note that this adapter does not support using [Cloudflare Pages Middleware](https://developers.cloudflare.com/pages/platform/functions/middleware/). Astro will bundle the [Astro middleware](https://docs.astro.build/en/guides/middleware/) into each page.
### routes.strategy
`routes.strategy: "auto" | "include" | "exclude"`
default `"auto"`
Determines how `routes.json` will be generated if no [custom `_routes.json`](#custom-_routesjson) is provided.
There are three options available:
- **`"auto"` (default):** Will automatically select the strategy that generates the fewest entries. This should almost always be sufficient, so choose this option unless you have a specific reason not to.
- **`include`:** Pages and endpoints that are not pre-rendered are listed as `include` entries, telling Cloudflare to invoke these routes as functions. `exclude` entries are only used to resolve conflicts. Usually the best strategy when your website has mostly static pages and only a few dynamic pages or endpoints.
Example: For `src/pages/index.astro` (static), `src/pages/company.astro` (static), `src/pages/users/faq.astro` (static) and `/src/pages/users/[id].astro` (SSR) this will produce the following `_routes.json`:
```json
{
"version": 1,
"include": [
"/_image", // Astro's image endpoint
"/users/*" // Dynamic route
],
"exclude": [
// Static routes that needs to be exempted from the dynamic wildcard route above
"/users/faq/",
"/users/faq/index.html"
]
}
```
- **`exclude`:** Pre-rendered pages are listed as `exclude` entries (telling Cloudflare to handle these routes as static assets). Usually the best strategy when your website has mostly dynamic pages or endpoints and only a few static pages.
Example: For the same pages as in the previous example this will produce the following `_routes.json`:
```json
{
"version": 1,
"include": [
"/*" // Handle everything as function except the routes below
],
"exclude": [
// All static assets
"/",
"/company/",
"/index.html",
"/users/faq/",
"/favicon.png",
"/company/index.html",
"/users/faq/index.html"
]
}
```
### routes.include
`routes.include: string[]`
default `[]`
If you want to use the automatic `_routes.json` generation, but want to include additional routes (e.g. when having custom functions in the `functions` folder), you can use the `routes.include` option to add additional routes to the `include` array.
### routes.exclude
`routes.exclude: string[]`
default `[]`
If you want to use the automatic `_routes.json` generation, but want to exclude additional routes, you can use the `routes.exclude` option to add additional routes to the `exclude` array.
The following example automatically generates `_routes.json` while including and excluding additional routes. Note that that is only necessary if you have custom functions in the `functions` folder that are not handled by Astro.
```diff
// astro.config.mjs
export default defineConfig({
adapter: cloudflare({
mode: 'directory',
+ routes: {
+ strategy: 'include',
+ include: ['/users/*'], // handled by custom function: functions/users/[id].js
+ exclude: ['/users/faq'], // handled by static page: pages/users/faq.astro
+ },
}),
});
```
## Enabling Preview
In order for preview to work you must install `wrangler`

View file

@ -1,7 +1,7 @@
{
"name": "@astrojs/cloudflare",
"description": "Deploy your site to Cloudflare Workers/Pages",
"version": "7.2.0",
"version": "7.3.0",
"type": "module",
"types": "./dist/index.d.ts",
"author": "withastro",
@ -52,7 +52,7 @@
"vite": "^4.4.9"
},
"peerDependencies": {
"astro": "workspace:^3.1.2"
"astro": "workspace:^3.1.4"
},
"devDependencies": {
"@types/iarna__toml": "^2.0.2",

View file

@ -21,6 +21,19 @@ export type { DirectoryRuntime } from './server.directory.js';
type Options = {
mode?: 'directory' | 'advanced';
functionPerRoute?: boolean;
/** Configure automatic `routes.json` generation */
routes?: {
/** Strategy for generating `include` and `exclude` patterns
* - `auto`: Will use the strategy that generates the least amount of entries.
* - `include`: For each page or endpoint in your application that is not prerendered, an entry in the `include` array will be generated. For each page that is prerendered and whoose path is matched by an `include` entry, an entry in the `exclude` array will be generated.
* - `exclude`: One `"/*"` entry in the `include` array will be generated. For each page that is prerendered, an entry in the `exclude` array will be generated.
* */
strategy?: 'auto' | 'include' | 'exclude';
/** Additional `include` patterns */
include?: string[];
/** Additional `exclude` patterns */
exclude?: string[];
};
/**
* 'off': current behaviour (wrangler is needed)
* 'local': use a static req.cf object, and env vars defined in wrangler.toml & .dev.vars (astro dev is enough)
@ -467,6 +480,7 @@ export default function createIntegration(args?: Options): AstroIntegration {
// move cloudflare specific files to the root
const cloudflareSpecialFiles = ['_headers', '_redirects', '_routes.json'];
if (_config.base !== '/') {
for (const file of cloudflareSpecialFiles) {
try {
@ -480,6 +494,11 @@ export default function createIntegration(args?: Options): AstroIntegration {
}
}
// Add also the worker file so it's excluded from the _routes.json generation
if (!isModeDirectory) {
cloudflareSpecialFiles.push('_worker.js');
}
const routesExists = await fs.promises
.stat(new URL('./_routes.json', _config.outDir))
.then((stat) => stat.isFile())
@ -587,39 +606,61 @@ export default function createIntegration(args?: Options): AstroIntegration {
staticPathList.push(...routes.filter((r) => r.type === 'redirect').map((r) => r.route));
// In order to product the shortest list of patterns, we first try to
// include all function endpoints, and then exclude all static paths
let include = deduplicatePatterns(
functionEndpoints.map((endpoint) => endpoint.includePattern)
);
let exclude = deduplicatePatterns(
staticPathList.filter((file: string) =>
functionEndpoints.some((endpoint) => endpoint.regexp.test(file))
)
);
const strategy = args?.routes?.strategy ?? 'auto';
// Strategy `include`: include all function endpoints, and then exclude static paths that would be matched by an include pattern
const includeStrategy =
strategy === 'exclude'
? undefined
: {
include: deduplicatePatterns(
functionEndpoints
.map((endpoint) => endpoint.includePattern)
.concat(args?.routes?.include ?? [])
),
exclude: deduplicatePatterns(
staticPathList
.filter((file: string) =>
functionEndpoints.some((endpoint) => endpoint.regexp.test(file))
)
.concat(args?.routes?.exclude ?? [])
),
};
// Cloudflare requires at least one include pattern:
// https://developers.cloudflare.com/pages/platform/functions/routing/#limits
// So we add a pattern that we immediately exclude again
if (include.length === 0) {
include = ['/'];
exclude = ['/'];
if (includeStrategy?.include.length === 0) {
includeStrategy.include = ['/'];
includeStrategy.exclude = ['/'];
}
// If using only an exclude list would produce a shorter list of patterns,
// we use that instead
if (include.length + exclude.length > staticPathList.length) {
include = ['/*'];
exclude = deduplicatePatterns(staticPathList);
}
// Strategy `exclude`: include everything, and then exclude all static paths
const excludeStrategy =
strategy === 'include'
? undefined
: {
include: ['/*'],
exclude: deduplicatePatterns(staticPathList.concat(args?.routes?.exclude ?? [])),
};
const includeStrategyLength = includeStrategy
? includeStrategy.include.length + includeStrategy.exclude.length
: Infinity;
const excludeStrategyLength = excludeStrategy
? excludeStrategy.include.length + excludeStrategy.exclude.length
: Infinity;
const winningStrategy =
includeStrategyLength <= excludeStrategyLength ? includeStrategy : excludeStrategy;
await fs.promises.writeFile(
new URL('./_routes.json', _config.outDir),
JSON.stringify(
{
version: 1,
include,
exclude,
...winningStrategy,
},
null,
2

View file

@ -1,11 +1,9 @@
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
adapter: cloudflare({ mode: 'directory' }),
// adapter will be set dynamically by the test
output: 'hybrid',
redirects: {
'/a/redirect': '/',
},
srcDir: process.env.SRC
});

View file

@ -0,0 +1 @@
/redirectme / 302

View file

@ -0,0 +1,5 @@
---
export const prerender=false;
---
ok

View file

@ -0,0 +1,5 @@
---
export const prerender=false;
---
ok

View file

@ -0,0 +1,211 @@
import { expect } from 'chai';
import { loadFixture } from './test-utils.js';
import cloudflare from '../dist/index.js';
/** @type {import('./test-utils.js').Fixture} */
describe('_routes.json generation', () => {
for (const mode of ['directory', 'advanced']) {
for (const functionPerRoute of [false, true]) {
describe(`with mode=${mode}, functionPerRoute=${functionPerRoute}`, () => {
describe('of both functions and static files', () => {
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/routes-json/',
srcDir: './src/mixed',
adapter: cloudflare({
mode,
functionPerRoute,
}),
});
await fixture.build();
});
it('creates `include` for functions and `exclude` for static files where needed', async () => {
const _routesJson = await fixture.readFile('/_routes.json');
const routes = JSON.parse(_routesJson);
expect(routes).to.deep.equal({
version: 1,
include: ['/a/*', '/_image'],
exclude: ['/a/', '/a/redirect', '/a/index.html'],
});
});
});
describe('of only functions', () => {
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/routes-json/',
srcDir: './src/dynamicOnly',
adapter: cloudflare({
mode,
functionPerRoute,
}),
});
await fixture.build();
});
it('creates a wildcard `include` and `exclude` only for static assets and redirects', async () => {
const _routesJson = await fixture.readFile('/_routes.json');
const routes = JSON.parse(_routesJson);
expect(routes).to.deep.equal({
version: 1,
include: ['/*'],
exclude: ['/public.txt', '/redirectme', '/a/redirect'],
});
});
});
describe('of only static files', () => {
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/routes-json/',
srcDir: './src/staticOnly',
adapter: cloudflare({
mode,
functionPerRoute,
}),
});
await fixture.build();
});
it('create only one `include` and `exclude` that are supposed to match nothing', async () => {
const _routesJson = await fixture.readFile('/_routes.json');
const routes = JSON.parse(_routesJson);
expect(routes).to.deep.equal({
version: 1,
include: ['/_image'],
exclude: [],
});
});
});
describe('with strategy `"include"`', () => {
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/routes-json/',
srcDir: './src/dynamicOnly',
adapter: cloudflare({
mode,
functionPerRoute,
routes: { strategy: 'include' },
}),
});
await fixture.build();
});
it('creates `include` entries even though the `"exclude"` strategy would have produced less entries.', async () => {
const _routesJson = await fixture.readFile('/_routes.json');
const routes = JSON.parse(_routesJson);
expect(routes).to.deep.equal({
version: 1,
include: ['/', '/_image', '/dynamic1', '/dynamic2', '/dynamic3'],
exclude: [],
});
});
});
describe('with strategy `"exclude"`', () => {
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/routes-json/',
srcDir: './src/staticOnly',
adapter: cloudflare({
mode,
functionPerRoute,
routes: { strategy: 'exclude' },
}),
});
await fixture.build();
});
it('creates `exclude` entries even though the `"include"` strategy would have produced less entries.', async () => {
const _routesJson = await fixture.readFile('/_routes.json');
const routes = JSON.parse(_routesJson);
expect(routes).to.deep.equal({
version: 1,
include: ['/*'],
exclude: ['/', '/index.html', '/public.txt', '/redirectme', '/a/redirect'],
});
});
});
describe('with additional `include` entries', () => {
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/routes-json/',
srcDir: './src/mixed',
adapter: cloudflare({
mode,
functionPerRoute,
routes: {
strategy: 'include',
include: ['/another', '/a/redundant'],
},
}),
});
await fixture.build();
});
it('creates `include` for functions and `exclude` for static files where needed', async () => {
const _routesJson = await fixture.readFile('/_routes.json');
const routes = JSON.parse(_routesJson);
expect(routes).to.deep.equal({
version: 1,
include: ['/a/*', '/_image', '/another'],
exclude: ['/a/', '/a/redirect', '/a/index.html'],
});
});
});
describe('with additional `exclude` entries', () => {
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/routes-json/',
srcDir: './src/mixed',
adapter: cloudflare({
mode,
functionPerRoute,
routes: {
strategy: 'include',
exclude: ['/another', '/a/*', '/a/index.html'],
},
}),
});
await fixture.build();
});
it('creates `include` for functions and `exclude` for static files where needed', async () => {
const _routesJson = await fixture.readFile('/_routes.json');
const routes = JSON.parse(_routesJson);
expect(routes).to.deep.equal({
version: 1,
include: ['/a/*', '/_image'],
exclude: ['/a/', '/a/*', '/another'],
});
});
});
});
}
}
});

View file

@ -1,78 +0,0 @@
import { expect } from 'chai';
import { loadFixture } from './test-utils.js';
/** @type {import('./test-utils.js').Fixture} */
describe('_routes.json generation', () => {
after(() => {
delete process.env.SRC;
});
describe('of both functions and static files', () => {
let fixture;
before(async () => {
process.env.SRC = './src/mixed';
fixture = await loadFixture({
root: './fixtures/routesJson/',
});
await fixture.build();
});
it('creates `include` for functions and `exclude` for static files where needed', async () => {
const _routesJson = await fixture.readFile('/_routes.json');
const routes = JSON.parse(_routesJson);
expect(routes).to.deep.equal({
version: 1,
include: ['/a/*', '/_image'],
exclude: ['/a/', '/a/redirect', '/a/index.html'],
});
});
});
describe('of only functions', () => {
let fixture;
before(async () => {
process.env.SRC = './src/dynamicOnly';
fixture = await loadFixture({
root: './fixtures/routesJson/',
});
await fixture.build();
});
it('creates a wildcard `include` and `exclude` only for the redirect', async () => {
const _routesJson = await fixture.readFile('/_routes.json');
const routes = JSON.parse(_routesJson);
expect(routes).to.deep.equal({
version: 1,
include: ['/*'],
exclude: ['/a/redirect'],
});
});
});
describe('of only static files', () => {
let fixture;
before(async () => {
process.env.SRC = './src/staticOnly';
fixture = await loadFixture({
root: './fixtures/routesJson/',
});
await fixture.build();
});
it('create only one `include` and `exclude` that are supposed to match nothing', async () => {
const _routesJson = await fixture.readFile('/_routes.json');
const routes = JSON.parse(_routesJson);
expect(routes).to.deep.equal({
version: 1,
include: ['/_image'],
exclude: [],
});
});
});
});

View file

@ -1,5 +1,14 @@
# @astrojs/deno
## 5.0.1
### Patch Changes
- [#8652](https://github.com/withastro/astro/pull/8652) [`954cadc1e`](https://github.com/withastro/astro/commit/954cadc1e534079a6fba369629272caa173d40ac) Thanks [@lilnasy](https://github.com/lilnasy)! - Fixed an issue where deno integration broke some frameworks.
- Updated dependencies [[`69fbf95b2`](https://github.com/withastro/astro/commit/69fbf95b22c0fb0d8e7e5fef9ec61e26cac9767f)]:
- astro@3.1.4
## 5.0.0
### Major Changes

View file

@ -1,7 +1,7 @@
{
"name": "@astrojs/deno",
"description": "Deploy your site to a Deno server",
"version": "5.0.0",
"version": "5.0.1",
"type": "module",
"types": "./dist/index.d.ts",
"author": "withastro",
@ -36,7 +36,7 @@
"esbuild": "^0.19.2"
},
"peerDependencies": {
"astro": "workspace:^3.1.2"
"astro": "workspace:^3.1.4"
},
"devDependencies": {
"astro": "workspace:*",

View file

@ -149,9 +149,6 @@ export default function createIntegration(args?: Options): AstroIntegration {
(vite.resolve.alias as Record<string, string>)[alias.find] = alias.replacement;
}
}
vite.ssr = {
noExternal: COMPATIBLE_NODE_MODULES,
};
if (Array.isArray(vite.build.rollupOptions.external)) {
vite.build.rollupOptions.external.push(DENO_IMPORTS_SHIM);

View file

@ -75,7 +75,7 @@
"zod": "3.21.1"
},
"peerDependencies": {
"astro": "workspace:^3.1.2"
"astro": "workspace:^3.1.4"
},
"devDependencies": {
"@astrojs/markdown-remark": "workspace:*",

View file

@ -51,7 +51,7 @@
"vfile": "^5.3.7"
},
"peerDependencies": {
"astro": "workspace:^3.1.2"
"astro": "workspace:^3.1.4"
},
"devDependencies": {
"@types/chai": "^4.3.5",

View file

@ -1,5 +1,15 @@
# @astrojs/netlify
## 3.0.2
### Patch Changes
- [#8661](https://github.com/withastro/astro/pull/8661) [`008f7647c`](https://github.com/withastro/astro/commit/008f7647c4788207aab55ab12c734bd80e6df9c5) Thanks [@Skn0tt](https://github.com/Skn0tt)! - fix build failures because of CJS builds and top-level await
- Updated dependencies [[`69fbf95b2`](https://github.com/withastro/astro/commit/69fbf95b22c0fb0d8e7e5fef9ec61e26cac9767f)]:
- astro@3.1.4
- @astrojs/underscore-redirects@0.3.0
## 3.0.1
### Patch Changes

View file

@ -1,7 +1,7 @@
{
"name": "@astrojs/netlify",
"description": "Deploy your site to Netlify",
"version": "3.0.1",
"version": "3.0.2",
"type": "module",
"types": "./dist/index.d.ts",
"author": "withastro",
@ -43,7 +43,7 @@
"esbuild": "^0.19.2"
},
"peerDependencies": {
"astro": "workspace:^3.1.2"
"astro": "workspace:^3.1.4"
},
"devDependencies": {
"@netlify/edge-functions": "^2.0.0",

View file

@ -1,5 +1,6 @@
import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro';
import { extname } from 'node:path';
import { writeFile } from 'node:fs/promises';
import { extname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { generateEdgeMiddleware } from './middleware.js';
import type { Args } from './netlify-functions.js';
@ -85,6 +86,15 @@ function netlifyFunctions({
}
},
'astro:build:done': async ({ routes, dir }) => {
const functionsConfig = {
version: 1,
config: {
nodeModuleFormat: 'esm',
},
};
const functionsConfigPath = join(fileURLToPath(_config.build.server), 'entry.json');
await writeFile(functionsConfigPath, JSON.stringify(functionsConfig));
const type = builders ? 'builders' : 'functions';
const kind = type ?? 'functions';

View file

@ -37,7 +37,7 @@
"server-destroy": "^1.0.1"
},
"peerDependencies": {
"astro": "workspace:^3.1.2"
"astro": "workspace:^3.1.4"
},
"devDependencies": {
"@types/node": "^18.17.8",

View file

@ -48,7 +48,7 @@
"vite": "^4.4.9"
},
"peerDependencies": {
"astro": "workspace:^3.1.2",
"astro": "workspace:^3.1.4",
"svelte": "^3.55.0 || ^4.0.0"
},
"engines": {

View file

@ -43,7 +43,7 @@
"vite": "^4.4.9"
},
"peerDependencies": {
"astro": "workspace:^3.1.2",
"astro": "workspace:^3.1.4",
"tailwindcss": "^3.0.24"
}
}

View file

@ -61,7 +61,7 @@
"web-vitals": "^3.4.0"
},
"peerDependencies": {
"astro": "workspace:^3.1.2"
"astro": "workspace:^3.1.4"
},
"devDependencies": {
"@types/set-cookie-parser": "^2.4.3",

View file

@ -56,7 +56,7 @@
"vue": "^3.3.4"
},
"peerDependencies": {
"astro": "workspace:^3.1.2",
"astro": "workspace:^3.1.4",
"vue": "^3.2.30"
},
"engines": {

64
pnpm-lock.yaml generated
View file

@ -125,7 +125,7 @@ importers:
examples/basics:
dependencies:
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
examples/blog:
@ -140,23 +140,23 @@ importers:
specifier: ^3.0.0
version: link:../../packages/integrations/sitemap
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
examples/component:
devDependencies:
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
examples/deno:
dependencies:
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
devDependencies:
'@astrojs/deno':
specifier: ^5.0.0
specifier: ^5.0.1
version: link:../../packages/integrations/deno
examples/framework-alpine:
@ -171,7 +171,7 @@ importers:
specifier: ^3.12.3
version: 3.12.3
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
examples/framework-lit:
@ -183,7 +183,7 @@ importers:
specifier: ^0.2.1
version: 0.2.1
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
lit:
specifier: ^2.8.0
@ -207,7 +207,7 @@ importers:
specifier: ^3.0.0
version: link:../../packages/integrations/vue
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
preact:
specifier: ^10.17.1
@ -237,7 +237,7 @@ importers:
specifier: ^1.2.1
version: 1.2.1(preact@10.17.1)
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
preact:
specifier: ^10.17.1
@ -255,7 +255,7 @@ importers:
specifier: ^18.2.7
version: 18.2.7
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
react:
specifier: ^18.2.0
@ -270,7 +270,7 @@ importers:
specifier: ^3.0.1
version: link:../../packages/integrations/solid
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
solid-js:
specifier: ^1.7.11
@ -282,7 +282,7 @@ importers:
specifier: ^4.0.2
version: link:../../packages/integrations/svelte
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
svelte:
specifier: ^4.2.0
@ -294,7 +294,7 @@ importers:
specifier: ^3.0.0
version: link:../../packages/integrations/vue
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
vue:
specifier: ^3.3.4
@ -306,13 +306,13 @@ importers:
specifier: ^6.0.1
version: link:../../packages/integrations/node
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
examples/integration:
devDependencies:
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
examples/middleware:
@ -321,7 +321,7 @@ importers:
specifier: ^6.0.1
version: link:../../packages/integrations/node
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
html-minifier:
specifier: ^4.0.0
@ -330,19 +330,19 @@ importers:
examples/minimal:
dependencies:
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
examples/non-html-pages:
dependencies:
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
examples/portfolio:
dependencies:
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
examples/ssr:
@ -354,7 +354,7 @@ importers:
specifier: ^4.0.2
version: link:../../packages/integrations/svelte
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
svelte:
specifier: ^4.2.0
@ -366,7 +366,7 @@ importers:
specifier: ^0.5.0
version: link:../../packages/integrations/markdoc
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
examples/with-markdown-plugins:
@ -375,7 +375,7 @@ importers:
specifier: ^3.2.0
version: link:../../packages/markdown/remark
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
hast-util-select:
specifier: ^5.0.5
@ -396,7 +396,7 @@ importers:
examples/with-markdown-shiki:
dependencies:
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
examples/with-mdx:
@ -408,7 +408,7 @@ importers:
specifier: ^3.0.0
version: link:../../packages/integrations/preact
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
preact:
specifier: ^10.17.1
@ -423,7 +423,7 @@ importers:
specifier: ^0.5.0
version: 0.5.0(nanostores@0.9.3)(preact@10.17.1)
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
nanostores:
specifier: ^0.9.3
@ -444,7 +444,7 @@ importers:
specifier: ^1.6.0
version: 1.6.0
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
autoprefixer:
specifier: ^10.4.15
@ -462,7 +462,7 @@ importers:
examples/with-vite-plugin-pwa:
dependencies:
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
vite-plugin-pwa:
specifier: 0.16.4
@ -474,7 +474,7 @@ importers:
examples/with-vitest:
dependencies:
astro:
specifier: ^3.1.2
specifier: ^3.1.4
version: link:../../packages/astro
vitest:
specifier: ^0.34.2
@ -3512,6 +3512,12 @@ importers:
specifier: workspace:*
version: link:../../..
packages/astro/test/fixtures/view-transitions:
dependencies:
astro:
specifier: workspace:*
version: link:../../..
packages/astro/test/fixtures/virtual-astro-file:
dependencies:
astro:
@ -3720,7 +3726,7 @@ importers:
specifier: workspace:*
version: link:../../../../../astro
packages/integrations/cloudflare/test/fixtures/routesJson:
packages/integrations/cloudflare/test/fixtures/routes-json:
dependencies:
'@astrojs/cloudflare':
specifier: workspace:*