diff --git a/packages/nuxt/src/app/components/nuxt-link.ts b/packages/nuxt/src/app/components/nuxt-link.ts index 4a8f5d3537..5657fdedb5 100644 --- a/packages/nuxt/src/app/components/nuxt-link.ts +++ b/packages/nuxt/src/app/components/nuxt-link.ts @@ -67,7 +67,6 @@ export function defineNuxtLink (options: NuxtLinkOptions) { } } - // TODO migrate to TypeScript props return defineComponent({ name: componentName, props: { @@ -161,7 +160,9 @@ export function defineNuxtLink (options: NuxtLinkOptions) { const prefetched = ref(false) const el = import.meta.server ? undefined : ref(null) - const elRef = import.meta.server ? undefined : (ref: any) => { el!.value = props.custom ? ref?.$el?.nextElementSibling : ref?.$el } + const elRef = import.meta.server ? undefined : (ref: any) => { + el!.value = props.custom ? ref?.$el?.nextElementSibling : ref?.$el + } const link = computed(() => { checkPropConflicts(props, 'to', 'href') @@ -170,7 +171,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) { const href = computed(() => { return typeof link.value === 'string' ? link.value : router.resolve(link.value).path }) - const isAbsoluteLink = computed(() => hasProtocol(href.value, { acceptRelative: true })) + const isAbsoluteLink = computed(() => hasProtocol(href.value, {acceptRelative: true})) const as = computed(() => { const forceAnchorTag = props.external if (forceAnchorTag || isAbsoluteLink.value) { @@ -209,7 +210,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) { }) const anchorProps = computed(() => { - const to = link.value + const to = href.value // Resolves `target` value const target = props.target || null @@ -241,8 +242,10 @@ export function defineNuxtLink (options: NuxtLinkOptions) { unobserve = null await Promise.all([ - nuxtApp.hooks.callHook('link:prefetch', href.value).catch(() => {}), - as.value === 'RouterLink' && preloadRouteComponents(link.value, router).catch(() => {}) + nuxtApp.hooks.callHook('link:prefetch', href.value).catch(() => { + }), + as.value === 'RouterLink' && preloadRouteComponents(link.value, router).catch(() => { + }) ]) prefetched.value = true }) @@ -251,7 +254,9 @@ export function defineNuxtLink (options: NuxtLinkOptions) { }) }) onBeforeUnmount(() => { - if (idleId) { cancelIdleCallback(idleId) } + if (idleId) { + cancelIdleCallback(idleId) + } unobserve?.() unobserve = null }) @@ -261,7 +266,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) { if (import.meta.dev && import.meta.server && !props.custom) { const isNuxtLinkChild = inject(NuxtLinkDevKeySymbol, false) if (isNuxtLinkChild) { - console.log('[nuxt] [NuxtLink] You can\'t nest one inside another . This will cause a hydration error on client-side. You can pass the `custom` prop to take full control of the markup.') + console.warn('[nuxt] [NuxtLink] You can\'t nest one inside another . This will cause a hydration error on client-side. You can pass the `custom` prop to take full control of the markup.') } else { provide(NuxtLinkDevKeySymbol, true) } @@ -277,14 +282,13 @@ export function defineNuxtLink (options: NuxtLinkOptions) { ) } - if (typeof link.value === 'object') { - import.meta.dev && console.log('[nuxt] [NuxtLink] Providing `to` as a vue-router route is not supported with external links.', link.value) - return null + if (import.meta.dev && typeof link.value === 'object') { + console.warn('[nuxt] [NuxtLink] Providing `to` as a vue-router route is not supported with external links.', href.value) } const navigate = () => { if (isAbsoluteLink.value) { - import.meta.dev && console.log('[nuxt] [NuxtLink] Navigating to an absolute link using `navigate()` isn\'t supported', anchorProps.value.href) + import.meta.dev && console.warn('[nuxt] [NuxtLink] Navigating to an absolute link using `navigate()` isn\'t supported.', href.value) return } return navigateTo(anchorProps.value.href, { diff --git a/test/basic.test.ts b/test/basic.test.ts index 56dd3b641e..66c0592e2e 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -526,55 +526,164 @@ describe('nuxt links', () => { const data: Record = {} for (const selector of ['nuxt-link', 'router-link', 'link-with-trailing-slash', 'link-without-trailing-slash']) { data[selector] = [] - for (const match of html.matchAll(new RegExp(`href="([^"]*)"[^>]*class="[^"]*\\b${selector}\\b`, 'g'))) { - data[selector].push(match[1]) + // extract all anchor tags + for (const match of html.matchAll(new RegExp(`]+class="[^"]*${selector}[^"]*"[^>]*>`, 'g'))) { + data[selector].push(match) } } expect(data).toMatchInlineSnapshot(` { "link-with-trailing-slash": [ - "/", - "/nuxt-link/trailing-slash/", - "/nuxt-link/trailing-slash/", - "/nuxt-link/trailing-slash/?test=true&thing=other/thing#thing-other", - "/nuxt-link/trailing-slash/?test=true&thing=other/thing#thing-other", - "/nuxt-link/trailing-slash/", - "/nuxt-link/trailing-slash/?with-state=true", - "/nuxt-link/trailing-slash/?without-state=true", - "https://example.com/page.html", + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], ], "link-without-trailing-slash": [ - "/", - "/nuxt-link/trailing-slash", - "/nuxt-link/trailing-slash", - "/nuxt-link/trailing-slash?test=true&thing=other/thing#thing-other", - "/nuxt-link/trailing-slash?test=true&thing=other/thing#thing-other", - "/nuxt-link/trailing-slash", - "/nuxt-link/trailing-slash?with-state=true", - "/nuxt-link/trailing-slash?without-state=true", - "https://example.com/page.html", + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], ], "nuxt-link": [ - "/", - "/nuxt-link/trailing-slash", - "/nuxt-link/trailing-slash/", - "/nuxt-link/trailing-slash?test=true&thing=other/thing#thing-other", - "/nuxt-link/trailing-slash/?test=true&thing=other/thing#thing-other", - "/nuxt-link/trailing-slash", - "/nuxt-link/trailing-slash?with-state=true", - "/nuxt-link/trailing-slash?without-state=true", - "https://example.com/page.html", + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], ], "router-link": [ - "/", - "/nuxt-link/trailing-slash", - "/nuxt-link/trailing-slash/", - "/nuxt-link/trailing-slash?test=true&thing=other/thing#thing-other", - "/nuxt-link/trailing-slash/?test=true&thing=other/thing#thing-other", - "/nuxt-link/trailing-slash", - "/nuxt-link/trailing-slash?with-state=true", - "/nuxt-link/trailing-slash?without-state=true", - "/nuxt-link/https://example.com/page.html", + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], + [ + "", + ], ], } `) diff --git a/test/fixtures/basic/pages/nuxt-link/trailing-slash.vue b/test/fixtures/basic/pages/nuxt-link/trailing-slash.vue index 526cdca842..ac30a33a5b 100644 --- a/test/fixtures/basic/pages/nuxt-link/trailing-slash.vue +++ b/test/fixtures/basic/pages/nuxt-link/trailing-slash.vue @@ -6,15 +6,22 @@ const LinkWithoutTrailingSlash = defineNuxtLink({ trailingSlash: 'remove' }) const links = [ - '/', - '/nuxt-link/trailing-slash', - '/nuxt-link/trailing-slash/', - '/nuxt-link/trailing-slash?test=true&thing=other/thing#thing-other', - '/nuxt-link/trailing-slash/?test=true&thing=other/thing#thing-other', - { name: 'nuxt-link-trailing-slash' }, - { query: { 'with-state': 'true' }, state: { foo: 'bar' } }, - { query: { 'without-state': 'true' } }, - 'https://example.com/page.html' + { to: '/', }, + { to: '/nuxt-link/trailing-slash',}, + { to: '/nuxt-link/trailing-slash/',}, + { to: '/nuxt-link/trailing-slash?test=true&thing=other/thing#thing-other',}, + { to: '/nuxt-link/trailing-slash/?test=true&thing=other/thing#thing-other',}, + { to: { name: 'nuxt-link-trailing-slash' },}, + { to: { query: { 'with-state': 'true' }, state: { foo: 'bar' } },}, + { to: { query: { 'without-state': 'true' } }}, + // Trailing slashes are applied to implicit external links + { to: 'https://example.com/page.html' }, + // Explicit external links do not when using vue-router object + { to: { path: 'https://example.com/page.html' }, external: true }, + // Explicit external links (that are relative) that use vue-router object adds base and trailing slash + { to: { path: '/foo' }, external: true }, + // Explicit external for relative path trailing slashes is applied + { to: '/foo', external: true }, ] as const const route = useRoute() @@ -42,13 +49,13 @@ const windowState = computed(() => { :key="index" > {{ href }} @@ -63,13 +70,13 @@ const windowState = computed(() => { :key="index" > {{ href }} @@ -84,13 +91,13 @@ const windowState = computed(() => { :key="index" > {{ href }} @@ -105,13 +112,13 @@ const windowState = computed(() => { :key="index" > {{ href }}