mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 21:55:11 +00:00
fix(nuxt): resolve internal target: blank
links with base (#23751)
This commit is contained in:
parent
7fcdce26b2
commit
ffa6b6e60c
@ -1,12 +1,12 @@
|
|||||||
import type { ComputedRef, DefineComponent, InjectionKey, PropType } from 'vue'
|
import type { ComputedRef, DefineComponent, InjectionKey, PropType } from 'vue'
|
||||||
import { computed, defineComponent, h, inject, onBeforeUnmount, onMounted, provide, ref, resolveComponent } from 'vue'
|
import { computed, defineComponent, h, inject, onBeforeUnmount, onMounted, provide, 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 { hasProtocol, joinURL, parseQuery, parseURL, withTrailingSlash, withoutTrailingSlash } from 'ufo'
|
||||||
|
|
||||||
import { preloadRouteComponents } from '../composables/preload'
|
import { preloadRouteComponents } from '../composables/preload'
|
||||||
import { onNuxtReady } from '../composables/ready'
|
import { onNuxtReady } from '../composables/ready'
|
||||||
import { navigateTo, useRouter } from '../composables/router'
|
import { navigateTo, useRouter } from '../composables/router'
|
||||||
import { useNuxtApp } from '../nuxt'
|
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
|
||||||
import { cancelIdleCallback, requestIdleCallback } from '../compat/idle-callback'
|
import { cancelIdleCallback, requestIdleCallback } from '../compat/idle-callback'
|
||||||
|
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
@ -170,6 +170,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
},
|
},
|
||||||
setup (props, { slots }) {
|
setup (props, { slots }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
// Resolving `to` value from `to` and `href` props
|
// Resolving `to` value from `to` and `href` props
|
||||||
const to: ComputedRef<string | RouteLocationRaw> = computed(() => {
|
const to: ComputedRef<string | RouteLocationRaw> = computed(() => {
|
||||||
@ -180,6 +181,9 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
return resolveTrailingSlashBehavior(path, router.resolve)
|
return resolveTrailingSlashBehavior(path, router.resolve)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Lazily check whether to.value has a protocol
|
||||||
|
const isProtocolURL = computed(() => typeof to.value === 'string' && hasProtocol(to.value, { acceptRelative: true }))
|
||||||
|
|
||||||
// Resolving link type
|
// Resolving link type
|
||||||
const isExternal = computed<boolean>(() => {
|
const isExternal = computed<boolean>(() => {
|
||||||
// External prop is explicitly set
|
// External prop is explicitly set
|
||||||
@ -197,7 +201,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return to.value === '' || hasProtocol(to.value, { acceptRelative: true })
|
return to.value === '' || isProtocolURL.value
|
||||||
})
|
})
|
||||||
|
|
||||||
// Prefetching
|
// Prefetching
|
||||||
@ -280,7 +284,11 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
|
|
||||||
// Resolves `to` value if it's a route location object
|
// Resolves `to` value if it's a route location object
|
||||||
// converts `""` to `null` to prevent the attribute from being added as empty (`href=""`)
|
// converts `""` to `null` to prevent the attribute from being added as empty (`href=""`)
|
||||||
const href = typeof to.value === 'object' ? router.resolve(to.value)?.href ?? null : to.value || null
|
const href = typeof to.value === 'object'
|
||||||
|
? router.resolve(to.value)?.href ?? null
|
||||||
|
: (to.value && !props.external && !isProtocolURL.value)
|
||||||
|
? resolveTrailingSlashBehavior(joinURL(config.app.baseURL, to.value), router.resolve) as string
|
||||||
|
: to.value || null
|
||||||
|
|
||||||
// Resolves `target` value
|
// Resolves `target` value
|
||||||
const target = props.target || null
|
const target = props.target || null
|
||||||
|
@ -2,6 +2,16 @@ import { describe, expect, it, vi } from 'vitest'
|
|||||||
import type { RouteLocation, RouteLocationRaw } from 'vue-router'
|
import type { RouteLocation, RouteLocationRaw } from 'vue-router'
|
||||||
import type { NuxtLinkOptions, NuxtLinkProps } from '../src/app/components/nuxt-link'
|
import type { NuxtLinkOptions, NuxtLinkProps } from '../src/app/components/nuxt-link'
|
||||||
import { defineNuxtLink } from '../src/app/components/nuxt-link'
|
import { defineNuxtLink } from '../src/app/components/nuxt-link'
|
||||||
|
import { useRuntimeConfig } from '../src/app/nuxt'
|
||||||
|
|
||||||
|
// mocks `useRuntimeConfig()`
|
||||||
|
vi.mock('../src/app/nuxt', () => ({
|
||||||
|
useRuntimeConfig: vi.fn(() => ({
|
||||||
|
app: {
|
||||||
|
baseURL: '/'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
|
||||||
// Mocks `h()`
|
// Mocks `h()`
|
||||||
vi.mock('vue', async () => {
|
vi.mock('vue', async () => {
|
||||||
@ -125,6 +135,40 @@ describe('nuxt-link:propsOrAttributes', () => {
|
|||||||
it('defaults to `null`', () => {
|
it('defaults to `null`', () => {
|
||||||
expect(nuxtLink({ to: 'https://nuxtjs.org' }).props.target).toBe(null)
|
expect(nuxtLink({ to: 'https://nuxtjs.org' }).props.target).toBe(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('prefixes target="_blank" internal links with baseURL', () => {
|
||||||
|
vi.mocked(useRuntimeConfig).withImplementation(() => {
|
||||||
|
return {
|
||||||
|
app: {
|
||||||
|
baseURL: '/base'
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
}, () => {
|
||||||
|
expect(nuxtLink({ to: '/', target: '_blank' }).props.href).toBe('/base')
|
||||||
|
expect(nuxtLink({ to: '/base', target: '_blank' }).props.href).toBe('/base/base')
|
||||||
|
expect(nuxtLink({ to: '/to', target: '_blank' }).props.href).toBe('/base/to')
|
||||||
|
expect(nuxtLink({ to: '/base/to', target: '_blank' }).props.href).toBe('/base/base/to')
|
||||||
|
expect(nuxtLink({ to: '//base/to', target: '_blank' }).props.href).toBe('//base/to')
|
||||||
|
expect(nuxtLink({ to: '//to.com/thing', target: '_blank' }).props.href).toBe('//to.com/thing')
|
||||||
|
expect(nuxtLink({ to: 'https://test.com/to', target: '_blank' }).props.href).toBe('https://test.com/to')
|
||||||
|
|
||||||
|
expect(nuxtLink({ to: '/', target: '_blank' }, { trailingSlash: 'append' }).props.href).toBe('/base/')
|
||||||
|
expect(nuxtLink({ to: '/base/', target: '_blank' }, { trailingSlash: 'remove' }).props.href).toBe('/base/base')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('excludes the baseURL for external links', () => {
|
||||||
|
vi.mocked(useRuntimeConfig).withImplementation(() => {
|
||||||
|
return {
|
||||||
|
app: {
|
||||||
|
baseURL: '/base'
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
}, () => {
|
||||||
|
expect(nuxtLink({ to: 'http://nuxtjs.org/app/about', target: '_blank' }).props.href).toBe('http://nuxtjs.org/app/about')
|
||||||
|
expect(nuxtLink({ to: '//nuxtjs.org/app/about', target: '_blank' }).props.href).toBe('//nuxtjs.org/app/about')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('rel', () => {
|
describe('rel', () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user