diff --git a/packages/nuxt/src/app/composables/index.ts b/packages/nuxt/src/app/composables/index.ts index 485b37d727f..f7410691255 100644 --- a/packages/nuxt/src/app/composables/index.ts +++ b/packages/nuxt/src/app/composables/index.ts @@ -10,7 +10,7 @@ export type { FetchResult, UseFetchOptions } from './fetch' export { useCookie } from './cookie' export type { CookieOptions, CookieRef } from './cookie' export { useRequestHeaders, useRequestEvent, setResponseStatus } from './ssr' -export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, setPageLayout, navigateTo, useRoute, useActiveRoute, useRouter } from './router' +export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useActiveRoute, useRouter } from './router' export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router' export { preloadComponents, prefetchComponents, preloadRouteComponents } from './preload' export { isPrerendered, loadPayload, preloadPayload } from './payload' diff --git a/packages/nuxt/src/app/composables/router.ts b/packages/nuxt/src/app/composables/router.ts index 2ee264d5449..b4f3950f74c 100644 --- a/packages/nuxt/src/app/composables/router.ts +++ b/packages/nuxt/src/app/composables/router.ts @@ -1,4 +1,4 @@ -import { getCurrentInstance, inject } from 'vue' +import { getCurrentInstance, inject, onUnmounted } from 'vue' import type { Router, RouteLocationNormalizedLoaded, NavigationGuard, RouteLocationNormalized, RouteLocationRaw, NavigationFailure, RouteLocationPathRaw } from 'vue-router' import { sendRedirect } from 'h3' import { hasProtocol, joinURL, parseURL } from 'ufo' @@ -17,6 +17,19 @@ export const useRoute = (): RouteLocationNormalizedLoaded => { return useNuxtApp()._route } +export const onBeforeRouteLeave = (guard: NavigationGuard) => { + const unsubscribe = useRouter().beforeEach((to, from, next) => { + if (to === from) { return } + return guard(to, from, next) + }) + onUnmounted(unsubscribe) +} + +export const onBeforeRouteUpdate = (guard: NavigationGuard) => { + const unsubscribe = useRouter().beforeEach(guard) + onUnmounted(unsubscribe) +} + /** @deprecated Use `useRoute` instead. */ export const useActiveRoute = (): RouteLocationNormalizedLoaded => { return useNuxtApp()._route diff --git a/packages/nuxt/src/imports/module.ts b/packages/nuxt/src/imports/module.ts index b18e824a8bc..05d4e1d400e 100644 --- a/packages/nuxt/src/imports/module.ts +++ b/packages/nuxt/src/imports/module.ts @@ -31,23 +31,26 @@ export default defineNuxtModule>({ options = defu(nuxt.options.autoImports, options) } + // TODO: fix sharing of defaults between invocations of modules + const presets = JSON.parse(JSON.stringify(options.presets)) as ImportPresetWithDeprecation[] + // Allow modules extending sources - await nuxt.callHook('imports:sources', options.presets as ImportPresetWithDeprecation[]) + await nuxt.callHook('imports:sources', presets) - options.presets?.forEach((_i) => { + for (const _i of presets) { const i = _i as ImportPresetWithDeprecation | string if (typeof i !== 'string' && i.names && !i.imports) { i.imports = i.names logger.warn('imports: presets.names is deprecated, use presets.imports instead') } - }) + } // Filter disabled sources // options.sources = options.sources.filter(source => source.disabled !== true) // Create a context to share state between module internals const ctx = createUnimport({ - presets: options.presets, + presets, imports: options.imports, virtualImports: ['#imports'], addons: { diff --git a/packages/nuxt/src/imports/presets.ts b/packages/nuxt/src/imports/presets.ts index 1d1a6cadf3d..5c29c86eeed 100644 --- a/packages/nuxt/src/imports/presets.ts +++ b/packages/nuxt/src/imports/presets.ts @@ -64,6 +64,15 @@ const appPreset = defineUnimportPreset({ ] }) +// vue-router +const routerPreset = defineUnimportPreset({ + from: '#app', + imports: [ + 'onBeforeRouteLeave', + 'onBeforeRouteUpdate' + ] +}) + // vue const vuePreset = defineUnimportPreset({ from: 'vue', @@ -140,5 +149,6 @@ const vuePreset = defineUnimportPreset({ export const defaultPresets: InlinePreset[] = [ ...commonPresets, appPreset, + routerPreset, vuePreset ] diff --git a/packages/nuxt/src/pages/module.ts b/packages/nuxt/src/pages/module.ts index 232620dd4ea..d9ec94af32c 100644 --- a/packages/nuxt/src/pages/module.ts +++ b/packages/nuxt/src/pages/module.ts @@ -54,6 +54,14 @@ export default defineNuxtModule({ references.push({ types: 'vue-router' }) }) + // Add vue-router route guard imports + nuxt.hook('imports:sources', (sources) => { + const routerImports = sources.find(s => s.from === '#app' && s.imports.includes('onBeforeRouteLeave')) + if (routerImports) { + routerImports.from = 'vue-router' + } + }) + // Regenerate templates when adding or removing pages nuxt.hook('builder:watch', async (event, path) => { const dirs = [ diff --git a/packages/nuxt/test/auto-imports.test.ts b/packages/nuxt/test/auto-imports.test.ts index eaa33a5fa94..173b6871e4a 100644 --- a/packages/nuxt/test/auto-imports.test.ts +++ b/packages/nuxt/test/auto-imports.test.ts @@ -66,7 +66,7 @@ describe('imports:nuxt', () => { continue } it(`should register ${name} globally`, () => { - expect(defaultPresets.find(a => a.from === '#app')!.imports).to.include(name) + expect(defaultPresets.flatMap(a => a.from === '#app' ? a.imports : [])).to.include(name) }) } } catch (e) {