chore: clean up

This commit is contained in:
Harlan Wilton 2024-02-01 01:16:15 +11:00
parent da7a9e952e
commit 5ecde8037c
3 changed files with 187 additions and 67 deletions

View File

@ -67,7 +67,6 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
} }
} }
// TODO migrate to TypeScript props
return defineComponent({ return defineComponent({
name: componentName, name: componentName,
props: { props: {
@ -161,7 +160,9 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
const prefetched = ref(false) const prefetched = ref(false)
const el = import.meta.server ? undefined : ref<HTMLElement | null>(null) const el = import.meta.server ? undefined : ref<HTMLElement | null>(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(() => { const link = computed(() => {
checkPropConflicts(props, 'to', 'href') checkPropConflicts(props, 'to', 'href')
@ -170,7 +171,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
const href = computed(() => { const href = computed(() => {
return typeof link.value === 'string' ? link.value : router.resolve(link.value).path 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 as = computed(() => {
const forceAnchorTag = props.external const forceAnchorTag = props.external
if (forceAnchorTag || isAbsoluteLink.value) { if (forceAnchorTag || isAbsoluteLink.value) {
@ -209,7 +210,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
}) })
const anchorProps = computed(() => { const anchorProps = computed(() => {
const to = link.value const to = href.value
// Resolves `target` value // Resolves `target` value
const target = props.target || null const target = props.target || null
@ -241,8 +242,10 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
unobserve = null unobserve = null
await Promise.all([ await Promise.all([
nuxtApp.hooks.callHook('link:prefetch', href.value).catch(() => {}), nuxtApp.hooks.callHook('link:prefetch', href.value).catch(() => {
as.value === 'RouterLink' && preloadRouteComponents(link.value, router).catch(() => {}) }),
as.value === 'RouterLink' && preloadRouteComponents(link.value, router).catch(() => {
})
]) ])
prefetched.value = true prefetched.value = true
}) })
@ -251,7 +254,9 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
}) })
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (idleId) { cancelIdleCallback(idleId) } if (idleId) {
cancelIdleCallback(idleId)
}
unobserve?.() unobserve?.()
unobserve = null unobserve = null
}) })
@ -261,7 +266,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
if (import.meta.dev && import.meta.server && !props.custom) { if (import.meta.dev && import.meta.server && !props.custom) {
const isNuxtLinkChild = inject(NuxtLinkDevKeySymbol, false) const isNuxtLinkChild = inject(NuxtLinkDevKeySymbol, false)
if (isNuxtLinkChild) { if (isNuxtLinkChild) {
console.log('[nuxt] [NuxtLink] You can\'t nest one <a> inside another <a>. 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 <a> inside another <a>. This will cause a hydration error on client-side. You can pass the `custom` prop to take full control of the markup.')
} else { } else {
provide(NuxtLinkDevKeySymbol, true) provide(NuxtLinkDevKeySymbol, true)
} }
@ -277,14 +282,13 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
) )
} }
if (typeof link.value === 'object') { if (import.meta.dev && 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) console.warn('[nuxt] [NuxtLink] Providing `to` as a vue-router route is not supported with external links.', href.value)
return null
} }
const navigate = () => { const navigate = () => {
if (isAbsoluteLink.value) { 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
} }
return navigateTo(anchorProps.value.href, { return navigateTo(anchorProps.value.href, {

View File

@ -526,55 +526,164 @@ describe('nuxt links', () => {
const data: Record<string, string[]> = {} const data: Record<string, string[]> = {}
for (const selector of ['nuxt-link', 'router-link', 'link-with-trailing-slash', 'link-without-trailing-slash']) { for (const selector of ['nuxt-link', 'router-link', 'link-with-trailing-slash', 'link-without-trailing-slash']) {
data[selector] = [] data[selector] = []
for (const match of html.matchAll(new RegExp(`href="([^"]*)"[^>]*class="[^"]*\\b${selector}\\b`, 'g'))) { // extract all anchor tags
data[selector].push(match[1]) for (const match of html.matchAll(new RegExp(`<a[^>]+class="[^"]*${selector}[^"]*"[^>]*>`, 'g'))) {
data[selector].push(match)
} }
} }
expect(data).toMatchInlineSnapshot(` expect(data).toMatchInlineSnapshot(`
{ {
"link-with-trailing-slash": [ "link-with-trailing-slash": [
"/", [
"/nuxt-link/trailing-slash/", "<a href="/" class="link-with-trailing-slash">",
"/nuxt-link/trailing-slash/", ],
"/nuxt-link/trailing-slash/?test=true&amp;thing=other/thing#thing-other", [
"/nuxt-link/trailing-slash/?test=true&amp;thing=other/thing#thing-other", "<a aria-current="page" href="/nuxt-link/trailing-slash/" class="foo-active-class bar-exact-active-class link-with-trailing-slash">",
"/nuxt-link/trailing-slash/", ],
"/nuxt-link/trailing-slash/?with-state=true", [
"/nuxt-link/trailing-slash/?without-state=true", "<a aria-current="page" href="/nuxt-link/trailing-slash/" class="foo-active-class bar-exact-active-class link-with-trailing-slash">",
"https://example.com/page.html", ],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash/?test=true&amp;thing=other/thing#thing-other" class="foo-active-class bar-exact-active-class link-with-trailing-slash">",
],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash/?test=true&amp;thing=other/thing#thing-other" class="foo-active-class bar-exact-active-class link-with-trailing-slash">",
],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash/" class="foo-active-class bar-exact-active-class link-with-trailing-slash">",
],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash/?with-state=true" class="foo-active-class bar-exact-active-class link-with-trailing-slash">",
],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash/?without-state=true" class="foo-active-class bar-exact-active-class link-with-trailing-slash">",
],
[
"<a href="https://example.com/page.html" rel="noopener noreferrer" class="link-with-trailing-slash">",
],
[
"<a href="/nuxt-link/https://example.com/page.html" rel="noopener noreferrer" class="link-with-trailing-slash">",
],
[
"<a href="/foo" rel="noopener noreferrer" class="link-with-trailing-slash">",
],
[
"<a href="/foo" rel="noopener noreferrer" class="link-with-trailing-slash">",
],
], ],
"link-without-trailing-slash": [ "link-without-trailing-slash": [
"/", [
"/nuxt-link/trailing-slash", "<a href="/" class="link-without-trailing-slash">",
"/nuxt-link/trailing-slash", ],
"/nuxt-link/trailing-slash?test=true&amp;thing=other/thing#thing-other", [
"/nuxt-link/trailing-slash?test=true&amp;thing=other/thing#thing-other", "<a aria-current="page" href="/nuxt-link/trailing-slash" class="foo-active-class bar-exact-active-class link-without-trailing-slash">",
"/nuxt-link/trailing-slash", ],
"/nuxt-link/trailing-slash?with-state=true", [
"/nuxt-link/trailing-slash?without-state=true", "<a aria-current="page" href="/nuxt-link/trailing-slash" class="foo-active-class bar-exact-active-class link-without-trailing-slash">",
"https://example.com/page.html", ],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash?test=true&amp;thing=other/thing#thing-other" class="foo-active-class bar-exact-active-class link-without-trailing-slash">",
],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash?test=true&amp;thing=other/thing#thing-other" class="foo-active-class bar-exact-active-class link-without-trailing-slash">",
],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash" class="foo-active-class bar-exact-active-class link-without-trailing-slash">",
],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash?with-state=true" class="foo-active-class bar-exact-active-class link-without-trailing-slash">",
],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash?without-state=true" class="foo-active-class bar-exact-active-class link-without-trailing-slash">",
],
[
"<a href="https://example.com/page.html" rel="noopener noreferrer" class="link-without-trailing-slash">",
],
[
"<a href="/nuxt-link/https://example.com/page.html" rel="noopener noreferrer" class="link-without-trailing-slash">",
],
[
"<a href="/foo" rel="noopener noreferrer" class="link-without-trailing-slash">",
],
[
"<a href="/foo" rel="noopener noreferrer" class="link-without-trailing-slash">",
],
], ],
"nuxt-link": [ "nuxt-link": [
"/", [
"/nuxt-link/trailing-slash", "<a href="/" class="nuxt-link">",
"/nuxt-link/trailing-slash/", ],
"/nuxt-link/trailing-slash?test=true&amp;thing=other/thing#thing-other", [
"/nuxt-link/trailing-slash/?test=true&amp;thing=other/thing#thing-other", "<a aria-current="page" href="/nuxt-link/trailing-slash" class="foo-active-class bar-exact-active-class nuxt-link">",
"/nuxt-link/trailing-slash", ],
"/nuxt-link/trailing-slash?with-state=true", [
"/nuxt-link/trailing-slash?without-state=true", "<a aria-current="page" href="/nuxt-link/trailing-slash/" class="foo-active-class bar-exact-active-class nuxt-link">",
"https://example.com/page.html", ],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash?test=true&amp;thing=other/thing#thing-other" class="foo-active-class bar-exact-active-class nuxt-link">",
],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash/?test=true&amp;thing=other/thing#thing-other" class="foo-active-class bar-exact-active-class nuxt-link">",
],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash" class="foo-active-class bar-exact-active-class nuxt-link">",
],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash?with-state=true" class="foo-active-class bar-exact-active-class nuxt-link">",
],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash?without-state=true" class="foo-active-class bar-exact-active-class nuxt-link">",
],
[
"<a href="https://example.com/page.html" rel="noopener noreferrer" class="nuxt-link">",
],
[
"<a href="/nuxt-link/https://example.com/page.html" rel="noopener noreferrer" class="nuxt-link">",
],
[
"<a href="/foo" rel="noopener noreferrer" class="nuxt-link">",
],
[
"<a href="/foo" rel="noopener noreferrer" class="nuxt-link">",
],
], ],
"router-link": [ "router-link": [
"/", [
"/nuxt-link/trailing-slash", "<a href="/" class="router-link">",
"/nuxt-link/trailing-slash/", ],
"/nuxt-link/trailing-slash?test=true&amp;thing=other/thing#thing-other", [
"/nuxt-link/trailing-slash/?test=true&amp;thing=other/thing#thing-other", "<a aria-current="page" href="/nuxt-link/trailing-slash" class="foo-active-class bar-exact-active-class router-link">",
"/nuxt-link/trailing-slash", ],
"/nuxt-link/trailing-slash?with-state=true", [
"/nuxt-link/trailing-slash?without-state=true", "<a aria-current="page" href="/nuxt-link/trailing-slash/" class="foo-active-class bar-exact-active-class router-link">",
"/nuxt-link/https://example.com/page.html", ],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash?test=true&amp;thing=other/thing#thing-other" class="foo-active-class bar-exact-active-class router-link">",
],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash/?test=true&amp;thing=other/thing#thing-other" class="foo-active-class bar-exact-active-class router-link">",
],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash" class="foo-active-class bar-exact-active-class router-link">",
],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash?with-state=true" class="foo-active-class bar-exact-active-class router-link">",
],
[
"<a aria-current="page" href="/nuxt-link/trailing-slash?without-state=true" class="foo-active-class bar-exact-active-class router-link">",
],
[
"<a href="/nuxt-link/https://example.com/page.html" class="router-link">",
],
[
"<a href="/nuxt-link/https://example.com/page.html" class="router-link" external="true">",
],
[
"<a href="/foo" class="router-link" external="true">",
],
[
"<a href="/foo" class="router-link" external="true">",
],
], ],
} }
`) `)

