nuxt / nuxt

The Intuitive Vue Framework.
https://nuxt.com
MIT License
53.13k stars 4.88k forks source link

Can't use is="NuxtLink" with component #13659

Closed AxelBriche closed 2 years ago

AxelBriche commented 2 years ago

Environment


Reproduction

<component is="NuxtLink" to="/">Example</component>

Generate this html:

<nuxtlink to="/">Example</nuxtlink>

Expected html:

<a aria-current="page" href="/" class="router-link-active router-link-exact-active">Example</a>

Describe the bug

Usage of <component is="NuxtLink" to="/"> doesn't generate a link, I can't use <component :is="to ? 'NuxtLink' : href ? 'a' : 'button'"

Additional context

No response

Logs

No response

lukaszflorczak commented 2 years ago

First you can use nuxt-link for external links now. Check the doc: https://v3.nuxtjs.org/docs/usage/nuxt-link.

If you still need to use it in is prop, you can use defineNuxtLink({}) instead of nuxt-link:

const component = computed(() => {
  if (props.to || props.href) return defineNuxtLink({})
  return 'button'
})
danielroe commented 2 years ago

Or, to reuse the existing NuxtLink component:

const component = computed(() => {
  if (props.to || props.href) return resolveComponent('NuxtLink')
  return 'button'
})

Example: https://stackblitz.com/edit/github-vwzpk9?file=app.vue.

The reason this is required is that if you don't use NuxtLink it won't be included in your bundle. So we need more than just a string to tell us.

If you want it globally registered then you can create your own NuxtLink component with defineNuxtLink in your components directory and set components.global to true in your nuxt.config. See https://github.com/nuxt/framework/pull/3305 for some context.

iamandrewluca commented 1 year ago

Does resolveComponent work only in an SFC context? 🤔

I tried it in a .vue file, it returns the actual component I tried in a .ts file, it returns NuxtLink string

Dodje commented 1 year ago

There's kinda strange behavior when I try to resolveComponent inside of ui-library. It cant resolve NuxtLink component

I have a nuxt page where I use a link component:

<template>
    <base-link as="nuxt-link">Link</base-link>
 </template>

 <script setup>
import BaseLink from '@private-library/ui';
 </script>

The components looks like

<template>
    <component is="linkComponent" ...>
    ...
</template>
<script ...>
...
const linkComponent = resolveComponent('NuxtLink');
// console.log(linkConponent); -> null
</script>  
danielroe commented 1 year ago

@Dodje It's because NuxtLink isn't globally registered - it's auto-imported. Within a Nuxt project we detect resolveComponent('NuxtLink') and rewrite this into an import statement.

You can probably resolve this in your case by adding your ui library to build.transpile, and using import { NuxtLink } from '#imports'...

Dodje commented 1 year ago

@danielroe thanks for a quick response Due your suggestion I was able to create ConcreteComponent with import { defineNuxtLink } from '#imports' (or just import NuxtLink from #components, and it seems to work fine on server side, but i'm getting error "Failed to resolve component: NuxtLink" on the client side after hydration

danielroe commented 1 year ago

I'll happily look at a reproduction.

Dodje commented 1 year ago

Sometimes life is like a joke, I've created a reproduction repo and... it works just fine :) Tried again in the project, and it works too.. So, thanks again 👍

danielroe commented 1 year ago

Excellent. 👌

mrleblanc101 commented 1 year ago

@danielroe import { NuxtLink } from '#imports' does not work for me.

Internal server error: [unimport] failed to find "NuxtLink" imported from "#imports"

mrleblanc101 commented 1 year ago

For those wondering, the correct import is import { NuxtLink } from '#components'; Then you can use: <component :is="to ? NuxtLink : 'div'" :to="to">, Nuxt need better documentation about the Virtual Files, they are helpful, but confusing.

rstainsby commented 10 months ago

I'm having issues with this when generating a NuxtLink using a dynamic component in an external library.

I'm trying to support both Vue and Nuxt projects, so I have a component that detects the environment and renders the correct component (see below).

// How I'm detecting the environment/resolving the component
function computeTag(to: string | undefined) {
  const instance = getCurrentInstance();

  if (!instance || !instance.proxy) {
    throw new Error('No instance of Vue detected');
  }  

  const hasNuxt = instance.proxy && '$nuxt' in instance.proxy;
  const hasRouter = instance.proxy && '$router' in instance.proxy;

  if (!hasRouter || (hasRouter && !to)) {
    return 'a';
  }

  return hasNuxt ? resolveComponent('NuxtLink') : resolveComponent('router-link');
}

// The component template
<component
      :is="computedTag"
      v-bind="computedProps"
    >
      <i v-if="icon" :class="icon"></i>

      <span class="font-light">
        <slot></slot>
      </span>

      <i v-if="isExternal" class="ml-auto pi pi-external-link" style="font-size: 0.75rem;"></i>
      <Badge v-else-if="badge" class="ml-auto" :class="badgeClass" :value="badge"> </Badge>
    </component>

The works for Vue projects but on Nuxt projects it is unable to resolve the NuxtLink component. Is there something I'm missing or is this just not possible with Nuxt?

I should also add that this library is written using just Vue, not Nuxt, due to some other restrictions.

------EDIT --------

After spinning my wheels on this for a few hours I fixed the issue within 2 minutes of posting this, typical.

It seems like the Vue instance isn't actually aware of the NuxtLink when it renders my component, I'm guessing has something to do with Nuxt's auto-importing. So, as part of my library import I added this to my plugin definition:

// ./plugins/my-ui-library.ts
import { defineNuxtPlugin } from "#app";
import { NuxtLink } from "#components";

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.component("NuxtLink", NuxtLink);
});

Can someone confirm if this is the right way to resolve this issue and maybe give a bit of clarification as to why it works?

danielroe commented 10 months ago

Yes, that's a very reasonable solution. The only reason we don't register NuxtLink globally as if you don't use it we can tree-shake it out of your build.

deflexor commented 9 months ago

For those wondering, the correct import is import { NuxtLink } from '#components'; Then you can use: <component :is="to ? NuxtLink : 'div'" :to="to">, Nuxt need better documentation about the Virtual Files, they are helpful, but confusing.

This doesnt seem to work, getting this all the time:

Property "NuxtLink" was accessed during render but is not defined on instance

danielroe commented 9 months ago

@deflexor it will work if you use a script setup block, where the import is automatically exposed. Otherwise you need to pass it explicitly to the template.