Skip to content

Commit

Permalink
feat: update new slot syntax per RFC revision
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jan 16, 2019
1 parent a47a0ee commit 4fca045
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 77 deletions.
118 changes: 60 additions & 58 deletions src/compiler/parser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ export const dirRE = /^v-|^@|^:|^\./
export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
const stripParensRE = /^\(|\)$/g
const dynamicKeyRE = /^\[.*\]$/

const argRE = /:(.*)$/
export const bindRE = /^:|^\.|^v-bind:/
const propBindRE = /^\./
const modifierRE = /\.[^.]+/g

const scopedSlotShorthandRE = /^:?\(.*\)$/
const slotRE = /^v-slot(:|$)|^#/

const lineBreakRE = /[\r\n]/
const whitespaceRE = /\s+/g
Expand Down Expand Up @@ -566,27 +567,7 @@ function processSlotContent (el) {
true
)
}
el.slotScope = (
slotScope ||
getAndRemoveAttr(el, 'slot-scope')
)
if (process.env.NEW_SLOT_SYNTAX) {
// new in 2.6: slot-props and its shorthand works the same as slot-scope
// when used on <template> containers
el.slotScope = el.slotScope || getAndRemoveAttr(el, 'slot-props')
// 2.6 shorthand syntax
const shorthand = getAndRemoveAttrByRegex(el, scopedSlotShorthandRE)
if (shorthand) {
if (process.env.NODE_ENV !== 'production' && el.slotScope) {
warn(
`Unexpected mixed usage of different slot syntaxes.`,
el
)
}
el.slotTarget = getScopedSlotShorthandName(shorthand)
el.slotScope = shorthand.value
}
}
el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')
} else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) {
Expand All @@ -599,36 +580,6 @@ function processSlotContent (el) {
)
}
el.slotScope = slotScope
} else if (process.env.NEW_SLOT_SYNTAX) {
// 2.6: slot-props on component, denotes default slot
slotScope = getAndRemoveAttr(el, 'slot-props')
const shorthand = getAndRemoveAttrByRegex(el, scopedSlotShorthandRE)
if (slotScope || shorthand) {
if (process.env.NODE_ENV !== 'production') {
if (!maybeComponent(el)) {
warn(
`slot-props cannot be used on non-component elements.`,
el.rawAttrsMap['slot-props'] || el.rawAttrsMap['()']
)
}
if (slotScope && shorthand) {
warn(
`Unexpected mixed usage of different slot syntaxes.`,
el
)
}
}
// add the component's children to its default slot
const slots = el.scopedSlots || (el.scopedSlots = {})
const target = shorthand ? getScopedSlotShorthandName(shorthand) : `"default"`
const slotContainer = slots[target] = createASTElement('template', [], el)
slotContainer.children = el.children
slotContainer.slotScope = shorthand ? shorthand.value : slotScope
// remove children as they are returned from scopedSlots now
el.children = []
// mark el non-plain so data gets generated
el.plain = false
}
}

// slot="xxx"
Expand All @@ -641,14 +592,65 @@ function processSlotContent (el) {
addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot'))
}
}

// 2.6 v-slot syntax
if (process.env.NEW_SLOT_SYNTAX) {
if (el.tag === 'template') {
// v-slot on <template>
const slotBinding = getAndRemoveAttrByRegex(el, slotRE)
if (slotBinding) {
if (
process.env.NODE_ENV !== 'production' &&
(el.slotTarget || el.slotScope)
) {
warn(
`Unexpected mixed usage of different slot syntaxes.`,
el
)
}
el.slotTarget = getSlotName(slotBinding)
el.slotScope = slotBinding.value
}
} else {
// v-slot on component, denotes default slot
const slotBinding = getAndRemoveAttrByRegex(el, slotRE)
if (slotBinding) {
if (process.env.NODE_ENV !== 'production') {
if (!maybeComponent(el)) {
warn(
`v-slot cannot be used on non-component elements.`,
slotBinding
)
}
if (el.slotScope || el.slotTarget) {
warn(
`Unexpected mixed usage of different slot syntaxes.`,
el
)
}
}
// add the component's children to its default slot
const slots = el.scopedSlots || (el.scopedSlots = {})
const target = getSlotName(slotBinding)
const slotContainer = slots[target] = createASTElement('template', [], el)
slotContainer.children = el.children
slotContainer.slotScope = slotBinding.value
// remove children as they are returned from scopedSlots now
el.children = []
// mark el non-plain so data gets generated
el.plain = false
}
}
}
}

