vuejs / eslint-plugin-vue

Official ESLint plugin for Vue.js
https://eslint.vuejs.org/
MIT License
4.47k stars 666 forks source link

`valid-v-slot` errors on working code? #1229

Open jods4 opened 4 years ago

jods4 commented 4 years ago

This part of the valid-v-slot rule that is in 7.0.0-beta.8 is triggered in my code: https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-slot.js#L210

Basically, a named slot on anything but a template triggers it, for example:

<master-list #actions>
  <button>OK</button>
</master-list>

What it would like me to do is:

<master-list>
  <template #actions>
    <button>OK</button>
  </template>
</master-list>

I don't quite understand why this rule is in place, as that first code snippet works for me (at least in Vue 3, no idea if it works in Vue 2 as well) and the second one is more verbose with no (visible?) benefit.

Looking at the rule documentation, this case is not listed in text nor given as a code example: https://eslint.vuejs.org/rules/valid-v-slot.html

Could you provide guidance here? What's the motivation behind the rule?

jods4 commented 4 years ago

For reference, here's a working demo: https://codesandbox.io/s/laughing-mountain-d9z9l?file=/index.html

ota-meshi commented 4 years ago

Thank you for this issue.

First, Vue.js official documentation states that,

when only the default slot is provided content, the component’s tags can be used as the slot’s template. This allows us to use v-slot directly on the component

An example this in the valid-v-slot rule documentation is:

The directive is a named slot and is on a custom element directly. E.g. <my-component v-slot:foo></my-component>

As I can see, the rule is working correctly, but as you said, the shorthand syntax seems to work for more than the default slot.

Hmm... I think we need to make sure that it is the intended behavior.

jods4 commented 4 years ago

First, Vue.js official documentation states that,

when only the default slot is provided content, the component’s tags can be used as the slot’s template. This allows us to use v-slot directly on the component

I read the whole paragraph and unless I made a logic mistake it doesn't say you can't use the component tag for a non-default slot when only it's the only content. Then again it doesn't so you can either so: 😕

Also it's the Vue 2 documentation and slots have changed quite a bit, so I am not sure if it's 100% reliable for Vue 3.

An example this in the valid-v-slot rule documentation is:

You are right, sorry. I always use the shorthand #foo and got confused.

Hmm... I think we need to make sure that it is the intended behavior.

Thank you! I'm watching this issue 👀

ota-meshi commented 4 years ago

I forgot to tell you one. Your template will probably work with Vue.js 2.

https://template-explorer.vuejs.org/#%3Cmaster-list%20%23actions%3E%0A%20%20%3Cbutton%3EOK%3C%2Fbutton%3E%0A%3C%2Fmaster-list%3E

ascott18 commented 4 years ago

There is also a use case here that #default is perfectly valid and works correctly to make the default slot behave as a scoped slot rather than a non-scoped slot:

<my-component #default>
    <!-- lazy default slot content - only evaluated if my-component actually renders the default slot -->
    Default Slot Content
</my-component>

The above is functionally different from:

<my-component>
    <!-- eager default slot content - evaluated even if my-component does not render the default slot -->
    Default Slot Content
</my-component>
sirlancelot commented 4 years ago

image To re-iterate the above comments. This is valid and shouldn't emit a warning when vue/valid-v-slot is on.

jods4 commented 4 years ago

There is also a use case here that #default is perfectly valid and works correctly to make the default slot behave as a scoped slot rather than a non-scoped slot:

<my-component #default>
  <!-- lazy default slot content - only evaluated if my-component actually renders the default slot -->
  Default Slot Content
</my-component>

The above is functionally different from:

<my-component>
  <!-- eager default slot content - evaluated even if my-component does not render the default slot -->
  Default Slot Content
</my-component>

@ascott18 is this documented somewhere? Intuitively, it feels very surprising to the point I think it may be a bug. When a component has contents, I'd expected to work as if it had #default... by default.

ascott18 commented 4 years ago

@ascott18 is this documented somewhere? Intuitively, it feels very surprising to the point I think it may be a bug. When a component has contents, I'd expected to work as if it had #default... by default.

@jods4 Yes - its defined in the RFC that introduced v-slot: https://github.com/vuejs/rfcs/blob/master/active-rfcs/0001-new-slot-syntax.md#introducing-a-new-directive-v-slot

v-slot can be used directly on a component, without an argument, to indicate that the component's default slot is a scoped slot, and that props passed to the default slot should be available as the variable declared in its attribute value:

In my example, #default is just an alternate form of v-slot. v-slot == v-slot:default == #default. You can't take this to the furthest extreme (a single #) for obvious reasons.

See template compilation result - the first 3 are identical, the last one is not: https://template-explorer.vuejs.org/#%3Cdiv%3E%0A%20%20%3Cmy-component%20v-slot%3E%0A%20%20%20%20lazy%20content%0A%20%20%3C%2Fmy-component%3E%0A%0A%20%20%3Cmy-component%20v-slot%3Adefault%3E%0A%20%20%20%20lazy%20content%0A%20%20%3C%2Fmy-component%3E%0A%20%20%0A%20%20%3Cmy-component%20%23default%3E%0A%20%20%20%20lazy%20content%0A%20%20%3C%2Fmy-component%3E%0A%20%20%0A%20%20%3Cmy-component%3E%0A%20%20%20%20eager%20content%0A%20%20%3C%2Fmy-component%3E%0A%3C%2Fdiv%3E

