diff --git a/packages/nuxt/src/pages/module.ts b/packages/nuxt/src/pages/module.ts index dd56da61fb..4c661d1dd6 100644 --- a/packages/nuxt/src/pages/module.ts +++ b/packages/nuxt/src/pages/module.ts @@ -1,6 +1,6 @@ import { existsSync, readdirSync } from 'node:fs' import { mkdir, readFile } from 'node:fs/promises' -import { addComponent, addPlugin, addTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, findPath, updateTemplates } from '@nuxt/kit' +import { addBuildPlugin, addComponent, addPlugin, addTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, findPath, updateTemplates } from '@nuxt/kit' import { dirname, join, relative, resolve } from 'pathe' import { genImport, genObjectFromRawEntries, genString } from 'knitwork' import { joinURL } from 'ufo' @@ -13,6 +13,7 @@ import { distDir } from '../dirs' import { normalizeRoutes, resolvePagesRoutes } from './utils' import type { PageMetaPluginOptions } from './page-meta' import { PageMetaPlugin } from './page-meta' +import { RouteInjectionPlugin } from './route-injection' const OPTIONAL_PARAM_RE = /^\/?:.*(\?|\(\.\*\)\*)$/ @@ -253,6 +254,11 @@ export default defineNuxtModule({ // Add prefetching support for middleware & layouts addPlugin(resolve(runtimeDir, 'plugins/prefetch.client')) + // Add build plugin to ensure template $route is kept in sync with `` + if (nuxt.options.experimental.templateRouteInjection) { + addBuildPlugin(RouteInjectionPlugin(nuxt), { server: false }) + } + // Add router plugin addPlugin(resolve(runtimeDir, 'plugins/router')) diff --git a/packages/nuxt/src/pages/route-injection.ts b/packages/nuxt/src/pages/route-injection.ts new file mode 100644 index 0000000000..b1d6bc00cc --- /dev/null +++ b/packages/nuxt/src/pages/route-injection.ts @@ -0,0 +1,39 @@ +import { createUnplugin } from 'unplugin' +import MagicString from 'magic-string' +import type { Nuxt } from '@nuxt/schema' +import { isVue } from '../core/utils' + +const INJECTION_RE = /\b_ctx\.\$route\b/g +const INJECTION_SINGLE_RE = /\b_ctx\.\$route\b/ + +export const RouteInjectionPlugin = (nuxt: Nuxt) => createUnplugin(() => { + return { + name: 'nuxt:route-injection-plugin', + enforce: 'post', + transformInclude (id) { + return isVue(id, { type: ['template', 'script'] }) + }, + transform (code) { + if (!INJECTION_SINGLE_RE.test(code)) { return } + + let replaced = false + const s = new MagicString(code) + s.replace(INJECTION_RE, () => { + replaced = true + return '_ctx._.provides[__nuxt_route_symbol]' + }) + if (replaced) { + s.prepend('import { PageRouteSymbol as __nuxt_route_symbol } from \'#app/components/injections\';\n') + } + + if (s.hasChanged()) { + return { + code: s.toString(), + map: nuxt.options.sourcemap.client || nuxt.options.sourcemap.server + ? s.generateMap({ hires: true }) + : undefined + } + } + } + } +}) diff --git a/packages/schema/src/config/experimental.ts b/packages/schema/src/config/experimental.ts index 990163481f..9f4f402af5 100644 --- a/packages/schema/src/config/experimental.ts +++ b/packages/schema/src/config/experimental.ts @@ -58,6 +58,17 @@ export default defineUntypedSchema({ }, }, + /** + * By default the route object returned by the auto-imported `useRoute()` composable + * is kept in sync with the current page in view in ``. This is not true for + * `vue-router`'s exported `useRoute` or for the default `$route` object available in your + * Vue templates. + * + * By enabling this option a mixin will be injected to keep the `$route` template object + * in sync with Nuxt's managed `useRoute()`. + */ + templateRouteInjection: true, + /** * Whether to restore Nuxt app state from `sessionStorage` when reloading the page * after a chunk error or manual `reloadNuxtApp()` call.