Merge branch 'main' into patch-21

This commit is contained in:
Michael Brevard 2024-06-07 19:47:33 +03:00 committed by GitHub
commit 6fa4dd2807
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 61 additions and 16 deletions

View File

@ -125,6 +125,10 @@ When you are ready to remove the error page, you can call the [`clearError`](/do
Make sure to check before using anything dependent on Nuxt plugins, such as `$route` or `useRouter`, as if a plugin threw an error, then it won't be re-run until you clear the error.
::
::note
Rendering an error page is an entirely separate page load, meaning any registered middleware will run again. You can use [`useError`](#useerror) in middleware to check if an error is being handled.
::
::note
If you are running on Node 16 and you set any cookies when rendering your error page, they will [overwrite cookies previously set](https://github.com/nuxt/nuxt/pull/20585). We recommend using a newer version of Node as Node 16 reached end-of-life in September 2023.
::

View File

@ -134,6 +134,10 @@ export default defineNuxtRouteMiddleware(to => {
})
```
::note
Rendering an error page is an entirely separate page load, meaning any registered middleware will run again. You can use [`useError`](/docs/getting-started/error-handling#useerror) in middleware to check if an error is being handled.
::
## Adding Middleware Dynamically
It is possible to add global or named route middleware manually using the [`addRouteMiddleware()`](/docs/api/utils/add-route-middleware) helper function, such as from within a plugin.

View File

@ -98,7 +98,7 @@ Hook | Arguments | Description
`render:html` | `html, { event }` | Called before constructing the HTML. | [html](https://github.com/nuxt/nuxt/blob/71ef8bd3ff207fd51c2ca18d5a8c7140476780c7/packages/nuxt/src/core/runtime/nitro/renderer.ts#L15), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38)
`render:island` | `islandResponse, { event, islandContext }` | Called before constructing the island HTML. | [islandResponse](https://github.com/nuxt/nuxt/blob/e50cabfed1984c341af0d0c056a325a8aec26980/packages/nuxt/src/core/runtime/nitro/renderer.ts#L28), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38), [islandContext](https://github.com/nuxt/nuxt/blob/e50cabfed1984c341af0d0c056a325a8aec26980/packages/nuxt/src/core/runtime/nitro/renderer.ts#L38)
`close` | - | Called when Nitro is closed. | -
`error` | `error, { event? }` | Called when an error occurs. | [error](https://github.com/unjs/nitro/blob/main/src/runtime/types.ts#L24), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38)
`error` | `error, { event? }` | Called when an error occurs. | [error](https://github.com/unjs/nitro/blob/d20ffcbd16fc4003b774445e1a01e698c2bb078a/src/types/runtime/nitro.ts#L48), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38)
`request` | `event` | Called when a request is received. | [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38)
`beforeResponse` | `event, { body }` | Called before sending the response. | [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38), unknown
`afterResponse` | `event, { body }` | Called after sending the response. | [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38), unknown

View File

@ -382,7 +382,7 @@ export function createNuxtApp (options: CreateOptions) {
// Expose runtime config
const runtimeConfig = import.meta.server ? options.ssrContext!.runtimeConfig : nuxtApp.payload.config!
nuxtApp.provide('config', runtimeConfig)
nuxtApp.provide('config', import.meta.client && import.meta.dev ? wrappedConfig(runtimeConfig) : runtimeConfig)
return nuxtApp
}
@ -545,3 +545,20 @@ function defineGetter<K extends string | number | symbol, V> (obj: Record<K, V>,
export function defineAppConfig<C extends AppConfigInput> (config: C): C {
return config
}
/**
* Configure error getter on runtime secret property access that doesn't exist on the client side
*/
function wrappedConfig (runtimeConfig: Record<string, unknown>) {
if (!import.meta.dev || import.meta.server) { return runtimeConfig }
const keys = Object.keys(runtimeConfig).map(key => `\`${key}\``)
const lastKey = keys.pop()
return new Proxy(runtimeConfig, {
get (target, p: string, receiver) {
if (p !== 'public' && !(p in target) && !p.startsWith('__v') /* vue check for reactivity, e.g. `__v_isRef` */) {
console.warn(`[nuxt] Could not access \`${p}\`. The only available runtime config keys on the client side are ${keys.join(', ')} and ${lastKey}. See \`https://nuxt.com/docs/guide/going-further/runtime-config\` for more information.`)
}
return Reflect.get(target, p, receiver)
},
})
}

View File

@ -4,6 +4,8 @@ import type { NuxtAppManifestMeta } from '../composables/manifest'
import { onNuxtReady } from '../composables/ready'
// @ts-expect-error virtual file
import { buildAssetsURL } from '#internal/nuxt/paths'
// @ts-expect-error virtual file
import { outdatedBuildInterval } from '#build/nuxt.config.mjs'
export default defineNuxtPlugin((nuxtApp) => {
if (import.meta.test) { return }
@ -13,7 +15,7 @@ export default defineNuxtPlugin((nuxtApp) => {
async function getLatestManifest () {
const currentManifest = await getAppManifest()
if (timeout) { clearTimeout(timeout) }
timeout = setTimeout(getLatestManifest, 1000 * 60 * 60)
timeout = setTimeout(getLatestManifest, outdatedBuildInterval)
try {
const meta = await $fetch<NuxtAppManifestMeta>(buildAssetsURL('builds/latest.json') + `?${Date.now()}`)
if (meta.id !== currentManifest.id) {
@ -25,5 +27,5 @@ export default defineNuxtPlugin((nuxtApp) => {
}
}
onNuxtReady(() => { timeout = setTimeout(getLatestManifest, 1000 * 60 * 60) })
onNuxtReady(() => { timeout = setTimeout(getLatestManifest, outdatedBuildInterval) })
})

View File

@ -508,7 +508,9 @@ async function initNuxt (nuxt: Nuxt) {
global: true,
})
addPlugin(resolve(nuxt.options.appDir, 'plugins/check-outdated-build.client'))
if (nuxt.options.experimental.checkOutdatedBuildInterval !== false) {
addPlugin(resolve(nuxt.options.appDir, 'plugins/check-outdated-build.client'))
}
}
nuxt.hooks.hook('builder:watch', (event, relativePath) => {

View File

@ -413,6 +413,7 @@ export const nuxtConfigTemplate: NuxtTemplate = {
`export const vueAppRootContainer = ${ctx.nuxt.options.app.rootId ? `'#${ctx.nuxt.options.app.rootId}'` : `'body > ${ctx.nuxt.options.app.rootTag}'`}`,
`export const viewTransition = ${ctx.nuxt.options.experimental.viewTransition}`,
`export const appId = ${JSON.stringify(ctx.nuxt.options.appId)}`,
`export const outdatedBuildInterval = ${ctx.nuxt.options.experimental.checkOutdatedBuildInterval}`,
].join('\n\n')
},
}

View File

@ -326,6 +326,14 @@ export default defineUntypedSchema({
*/
appManifest: true,
/**
* Set the time interval (in ms) to check for new builds. Disabled when `experimental.appManifest` is `false`.
*
* Set to `false` to disable.
* @type {number | false}
*/
checkOutdatedBuildInterval: 1000 * 60 * 60,
/**
* Set an alternative watcher that will be used as the watching service for Nuxt.
*

View File

@ -31,6 +31,8 @@
"main"
],
"ignoreDeps": [
"jiti",
"@vitejs/plugin-vue",
"nuxt",
"nuxt3",
"@nuxt/kit"

View File

@ -20,7 +20,6 @@ import { callOnce } from '#app/composables/once'
import { useLoadingIndicator } from '#app/composables/loading-indicator'
import { useRouteAnnouncer } from '#app/composables/route-announcer'
// @ts-expect-error virtual file
import { asyncDataDefaults, nuxtDefaultErrorValue } from '#build/nuxt.config.mjs'
registerEndpoint('/api/test', defineEventHandler(event => ({
@ -38,7 +37,6 @@ describe('app config', () => {
`)
updateAppConfig({
new: 'value',
// @ts-expect-error property does not exist
nuxt: { nested: 42 },
})
expect(appConfig).toMatchInlineSnapshot(`
@ -165,7 +163,7 @@ describe('useAsyncData', () => {
// https://github.com/nuxt/nuxt/issues/23411
it('should initialize with error set to null when immediate: false', async () => {
const { error, execute } = useAsyncData(() => ({}), { immediate: false })
const { error, execute } = useAsyncData(() => Promise.resolve({}), { immediate: false })
expect(error.value).toBe(asyncDataDefaults.errorValue)
await execute()
expect(error.value).toBe(asyncDataDefaults.errorValue)
@ -217,7 +215,7 @@ describe('useAsyncData', () => {
})
it('allows custom access to a cache', async () => {
const { data } = await useAsyncData(() => ({ val: true }), { getCachedData: () => ({ val: false }) })
const { data } = await useAsyncData(() => Promise.resolve({ val: true }), { getCachedData: () => ({ val: false }) })
expect(data.value).toMatchInlineSnapshot(`
{
"val": false,
@ -317,6 +315,7 @@ describe('useFetch', () => {
it('should timeout', async () => {
const { status, error } = await useFetch(
// @ts-expect-error should resolve to a string
() => new Promise(resolve => setTimeout(resolve, 5000)),
{ timeout: 1 },
)
@ -534,6 +533,7 @@ describe('loading state', () => {
describe.skipIf(process.env.TEST_MANIFEST === 'manifest-off')('app manifests', () => {
it('getAppManifest', async () => {
const manifest = await getAppManifest()
// @ts-expect-error timestamp is not optional
delete manifest.timestamp
expect(manifest).toMatchInlineSnapshot(`
{

View File

@ -263,12 +263,12 @@ describe('client components', () => {
const componentId = 'ClientWithSlot-12345'
vi.doMock(mockPath, () => ({
default: {
default: defineComponent({
name: 'ClientWithSlot',
setup (_, { slots }) {
return () => h('div', { class: 'client-component' }, slots.default())
return () => h('div', { class: 'client-component' }, slots.default?.())
},
},
}),
}))
const stubFetch = vi.fn(() => {

View File

@ -11,9 +11,10 @@ vi.mock('#app', async (original) => {
}
})
function pluginFactory (name: string, dependsOn?: string[], sequence: string[], parallel = true) {
function pluginFactory (name: string, dependsOn: string[] | undefined, sequence: string[], parallel = true) {
return defineNuxtPlugin({
name,
// @ts-expect-error we have a strong type for plugin names
dependsOn,
async setup () {
sequence.push(`start ${name}`)
@ -71,7 +72,7 @@ describe('plugin dependsOn', () => {
pluginFactory('A', undefined, sequence),
pluginFactory('B', ['A'], sequence),
defineNuxtPlugin({
name,
name: 'some plugin',
async setup () {
sequence.push('start C')
await new Promise(resolve => setTimeout(resolve, 5))
@ -99,7 +100,7 @@ describe('plugin dependsOn', () => {
const plugins = [
pluginFactory('A', undefined, sequence),
defineNuxtPlugin({
name,
name: 'some plugin',
async setup () {
sequence.push('start C')
await new Promise(resolve => setTimeout(resolve, 50))

3
test/nuxt/tsconfig.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "../../.nuxt/tsconfig.json"
}

View File

@ -44,7 +44,8 @@
"**/examples/**",
"**/docs/**",
"**/playground/**",
"**/test/nuxt/**",
"**/test/fixtures/**",
"test/nuxt/**"
"**/test/fixtures-temp/**"
]
}