eslint-plugin-vue icon indicating copy to clipboard operation
eslint-plugin-vue copied to clipboard

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

Open jods4 opened this issue 4 years ago • 14 comments

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 avatar Jul 01 '20 21:07 jods4

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

jods4 avatar Jul 01 '20 21:07 jods4

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

  • https://vuejs.org/v2/guide/components-slots.html#Abbreviated-Syntax-for-Lone-Default-Slots
  • https://vuejs.org/v2/api/index.html#v-slot

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.

ota-meshi avatar Jul 02 '20 00:07 ota-meshi

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 👀

jods4 avatar Jul 02 '20 08:07 jods4

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

ota-meshi avatar Jul 02 '20 08:07 ota-meshi

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 avatar Aug 06 '20 22:08 ascott18

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

sirlancelot avatar Aug 26 '20 23:08 sirlancelot

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.

jods4 avatar Sep 03 '20 21:09 jods4

@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.

ascott18 avatar Sep 03 '20 23:09 ascott18

@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"?

jods4 avatar Sep 06 '20 19:09 jods4

@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

ascott18 avatar Sep 07 '20 19:09 ascott18

@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

jods4 avatar Sep 08 '20 22:09 jods4

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

ricochet1k avatar Sep 10 '20 00:09 ricochet1k

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

icleolion avatar Sep 30 '20 08:09 icleolion

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.

OJFord avatar May 05 '21 17:05 OJFord