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 supportsViewTransitions = !!document.startViewTransition;
|
||||||
const transitionEnabledOnThisPage = () =>
|
const transitionEnabledOnThisPage = () =>
|
||||||
!!document.querySelector('[name="astro-view-transitions-enabled"]');
|
!!document.querySelector('[name="astro-view-transitions-enabled"]');
|
||||||
|
const onload = () => document.dispatchEvent(new Event('astro:load'));
|
||||||
|
|
||||||
async function getHTML(href: string) {
|
async function getHTML(href: string) {
|
||||||
const res = await fetch(href);
|
const res = await fetch(href);
|
||||||
|
@ -40,6 +41,23 @@ const { fallback = 'animate' } = Astro.props as Props;
|
||||||
return 'animate';
|
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();
|
const parser = new DOMParser();
|
||||||
|
|
||||||
async function updateDOM(dir: Direction, html: string, fallback?: Fallback) {
|
async function updateDOM(dir: Direction, html: string, fallback?: Fallback) {
|
||||||
|
@ -95,6 +113,8 @@ const { fallback = 'animate' } = Astro.props as Props;
|
||||||
await finished;
|
await finished;
|
||||||
} finally {
|
} finally {
|
||||||
document.documentElement.removeAttribute('data-astro-transition');
|
document.documentElement.removeAttribute('data-astro-transition');
|
||||||
|
await runScripts();
|
||||||
|
onload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,5 +191,6 @@ const { fallback = 'animate' } = Astro.props as Props;
|
||||||
{ passive: true, capture: true }
|
{ passive: true, capture: true }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
addEventListener('load', onload)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -3,4 +3,10 @@ import Layout from '../components/Layout.astro';
|
||||||
---
|
---
|
||||||
<Layout link="/two.css">
|
<Layout link="/two.css">
|
||||||
<p id="two">Page 2</p>
|
<p id="two">Page 2</p>
|
||||||
|
<article id="twoarticle"></article>
|
||||||
</Layout>
|
</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 }) => {
|
test('Stylesheets in the head are waited on', async ({ page, astro }) => {
|
||||||
page.addListener('console', (data) => {
|
|
||||||
console.log(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Go to page 1
|
// Go to page 1
|
||||||
await page.goto(astro.resolveUrl('/one'));
|
await page.goto(astro.resolveUrl('/one'));
|
||||||
let p = page.locator('#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, 'should have content').toHaveText('Page 2');
|
||||||
await expect(p, 'imported CSS updated').toHaveCSS('font-size', '24px');
|
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