mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-21 21:25:11 +00:00
feat(nuxt): add experimental typedPages
option (#20367)
This commit is contained in:
parent
80d7899f49
commit
5781cf1569
@ -43,6 +43,10 @@
|
||||
{
|
||||
"pattern": "@nuxt/test-utils",
|
||||
"group": "external"
|
||||
},
|
||||
{
|
||||
"pattern": "#vue-router",
|
||||
"group": "external"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -94,6 +94,7 @@
|
||||
"unenv": "^1.4.1",
|
||||
"unimport": "^3.0.6",
|
||||
"unplugin": "^1.3.1",
|
||||
"unplugin-vue-router": "^0.6.4",
|
||||
"untyped": "^1.3.2",
|
||||
"vue": "^3.2.47",
|
||||
"vue-bundle-renderer": "^1.0.3",
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { ComputedRef, DefineComponent, PropType } from 'vue'
|
||||
import { computed, defineComponent, h, onBeforeUnmount, onMounted, ref, resolveComponent } from 'vue'
|
||||
import type { RouteLocation, RouteLocationRaw } from 'vue-router'
|
||||
import type { RouteLocation, RouteLocationRaw } from '#vue-router'
|
||||
import { hasProtocol, parseQuery, parseURL, withTrailingSlash, withoutTrailingSlash } from 'ufo'
|
||||
|
||||
import { preloadRouteComponents } from '../composables/preload'
|
||||
@ -24,8 +24,8 @@ export type NuxtLinkOptions = {
|
||||
|
||||
export type NuxtLinkProps = {
|
||||
// Routing
|
||||
to?: string | RouteLocationRaw
|
||||
href?: string | RouteLocationRaw
|
||||
to?: RouteLocationRaw
|
||||
href?: RouteLocationRaw
|
||||
external?: boolean
|
||||
replace?: boolean
|
||||
custom?: boolean
|
||||
@ -81,12 +81,12 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
||||
props: {
|
||||
// Routing
|
||||
to: {
|
||||
type: [String, Object] as PropType<string | RouteLocationRaw>,
|
||||
type: [String, Object] as PropType<RouteLocationRaw>,
|
||||
default: undefined,
|
||||
required: false
|
||||
},
|
||||
href: {
|
||||
type: [String, Object] as PropType<string | RouteLocationRaw>,
|
||||
type: [String, Object] as PropType<RouteLocationRaw>,
|
||||
default: undefined,
|
||||
required: false
|
||||
},
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { Component } from 'vue'
|
||||
import type { RouteLocationRaw, Router } from 'vue-router'
|
||||
import type { RouteLocationRaw, Router } from '#vue-router'
|
||||
import { useNuxtApp } from '../nuxt'
|
||||
import { useRouter } from './router'
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { getCurrentInstance, inject, onUnmounted } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import type { NavigationFailure, NavigationGuard, RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteLocationPathRaw, RouteLocationRaw, Router } from 'vue-router'
|
||||
import type { NavigationFailure, NavigationGuard, RouteLocationNormalized, RouteLocationPathRaw, RouteLocationRaw, Router, useRoute as _useRoute, useRouter as _useRouter } from '#vue-router'
|
||||
import { sanitizeStatusCode } from 'h3'
|
||||
import { hasProtocol, joinURL, parseURL } from 'ufo'
|
||||
|
||||
@ -11,11 +11,11 @@ import { useState } from './state'
|
||||
|
||||
import type { PageMeta } from '#app'
|
||||
|
||||
export const useRouter = () => {
|
||||
export const useRouter: typeof _useRouter = () => {
|
||||
return useNuxtApp()?.$router as Router
|
||||
}
|
||||
|
||||
export const useRoute = (): RouteLocationNormalizedLoaded => {
|
||||
export const useRoute: typeof _useRoute = () => {
|
||||
if (process.dev && isProcessingMiddleware()) {
|
||||
console.warn('[nuxt] Calling `useRoute` within middleware may lead to misleading results. Instead, use the (to, from) arguments passed to the middleware to access the new and old routes.')
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* eslint-disable no-use-before-define */
|
||||
import { getCurrentInstance, reactive } from 'vue'
|
||||
import type { App, Ref, VNode, onErrorCaptured } from 'vue'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import type { RouteLocationNormalizedLoaded } from '#vue-router'
|
||||
import type { HookCallback, Hookable } from 'hookable'
|
||||
import { createHooks } from 'hookable'
|
||||
import { getContext } from 'unctx'
|
||||
|
@ -1,10 +1,14 @@
|
||||
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 { join, relative, resolve } from 'pathe'
|
||||
import { dirname, join, relative, resolve } from 'pathe'
|
||||
import { genImport, genObjectFromRawEntries, genString } from 'knitwork'
|
||||
import escapeRE from 'escape-string-regexp'
|
||||
import { joinURL } from 'ufo'
|
||||
import type { NuxtApp, NuxtPage } from 'nuxt/schema'
|
||||
import { createRoutesContext } from 'unplugin-vue-router'
|
||||
import { resolveOptions } from 'unplugin-vue-router/options'
|
||||
import type { EditableTreeNode, Options as TypedRouterOptions } from 'unplugin-vue-router'
|
||||
|
||||
import { distDir } from '../dirs'
|
||||
import { normalizeRoutes, resolvePagesRoutes } from './utils'
|
||||
@ -15,7 +19,9 @@ export default defineNuxtModule({
|
||||
meta: {
|
||||
name: 'pages'
|
||||
},
|
||||
setup (_options, nuxt) {
|
||||
async setup (_options, nuxt) {
|
||||
const useExperimentalTypedPages = nuxt.options.experimental.typedPages
|
||||
|
||||
const pagesDirs = nuxt.options._layers.map(
|
||||
layer => resolve(layer.config.srcDir, layer.config.dir?.pages || 'pages')
|
||||
)
|
||||
@ -67,18 +73,82 @@ export default defineNuxtModule({
|
||||
return
|
||||
}
|
||||
|
||||
if (useExperimentalTypedPages) {
|
||||
const declarationFile = './types/typed-router.d.ts'
|
||||
|
||||
const options: TypedRouterOptions = {
|
||||
routesFolder: [],
|
||||
dts: resolve(nuxt.options.buildDir, declarationFile),
|
||||
logs: nuxt.options.debug,
|
||||
async beforeWriteFiles (rootPage) {
|
||||
rootPage.children.forEach(child => child.delete())
|
||||
const pages = await resolvePagesRoutes()
|
||||
await nuxt.callHook('pages:extend', pages)
|
||||
function addPage (parent: EditableTreeNode, page: NuxtPage) {
|
||||
// @ts-expect-error TODO: either fix types upstream or figure out another
|
||||
// way to add a route without a file, which must be possible
|
||||
const route = parent.insert(page.path, page.file)
|
||||
if (page.meta) {
|
||||
route.addToMeta(page.meta)
|
||||
}
|
||||
if (page.alias) {
|
||||
route.addAlias(page.alias)
|
||||
}
|
||||
if (page.name) {
|
||||
route.name = page.name
|
||||
}
|
||||
// TODO: implement redirect support
|
||||
// if (page.redirect) {}
|
||||
if (page.children) {
|
||||
page.children.forEach(child => addPage(route, child))
|
||||
}
|
||||
}
|
||||
|
||||
for (const page of pages) {
|
||||
addPage(rootPage, page)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nuxt.hook('prepare:types', ({ references }) => {
|
||||
// This file will be generated by unplugin-vue-router
|
||||
references.push({ path: declarationFile })
|
||||
})
|
||||
|
||||
const context = createRoutesContext(resolveOptions(options))
|
||||
const dtsFile = resolve(nuxt.options.buildDir, declarationFile)
|
||||
await mkdir(dirname(dtsFile), { recursive: true })
|
||||
await context.scanPages(false)
|
||||
|
||||
if (nuxt.options._prepare) {
|
||||
// TODO: could we generate this from context instead?
|
||||
const dts = await readFile(dtsFile, 'utf-8')
|
||||
addTemplate({
|
||||
filename: 'types/typed-router.d.ts',
|
||||
getContents: () => dts
|
||||
})
|
||||
}
|
||||
|
||||
// Regenerate types/typed-router.d.ts when adding or removing pages
|
||||
nuxt.hook('builder:generateApp', async (options) => {
|
||||
if (!options?.filter || options.filter({ filename: 'routes.mjs' } as any)) {
|
||||
await context.scanPages()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const runtimeDir = resolve(distDir, 'pages/runtime')
|
||||
|
||||
// Add $router types
|
||||
nuxt.hook('prepare:types', ({ references }) => {
|
||||
references.push({ types: 'vue-router' })
|
||||
references.push({ types: useExperimentalTypedPages ? 'vue-router/auto' : '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'
|
||||
routerImports.from = '#vue-router'
|
||||
}
|
||||
})
|
||||
|
||||
@ -144,7 +214,7 @@ export default defineNuxtModule({
|
||||
nuxt.hook('imports:extend', (imports) => {
|
||||
imports.push(
|
||||
{ name: 'definePageMeta', as: 'definePageMeta', from: resolve(runtimeDir, 'composables') },
|
||||
{ name: 'useLink', as: 'useLink', from: 'vue-router' }
|
||||
{ name: 'useLink', as: 'useLink', from: '#vue-router' }
|
||||
)
|
||||
})
|
||||
|
||||
@ -177,6 +247,7 @@ export default defineNuxtModule({
|
||||
await nuxt.callHook('pages:extend', pages)
|
||||
|
||||
const sourceFiles = getSources(pages)
|
||||
|
||||
for (const key in manifest) {
|
||||
if (manifest[key].isEntry) {
|
||||
manifest[key].dynamicImports =
|
||||
@ -185,6 +256,17 @@ export default defineNuxtModule({
|
||||
}
|
||||
})
|
||||
|
||||
// adds support for #vue-router alias
|
||||
addTemplate({
|
||||
filename: 'vue-router.mjs',
|
||||
// TODO: use `vue-router/auto` when we have support for page metadata
|
||||
getContents: () => 'export * from \'vue-router\';'
|
||||
})
|
||||
addTemplate({
|
||||
filename: 'vue-router.d.ts',
|
||||
getContents: () => `export * from '${useExperimentalTypedPages ? 'vue-router/auto' : 'vue-router'}'`
|
||||
})
|
||||
|
||||
// Add routes template
|
||||
addTemplate({
|
||||
filename: 'routes.mjs',
|
||||
@ -278,10 +360,13 @@ export default defineNuxtModule({
|
||||
filePath: resolve(distDir, 'pages/runtime/page')
|
||||
})
|
||||
|
||||
nuxt.options.alias['#vue-router'] = join(nuxt.options.buildDir, 'vue-router')
|
||||
|
||||
// Add declarations for middleware keys
|
||||
nuxt.hook('prepare:types', ({ references }) => {
|
||||
references.push({ path: resolve(nuxt.options.buildDir, 'types/middleware.d.ts') })
|
||||
references.push({ path: resolve(nuxt.options.buildDir, 'types/layouts.d.ts') })
|
||||
references.push({ path: resolve(nuxt.options.buildDir, 'vue-router.d.ts') })
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { KeepAliveProps, TransitionProps, UnwrapRef } from 'vue'
|
||||
import { getCurrentInstance } from 'vue'
|
||||
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteRecordRedirectOption } from 'vue-router'
|
||||
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteRecordRedirectOption } from '#vue-router'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { NuxtError } from '#app'
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Suspense, Transition, computed, defineComponent, h, nextTick, onMounted, provide, reactive } from 'vue'
|
||||
import type { KeepAliveProps, TransitionProps, VNode } from 'vue'
|
||||
import { RouterView } from 'vue-router'
|
||||
import { RouterView } from '#vue-router'
|
||||
import { defu } from 'defu'
|
||||
import type { RouteLocation, RouteLocationNormalized, RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import type { RouteLocation, RouteLocationNormalized, RouteLocationNormalizedLoaded } from '#vue-router'
|
||||
|
||||
import type { RouterViewSlotProps } from './utils'
|
||||
import { generateRouteKey, wrapInKeepAlive } from './utils'
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { computed, isReadonly, reactive, shallowRef } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import type { RouteLocation, Router } from 'vue-router'
|
||||
import type { RouteLocation, Router } from '#vue-router'
|
||||
import {
|
||||
createMemoryHistory,
|
||||
createRouter,
|
||||
createWebHashHistory,
|
||||
createWebHistory
|
||||
} from 'vue-router'
|
||||
} from '#vue-router'
|
||||
import { createError } from 'h3'
|
||||
import { withoutBase } from 'ufo'
|
||||
|
||||
@ -44,7 +44,7 @@ function createCurrentLocation (
|
||||
return path + search + hash
|
||||
}
|
||||
|
||||
export default defineNuxtPlugin({
|
||||
const plugin: Plugin<{ router: Router }> = defineNuxtPlugin({
|
||||
name: 'nuxt:router',
|
||||
enforce: 'pre',
|
||||
async setup (nuxtApp) {
|
||||
@ -201,4 +201,6 @@ export default defineNuxtPlugin({
|
||||
|
||||
return { provide: { router } }
|
||||
}
|
||||
}) as Plugin<{ router: Router }>
|
||||
})
|
||||
|
||||
export default plugin
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { RouteLocationNormalized, RouterScrollBehavior } from 'vue-router'
|
||||
import type { RouteLocationNormalized, RouterScrollBehavior } from '#vue-router'
|
||||
import { nextTick } from 'vue'
|
||||
import type { RouterConfig } from 'nuxt/schema'
|
||||
import { useNuxtApp } from '#app/nuxt'
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { KeepAlive, h } from 'vue'
|
||||
import type { RouteLocationMatched, RouteLocationNormalizedLoaded, RouterView } from 'vue-router'
|
||||
import type { RouteLocationMatched, RouteLocationNormalizedLoaded, RouterView } from '#vue-router'
|
||||
|
||||
type InstanceOf<T> = T extends new (...args: any[]) => infer R ? R : never
|
||||
type RouterViewSlot = Exclude<InstanceOf<typeof RouterView>['$slots']['default'], undefined>
|
||||
|
@ -156,6 +156,9 @@ export default defineUntypedSchema({
|
||||
/** Resolve `~`, `~~`, `@` and `@@` aliases located within layers with respect to their layer source and root directories. */
|
||||
localLayerAliases: true,
|
||||
|
||||
/** Enable the new experimental typed router using [unplugin-vue-router](https://github.com/posva/unplugin-vue-router). */
|
||||
typedPages: false,
|
||||
|
||||
/**
|
||||
* Set an alternative watcher that will be used as the watching service for Nuxt.
|
||||
*
|
||||
|
135
pnpm-lock.yaml
135
pnpm-lock.yaml
@ -447,6 +447,9 @@ importers:
|
||||
unplugin:
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1
|
||||
unplugin-vue-router:
|
||||
specifier: ^0.6.4
|
||||
version: 0.6.4(rollup@3.21.5)(vue-router@4.1.6)(vue@3.2.47)
|
||||
untyped:
|
||||
specifier: ^1.3.2
|
||||
version: 1.3.2
|
||||
@ -1068,6 +1071,11 @@ packages:
|
||||
resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
/@babel/helper-string-parser@7.21.5:
|
||||
resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dev: false
|
||||
|
||||
/@babel/helper-validator-identifier@7.19.1:
|
||||
resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@ -1278,6 +1286,15 @@ packages:
|
||||
'@babel/helper-validator-identifier': 7.19.1
|
||||
to-fast-properties: 2.0.0
|
||||
|
||||
/@babel/types@7.21.5:
|
||||
resolution: {integrity: sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.21.5
|
||||
'@babel/helper-validator-identifier': 7.19.1
|
||||
to-fast-properties: 2.0.0
|
||||
dev: false
|
||||
|
||||
/@cloudflare/kv-asset-handler@0.3.0:
|
||||
resolution: {integrity: sha512-9CB/MKf/wdvbfkUdfrj+OkEwZ5b7rws0eogJ4293h+7b6KX5toPwym+VQKmILafNB9YiehqY0DlNrDcDhdWHSQ==}
|
||||
dependencies:
|
||||
@ -2505,6 +2522,25 @@ packages:
|
||||
'@volar/vue-language-core': 1.6.4
|
||||
typescript: 5.0.4
|
||||
|
||||
/@vue-macros/common@1.3.1(rollup@3.21.5)(vue@3.2.47):
|
||||
resolution: {integrity: sha512-Lc5aP/8HNJD1XrnvpeNuWcCf82bZdR3auN/chA1b/1rKZgSnmQkH9f33tKO9qLwXSy+u4hpCi8Rw+oUuF1KCeg==}
|
||||
engines: {node: '>=14.19.0'}
|
||||
peerDependencies:
|
||||
vue: ^2.7.0 || ^3.2.25
|
||||
peerDependenciesMeta:
|
||||
vue:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/types': 7.21.5
|
||||
'@rollup/pluginutils': 5.0.2(rollup@3.21.5)
|
||||
'@vue/compiler-sfc': 3.3.0-beta.5
|
||||
local-pkg: 0.4.3
|
||||
magic-string-ast: 0.1.2
|
||||
vue: 3.2.47
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
dev: false
|
||||
|
||||
/@vue/babel-helper-vue-transform-on@1.0.2:
|
||||
resolution: {integrity: sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==}
|
||||
|
||||
@ -2540,6 +2576,15 @@ packages:
|
||||
estree-walker: 2.0.2
|
||||
source-map-js: 1.0.2
|
||||
|
||||
/@vue/compiler-core@3.3.0-beta.5:
|
||||
resolution: {integrity: sha512-rwKXIMPDKBzKypcZ7Zc+i4e7ItnhlMEu9QZveek2yLxzMG0QimvZnVKB7eD21cQ9MCwEYW4bb8zjisPMJNNaqQ==}
|
||||
dependencies:
|
||||
'@babel/parser': 7.21.4
|
||||
'@vue/shared': 3.3.0-beta.5
|
||||
estree-walker: 2.0.2
|
||||
source-map-js: 1.0.2
|
||||
dev: false
|
||||
|
||||
/@vue/compiler-dom@3.2.47:
|
||||
resolution: {integrity: sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==}
|
||||
dependencies:
|
||||
@ -2552,6 +2597,13 @@ packages:
|
||||
'@vue/compiler-core': 3.3.0-beta.3
|
||||
'@vue/shared': 3.3.0-beta.3
|
||||
|
||||
/@vue/compiler-dom@3.3.0-beta.5:
|
||||
resolution: {integrity: sha512-OsYuAzl8zHRym5TfDhCLrcTSBt71BFJXnTC9uWO+SfgqadadWZxv1piPebjtwJcODkks5OAGfdhxzKdNzzddXw==}
|
||||
dependencies:
|
||||
'@vue/compiler-core': 3.3.0-beta.5
|
||||
'@vue/shared': 3.3.0-beta.5
|
||||
dev: false
|
||||
|
||||
/@vue/compiler-sfc@3.2.47:
|
||||
resolution: {integrity: sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==}
|
||||
dependencies:
|
||||
@ -2580,6 +2632,21 @@ packages:
|
||||
postcss: 8.4.23
|
||||
source-map-js: 1.0.2
|
||||
|
||||
/@vue/compiler-sfc@3.3.0-beta.5:
|
||||
resolution: {integrity: sha512-CbiY2dkzU5IG652ygLUSufLGvXPKI12TQp1PeHs9acjgFjhvSJCSKmAOaCWnXgFsAgpbipPHgAMfWJ8B0h6Sjw==}
|
||||
dependencies:
|
||||
'@babel/parser': 7.21.4
|
||||
'@vue/compiler-core': 3.3.0-beta.5
|
||||
'@vue/compiler-dom': 3.3.0-beta.5
|
||||
'@vue/compiler-ssr': 3.3.0-beta.5
|
||||
'@vue/reactivity-transform': 3.3.0-beta.5
|
||||
'@vue/shared': 3.3.0-beta.5
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.0
|
||||
postcss: 8.4.23
|
||||
source-map-js: 1.0.2
|
||||
dev: false
|
||||
|
||||
/@vue/compiler-ssr@3.2.47:
|
||||
resolution: {integrity: sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==}
|
||||
dependencies:
|
||||
@ -2592,6 +2659,13 @@ packages:
|
||||
'@vue/compiler-dom': 3.3.0-beta.3
|
||||
'@vue/shared': 3.3.0-beta.3
|
||||
|
||||
/@vue/compiler-ssr@3.3.0-beta.5:
|
||||
resolution: {integrity: sha512-16njciFrQ8ejVdH5tsaPbJwpkpBB2z2VWCxfC69UOylCgPxiW01syE9S/mozRvv5Ken9Sr9bd2MjebG/SEpPNg==}
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.3.0-beta.5
|
||||
'@vue/shared': 3.3.0-beta.5
|
||||
dev: false
|
||||
|
||||
/@vue/devtools-api@6.5.0:
|
||||
resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==}
|
||||
|
||||
@ -2613,6 +2687,16 @@ packages:
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.0
|
||||
|
||||
/@vue/reactivity-transform@3.3.0-beta.5:
|
||||
resolution: {integrity: sha512-v/PCri6+vL8WwwgowHlIopwiqonCj9wV60ZYDdX8AMDxk9Q01h2cWh61JF4XOX7qDs7NdJ7zSVyKSz4B98OXBw==}
|
||||
dependencies:
|
||||
'@babel/parser': 7.21.4
|
||||
'@vue/compiler-core': 3.3.0-beta.5
|
||||
'@vue/shared': 3.3.0-beta.5
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.0
|
||||
dev: false
|
||||
|
||||
/@vue/reactivity@3.2.47:
|
||||
resolution: {integrity: sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==}
|
||||
dependencies:
|
||||
@ -2651,6 +2735,10 @@ packages:
|
||||
/@vue/shared@3.3.0-beta.3:
|
||||
resolution: {integrity: sha512-st1SnB/Bkbb9TsieeI4TRX9TqHYIR5wvIma3ZtEben55EYSWa1q5u2BhTNgABSdH+rv3Xwfrvpwh5PmCw6Y53g==}
|
||||
|
||||
/@vue/shared@3.3.0-beta.5:
|
||||
resolution: {integrity: sha512-ImwhHfOzuQrfA05Kx4s7J9g7QJt0sZqSlPvPdd6xj5tTEnPNNJYZOHaIP973mtuEuv4Zfh9v+CLiER6E6gtSqg==}
|
||||
dev: false
|
||||
|
||||
/@webassemblyjs/ast@1.11.5:
|
||||
resolution: {integrity: sha512-LHY/GSAZZRpsNQH+/oHqhRQ5FT7eoULcBqgfyTB5nQHogFnK3/7QoN7dLnwSE/JkUAF0SrRuclT7ODqMFtWxxQ==}
|
||||
dependencies:
|
||||
@ -2997,6 +3085,14 @@ packages:
|
||||
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
|
||||
dev: true
|
||||
|
||||
/ast-walker-scope@0.4.1:
|
||||
resolution: {integrity: sha512-Ro3nmapMxi/remlJdzFH0tiA7A59KDbxVoLlKWaLDrPELiftb9b8w+CCyWRM+sXZH5KHRAgv8feedW6mihvCHA==}
|
||||
engines: {node: '>=14.19.0'}
|
||||
dependencies:
|
||||
'@babel/parser': 7.21.4
|
||||
'@babel/types': 7.21.5
|
||||
dev: false
|
||||
|
||||
/async-sema@3.1.1:
|
||||
resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==}
|
||||
|
||||
@ -6077,6 +6173,13 @@ packages:
|
||||
resolution: {integrity: sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==}
|
||||
engines: {node: 14 || >=16.14}
|
||||
|
||||
/magic-string-ast@0.1.2:
|
||||
resolution: {integrity: sha512-P53AZrzq7hclCU6HWj88xNZHmP15DKjMmK/vBytO1qnpYP3ul4IEZlyCE0aU3JRnmgWmZPmoTKj4Bls7v0pMyA==}
|
||||
engines: {node: '>=14.19.0'}
|
||||
dependencies:
|
||||
magic-string: 0.30.0
|
||||
dev: false
|
||||
|
||||
/magic-string@0.30.0:
|
||||
resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==}
|
||||
engines: {node: '>=12'}
|
||||
@ -8352,6 +8455,33 @@ packages:
|
||||
resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
/unplugin-vue-router@0.6.4(rollup@3.21.5)(vue-router@4.1.6)(vue@3.2.47):
|
||||
resolution: {integrity: sha512-9THVhhtbVFxbsIibjK59oPwMI1UCxRWRPX7azSkTUABsxovlOXJys5SJx0kd/0oKIqNJuYgkRfAgPuO77SqCOg==}
|
||||
peerDependencies:
|
||||
vue-router: ^4.1.0
|
||||
peerDependenciesMeta:
|
||||
vue-router:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/types': 7.21.5
|
||||
'@rollup/pluginutils': 5.0.2(rollup@3.21.5)
|
||||
'@vue-macros/common': 1.3.1(rollup@3.21.5)(vue@3.2.47)
|
||||
ast-walker-scope: 0.4.1
|
||||
chokidar: 3.5.3
|
||||
fast-glob: 3.2.12
|
||||
json5: 2.2.3
|
||||
local-pkg: 0.4.3
|
||||
mlly: 1.2.0
|
||||
pathe: 1.1.0
|
||||
scule: 1.0.0
|
||||
unplugin: 1.3.1
|
||||
vue-router: 4.1.6(vue@3.2.47)
|
||||
yaml: 2.2.2
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
- vue
|
||||
dev: false
|
||||
|
||||
/unplugin@1.3.1:
|
||||
resolution: {integrity: sha512-h4uUTIvFBQRxUKS2Wjys6ivoeofGhxzTe2sRWlooyjHXVttcVfV/JiavNd3d4+jty0SVV0dxGw9AkY9MwiaCEw==}
|
||||
dependencies:
|
||||
@ -9044,6 +9174,11 @@ packages:
|
||||
engines: {node: '>= 14'}
|
||||
dev: true
|
||||
|
||||
/yaml@2.2.2:
|
||||
resolution: {integrity: sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==}
|
||||
engines: {node: '>= 14'}
|
||||
dev: false
|
||||
|
||||
/yargs-parser@21.1.1:
|
||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||
engines: {node: '>=12'}
|
||||
|
1
test/fixtures/basic/nuxt.config.ts
vendored
1
test/fixtures/basic/nuxt.config.ts
vendored
@ -198,6 +198,7 @@ export default defineNuxtConfig({
|
||||
}
|
||||
},
|
||||
experimental: {
|
||||
typedPages: true,
|
||||
polyfillVueUseHead: true,
|
||||
renderJsonPayloads: process.env.TEST_PAYLOAD !== 'js',
|
||||
respectNoSSRHeader: true,
|
||||
|
5
test/fixtures/basic/pages/[...slug].vue
vendored
5
test/fixtures/basic/pages/[...slug].vue
vendored
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>[...slug].vue</h1>
|
||||
<div>catchall at {{ $route.params.slug[0] }}</div>
|
||||
<div>catchall at {{ route.params.slug?.[0] }}</div>
|
||||
<div>Middleware ran: {{ !!($route.meta.override as any)?.includes('extended middleware') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -11,7 +11,8 @@ definePageMeta({
|
||||
middleware: ['override'],
|
||||
validate: to => to.path !== '/forbidden'
|
||||
})
|
||||
if (useRoute().path.includes('navigate-some-path')) {
|
||||
const route = useRoute('slug')
|
||||
if (route.path.includes('navigate-some-path')) {
|
||||
throw createError('navigate-some-path setup running')
|
||||
}
|
||||
</script>
|
||||
|
@ -1,7 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
const route = useRoute('nested-foo-bar')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div>nested/[foo]/[bar].vue</div>
|
||||
<div>foo: {{ $route.params.foo }}</div>
|
||||
<div>bar: {{ $route.params.bar }}</div>
|
||||
<div>foo: {{ route.params.foo }}</div>
|
||||
<div>bar: {{ route.params.bar }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,6 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
const route = useRoute('nested-foo')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div>nested/[foo]/index.vue</div>
|
||||
<div>foo: {{ $route.params.foo }}</div>
|
||||
<div>foo: {{ route.params.foo }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,7 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
const route = useRoute('nested-foo-user-group')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div>nested/[foo]/user-[group].vue</div>
|
||||
<div>foo: {{ $route.params.foo }}</div>
|
||||
<div>group: {{ $route.params.group }}</div>
|
||||
<div>foo: {{ route.params.foo }}</div>
|
||||
<div>group: {{ route.params.group }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -14,7 +14,7 @@ const links = [
|
||||
{ name: 'nuxt-link-trailing-slash' },
|
||||
{ query: { 'with-state': 'true' }, state: { foo: 'bar' } },
|
||||
{ query: { 'without-state': 'true' } }
|
||||
]
|
||||
] as const
|
||||
|
||||
const route = useRoute()
|
||||
const windowState = computed(() => {
|
||||
|
2
test/fixtures/basic/pages/random/[id].vue
vendored
2
test/fixtures/basic/pages/random/[id].vue
vendored
@ -35,7 +35,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
const route = useRoute('random-id')
|
||||
|
||||
const pageKey = 'rand_' + route.params.id
|
||||
|
||||
|
61
test/fixtures/basic/types.ts
vendored
61
test/fixtures/basic/types.ts
vendored
@ -1,13 +1,13 @@
|
||||
import { describe, expectTypeOf, it } from 'vitest'
|
||||
import type { Ref } from 'vue'
|
||||
import type { FetchError } from 'ofetch'
|
||||
import type { NavigationFailure, RouteLocationNormalizedLoaded, RouteLocationRaw, Router, useRouter as vueUseRouter } from 'vue-router'
|
||||
import type { NavigationFailure, RouteLocationNormalizedLoaded, RouteLocationRaw, Router, useRouter as vueUseRouter } from '#vue-router'
|
||||
|
||||
import type { AppConfig, RuntimeValue } from 'nuxt/schema'
|
||||
import { defineNuxtConfig } from 'nuxt/config'
|
||||
import { callWithNuxt, isVue3 } from '#app'
|
||||
import type { NavigateToOptions } from '#app/composables/router'
|
||||
import { NuxtPage } from '#components'
|
||||
import { NuxtLink, NuxtPage } from '#components'
|
||||
import { useRouter } from '#imports'
|
||||
|
||||
interface TestResponse { message: string }
|
||||
@ -107,6 +107,63 @@ describe('middleware', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('typed router integration', () => {
|
||||
it('allows typing useRouter', () => {
|
||||
const router = useRouter()
|
||||
// @ts-expect-error this named route does not exist
|
||||
router.push({ name: 'some-thing' })
|
||||
// this one does
|
||||
router.push({ name: 'fixed-keyed-child-parent' })
|
||||
// @ts-expect-error this is an invalid param
|
||||
router.push({ name: 'random-id', params: { bob: 23 } })
|
||||
router.push({ name: 'random-id', params: { id: 4 } })
|
||||
})
|
||||
|
||||
it('allows typing useRoute', () => {
|
||||
const route = useRoute('random-id')
|
||||
// @ts-expect-error this param does not exist
|
||||
const _invalid = route.params.something
|
||||
// this param does
|
||||
const _valid = route.params.id
|
||||
})
|
||||
|
||||
it('allows typing navigateTo', () => {
|
||||
// @ts-expect-error this named route does not exist
|
||||
navigateTo({ name: 'some-thing' })
|
||||
// this one does
|
||||
navigateTo({ name: 'fixed-keyed-child-parent' })
|
||||
// @ts-expect-error this is an invalid param
|
||||
navigateTo({ name: 'random-id', params: { bob: 23 } })
|
||||
navigateTo({ name: 'random-id', params: { id: 4 } })
|
||||
})
|
||||
|
||||
it('allows typing middleware', () => {
|
||||
defineNuxtRouteMiddleware((to) => {
|
||||
expectTypeOf(to.name).not.toBeAny()
|
||||
// @ts-expect-error this route does not exist
|
||||
expectTypeOf(to.name === 'bob').toMatchTypeOf<boolean>()
|
||||
expectTypeOf(to.name === 'assets').toMatchTypeOf<boolean>()
|
||||
})
|
||||
})
|
||||
|
||||
it('respects pages:extend augmentation', () => {
|
||||
// added via pages:extend
|
||||
expectTypeOf(useRoute().name === 'internal-async-parent').toMatchTypeOf<boolean>()
|
||||
// @ts-expect-error this route does not exist
|
||||
expectTypeOf(useRoute().name === 'invalid').toMatchTypeOf<boolean>()
|
||||
})
|
||||
|
||||
it('allows typing NuxtLink', () => {
|
||||
// @ts-expect-error this named route does not exist
|
||||
h(NuxtLink, { to: { name: 'some-thing' } })
|
||||
// this one does
|
||||
h(NuxtLink, { to: { name: 'fixed-keyed-child-parent' } })
|
||||
// @ts-expect-error this is an invalid param
|
||||
h(NuxtLink, { to: { name: 'random-id', params: { bob: 23 } } })
|
||||
h(NuxtLink, { to: { name: 'random-id', params: { id: 4 } } })
|
||||
})
|
||||
})
|
||||
|
||||
describe('layouts', () => {
|
||||
it('recognizes named layouts', () => {
|
||||
definePageMeta({ layout: 'custom' })
|
||||
|
@ -31,6 +31,9 @@
|
||||
],
|
||||
"#internal/nitro/utils": [
|
||||
"./node_modules/nitropack/dist/runtime/utils"
|
||||
],
|
||||
"#vue-router": [
|
||||
"./node_modules/vue-router"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user