View File

@ -6,15 +6,22 @@ const LinkWithoutTrailingSlash = defineNuxtLink({
trailingSlash: 'remove' trailingSlash: 'remove'
}) })
const links = [ const links = [
'/', { to: '/', },
'/nuxt-link/trailing-slash', { to: '/nuxt-link/trailing-slash',},
'/nuxt-link/trailing-slash/', { to: '/nuxt-link/trailing-slash/',},
'/nuxt-link/trailing-slash?test=true&thing=other/thing#thing-other', { to: '/nuxt-link/trailing-slash?test=true&thing=other/thing#thing-other',},
'/nuxt-link/trailing-slash/?test=true&thing=other/thing#thing-other', { to: '/nuxt-link/trailing-slash/?test=true&thing=other/thing#thing-other',},
{ name: 'nuxt-link-trailing-slash' }, { to: { name: 'nuxt-link-trailing-slash' },},
{ query: { 'with-state': 'true' }, state: { foo: 'bar' } }, { to: { query: { 'with-state': 'true' }, state: { foo: 'bar' } },},
{ query: { 'without-state': 'true' } }, { to: { query: { 'without-state': 'true' } }},
'https://example.com/page.html' // 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 ] as const
const route = useRoute() const route = useRoute()
@ -42,13 +49,13 @@ const windowState = computed(() => {
:key="index" :key="index"
> >
<LinkWithTrailingSlash <LinkWithTrailingSlash
:to="link" v-bind="link"
class="link-with-trailing-slash" class="link-with-trailing-slash"
> >
<LinkWithTrailingSlash <LinkWithTrailingSlash
v-slot="{ href }" v-slot="{ href }"
custom custom
:to="link" v-bind="link"
> >
{{ href }} {{ href }}
</LinkWithTrailingSlash> </LinkWithTrailingSlash>
@ -63,13 +70,13 @@ const windowState = computed(() => {
:key="index" :key="index"
> >
<LinkWithoutTrailingSlash <LinkWithoutTrailingSlash
:to="link" v-bind="link"
class="link-without-trailing-slash" class="link-without-trailing-slash"
> >
<LinkWithoutTrailingSlash <LinkWithoutTrailingSlash
v-slot="{ href }" v-slot="{ href }"
custom custom
:to="link" v-bind="link"
> >
{{ href }} {{ href }}
</LinkWithoutTrailingSlash> </LinkWithoutTrailingSlash>
@ -84,13 +91,13 @@ const windowState = computed(() => {
:key="index" :key="index"
> >
<NuxtLink <NuxtLink
:to="link" v-bind="link"
class="nuxt-link" class="nuxt-link"
> >
<NuxtLink <NuxtLink
v-slot="{ href }" v-slot="{ href }"
custom custom
:to="link" v-bind="link"
> >
{{ href }} {{ href }}
</NuxtLink> </NuxtLink>
@ -105,13 +112,13 @@ const windowState = computed(() => {
:key="index" :key="index"
> >
<RouterLink <RouterLink
:to="link" v-bind="link"
class="router-link" class="router-link"
> >
<RouterLink <RouterLink
v-slot="{ href }" v-slot="{ href }"
custom custom
:to="link" v-bind="link"
> >
{{ href }} {{ href }}
</RouterLink> </RouterLink>