function getScopedSlotShorthandName ({ name }) {
return name.charAt(0) === ':'
// dynamic :(name)
? name.slice(2, -1) || `"default"`
// static (name)
: `"${name.slice(1, -1) || `default`}"`
function getSlotName ({ name }) {
name = name.replace(slotRE, '')
return dynamicKeyRE.test(name)
// dynamic [name]
? name.slice(1, -1)
// static name
: `"${name || `default`}"`
}

// handle <slot/> outlets
Expand Down
50 changes: 31 additions & 19 deletions test/unit/features/component/component-scoped-slot.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ describe('Component scoped slot', () => {

// 2.6 new slot syntax
if (process.env.NEW_SLOT_SYNTAX) {
describe('slot-props syntax', () => {
describe('v-slot syntax', () => {
const Foo = {
render(h) {
return h('div', [
Expand All @@ -657,6 +657,10 @@ describe('Component scoped slot', () => {
}
}

const toNamed = (syntax, name) => syntax.length === 1
? syntax + name // shorthand
: syntax + ':' + name // full syntax

function runSuite(syntax) {
it('default slot', () => {
const vm = new Vue({
Expand Down Expand Up @@ -685,12 +689,12 @@ describe('Component scoped slot', () => {
it('default + named slots', () => {
const vm = new Vue({
template: `
<foo ()="foo">
<foo #="foo">
{{ foo }}
<template slot="one" ${syntax}="one">
<template ${toNamed(syntax, 'one')}="one">
{{ one }}
</template>
<template slot="two" ${syntax}="two">
<template ${toNamed(syntax, 'two')}="two">
{{ two }}
</template>
</foo>
Expand All @@ -704,12 +708,12 @@ describe('Component scoped slot', () => {
const vm = new Vue({
template: `
<foo>
<template slot="one" ${syntax}="one">
<template ${toNamed(syntax, 'one')}="one">
<bar ${syntax}="bar">
{{ one }} {{ bar }}
</bar>
</template>
<template slot="two" ${syntax}="two">
<template ${toNamed(syntax, 'two')}="two">
<baz ${syntax}="baz">
{{ two }} {{ baz }}
</baz>
Expand All @@ -721,27 +725,35 @@ describe('Component scoped slot', () => {
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`from foo one from bar from foo two from baz`)
})

it('should warn slot-props usage on non-component elements', () => {
it('should warn v-slot usage on non-component elements', () => {
const vm = new Vue({
template: `<div ${syntax}="foo"/>`
}).$mount()
expect(`slot-props cannot be used on non-component elements`).toHaveBeenWarned()
expect(`v-slot cannot be used on non-component elements`).toHaveBeenWarned()
})

it('should warn mixed usage', () => {
const vm = new Vue({
template: `<foo><bar slot="one" slot-scope="bar" ${syntax}="bar"></bar></foo>`,
components: { Foo, Bar }
}).$mount()
expect(`Unexpected mixed usage of different slot syntaxes`).toHaveBeenWarned()
})
}

// run tests for both full syntax and shorthand
runSuite('slot-props')
runSuite('()')
runSuite('v-slot')
runSuite('#')

it('shorthand named slots', () => {
const vm = new Vue({
template: `
<foo ()="foo">
<foo #="foo">
{{ foo }}
<template (one)="one">
<template #one="one">
{{ one }}
</template>
<template (two)="two">
<template #two="two">
{{ two }}
</template>
</foo>
Expand All @@ -755,8 +767,8 @@ describe('Component scoped slot', () => {
const vm = new Vue({
template: `
<foo>
<template (one)>one</template>
<template (two)>two</template>
<template #one>one</template>
<template #two>two</template>
</foo>
`,
components: { Foo }
Expand All @@ -767,7 +779,7 @@ describe('Component scoped slot', () => {
it('shorthand named slots on root', () => {
const vm = new Vue({
template: `
<foo (one)="one">
<foo #one="one">
{{ one }}
</foo>
`,
Expand All @@ -776,16 +788,16 @@ describe('Component scoped slot', () => {
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`from foo one`)
})

it('dynamic shorthand', () => {
it('dynamic slot name', () => {
const vm = new Vue({
data: {
a: 'one',
b: 'two'
},
template: `
<foo>
<template :(a)="one">{{ one }} </template>
<template :(b)="two">{{ two }}</template>
<template #[a]="one">{{ one }} </template>
<template v-slot:[b]="two">{{ two }}</template>
</foo>
`,
components: { Foo }
Expand Down

4 comments on commit 4fca045

@decademoon
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the following example, what does a refer to inside the innermost (named) slot?

<foo v-slot="{ a }">
  {{ a }}
  <template v-slot:named="{ b }">
    {{ a }} | {{ b }}
  </template>
</foo>

@Justineo
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@decademoon

This kind of usage will be illegitimate. When you have both default slot and named slot(s), you will be required to use this instead:

<foo>
  <template v-slot="{ a }">
    {{ a }}
  </template>
  <template v-slot:named="{ b }">
    {{ a }} | {{ b }}
  </template>
</foo>

And this will eliminate the ambiguity.

@alexjoverm
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Justineo is that example actually right? the v-slot:named doesn't seem to have access to a, since it's declared on the template of the default slot...

@Justineo
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexjoverm Oops it was a mistake. You are right, a isn't accessible inside the named slot.

Please sign in to comment.