Execute scripts on page navigation (view transitions) (#7786)

* Execute scripts on page navigation (view transitions)

* Update .changeset/sharp-seas-smile.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

---------

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
Matthew Phillips 2023-07-25 08:35:47 -04:00 committed by GitHub
parent 25e04a2ecb
commit 188eeddd47
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 61 additions and 4 deletions

View file

@ -0,0 +1,15 @@
---
'astro': patch
---
Execute scripts when navigating to a new page.
When navigating to an new page with client-side navigation, scripts are executed (and re-executed) so that any new scripts on the incoming page are run and the DOM can be updated.
However, `type=module` scripts never re-execute in Astro, and will not do so in client-side routing. To support cases where you want to modify the DOM, a new `astro:load` event listener been added:
```js
document.addEventListener('astro:load', () => {
updateTheDOMSomehow();
});
```

View file

@ -25,6 +25,7 @@ const { fallback = 'animate' } = Astro.props as Props;
const supportsViewTransitions = !!document.startViewTransition;
const transitionEnabledOnThisPage = () =>
!!document.querySelector('[name="astro-view-transitions-enabled"]');
const onload = () => document.dispatchEvent(new Event('astro:load'));
async function getHTML(href: string) {
const res = await fetch(href);
@ -40,6 +41,23 @@ const { fallback = 'animate' } = Astro.props as Props;
return 'animate';
}
function runScripts() {
let wait = Promise.resolve();
for(const script of Array.from(document.scripts)) {
const s = document.createElement('script');
s.innerHTML = script.innerHTML;
for(const attr of script.attributes) {
if(attr.name === 'src') {
const p = new Promise(r => {s.onload = r});
wait = wait.then(() => p as any);
}
s.setAttribute(attr.name, attr.value);
}
script.replaceWith(s);
}
return wait;
}
const parser = new DOMParser();
async function updateDOM(dir: Direction, html: string, fallback?: Fallback) {
@ -95,6 +113,8 @@ const { fallback = 'animate' } = Astro.props as Props;
await finished;
} finally {
document.documentElement.removeAttribute('data-astro-transition');
await runScripts();
onload();
}
}
@ -171,5 +191,6 @@ const { fallback = 'animate' } = Astro.props as Props;
{ passive: true, capture: true }
);
});
addEventListener('load', onload)
}
</script>

View file

@ -3,4 +3,10 @@ import Layout from '../components/Layout.astro';
---
<Layout link="/two.css">
<p id="two">Page 2</p>
<article id="twoarticle"></article>
</Layout>
<script>
document.addEventListener('astro:load', () => {
document.getElementById('twoarticle')!.textContent = 'works';
}, { once: true });
</script>

View file

@ -128,10 +128,6 @@ test.describe('View Transitions', () => {
});
test('Stylesheets in the head are waited on', async ({ page, astro }) => {
page.addListener('console', (data) => {
console.log(data);
});
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
let p = page.locator('#one');
@ -143,4 +139,23 @@ test.describe('View Transitions', () => {
await expect(p, 'should have content').toHaveText('Page 2');
await expect(p, 'imported CSS updated').toHaveCSS('font-size', '24px');
});
test('astro:load event fires when navigating to new page', async ({ page, astro }) => {
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
const p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// go to page 2
await page.click('#click-two');
const article = page.locator('#twoarticle');
await expect(article, 'should have script content').toHaveText('works');
});
test('astro:load event fires when navigating directly to a page', async ({ page, astro }) => {
// Go to page 1
await page.goto(astro.resolveUrl('/two'));
const article = page.locator('#twoarticle');
await expect(article, 'should have script content').toHaveText('works');
});
});