fix(client): ignore retries from superseded page loads

pull/5450/head
lllleolin-max 2 weeks ago
parent 31633ed9d7
commit d5b4479134

@ -0,0 +1,16 @@
<script setup>
import { useRouter } from 'vitepress'
const router = useRouter()
function loadSlowPage() {
router.go('/router-race/slow').finally(() => {
document.documentElement.dataset.slowNavigationFinished = 'true'
})
}
</script>
# Navigation Race
<button id="slow" @click="loadSlowPage">Load slow page</button>
<button id="fast" @click="router.go('/router-race/fast')">Load fast page</button>

@ -0,0 +1,95 @@
import type { Route } from 'playwright-chromium'
const slowModule = /(?:\/router-race\/slow\.md\?|\/router-race_slow\.md\.)/
describe.runIf(process.env.VITE_TEST_BUILD)('page load retries', () => {
let slowRequest: Route | undefined
beforeEach(async () => {
slowRequest = undefined
await page.route(slowModule, (route) => {
if (slowRequest) return route.continue()
slowRequest = route
})
await goto('/router-race/')
})
afterEach(async () => {
await page.unrouteAll({ behavior: 'ignoreErrors' })
})
async function startSlowNavigation() {
await page.locator('#slow').click()
await expect.poll(() => !!slowRequest).toBe(true)
}
async function navigateToFastPage() {
await page.locator('#fast').click()
await expect
.poll(() => page.locator('h1').textContent())
.toContain('Fast Page')
}
async function expectFastPageAfterSlowNavigation() {
await page.waitForFunction(
() => document.documentElement.dataset.slowNavigationFinished === 'true'
)
expect(await page.locator('h1').textContent()).toContain('Fast Page')
expect(new URL(page.url()).pathname).toBe('/router-race/fast.html')
}
test('ignores a failed page request after another navigation completes', async () => {
await startSlowNavigation()
await navigateToFastPage()
await slowRequest!.abort()
await expectFastPageAfterSlowNavigation()
})
test('ignores a retry after another navigation completes during a hash map fetch', async () => {
let hashMapRequest: Route | undefined
await page.route('**/hashmap.json', (route) => {
hashMapRequest = route
})
await startSlowNavigation()
await slowRequest!.abort()
await expect.poll(() => !!hashMapRequest).toBe(true)
await navigateToFastPage()
await hashMapRequest!.continue()
await expectFastPageAfterSlowNavigation()
})
test('retries the current page with its updated hash after a deployment', async () => {
await page.route('**/hashmap.json', async (route) => {
const response = await route.fetch()
const map = await response.json()
map['router-race_slow.md'] = 'retry'
await route.fulfill({ json: map })
})
await startSlowNavigation()
const response = await slowRequest!.fetch()
await page.route('**/router-race_slow.md.retry.js', (route) =>
route.fulfill({ response })
)
await slowRequest!.abort()
await page.waitForFunction(
() => document.documentElement.dataset.slowNavigationFinished === 'true'
)
expect(await page.locator('h1').textContent()).toContain('Slow Page')
expect(new URL(page.url()).pathname).toBe('/router-race/slow.html')
})
test('still shows the not-found page when the current retry fails', async () => {
await startSlowNavigation()
await slowRequest!.abort()
await page.waitForFunction(
() => document.documentElement.dataset.slowNavigationFinished === 'true'
)
expect(await page.locator('h1').textContent()).toBe('PAGE NOT FOUND')
})
})

@ -106,12 +106,12 @@ export function createRouter(
if (!page) throw new Error(`Page not found: ${pendingPath}`) if (!page) throw new Error(`Page not found: ${pendingPath}`)
if (latestPendingPath === pendingPath) { if (latestPendingPath === pendingPath) {
latestPendingPath = null
const { default: comp, __pageData } = page const { default: comp, __pageData } = page
if (!comp) throw new Error(`Invalid route component: ${comp}`) if (!comp) throw new Error(`Invalid route component: ${comp}`)
await router.onAfterPageLoad?.(href) await router.onAfterPageLoad?.(href)
if (latestPendingPath !== pendingPath) return
latestPendingPath = null
route.path = inBrowser ? pendingPath : withBase(pendingPath) route.path = inBrowser ? pendingPath : withBase(pendingPath)
route.component = markRaw(comp) route.component = markRaw(comp)
@ -151,10 +151,12 @@ export function createRouter(
// retry on fetch fail: the page to hash map may have been invalidated // retry on fetch fail: the page to hash map may have been invalidated
// because a new deploy happened while the page is open. Try to fetch // because a new deploy happened while the page is open. Try to fetch
// the updated pageToHash map and fetch again. // the updated pageToHash map and fetch again.
if (!isRetry) { if (!isRetry && latestPendingPath === pendingPath) {
try { try {
const res = await fetch(runtimeBase() + 'hashmap.json') const res = await fetch(runtimeBase() + 'hashmap.json')
;(window as any).__VP_HASH_MAP__ = await res.json() const hashMap = await res.json()
if (latestPendingPath !== pendingPath) return
;(window as any).__VP_HASH_MAP__ = hashMap
await loadPage(href, { scrollPosition, isRetry: true, initialLoad }) await loadPage(href, { scrollPosition, isRetry: true, initialLoad })
return return
} catch (e) {} } catch (e) {}

Loading…
Cancel
Save