@sirlancelot 's example is essentially the same thing - just a different syntax.

jods4 commented 4 years ago

@ascott18 This is documentation that v-slot by itself is v-slot:default, which I would expect.

The part that is nagging me is what happens of the content of a custom element without v-slot.

Intuitively I would expect it to be either dropped silently (no evaluation) or better, to be the same as v-slot:default. Is there any documentation that describes what you called "eager default slot" and how it differs in behavior from what you called "lazy default slot"?

ascott18 commented 4 years ago

@jods4 The contents of scoped slots in a compiled template are placed in the body of a closure. This allows the slot content to be rendered independent from the template root where the slot contents were defined. This also allows the slot contents to be conditionally rendered into a VNode tree only if the component actually renders the slot.

The contents of non-scoped slots are rendered into a VNode tree whenever the component where the slot contents were defined is rendered, regardless of whether that slot is actually being rendered by the parent component or not.

In Vue 3, all slots will be treated like scoped slots are in Vue 2 - there won't distinction anymore between scoped (lazy) and unscoped (eager) slots.

You can see all of this quite clearly if you look at the template compiler output I linked above. Here's a new link with one more example using the legacy slot attribute (which creates an unscoped slot):

https://template-explorer.vuejs.org/#%3Cdiv%3E%0A%20%20%3Cmy-component%20v-slot%3E%0A%20%20%20%20lazy%20content%20%231%0A%20%20%3C%2Fmy-component%3E%0A%0A%20%20%3Cmy-component%20v-slot%3Adefault%3E%0A%20%20%20%20lazy%20content%20%232%0A%20%20%3C%2Fmy-component%3E%0A%20%20%0A%20%20%3Cmy-component%20%23default%3E%0A%20%20%20%20lazy%20content%20%233%0A%20%20%3C%2Fmy-component%3E%0A%20%20%0A%20%20%3Cmy-component%3E%0A%20%20%20%20eager%20content%20%234%0A%20%20%3C%2Fmy-component%3E%0A%20%20%0A%20%20%3Cmy-component%3E%0A%20%20%20%20%3Ctemplate%20slot%3D%22default%22%3E%20%0A%20%20%20%20%09eager%20content%20%235%0A%20%20%20%20%3C%2Ftemplate%3E%0A%20%20%3C%2Fmy-component%3E%0A%3C%2Fdiv%3E

jods4 commented 4 years ago

@ascott18 great, so in Vue 3 using v-slot or not compiles to the same thing, which is what I expected. Your link is for Vue 2 (which is not compiling both cases to the same code); for reference if anyone wants to check this is the Vue 3 version, where all cases compile to the same: https://vue-next-template-explorer.netlify.app/#%7B%22src%22%3A%22%3Cdiv%3E%5Cr%5Cn%20%20%3Cmy-component%20v-slot%3E%5Cr%5Cn%20%20%20%20lazy%20content%20%231%5Cr%5Cn%20%20%3C%2Fmy-component%3E%5Cr%5Cn%5Cr%5Cn%20%20%3Cmy-component%20v-slot%3Adefault%3E%5Cr%5Cn%20%20%20%20lazy%20content%20%232%5Cr%5Cn%20%20%3C%2Fmy-component%3E%5Cr%5Cn%20%20%5Cr%5Cn%20%20%3Cmy-component%20%23default%3E%5Cr%5Cn%20%20%20%20lazy%20content%20%233%5Cr%5Cn%20%20%3C%2Fmy-component%3E%5Cr%5Cn%20%20%5Cr%5Cn%20%20%3Cmy-component%3E%5Cr%5Cn%20%20%20%20eager%20content%20%234%5Cr%5Cn%20%20%3C%2Fmy-component%3E%5Cr%5Cn%20%20%5Cr%5Cn%20%20%3Cmy-component%3E%5Cr%5Cn%20%20%20%20%3Ctemplate%20slot%3D%5C%22default%5C%22%3E%20%5Cr%5Cn%20%20%20%20%5Cteager%20content%20%235%5Cr%5Cn%20%20%20%20%3C%2Ftemplate%3E%5Cr%5Cn%20%20%3C%2Fmy-component%3E%5Cr%5Cn%3C%2Fdiv%3E%22%2C%22options%22%3A%7B%22mode%22%3A%22module%22%2C%22prefixIdentifiers%22%3Afalse%2C%22optimizeImports%22%3Afalse%2C%22hoistStatic%22%3Afalse%2C%22cacheHandlers%22%3Afalse%2C%22scopeId%22%3Anull%2C%22ssrCssVars%22%3A%22%7B%20color%20%7D%22%2C%22bindingMetadata%22%3A%7B%22TestComponent%22%3A%22setup%22%2C%22foo%22%3A%22setup%22%2C%22bar%22%3A%22props%22%7D%2C%22optimizeBindings%22%3Atrue%7D%7D

ricochet1k commented 4 years ago

Vuetify's v-data-table uses v-slot:item.column="" which valid-v-slot complains about. See example code here: https://vuetifyjs.com/en/components/data-tables/#slots

icleolion commented 4 years ago

@ricochet1k already reported in https://github.com/vuejs/eslint-plugin-vue/issues/1165

OJFord commented 3 years ago

This also false-positives on:

<template #head(foo.bar)="data">

With bootstrap-vue b-tables, where it's not easy to workaround other than ignoring the lint, because foo.bar and foo.baz might both be keys in the table fields.