mirror of
https://github.com/withastro/astro.git
synced 2025-01-06 22:10:10 -05:00
Initial View Transition Support (#7511)
* Basic support * Add the fade transition * Move CSS into a separate file * Add transition name * View Transitions changeset * Replace the boolean transition with 'morph' * Update to use `transition:animate` * Use head propagation * Move CSS into a separate file * Add builtin animations and namespaced module * Misquote * Remove unused code * Add automatic prefetching to the View Transitions router * Use a data attribute for back nav animations * Use [data-astro-transition] * Add view transitions to examples * Wait on the HTML response before calling startViewTransition * Updated stuff * Update the compiler * Fix * Fallback support * Properly do fallback * Simplify the selectors * Put viewTransitions support behind a flag * Upgrade the compiler * Remove unused import * Add tests * Use an explicit import instead of types * Fix case where the click comes from within nested content * Fix linting * Add a test for the back button * Prevent glitch in fallback * Do not combine selectors * Fallback to MPA nav if there is an issue fetching * Fallback swap if there are no animations * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update packages/astro/components/ViewTransitions.astro Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> * Update packages/astro/components/ViewTransitions.astro Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> * Update the changeset * PR review changes * Update more based on review comments. * Update the updateDOM default * Pass in transitions options to the compiler * Update broken tests * Update .changeset/silly-garlics-live.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update .changeset/silly-garlics-live.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update .changeset/silly-garlics-live.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update .changeset/silly-garlics-live.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * h2 -> h4 * Upgrade to stable compiler * Remove exp redirects from sitemap * Remove usage from examples * Remove example updates --------- Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
This commit is contained in:
parent
eafe996b60
commit
6a12fcecb0
30 changed files with 779 additions and 21 deletions
62
.changeset/silly-garlics-live.md
Normal file
62
.changeset/silly-garlics-live.md
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
---
|
||||||
|
'astro': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Built-in View Transitions Support (experimental)
|
||||||
|
|
||||||
|
Astro now supports [view transitions](https://developer.chrome.com/docs/web-platform/view-transitions/) through the new `<ViewTransitions />` component and the `transition:animate` (and associated) directives. View transitions are a great fit for content-oriented sites, and we see it as the best path to get the benefits of client-side routing (smoother transitions) without sacrificing the more simple mental model of MPAs.
|
||||||
|
|
||||||
|
Enable support for view transitions in Astro 2.9 by adding the experimental flag to your config:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
experimental: {
|
||||||
|
viewTransitions: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
This enables you to use the new APIs added.
|
||||||
|
|
||||||
|
#### <ViewTransitions />
|
||||||
|
|
||||||
|
This is a component which acts as the *router* for transitions between pages. Add it to the `<head>` section of each individual page where transitions should occur *in the client* as you navigate away to another page, instead of causing a full page browser refresh. To enable support throughout your entire app, add the component in some common layout or component that targets the `<head>` of every page.
|
||||||
|
|
||||||
|
__CommonHead.astro__
|
||||||
|
|
||||||
|
```astro
|
||||||
|
---
|
||||||
|
import { ViewTransitions } from 'astro:transitions';
|
||||||
|
---
|
||||||
|
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{Astro.props.title}</title>
|
||||||
|
<ViewTransitions />
|
||||||
|
```
|
||||||
|
|
||||||
|
With only this change, your app will now route completely in-client. You can then add transitions to individual elements using the `transition:animate` directive.
|
||||||
|
|
||||||
|
#### Animations
|
||||||
|
|
||||||
|
Add `transition:animate` to any element to use Astro's built-in animations.
|
||||||
|
|
||||||
|
```astro
|
||||||
|
<header transition:animate="slide">
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above, Astro's `slide` animation will cause the `<header>` element to slide out to the left, and then slide in from the right when you navigate away from the page.
|
||||||
|
|
||||||
|
You can also customize these animations using any CSS animation properties, for example, by specifying a duration:
|
||||||
|
|
||||||
|
```astro
|
||||||
|
---
|
||||||
|
import { slide } from 'astro:transition';
|
||||||
|
---
|
||||||
|
<header transition:animate={slide({ duration: 200 })}>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Continue learning
|
||||||
|
|
||||||
|
Check out the [client-side routing docs](https://docs.astro.build/en/guides/client-side-routing/) to learn more.
|
|
@ -1,4 +1,4 @@
|
||||||
---
|
---
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ const {
|
||||||
href="https://fonts.googleapis.com/css2?family=Public+Sans:ital,wght@0,400;0,700;1,400&family=Rubik:wght@500;600&display=swap"
|
href="https://fonts.googleapis.com/css2?family=Public+Sans:ital,wght@0,400;0,700;1,400&family=Rubik:wght@500;600&display=swap"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline>
|
||||||
// This code is inlined in the head to make dark mode instant & blocking.
|
// This code is inlined in the head to make dark mode instant & blocking.
|
||||||
const getThemePreference = () => {
|
const getThemePreference = () => {
|
||||||
|
|
9
packages/astro/client-base.d.ts
vendored
9
packages/astro/client-base.d.ts
vendored
|
@ -70,6 +70,15 @@ declare module 'astro:assets' {
|
||||||
export const { getImage, getConfiguredImageService, Image }: AstroAssets;
|
export const { getImage, getConfiguredImageService, Image }: AstroAssets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module 'astro:transitions' {
|
||||||
|
type TransitionModule = typeof import('./dist/transitions/index.js');
|
||||||
|
export const slide: TransitionModule['slide'];
|
||||||
|
export const fade: TransitionModule['fade'];
|
||||||
|
|
||||||
|
type ViewTransitionsModule = typeof import('./components/ViewTransitions.astro');
|
||||||
|
export const ViewTransitions: ViewTransitionsModule['default'];
|
||||||
|
}
|
||||||
|
|
||||||
type MD = import('./dist/@types/astro').MarkdownInstance<Record<string, any>>;
|
type MD = import('./dist/@types/astro').MarkdownInstance<Record<string, any>>;
|
||||||
interface ExportedMarkdownModuleEntities {
|
interface ExportedMarkdownModuleEntities {
|
||||||
frontmatter: MD['frontmatter'];
|
frontmatter: MD['frontmatter'];
|
||||||
|
|
146
packages/astro/components/ViewTransitions.astro
Normal file
146
packages/astro/components/ViewTransitions.astro
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
---
|
||||||
|
type Fallback = 'none' | 'animate' | 'swap';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
fallback?: Fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
// 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 = history.state?.index || 0;
|
||||||
|
if(!history.state) {
|
||||||
|
history.replaceState({index: currentHistoryIndex}, document.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
const supportsViewTransitions = !!document.startViewTransition;
|
||||||
|
const transitionEnabledOnThisPage = () => !!document.querySelector('[name="astro-view-transitions-enabled"]');
|
||||||
|
|
||||||
|
async function getHTML(href: string) {
|
||||||
|
const res = await fetch(href)
|
||||||
|
const html = await res.text();
|
||||||
|
return { ok: res.ok, html };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFallback(): Fallback {
|
||||||
|
const el = document.querySelector('[name="astro-view-transitions-fallback"]');
|
||||||
|
if(el) {
|
||||||
|
return el.getAttribute('content') as Fallback;
|
||||||
|
}
|
||||||
|
return 'animate';
|
||||||
|
}
|
||||||
|
|
||||||
|
const parser = new DOMParser();
|
||||||
|
|
||||||
|
async function updateDOM(dir: Direction, html: string, fallback?: Fallback) {
|
||||||
|
const doc = parser.parseFromString(html, 'text/html');
|
||||||
|
doc.documentElement.dataset.astroTransition = dir;
|
||||||
|
const swap = () => document.documentElement.replaceWith(doc.documentElement);
|
||||||
|
|
||||||
|
if(fallback === 'animate') {
|
||||||
|
let isAnimating = false;
|
||||||
|
addEventListener('animationstart', () => isAnimating = true, { once: true });
|
||||||
|
|
||||||
|
// Trigger the animations
|
||||||
|
document.documentElement.dataset.astroTransitionFallback = 'old';
|
||||||
|
doc.documentElement.dataset.astroTransitionFallback = 'new';
|
||||||
|
// If there are any animations, want for the animationend event.
|
||||||
|
addEventListener('animationend', swap, { once: true });
|
||||||
|
// If there are no animations, go ahead and swap on next tick
|
||||||
|
// This is necessary because we do not know if there are animations.
|
||||||
|
// The setTimeout is a fallback in case there are none.
|
||||||
|
setTimeout(() => !isAnimating && swap());
|
||||||
|
} else {
|
||||||
|
swap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function navigate(dir: Direction, href: string) {
|
||||||
|
let finished: Promise<void>;
|
||||||
|
const { html, ok } = await getHTML(href);
|
||||||
|
// If there is a problem fetching the new page, just do an MPA navigation to it.
|
||||||
|
if(!ok) {
|
||||||
|
location.href = href;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(supportsViewTransitions) {
|
||||||
|
finished = document.startViewTransition(() => updateDOM(dir, html)).finished;
|
||||||
|
} else {
|
||||||
|
finished = updateDOM(dir, html, getFallback());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await finished;
|
||||||
|
} finally {
|
||||||
|
document.documentElement.removeAttribute('data-astro-transition');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefetching
|
||||||
|
function maybePrefetch(pathname: string) {
|
||||||
|
if(document.querySelector(`link[rel=prefetch][href="${pathname}"]`)) return;
|
||||||
|
if(navigator.connection){
|
||||||
|
let conn = navigator.connection;
|
||||||
|
if(conn.saveData || /(2|3)g/.test(conn.effectiveType || '')) return;
|
||||||
|
}
|
||||||
|
let link = document.createElement('link');
|
||||||
|
link.setAttribute('rel', 'prefetch');
|
||||||
|
link.setAttribute('href', pathname);
|
||||||
|
document.head.append(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(supportsViewTransitions || getFallback() !== 'none') {
|
||||||
|
document.addEventListener('click', (ev) => {
|
||||||
|
let link = ev.target;
|
||||||
|
if(link instanceof Element && link.tagName !== 'A') {
|
||||||
|
link = link.closest('a');
|
||||||
|
}
|
||||||
|
// This check verifies that the click is happening on an anchor
|
||||||
|
// that is going to another page within the same origin. Basically it determines
|
||||||
|
// same-origin navigation, but omits special key combos for new tabs, etc.
|
||||||
|
if (link &&
|
||||||
|
link instanceof HTMLAnchorElement &&
|
||||||
|
link.href &&
|
||||||
|
(!link.target || link.target === '_self') &&
|
||||||
|
link.origin === location.origin &&
|
||||||
|
ev.button === 0 && // left clicks only
|
||||||
|
!ev.metaKey && // new tab (mac)
|
||||||
|
!ev.ctrlKey && // new tab (windows)
|
||||||
|
!ev.altKey && // download
|
||||||
|
!ev.shiftKey &&
|
||||||
|
!ev.defaultPrevented &&
|
||||||
|
transitionEnabledOnThisPage()
|
||||||
|
) {
|
||||||
|
ev.preventDefault();
|
||||||
|
navigate('forward', link.href);
|
||||||
|
currentHistoryIndex++;
|
||||||
|
history.pushState({index: currentHistoryIndex}, '', link.href);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('popstate', () => {
|
||||||
|
if(!transitionEnabledOnThisPage()) return;
|
||||||
|
const nextIndex = history.state?.index ?? (currentHistoryIndex + 1);
|
||||||
|
const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
|
||||||
|
navigate(direction, location.href);
|
||||||
|
currentHistoryIndex = nextIndex;
|
||||||
|
});
|
||||||
|
|
||||||
|
['mouseenter', 'touchstart', 'focus'].forEach(evName => {
|
||||||
|
document.addEventListener(evName, ev => {
|
||||||
|
if(ev.target instanceof HTMLAnchorElement) {
|
||||||
|
let el = ev.target;
|
||||||
|
if(el.origin === location.origin && el.pathname !== location.pathname && transitionEnabledOnThisPage()) {
|
||||||
|
maybePrefetch(el.pathname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { passive: true, capture: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,2 +1,3 @@
|
||||||
export { default as Code } from './Code.astro';
|
export { default as Code } from './Code.astro';
|
||||||
export { default as Debug } from './Debug.astro';
|
export { default as Debug } from './Debug.astro';
|
||||||
|
export { default as ViewTransitions } from './ViewTransitions.astro';
|
||||||
|
|
32
packages/astro/components/viewtransitions.css
Normal file
32
packages/astro/components/viewtransitions.css
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
@keyframes astroFadeInOut {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes astroFadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes astroFadeOut {
|
||||||
|
to { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes astroSlideFromRight {
|
||||||
|
from { transform: translateX(100%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes astroSlideFromLeft {
|
||||||
|
from { transform: translateX(-100%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes astroSlideToRight {
|
||||||
|
to { transform: translateX(100%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes astroSlideToLeft {
|
||||||
|
to { transform: translateX(-100%); }
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
|
||||||
|
// https://astro.build/config
|
||||||
|
export default defineConfig({
|
||||||
|
experimental: {
|
||||||
|
viewTransitions: true,
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "@e2e/view-transitions",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"astro": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
import { ViewTransitions } from 'astro:transitions';
|
||||||
|
---
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Testing</title>
|
||||||
|
<ViewTransitions />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header transition:animate="morph">
|
||||||
|
<h1>testing</h1>
|
||||||
|
</header>
|
||||||
|
<main transition:animate="slide">
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
import Layout from '../components/Layout.astro';
|
||||||
|
---
|
||||||
|
<Layout>
|
||||||
|
<p id="four">Page 4</p>
|
||||||
|
<a id="click-one" href="/one">
|
||||||
|
<div>
|
||||||
|
Nested
|
||||||
|
<span>go to 1</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Layout>
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
import Layout from '../components/Layout.astro';
|
||||||
|
---
|
||||||
|
<Layout>
|
||||||
|
<p id="one">Page 1</p>
|
||||||
|
<a id="click-two" href="/two">go to 2</a>
|
||||||
|
<a id="click-three" href="/three">go to 3</a>
|
||||||
|
</Layout>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Page 3</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<p id="three">Page 3</p>
|
||||||
|
<a id="click-two" href="/two">go to 2</a>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
import Layout from '../components/Layout.astro';
|
||||||
|
---
|
||||||
|
<Layout>
|
||||||
|
<p id="two">Page 2</p>
|
||||||
|
</Layout>
|
101
packages/astro/e2e/view-transitions.test.js
Normal file
101
packages/astro/e2e/view-transitions.test.js
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import { testFactory } from './test-utils.js';
|
||||||
|
|
||||||
|
const test = testFactory({ root: './fixtures/view-transitions/' });
|
||||||
|
|
||||||
|
let devServer;
|
||||||
|
|
||||||
|
test.beforeAll(async ({ astro }) => {
|
||||||
|
devServer = await astro.startDevServer();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(async () => {
|
||||||
|
await devServer.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('View Transitions', () => {
|
||||||
|
test('Moving from page 1 to page 2', async ({ page, astro }) => {
|
||||||
|
const loads = [];
|
||||||
|
page.addListener('load', p => {
|
||||||
|
loads.push(p.title());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Go to page 1
|
||||||
|
await page.goto(astro.resolveUrl('/one'));
|
||||||
|
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');
|
||||||
|
|
||||||
|
expect(loads.length, 'There should only be 1 page load').toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Back button is captured', async ({ page, astro }) => {
|
||||||
|
const loads = [];
|
||||||
|
page.addListener('load', p => {
|
||||||
|
loads.push(p.title());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Go to page 1
|
||||||
|
await page.goto(astro.resolveUrl('/one'));
|
||||||
|
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');
|
||||||
|
|
||||||
|
// Back to page 1
|
||||||
|
await page.goBack();
|
||||||
|
p = page.locator('#one');
|
||||||
|
await expect(p, 'should have content').toHaveText('Page 1');
|
||||||
|
|
||||||
|
expect(loads.length, 'There should only be 1 page load').toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Clicking on a link with nested content', async ({ page, astro }) => {
|
||||||
|
const loads = [];
|
||||||
|
page.addListener('load', p => {
|
||||||
|
loads.push(p.title());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Go to page 4
|
||||||
|
await page.goto(astro.resolveUrl('/four'));
|
||||||
|
let p = page.locator('#four');
|
||||||
|
await expect(p, 'should have content').toHaveText('Page 4');
|
||||||
|
|
||||||
|
// Go to page 1
|
||||||
|
await page.click('#click-one');
|
||||||
|
p = page.locator('#one');
|
||||||
|
await expect(p, 'should have content').toHaveText('Page 1');
|
||||||
|
|
||||||
|
expect(loads.length, 'There should only be 1 page load').toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Moving from a page without ViewTransitions triggers a full page navigation', async ({ page, astro }) => {
|
||||||
|
const loads = [];
|
||||||
|
page.addListener('load', p => {
|
||||||
|
loads.push(p.title());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Go to page 1
|
||||||
|
await page.goto(astro.resolveUrl('/one'));
|
||||||
|
let p = page.locator('#one');
|
||||||
|
await expect(p, 'should have content').toHaveText('Page 1');
|
||||||
|
|
||||||
|
// Go to page 3 which does *not* have ViewTransitions enabled
|
||||||
|
await page.click('#click-three');
|
||||||
|
p = page.locator('#three');
|
||||||
|
await expect(p, 'should have content').toHaveText('Page 3');
|
||||||
|
|
||||||
|
await page.click('#click-two');
|
||||||
|
p = page.locator('#two');
|
||||||
|
await expect(p, 'should have content').toHaveText('Page 2');
|
||||||
|
|
||||||
|
expect(loads.length, 'There should be 2 page loads. The original, then going from 3 to 2').toEqual(2);
|
||||||
|
});
|
||||||
|
});
|
|
@ -71,7 +71,8 @@
|
||||||
"./middleware": {
|
"./middleware": {
|
||||||
"types": "./dist/core/middleware/index.d.ts",
|
"types": "./dist/core/middleware/index.d.ts",
|
||||||
"default": "./dist/core/middleware/index.js"
|
"default": "./dist/core/middleware/index.js"
|
||||||
}
|
},
|
||||||
|
"./transitions": "./dist/transitions/index.js"
|
||||||
},
|
},
|
||||||
"imports": {
|
"imports": {
|
||||||
"#astro/*": "./dist/*.js"
|
"#astro/*": "./dist/*.js"
|
||||||
|
@ -114,7 +115,7 @@
|
||||||
"test:e2e:match": "playwright test -g"
|
"test:e2e:match": "playwright test -g"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/compiler": "^1.5.3",
|
"@astrojs/compiler": "^1.6.0",
|
||||||
"@astrojs/internal-helpers": "^0.1.1",
|
"@astrojs/internal-helpers": "^0.1.1",
|
||||||
"@astrojs/language-server": "^1.0.0",
|
"@astrojs/language-server": "^1.0.0",
|
||||||
"@astrojs/markdown-remark": "^2.2.1",
|
"@astrojs/markdown-remark": "^2.2.1",
|
||||||
|
@ -127,6 +128,7 @@
|
||||||
"@babel/traverse": "^7.22.5",
|
"@babel/traverse": "^7.22.5",
|
||||||
"@babel/types": "^7.22.5",
|
"@babel/types": "^7.22.5",
|
||||||
"@types/babel__core": "^7.20.1",
|
"@types/babel__core": "^7.20.1",
|
||||||
|
"@types/dom-view-transitions": "^1.0.1",
|
||||||
"@types/yargs-parser": "^21.0.0",
|
"@types/yargs-parser": "^21.0.0",
|
||||||
"acorn": "^8.9.0",
|
"acorn": "^8.9.0",
|
||||||
"boxen": "^6.2.1",
|
"boxen": "^6.2.1",
|
||||||
|
@ -150,6 +152,7 @@
|
||||||
"kleur": "^4.1.4",
|
"kleur": "^4.1.4",
|
||||||
"magic-string": "^0.27.0",
|
"magic-string": "^0.27.0",
|
||||||
"mime": "^3.0.0",
|
"mime": "^3.0.0",
|
||||||
|
"network-information-types": "^0.1.1",
|
||||||
"ora": "^6.3.1",
|
"ora": "^6.3.1",
|
||||||
"p-limit": "^4.0.0",
|
"p-limit": "^4.0.0",
|
||||||
"path-to-regexp": "^6.2.1",
|
"path-to-regexp": "^6.2.1",
|
||||||
|
|
|
@ -55,6 +55,27 @@ export interface AstroBuiltinProps {
|
||||||
'client:only'?: boolean | string;
|
'client:only'?: boolean | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TransitionAnimation {
|
||||||
|
name: string; // The name of the keyframe
|
||||||
|
delay?: number | string;
|
||||||
|
duration?: number | string;
|
||||||
|
easing?: string;
|
||||||
|
fillMode?: string;
|
||||||
|
direction?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransitionAnimationPair {
|
||||||
|
old: TransitionAnimation | TransitionAnimation[];
|
||||||
|
new: TransitionAnimation | TransitionAnimation[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransitionDirectionalAnimations {
|
||||||
|
forwards: TransitionAnimationPair;
|
||||||
|
backwards: TransitionAnimationPair;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TransitionAnimationValue = 'morph' | 'slide' | 'fade' | TransitionDirectionalAnimations;
|
||||||
|
|
||||||
// Allow users to extend this for astro-jsx.d.ts
|
// Allow users to extend this for astro-jsx.d.ts
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
export interface AstroClientDirectives {}
|
export interface AstroClientDirectives {}
|
||||||
|
@ -69,6 +90,8 @@ export interface AstroBuiltinAttributes {
|
||||||
'set:html'?: any;
|
'set:html'?: any;
|
||||||
'set:text'?: any;
|
'set:text'?: any;
|
||||||
'is:raw'?: boolean;
|
'is:raw'?: boolean;
|
||||||
|
'transition:animate'?: 'morph' | 'slide' | 'fade' | TransitionDirectionalAnimations;
|
||||||
|
'transition:name'?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AstroDefineVarsAttribute {
|
export interface AstroDefineVarsAttribute {
|
||||||
|
@ -1227,6 +1250,27 @@ export interface AstroUserConfig {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
assets?: boolean;
|
assets?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @docs
|
||||||
|
* @name experimental.viewTransitions
|
||||||
|
* @type {boolean}
|
||||||
|
* @default `false`
|
||||||
|
* @version 2.9.0
|
||||||
|
* @description
|
||||||
|
* Enable experimental support for the `<ViewTransitions / >` component. With this enabled
|
||||||
|
* you can opt-in to [client-side routing](https://docs.astro.build/en/guides/client-side-routing/) on a per-page basis using this component
|
||||||
|
* and enable animations with the `transition:animate` directive.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* {
|
||||||
|
* experimental: {
|
||||||
|
* viewTransitions: true,
|
||||||
|
* },
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
viewTransitions?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Legacy options to be removed
|
// Legacy options to be removed
|
||||||
|
|
|
@ -45,6 +45,8 @@ export async function compile({
|
||||||
astroGlobalArgs: JSON.stringify(astroConfig.site),
|
astroGlobalArgs: JSON.stringify(astroConfig.site),
|
||||||
scopedStyleStrategy: astroConfig.scopedStyleStrategy,
|
scopedStyleStrategy: astroConfig.scopedStyleStrategy,
|
||||||
resultScopedSlot: true,
|
resultScopedSlot: true,
|
||||||
|
experimentalTransitions: astroConfig.experimental.viewTransitions,
|
||||||
|
transitionsAnimationURL: 'astro/components/viewtransitions.css',
|
||||||
preprocessStyle: createStylePreprocessor({
|
preprocessStyle: createStylePreprocessor({
|
||||||
filename,
|
filename,
|
||||||
viteConfig,
|
viteConfig,
|
||||||
|
|
|
@ -45,6 +45,7 @@ const ASTRO_CONFIG_DEFAULTS = {
|
||||||
redirects: {},
|
redirects: {},
|
||||||
experimental: {
|
experimental: {
|
||||||
assets: false,
|
assets: false,
|
||||||
|
viewTransitions: false,
|
||||||
},
|
},
|
||||||
} satisfies AstroUserConfig & { server: { open: boolean } };
|
} satisfies AstroUserConfig & { server: { open: boolean } };
|
||||||
|
|
||||||
|
@ -232,6 +233,7 @@ export const AstroConfigSchema = z.object({
|
||||||
experimental: z
|
experimental: z
|
||||||
.object({
|
.object({
|
||||||
assets: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.assets),
|
assets: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.assets),
|
||||||
|
viewTransitions: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.viewTransitions),
|
||||||
})
|
})
|
||||||
.passthrough()
|
.passthrough()
|
||||||
.refine(
|
.refine(
|
||||||
|
|
|
@ -27,6 +27,7 @@ import astroScannerPlugin from '../vite-plugin-scanner/index.js';
|
||||||
import astroScriptsPlugin from '../vite-plugin-scripts/index.js';
|
import astroScriptsPlugin from '../vite-plugin-scripts/index.js';
|
||||||
import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js';
|
import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js';
|
||||||
import { vitePluginSSRManifest } from '../vite-plugin-ssr-manifest/index.js';
|
import { vitePluginSSRManifest } from '../vite-plugin-ssr-manifest/index.js';
|
||||||
|
import astroTransitions from '../transitions/vite-plugin-transitions.js';
|
||||||
import { joinPaths } from './path.js';
|
import { joinPaths } from './path.js';
|
||||||
|
|
||||||
interface CreateViteOptions {
|
interface CreateViteOptions {
|
||||||
|
@ -132,6 +133,7 @@ export async function createVite(
|
||||||
astroContentAssetPropagationPlugin({ mode, settings }),
|
astroContentAssetPropagationPlugin({ mode, settings }),
|
||||||
vitePluginSSRManifest(),
|
vitePluginSSRManifest(),
|
||||||
settings.config.experimental.assets ? [astroAssetsPlugin({ settings, logging, mode })] : [],
|
settings.config.experimental.assets ? [astroAssetsPlugin({ settings, logging, mode })] : [],
|
||||||
|
astroTransitions({ config: settings.config }),
|
||||||
],
|
],
|
||||||
publicDir: fileURLToPath(settings.config.publicDir),
|
publicDir: fileURLToPath(settings.config.publicDir),
|
||||||
root: fileURLToPath(settings.config.root),
|
root: fileURLToPath(settings.config.root),
|
||||||
|
|
|
@ -7,7 +7,7 @@ function validateArgs(args: unknown[]): args is Parameters<AstroComponentFactory
|
||||||
if (!args[0] || typeof args[0] !== 'object') return false;
|
if (!args[0] || typeof args[0] !== 'object') return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
function baseCreateComponent(cb: AstroComponentFactory, moduleId?: string): AstroComponentFactory {
|
function baseCreateComponent(cb: AstroComponentFactory, moduleId?: string, propagation?: PropagationHint): AstroComponentFactory {
|
||||||
const name = moduleId?.split('/').pop()?.replace('.astro', '') ?? '';
|
const name = moduleId?.split('/').pop()?.replace('.astro', '') ?? '';
|
||||||
const fn = (...args: Parameters<AstroComponentFactory>) => {
|
const fn = (...args: Parameters<AstroComponentFactory>) => {
|
||||||
if (!validateArgs(args)) {
|
if (!validateArgs(args)) {
|
||||||
|
@ -22,6 +22,7 @@ function baseCreateComponent(cb: AstroComponentFactory, moduleId?: string): Astr
|
||||||
// Add a flag to this callback to mark it as an Astro component
|
// Add a flag to this callback to mark it as an Astro component
|
||||||
fn.isAstroComponentFactory = true;
|
fn.isAstroComponentFactory = true;
|
||||||
fn.moduleId = moduleId;
|
fn.moduleId = moduleId;
|
||||||
|
fn.propagation = propagation;
|
||||||
return fn;
|
return fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,17 +33,17 @@ interface CreateComponentOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createComponentWithOptions(opts: CreateComponentOptions) {
|
function createComponentWithOptions(opts: CreateComponentOptions) {
|
||||||
const cb = baseCreateComponent(opts.factory, opts.moduleId);
|
const cb = baseCreateComponent(opts.factory, opts.moduleId, opts.propagation);
|
||||||
cb.propagation = opts.propagation;
|
|
||||||
return cb;
|
return cb;
|
||||||
}
|
}
|
||||||
// Used in creating the component. aka the main export.
|
// Used in creating the component. aka the main export.
|
||||||
export function createComponent(
|
export function createComponent(
|
||||||
arg1: AstroComponentFactory | CreateComponentOptions,
|
arg1: AstroComponentFactory | CreateComponentOptions,
|
||||||
moduleId?: string
|
moduleId?: string,
|
||||||
|
propagation?: PropagationHint,
|
||||||
) {
|
) {
|
||||||
if (typeof arg1 === 'function') {
|
if (typeof arg1 === 'function') {
|
||||||
return baseCreateComponent(arg1, moduleId);
|
return baseCreateComponent(arg1, moduleId, propagation);
|
||||||
} else {
|
} else {
|
||||||
return createComponentWithOptions(arg1);
|
return createComponentWithOptions(arg1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ export {
|
||||||
stringifyChunk,
|
stringifyChunk,
|
||||||
voidElementNames,
|
voidElementNames,
|
||||||
} from './render/index.js';
|
} from './render/index.js';
|
||||||
|
export { renderTransition } from './transition.js';
|
||||||
export type {
|
export type {
|
||||||
AstroComponentFactory,
|
AstroComponentFactory,
|
||||||
AstroComponentInstance,
|
AstroComponentInstance,
|
||||||
|
|
155
packages/astro/src/runtime/server/transition.ts
Normal file
155
packages/astro/src/runtime/server/transition.ts
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
import type {
|
||||||
|
SSRResult,
|
||||||
|
TransitionAnimation,
|
||||||
|
TransitionDirectionalAnimations,
|
||||||
|
TransitionAnimationValue,
|
||||||
|
} from '../../@types/astro';
|
||||||
|
import { markHTMLString } from './escape.js';
|
||||||
|
import { slide, fade } from '../../transitions/index.js';
|
||||||
|
|
||||||
|
const transitionNameMap = new WeakMap<SSRResult, number>();
|
||||||
|
function incrementTransitionNumber(result: SSRResult) {
|
||||||
|
let num = 1;
|
||||||
|
if(transitionNameMap.has(result)) {
|
||||||
|
num = transitionNameMap.get(result)! + 1;
|
||||||
|
}
|
||||||
|
transitionNameMap.set(result, num);
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTransitionScope(result: SSRResult, hash: string) {
|
||||||
|
const num = incrementTransitionNumber(result);
|
||||||
|
return `astro-${hash}-${num}`;
|
||||||
|
}
|
||||||
|
export function renderTransition(result: SSRResult, hash: string, animationName: TransitionAnimationValue | undefined, transitionName: string) {
|
||||||
|
let animations: TransitionDirectionalAnimations | null = null;
|
||||||
|
switch(animationName) {
|
||||||
|
case 'fade': {
|
||||||
|
animations = fade();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'slide': {
|
||||||
|
animations = slide();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
if(typeof animationName === 'object') {
|
||||||
|
animations = animationName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const scope = createTransitionScope(result, hash);
|
||||||
|
|
||||||
|
// Default transition name is the scope of the element, ie HASH-1
|
||||||
|
if(!transitionName) {
|
||||||
|
transitionName = scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = markHTMLString(`<style>[data-astro-transition-scope="${scope}"] {
|
||||||
|
view-transition-name: ${transitionName};
|
||||||
|
}
|
||||||
|
${!animations ? `` :
|
||||||
|
// Regular animations
|
||||||
|
`
|
||||||
|
::view-transition-old(${transitionName}) {
|
||||||
|
${stringifyAnimation(animations.forwards.old)}
|
||||||
|
}
|
||||||
|
[data-astro-transition-fallback=old] [data-astro-transition-scope="${scope}"] {
|
||||||
|
${stringifyAnimation(animations.forwards.old)}
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-new(${transitionName}) {
|
||||||
|
${stringifyAnimation(animations.forwards.new)}
|
||||||
|
}
|
||||||
|
[data-astro-transition-fallback=new] [data-astro-transition-scope="${scope}"] {
|
||||||
|
${stringifyAnimation(animations.forwards.new)}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-astro-transition=back]::view-transition-old(${transitionName}) {
|
||||||
|
${stringifyAnimation(animations.backwards.old)}
|
||||||
|
}
|
||||||
|
[data-astro-transition=back][data-astro-transition-fallback=old] [data-astro-transition-scope="${scope}"] {
|
||||||
|
${stringifyAnimation(animations.backwards.old)}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-astro-transition=back]::view-transition-new(${transitionName}) {
|
||||||
|
${stringifyAnimation(animations.backwards.new)}
|
||||||
|
}
|
||||||
|
[data-astro-transition=back][data-astro-transition-fallback=new] [data-astro-transition-scope="${scope}"] {
|
||||||
|
${stringifyAnimation(animations.backwards.new)}
|
||||||
|
}
|
||||||
|
`.trim()}
|
||||||
|
</style>`)
|
||||||
|
|
||||||
|
result._metadata.extraHead.push(styles);
|
||||||
|
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnimationBuilder = {
|
||||||
|
toString(): string;
|
||||||
|
[key: string]: string[] | ((k: string) => string);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAnimationProperty(builder: AnimationBuilder, prop: string, value: string | number) {
|
||||||
|
let arr = builder[prop];
|
||||||
|
if(Array.isArray(arr)) {
|
||||||
|
arr.push(value.toString());
|
||||||
|
} else {
|
||||||
|
builder[prop] = [value.toString()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function animationBuilder(): AnimationBuilder {
|
||||||
|
return {
|
||||||
|
toString() {
|
||||||
|
let out = '';
|
||||||
|
for(let k in this) {
|
||||||
|
let value = this[k];
|
||||||
|
if(Array.isArray(value)) {
|
||||||
|
out += `\n\t${k}: ${value.join(', ')};`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringifyAnimation(anim: TransitionAnimation | TransitionAnimation[]): string {
|
||||||
|
if(Array.isArray(anim)) {
|
||||||
|
return stringifyAnimations(anim);
|
||||||
|
} else {
|
||||||
|
return stringifyAnimations([anim]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringifyAnimations(anims: TransitionAnimation[]): string {
|
||||||
|
const builder = animationBuilder();
|
||||||
|
|
||||||
|
for(const anim of anims) {
|
||||||
|
/*300ms cubic-bezier(0.4, 0, 0.2, 1) both astroSlideFromRight;*/
|
||||||
|
if(anim.duration) {
|
||||||
|
addAnimationProperty(builder, 'animation-duration', toTimeValue(anim.duration));
|
||||||
|
}
|
||||||
|
if(anim.easing) {
|
||||||
|
addAnimationProperty(builder, 'animation-timing-function', anim.easing);
|
||||||
|
}
|
||||||
|
if(anim.direction) {
|
||||||
|
addAnimationProperty(builder, 'animation-direction', anim.direction);
|
||||||
|
}
|
||||||
|
if(anim.delay) {
|
||||||
|
addAnimationProperty(builder, 'animation-delay', anim.delay);
|
||||||
|
}
|
||||||
|
if(anim.fillMode) {
|
||||||
|
addAnimationProperty(builder, 'animation-fill-mode', anim.fillMode);
|
||||||
|
}
|
||||||
|
addAnimationProperty(builder, 'animation-name', anim.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toTimeValue(num: number | string) {
|
||||||
|
return typeof num === 'number' ? num + 'ms' : num;
|
||||||
|
}
|
65
packages/astro/src/transitions/index.ts
Normal file
65
packages/astro/src/transitions/index.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import type { TransitionDirectionalAnimations, TransitionAnimationPair } from '../@types/astro';
|
||||||
|
|
||||||
|
export function slide({
|
||||||
|
duration
|
||||||
|
}: {
|
||||||
|
duration?: string | number;
|
||||||
|
} = {}): TransitionDirectionalAnimations {
|
||||||
|
return {
|
||||||
|
forwards: {
|
||||||
|
old: [{
|
||||||
|
name: 'astroFadeOut',
|
||||||
|
duration: duration ?? '90ms',
|
||||||
|
easing: 'cubic-bezier(0.4, 0, 1, 1)',
|
||||||
|
fillMode: 'both'
|
||||||
|
}, {
|
||||||
|
name: 'astroSlideToLeft',
|
||||||
|
duration: duration ?? '300ms',
|
||||||
|
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
fillMode: 'both'
|
||||||
|
}],
|
||||||
|
new: [{
|
||||||
|
name: 'astroFadeIn',
|
||||||
|
duration: duration ?? '210ms',
|
||||||
|
easing: 'cubic-bezier(0, 0, 0.2, 1)',
|
||||||
|
delay: '90ms',
|
||||||
|
fillMode: 'both'
|
||||||
|
}, {
|
||||||
|
name: 'astroSlideFromRight',
|
||||||
|
duration: duration ?? '300ms',
|
||||||
|
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
fillMode: 'both'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
backwards: {
|
||||||
|
old: [{ name: 'astroFadeOut' }, { name: 'astroSlideToRight' }],
|
||||||
|
new: [{ name: 'astroFadeIn' }, { name: 'astroSlideFromLeft' }]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fade({
|
||||||
|
duration
|
||||||
|
}: {
|
||||||
|
duration?: string | number;
|
||||||
|
} = {}): TransitionDirectionalAnimations {
|
||||||
|
const anim = {
|
||||||
|
old: {
|
||||||
|
name: 'astroFadeInOut',
|
||||||
|
duration: duration ?? '0.2s',
|
||||||
|
easing: 'linear',
|
||||||
|
fillMode: 'forwards',
|
||||||
|
},
|
||||||
|
new: {
|
||||||
|
name: 'astroFadeInOut',
|
||||||
|
duration: duration ?? '0.3s',
|
||||||
|
easing: 'linear',
|
||||||
|
fillMode: 'backwards',
|
||||||
|
}
|
||||||
|
} satisfies TransitionAnimationPair;
|
||||||
|
|
||||||
|
return {
|
||||||
|
forwards: anim,
|
||||||
|
backwards: anim,
|
||||||
|
};
|
||||||
|
}
|
39
packages/astro/src/transitions/vite-plugin-transitions.ts
Normal file
39
packages/astro/src/transitions/vite-plugin-transitions.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import type { AstroConfig } from '../@types/astro';
|
||||||
|
import * as vite from 'vite';
|
||||||
|
import { AstroError } from '../core/errors/index.js';
|
||||||
|
|
||||||
|
const virtualModuleId = 'astro:transitions';
|
||||||
|
const resolvedVirtualModuleId = '\0' + virtualModuleId;
|
||||||
|
|
||||||
|
// The virtual module for the astro:transitions namespace
|
||||||
|
export default function astroTransitions({ config }: { config: AstroConfig; }): vite.Plugin {
|
||||||
|
return {
|
||||||
|
name: 'astro:transitions',
|
||||||
|
async resolveId(id) {
|
||||||
|
if (id === virtualModuleId) {
|
||||||
|
return resolvedVirtualModuleId;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
load(id) {
|
||||||
|
if (id === resolvedVirtualModuleId) {
|
||||||
|
if(!config.experimental.viewTransitions) {
|
||||||
|
throw new AstroError({
|
||||||
|
title: 'Experimental View Transitions not enabled',
|
||||||
|
message: `View Transitions support is experimental. To enable update your config to include:
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
experimental: {
|
||||||
|
viewTransitions: true
|
||||||
|
}
|
||||||
|
})`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
export * from "astro/transitions";
|
||||||
|
export { default as ViewTransitions } from "astro/components/ViewTransitions.astro";
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ describe('astro/src/core/compile', () => {
|
||||||
await cachedCompilation({
|
await cachedCompilation({
|
||||||
astroConfig: {
|
astroConfig: {
|
||||||
root: pathToFileURL('/'),
|
root: pathToFileURL('/'),
|
||||||
|
experimental: {}
|
||||||
},
|
},
|
||||||
viteConfig: await resolveConfig({ configFile: false }, 'serve'),
|
viteConfig: await resolveConfig({ configFile: false }, 'serve'),
|
||||||
filename: '/src/pages/index.astro',
|
filename: '/src/pages/index.astro',
|
||||||
|
|
|
@ -13,7 +13,7 @@ const viteConfig = await resolveConfig({ configFile: false }, 'serve');
|
||||||
async function compile(source, id) {
|
async function compile(source, id) {
|
||||||
return await cachedFullCompilation({
|
return await cachedFullCompilation({
|
||||||
compileProps: {
|
compileProps: {
|
||||||
astroConfig: { root: pathToFileURL('/'), base: '/' },
|
astroConfig: { root: pathToFileURL('/'), base: '/', experimental: {} },
|
||||||
viteConfig,
|
viteConfig,
|
||||||
filename: id,
|
filename: id,
|
||||||
source,
|
source,
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
"declarationDir": "./dist",
|
"declarationDir": "./dist",
|
||||||
"module": "ES2022",
|
"module": "ES2022",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"target": "ES2021"
|
"target": "ES2021",
|
||||||
|
"jsx": "preserve",
|
||||||
|
"types": ["@types/dom-view-transitions", "network-information-types"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,4 @@ export default defineConfig({
|
||||||
redirects: {
|
redirects: {
|
||||||
'/redirect': '/'
|
'/redirect': '/'
|
||||||
},
|
},
|
||||||
experimental: {
|
|
||||||
redirects: true
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -485,8 +485,8 @@ importers:
|
||||||
packages/astro:
|
packages/astro:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/compiler':
|
'@astrojs/compiler':
|
||||||
specifier: ^1.5.3
|
specifier: ^1.6.0
|
||||||
version: 1.5.3
|
version: 1.6.0
|
||||||
'@astrojs/internal-helpers':
|
'@astrojs/internal-helpers':
|
||||||
specifier: ^0.1.1
|
specifier: ^0.1.1
|
||||||
version: link:../internal-helpers
|
version: link:../internal-helpers
|
||||||
|
@ -523,6 +523,9 @@ importers:
|
||||||
'@types/babel__core':
|
'@types/babel__core':
|
||||||
specifier: ^7.20.1
|
specifier: ^7.20.1
|
||||||
version: 7.20.1
|
version: 7.20.1
|
||||||
|
'@types/dom-view-transitions':
|
||||||
|
specifier: ^1.0.1
|
||||||
|
version: 1.0.1
|
||||||
'@types/yargs-parser':
|
'@types/yargs-parser':
|
||||||
specifier: ^21.0.0
|
specifier: ^21.0.0
|
||||||
version: 21.0.0
|
version: 21.0.0
|
||||||
|
@ -592,6 +595,9 @@ importers:
|
||||||
mime:
|
mime:
|
||||||
specifier: ^3.0.0
|
specifier: ^3.0.0
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
|
network-information-types:
|
||||||
|
specifier: ^0.1.1
|
||||||
|
version: 0.1.1(typescript@5.0.2)
|
||||||
ora:
|
ora:
|
||||||
specifier: ^6.3.1
|
specifier: ^6.3.1
|
||||||
version: 6.3.1
|
version: 6.3.1
|
||||||
|
@ -1478,6 +1484,12 @@ importers:
|
||||||
specifier: ^18.1.0
|
specifier: ^18.1.0
|
||||||
version: 18.2.0(react@18.2.0)
|
version: 18.2.0(react@18.2.0)
|
||||||
|
|
||||||
|
packages/astro/e2e/fixtures/view-transitions:
|
||||||
|
dependencies:
|
||||||
|
astro:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../..
|
||||||
|
|
||||||
packages/astro/e2e/fixtures/vue-component:
|
packages/astro/e2e/fixtures/vue-component:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/mdx':
|
'@astrojs/mdx':
|
||||||
|
@ -5523,14 +5535,14 @@ packages:
|
||||||
sisteransi: 1.0.5
|
sisteransi: 1.0.5
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@astrojs/compiler@1.5.3:
|
/@astrojs/compiler@1.6.0:
|
||||||
resolution: {integrity: sha512-/HSFkJ+Yv+WUWSq0QVsIlhBKam5VUpGV+s8MvPguC/krHmw4Ww9TIgmfJSvV8/BN0sHJB7pCgf7yInae1zb+TQ==}
|
resolution: {integrity: sha512-vxuzp09jAW/ZQ8C4Itf6/OsF76TNjBQC06FNpcayKOzxYkCGHTLh7+0lF4ywmG/fDgSc+f1x7kKxxEKl4nqXvQ==}
|
||||||
|
|
||||||
/@astrojs/language-server@1.0.0:
|
/@astrojs/language-server@1.0.0:
|
||||||
resolution: {integrity: sha512-oEw7AwJmzjgy6HC9f5IdrphZ1GVgfV/+7xQuyf52cpTiRWd/tJISK3MsKP0cDkVlfodmNABNFnAaAWuLZEiiiA==}
|
resolution: {integrity: sha512-oEw7AwJmzjgy6HC9f5IdrphZ1GVgfV/+7xQuyf52cpTiRWd/tJISK3MsKP0cDkVlfodmNABNFnAaAWuLZEiiiA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/compiler': 1.5.3
|
'@astrojs/compiler': 1.6.0
|
||||||
'@jridgewell/trace-mapping': 0.3.18
|
'@jridgewell/trace-mapping': 0.3.18
|
||||||
'@vscode/emmet-helper': 2.8.8
|
'@vscode/emmet-helper': 2.8.8
|
||||||
events: 3.3.0
|
events: 3.3.0
|
||||||
|
@ -8580,6 +8592,10 @@ packages:
|
||||||
resolution: {integrity: sha512-OyiZ3jEKu7RtGO1yp9oOdK0cTwZ/10oE9PDJ6fyN3r9T5wkyOcvr6awdugjYdqF6KVO5eUvt7jx7rk2Eylufow==}
|
resolution: {integrity: sha512-OyiZ3jEKu7RtGO1yp9oOdK0cTwZ/10oE9PDJ6fyN3r9T5wkyOcvr6awdugjYdqF6KVO5eUvt7jx7rk2Eylufow==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/dom-view-transitions@1.0.1:
|
||||||
|
resolution: {integrity: sha512-A9S1ijj/4MX06I1W/6on8lhaYyq1Ir7gaOvfllW1o4RzVWW88HAeqX0pUx9VgOLnNpdiGeUW2CTkg18p5LWIrA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/estree-jsx@1.0.0:
|
/@types/estree-jsx@1.0.0:
|
||||||
resolution: {integrity: sha512-3qvGd0z8F2ENTGr/GG1yViqfiKmRfrXVx5sJyHGFu3z7m5g5utCQtGp/g29JnjflhtQJBv1WDQukHiT58xPcYQ==}
|
resolution: {integrity: sha512-3qvGd0z8F2ENTGr/GG1yViqfiKmRfrXVx5sJyHGFu3z7m5g5utCQtGp/g29JnjflhtQJBv1WDQukHiT58xPcYQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -14232,6 +14248,14 @@ packages:
|
||||||
engines: {node: '>= 0.4.0'}
|
engines: {node: '>= 0.4.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/network-information-types@0.1.1(typescript@5.0.2):
|
||||||
|
resolution: {integrity: sha512-mLXNafJYOkiJB6IlF727YWssTRpXitR+tKSLyA5VAdBi3SOvLf5gtizHgxf241YHPWocnAO/fAhVrB/68tPHDw==}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>= 3.0.0'
|
||||||
|
dependencies:
|
||||||
|
typescript: 5.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/nice-try@1.0.5:
|
/nice-try@1.0.5:
|
||||||
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
|
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -15298,7 +15322,7 @@ packages:
|
||||||
resolution: {integrity: sha512-dPzop0gKZyVGpTDQmfy+e7FKXC9JT3mlpfYA2diOVz+Ui+QR1U4G/s+OesKl2Hib2JJOtAYJs/l+ovgT0ljlFA==}
|
resolution: {integrity: sha512-dPzop0gKZyVGpTDQmfy+e7FKXC9JT3mlpfYA2diOVz+Ui+QR1U4G/s+OesKl2Hib2JJOtAYJs/l+ovgT0ljlFA==}
|
||||||
engines: {node: ^14.15.0 || >=16.0.0, pnpm: '>=7.14.0'}
|
engines: {node: ^14.15.0 || >=16.0.0, pnpm: '>=7.14.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/compiler': 1.5.3
|
'@astrojs/compiler': 1.6.0
|
||||||
prettier: 2.8.8
|
prettier: 2.8.8
|
||||||
sass-formatter: 0.7.6
|
sass-formatter: 0.7.6
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -15307,7 +15331,7 @@ packages:
|
||||||
resolution: {integrity: sha512-lJ/mG/Lz/ccSwNtwqpFS126mtMVzFVyYv0ddTF9wqwrEG4seECjKDAyw/oGv915rAcJi8jr89990nqfpmG+qdg==}
|
resolution: {integrity: sha512-lJ/mG/Lz/ccSwNtwqpFS126mtMVzFVyYv0ddTF9wqwrEG4seECjKDAyw/oGv915rAcJi8jr89990nqfpmG+qdg==}
|
||||||
engines: {node: ^14.15.0 || >=16.0.0, pnpm: '>=7.14.0'}
|
engines: {node: ^14.15.0 || >=16.0.0, pnpm: '>=7.14.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/compiler': 1.5.3
|
'@astrojs/compiler': 1.6.0
|
||||||
prettier: 2.8.8
|
prettier: 2.8.8
|
||||||
sass-formatter: 0.7.6
|
sass-formatter: 0.7.6
|
||||||
synckit: 0.8.5
|
synckit: 0.8.5
|
||||||
|
|
Loading…
Reference in a new issue