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:
parent
25e04a2ecb
commit
188eeddd47
4 changed files with 61 additions and 4 deletions
15
.changeset/sharp-seas-smile.md
Normal file
15
.changeset/sharp-seas-smile.md
Normal 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();
|
||||
});
|
||||
```
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue