mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
refactor(nuxt): use vite:preloadError
event (#28862)
This commit is contained in:
parent
66d0b8ccdc
commit
1b53df5bfb
@ -20,7 +20,7 @@ import type { LoadingIndicator } from '../app/composables/loading-indicator'
|
|||||||
import type { RouteAnnouncer } from '../app/composables/route-announcer'
|
import type { RouteAnnouncer } from '../app/composables/route-announcer'
|
||||||
|
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { appId, multiApp } from '#build/nuxt.config.mjs'
|
import { appId, chunkErrorEvent, multiApp } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
// TODO: temporary module for backwards compatibility
|
// TODO: temporary module for backwards compatibility
|
||||||
import type { DefaultAsyncDataErrorValue, DefaultErrorValue } from '#app/defaults'
|
import type { DefaultAsyncDataErrorValue, DefaultErrorValue } from '#app/defaults'
|
||||||
@ -375,12 +375,14 @@ export function createNuxtApp (options: CreateOptions) {
|
|||||||
defineGetter(nuxtApp.vueApp, '$nuxt', nuxtApp)
|
defineGetter(nuxtApp.vueApp, '$nuxt', nuxtApp)
|
||||||
defineGetter(nuxtApp.vueApp.config.globalProperties, '$nuxt', nuxtApp)
|
defineGetter(nuxtApp.vueApp.config.globalProperties, '$nuxt', nuxtApp)
|
||||||
|
|
||||||
// Listen to chunk load errors
|
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
window.addEventListener('nuxt.preloadError', (event) => {
|
// Listen to chunk load errors
|
||||||
|
if (chunkErrorEvent) {
|
||||||
|
window.addEventListener(chunkErrorEvent, (event) => {
|
||||||
nuxtApp.callHook('app:chunkError', { error: (event as Event & { payload: Error }).payload })
|
nuxtApp.callHook('app:chunkError', { error: (event as Event & { payload: Error }).payload })
|
||||||
|
event.preventDefault()
|
||||||
})
|
})
|
||||||
|
}
|
||||||
window.useNuxtApp = window.useNuxtApp || useNuxtApp
|
window.useNuxtApp = window.useNuxtApp || useNuxtApp
|
||||||
|
|
||||||
// Log errors captured when running plugins, in the `app:created` and `app:beforeMount` hooks
|
// Log errors captured when running plugins, in the `app:created` and `app:beforeMount` hooks
|
||||||
|
@ -26,7 +26,7 @@ export default defineNuxtPlugin({
|
|||||||
})
|
})
|
||||||
|
|
||||||
router.onError((error, to) => {
|
router.onError((error, to) => {
|
||||||
if (chunkErrors.has(error)) {
|
if (chunkErrors.has(error) || error.message.includes('Failed to fetch dynamically imported module')) {
|
||||||
reloadAppAtPath(to)
|
reloadAppAtPath(to)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -512,6 +512,7 @@ export const nuxtConfigTemplate: NuxtTemplate = {
|
|||||||
`export const appId = ${JSON.stringify(ctx.nuxt.options.appId)}`,
|
`export const appId = ${JSON.stringify(ctx.nuxt.options.appId)}`,
|
||||||
`export const outdatedBuildInterval = ${ctx.nuxt.options.experimental.checkOutdatedBuildInterval}`,
|
`export const outdatedBuildInterval = ${ctx.nuxt.options.experimental.checkOutdatedBuildInterval}`,
|
||||||
`export const multiApp = ${!!ctx.nuxt.options.future.multiApp}`,
|
`export const multiApp = ${!!ctx.nuxt.options.future.multiApp}`,
|
||||||
|
`export const chunkErrorEvent = ${ctx.nuxt.options.experimental.emitRouteChunkError ? ctx.nuxt.options.builder === '@nuxt/vite-builder' ? '"vite:preloadError"' : '"nuxt:preloadError"' : 'false'}`,
|
||||||
].join('\n\n')
|
].join('\n\n')
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ import { defu } from 'defu'
|
|||||||
import { env, nodeless } from 'unenv'
|
import { env, nodeless } from 'unenv'
|
||||||
import { appendCorsHeaders, appendCorsPreflightHeaders, defineEventHandler } from 'h3'
|
import { appendCorsHeaders, appendCorsPreflightHeaders, defineEventHandler } from 'h3'
|
||||||
import type { ViteConfig } from '@nuxt/schema'
|
import type { ViteConfig } from '@nuxt/schema'
|
||||||
import { chunkErrorPlugin } from './plugins/chunk-error'
|
|
||||||
import type { ViteBuildContext } from './vite'
|
import type { ViteBuildContext } from './vite'
|
||||||
import { devStyleSSRPlugin } from './plugins/dev-ssr-css'
|
import { devStyleSSRPlugin } from './plugins/dev-ssr-css'
|
||||||
import { runtimePathsPlugin } from './plugins/paths'
|
import { runtimePathsPlugin } from './plugins/paths'
|
||||||
@ -167,11 +166,6 @@ export async function buildClient (ctx: ViteBuildContext) {
|
|||||||
clientConfig.server!.hmr = false
|
clientConfig.server!.hmr = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit chunk errors if the user has opted in to `experimental.emitRouteChunkError`
|
|
||||||
if (ctx.nuxt.options.experimental.emitRouteChunkError) {
|
|
||||||
clientConfig.plugins!.push(chunkErrorPlugin({ sourcemap: !!ctx.nuxt.options.sourcemap.client }))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inject an h3-based CORS handler in preference to vite's
|
// Inject an h3-based CORS handler in preference to vite's
|
||||||
const useViteCors = clientConfig.server?.cors !== undefined
|
const useViteCors = clientConfig.server?.cors !== undefined
|
||||||
if (!useViteCors) {
|
if (!useViteCors) {
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import MagicString from 'magic-string'
|
|
||||||
import type { Plugin } from 'vite'
|
|
||||||
|
|
||||||
const vitePreloadHelperId = '\0vite/preload-helper'
|
|
||||||
|
|
||||||
// TODO: remove this function when we upgrade to vite 5
|
|
||||||
export function chunkErrorPlugin (options: { sourcemap?: boolean }): Plugin {
|
|
||||||
return {
|
|
||||||
name: 'nuxt:chunk-error',
|
|
||||||
transform (code, id) {
|
|
||||||
// Vite 5 has an id with extension
|
|
||||||
if (!(id === vitePreloadHelperId || id === `${vitePreloadHelperId}.js`) || code.includes('nuxt.preloadError')) { return }
|
|
||||||
|
|
||||||
const s = new MagicString(code)
|
|
||||||
s.replace(/__vitePreload/g, '___vitePreload')
|
|
||||||
s.append(`
|
|
||||||
export const __vitePreload = (...args) => ___vitePreload(...args).catch(err => {
|
|
||||||
const e = new Event("nuxt.preloadError");
|
|
||||||
e.payload = err;
|
|
||||||
window.dispatchEvent(e);
|
|
||||||
throw err;
|
|
||||||
})`)
|
|
||||||
|
|
||||||
return {
|
|
||||||
code: s.toString(),
|
|
||||||
map: options.sourcemap
|
|
||||||
? s.generateMap({ hires: true })
|
|
||||||
: undefined,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,11 +7,11 @@ const script = `
|
|||||||
if (typeof ${webpack.RuntimeGlobals.require} !== "undefined") {
|
if (typeof ${webpack.RuntimeGlobals.require} !== "undefined") {
|
||||||
var _ensureChunk = ${webpack.RuntimeGlobals.ensureChunk};
|
var _ensureChunk = ${webpack.RuntimeGlobals.ensureChunk};
|
||||||
${webpack.RuntimeGlobals.ensureChunk} = function (chunkId) {
|
${webpack.RuntimeGlobals.ensureChunk} = function (chunkId) {
|
||||||
return Promise.resolve(_ensureChunk(chunkId)).catch(err => {
|
return Promise.resolve(_ensureChunk(chunkId)).catch(error => {
|
||||||
const e = new Event("nuxt.preloadError");
|
const e = new Event('nuxt:preloadError', { cancelable: true })
|
||||||
e.payload = err;
|
e.payload = error
|
||||||
window.dispatchEvent(e);
|
window.dispatchEvent(e)
|
||||||
throw err;
|
throw error
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
};`
|
};`
|
||||||
|
@ -1167,14 +1167,15 @@ describe('errors', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// TODO: need to create test for webpack
|
// TODO: need to create test for webpack
|
||||||
it.runIf(!isDev() && !isWebpack)('should handle chunk loading errors', async () => {
|
it.runIf(!isDev())('should handle chunk loading errors', async () => {
|
||||||
const { page, consoleLogs } = await renderPage('/')
|
const { page, consoleLogs } = await renderPage('/')
|
||||||
await page.getByText('Increment state').click()
|
await page.getByText('Increment state').click()
|
||||||
await page.getByText('Increment state').click()
|
await page.getByText('Increment state').click()
|
||||||
expect(await page.innerText('div')).toContain('Some value: 3')
|
expect(await page.innerText('div')).toContain('Some value: 3')
|
||||||
|
await page.route(/.*/, route => route.abort('timedout'), { times: 1 })
|
||||||
await page.getByText('Chunk error').click()
|
await page.getByText('Chunk error').click()
|
||||||
await page.waitForURL(url('/chunk-error'))
|
await page.waitForURL(url('/chunk-error'))
|
||||||
expect(consoleLogs.map(c => c.text).join('')).toContain('caught chunk load error')
|
expect(consoleLogs.map(c => c.text).join('')).toContain('Failed to load resource')
|
||||||
expect(await page.innerText('div')).toContain('Chunk error page')
|
expect(await page.innerText('div')).toContain('Chunk error page')
|
||||||
await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/chunk-error')
|
await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/chunk-error')
|
||||||
await page.locator('div').getByText('State: 3').waitFor()
|
await page.locator('div').getByText('State: 3').waitFor()
|
||||||
|
10
test/fixtures/basic/pages/chunk-error.vue
vendored
10
test/fixtures/basic/pages/chunk-error.vue
vendored
@ -1,14 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
definePageMeta({
|
|
||||||
async middleware (to, from) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1))
|
|
||||||
const nuxtApp = useNuxtApp()
|
|
||||||
if (import.meta.client && from !== to && !nuxtApp.isHydrating) {
|
|
||||||
// trigger a loading error when navigated to via client-side navigation
|
|
||||||
await import(/* webpackIgnore: true */ /* @vite-ignore */ `some-non-exis${''}ting-module`)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const someValue = useState('val', () => 1)
|
const someValue = useState('val', () => 1)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
2
test/fixtures/basic/pages/index.vue
vendored
2
test/fixtures/basic/pages/index.vue
vendored
@ -36,8 +36,8 @@
|
|||||||
Immediate remove unmounted
|
Immediate remove unmounted
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
|
no-prefetch
|
||||||
to="/chunk-error"
|
to="/chunk-error"
|
||||||
:prefetch="false"
|
|
||||||
>
|
>
|
||||||
Chunk error
|
Chunk error
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
5
test/fixtures/basic/plugins/chunk-error.ts
vendored
5
test/fixtures/basic/plugins/chunk-error.ts
vendored
@ -1,5 +0,0 @@
|
|||||||
export default defineNuxtPlugin((nuxtApp) => {
|
|
||||||
nuxtApp.hook('app:chunkError', () => {
|
|
||||||
console.log('caught chunk load error')
|
|
||||||
})
|
|
||||||
})
|
|
Loading…
Reference in New Issue
Block a user