Skip to content

Commit

Permalink
revert: feat: support scoped-slot usage with $slot
Browse files Browse the repository at this point in the history
This reverts commit 7988a55.
  • Loading branch information
yyx990803 committed Jan 12, 2019
1 parent 6fe07eb commit 6a2994e
Show file tree
Hide file tree
Showing 5 changed files with 10 additions and 175 deletions.
9 changes: 1 addition & 8 deletions flow/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ declare type ASTDirective = {
end?: number;
};

declare type ASTNode = ASTElement | ASTText | ASTExpression
declare type ASTNode = ASTElement | ASTText | ASTExpression;

declare type ASTElement = {
type: 1;
Expand Down Expand Up @@ -167,9 +167,6 @@ declare type ASTElement = {

// weex specific
appendAsTree?: boolean;

// 2.6 $slot check
has$Slot?: boolean
};

declare type ASTExpression = {
Expand All @@ -182,8 +179,6 @@ declare type ASTExpression = {
ssrOptimizability?: number;
start?: number;
end?: number;
// 2.6 $slot check
has$Slot?: boolean
};

declare type ASTText = {
Expand All @@ -195,8 +190,6 @@ declare type ASTText = {
ssrOptimizability?: number;
start?: number;
end?: number;
// 2.6 $slot check
has$Slot?: boolean
};

// SFC-parser related declarations
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/codegen/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class CodegenState {
this.dataGenFns = pluckModuleFunction(options.modules, 'genData')
this.directives = extend(extend({}, baseDirectives), options.directives)
const isReservedTag = options.isReservedTag || no
this.maybeComponent = (el: ASTElement) => !!el.component || !isReservedTag(el.tag)
this.maybeComponent = (el: ASTElement) => el.component || !isReservedTag(el.tag)
this.onceId = 0
this.staticRenderFns = []
this.pre = false
Expand Down
3 changes: 1 addition & 2 deletions src/compiler/optimizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function optimize (root: ?ASTElement, options: CompilerOptions) {

function genStaticKeys (keys: string): Function {
return makeMap(
'type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap,has$Slot' +
'type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap' +
(keys ? ',' + keys : '')
)
}
Expand All @@ -43,7 +43,6 @@ function markStatic (node: ASTNode) {
// 2. static slot content fails for hot-reloading
if (
!isPlatformReservedTag(node.tag) &&
!node.component &&
node.tag !== 'slot' &&
node.attrsMap['inline-template'] == null
) {
Expand Down
83 changes: 7 additions & 76 deletions src/compiler/parser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { parseHTML } from './html-parser'
import { parseText } from './text-parser'
import { parseFilters } from './filter-parser'
import { genAssignmentCode } from '../directives/model'
import { extend, cached, no, camelize, hyphenate, hasOwn } from 'shared/util'
import { extend, cached, no, camelize, hyphenate } from 'shared/util'
import { isIE, isEdge, isServerRendering } from 'core/util/env'

import {
Expand Down Expand Up @@ -45,7 +45,6 @@ let postTransforms
let platformIsPreTag
let platformMustUseProp
let platformGetTagNamespace
let maybeComponent

export function createASTElement (
tag: string,
Expand Down Expand Up @@ -75,8 +74,6 @@ export function parse (
platformIsPreTag = options.isPreTag || no
platformMustUseProp = options.mustUseProp || no
platformGetTagNamespace = options.getTagNamespace || no
const isReservedTag = options.isReservedTag || no
maybeComponent = (el: ASTElement) => !!el.component || !isReservedTag(el.tag)

transforms = pluckModuleFunction(options.modules, 'transformNode')
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
Expand All @@ -102,7 +99,7 @@ export function parse (

function closeElement (element) {
if (!inVPre && !element.processed) {
element = processElement(element, options)
element = processElement(element, options, currentParent)
}
// tree management
if (!stack.length && element !== root) {
Expand Down Expand Up @@ -156,7 +153,7 @@ export function parse (
{ start: el.start }
)
}
if (hasOwn(el.attrsMap, 'v-for')) {
if (el.attrsMap.hasOwnProperty('v-for')) {
warnOnce(
'Cannot use v-for on stateful component root element because ' +
'it renders multiple elements.',
Expand Down Expand Up @@ -380,7 +377,8 @@ function processRawAttrs (el) {

export function processElement (
element: ASTElement,
options: CompilerOptions
options: CompilerOptions,
parent: ASTElement | undefined
) {
processKey(element)

Expand All @@ -393,7 +391,7 @@ export function processElement (
)

processRef(element)
processSlot(element)
processSlot(element, parent)
processComponent(element)
for (let i = 0; i < transforms.length; i++) {
element = transforms[i](element, options) || element
Expand Down Expand Up @@ -584,86 +582,19 @@ function processSlot (el) {
)
}
el.slotScope = slotScope
if (process.env.NODE_ENV !== 'production' && nodeHas$Slot(el)) {
warn('Unepxected mixed usage of `slot-scope` and `$slot`.', el)
}
} else {
// 2.6 $slot support
// Context: https://github.com/vuejs/vue/issues/9180
// Ideally, all slots should be compiled as functions (this is what we
// are doing in 3.x), but for 2.x e want to preserve complete backwards
// compatibility, and maintain the exact same compilation output for any
// code that does not use the new syntax.

// recursively check component children for presence of `$slot` in all
// expressions until running into a nested child component.
if (maybeComponent(el) && childrenHas$Slot(el)) {
processScopedSlots(el)
}
}
const slotTarget = getBindingAttr(el, 'slot')
if (slotTarget) {
el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
// preserve slot as an attribute for native shadow DOM compat
// only for non-scoped slots.
if (el.tag !== 'template' && !el.slotScope && !nodeHas$Slot(el)) {
if (el.tag !== 'template' && !el.slotScope) {
addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot'))
}
}
}
}

function childrenHas$Slot (el): boolean {
return el.children ? el.children.some(nodeHas$Slot) : false
}

const $slotRE = /(^|[^\w_$])\$slot($|[^\w_$])/
function nodeHas$Slot (node): boolean {
// caching
if (hasOwn(node, 'has$Slot')) {
return (node.has$Slot: any)
}
if (node.type === 1) { // element
for (const key in node.attrsMap) {
if (dirRE.test(key) && $slotRE.test(node.attrsMap[key])) {
return (node.has$Slot = true)
}
}
return (node.has$Slot = childrenHas$Slot(node))
} else if (node.type === 2) { // expression
// TODO more robust logic for checking $slot usage
return (node.has$Slot = $slotRE.test(node.expression))
}
return false
}

function processScopedSlots (el) {
// 1. group children by slot target
const groups: any = {}
for (let i = 0; i < el.children.length; i++) {
const child = el.children[i]
const target = child.slotTarget || '"default"'
if (!groups[target]) {
groups[target] = []
}
groups[target].push(child)
}
// 2. for each slot group, check if the group contains $slot
for (const name in groups) {
const group = groups[name]
if (group.some(nodeHas$Slot)) {
// 3. if a group contains $slot, all nodes in that group gets assigned
// as a scoped slot to el and removed from children
el.plain = false
const slots = el.scopedSlots || (el.scopedSlots = {})
const slotContainer = slots[name] = createASTElement('template', [], el)
slotContainer.children = group
slotContainer.slotScope = '$slot'
el.children = el.children.filter(c => group.indexOf(c) === -1)
}
}
}

function processComponent (el) {
let binding
if ((binding = getBindingAttr(el, 'is'))) {
Expand Down
88 changes: 0 additions & 88 deletions test/unit/features/component/component-scoped-slot.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -631,92 +631,4 @@ describe('Component scoped slot', () => {
expect(vm.$el.innerHTML).toBe('<p>hello</p>')
}).then(done)
})

// 2.6 $slot usage
describe('$slot support', () => {
it('should work', () => {
const vm = new Vue({
template: `<foo><div>{{$slot.foo}}</div></foo>`,
components: { foo: { template: `<div><slot foo="hello"/></div>` }}
}).$mount()
expect(vm.$el.innerHTML).toBe(`<div>hello</div>`)
})

it('should work for use of $slots in attributes', () => {
const vm = new Vue({
template: `<foo><div :id="$slot.foo"></div></foo>`,
components: { foo: { template: `<div><slot foo="hello"/></div>` }}
}).$mount()
expect(vm.$el.innerHTML).toBe(`<div id="hello"></div>`)
})

it('should work for root text nodes', () => {
const vm = new Vue({
template: `<foo>{{$slot.foo}}</foo>`,
components: { foo: { template: `<div><slot foo="hello"/></div>` }}
}).$mount()
expect(vm.$el.innerHTML).toBe(`hello`)
})

it('should work for mix of root text nodes and elements', () => {
const vm = new Vue({
template: `<foo>hi <div>{{ $slot.foo }}</div>{{$slot.foo}}</foo>`,
components: { foo: { template: `<div><slot foo="hello"/></div>` }}
}).$mount()
expect(vm.$el.innerHTML).toBe(`hi <div>hello</div>hello`)
})

it('should work for named slots', () => {
const vm = new Vue({
template: `<foo><div slot="foo">{{ $slot.foo }}</div></foo>`,
components: { foo: { template: `<div><slot name="foo" foo="hello"/></div>` }}
}).$mount()
expect(vm.$el.innerHTML).toBe(`<div>hello</div>`)
})

it('should work for mixed default and named slots', () => {
const vm = new Vue({
template: `<foo>{{ $slot.foo }}<div>{{ $slot.foo }}</div><div slot="foo">{{ $slot.foo }}</div></foo>`,
components: { foo: { template: `<div><slot foo="default"/><slot name="foo" foo="foo"/></div>` }}
}).$mount()
expect(vm.$el.innerHTML).toBe(`default<div>default</div><div>foo</div>`)
})

it('should work for mixed $slot and non-$slot slots', () => {
const vm = new Vue({
template: `<foo>{{ $slot.foo }}<div slot="foo">static</div><div>{{ $slot.foo }}</div></foo>`,
components: { foo: { template: `<div><slot foo="default"/><slot name="foo"/></div>` }}
}).$mount()
expect(vm.$el.innerHTML).toBe(`default<div>default</div><div>static</div>`)
})

// testing $slot detection: bracket access, using $slot alone, passing as arguments...
it('should work for alternative $slot usage', () => {
const vm = new Vue({
template: `<foo>{{ $slot['foo'] }}<div slot="foo">{{ $slot }}</div><div>{{ pick($slot) }}</div></foo>`,
methods: { pick: s => s.foo },
components: { foo: { template: `<div><slot foo="default"/><slot name="foo"/></div>` }}
}).$mount()
expect(vm.$el.innerHTML).toBe(`default<div>default</div><div>{}</div>`)
})

// should not consider detection if $slot is inside longer valid identifier
it('should not break when template expression uses $slots', () => {
const vm = new Vue({
data: { some$slot: 123 },
template: `<foo>{{ some$slot }}<div slot="foo">{{ $slots }}</div></foo>`,
components: {
foo: {
render(h) {
// should be compiled as normal slots
expect(this.$slots.default).toBeTruthy()
expect(this.$slots.foo).toBeTruthy()
return h('div', [this.$slots.default, this.$slots.foo])
}
}
}
}).$mount()
expect(vm.$el.innerHTML).toBe(`123<div>{}</div>`)
})
})
})

0 comments on commit 6a2994e

Please sign in to comment.