@@ -5,7 +5,7 @@ import { parseHTML } from './html-parser'
5
5
import { parseText } from './text-parser'
6
6
import { parseFilters } from './filter-parser'
7
7
import { genAssignmentCode } from '../directives/model'
8
- import { extend , cached , no , camelize , hyphenate } from 'shared/util'
8
+ import { extend , cached , no , camelize , hyphenate , hasOwn } from 'shared/util'
9
9
import { isIE , isEdge , isServerRendering } from 'core/util/env'
10
10
11
11
import {
@@ -44,6 +44,7 @@ let postTransforms
44
44
let platformIsPreTag
45
45
let platformMustUseProp
46
46
let platformGetTagNamespace
47
+ let maybeComponent
47
48
48
49
export function createASTElement (
49
50
tag : string ,
@@ -73,6 +74,8 @@ export function parse (
73
74
platformIsPreTag = options . isPreTag || no
74
75
platformMustUseProp = options . mustUseProp || no
75
76
platformGetTagNamespace = options . getTagNamespace || no
77
+ const isReservedTag = options . isReservedTag || no
78
+ maybeComponent = ( el : ASTElement ) => ! ! el . component || ! isReservedTag ( el . tag )
76
79
77
80
transforms = pluckModuleFunction ( options . modules , 'transformNode' )
78
81
preTransforms = pluckModuleFunction ( options . modules , 'preTransformNode' )
@@ -98,7 +101,7 @@ export function parse (
98
101
99
102
function closeElement ( element ) {
100
103
if ( ! inVPre && ! element . processed ) {
101
- element = processElement ( element , options , currentParent )
104
+ element = processElement ( element , options )
102
105
}
103
106
// tree management
104
107
if ( ! stack . length && element !== root ) {
@@ -152,7 +155,7 @@ export function parse (
152
155
{ start : el . start }
153
156
)
154
157
}
155
- if ( el . attrsMap . hasOwnProperty ( 'v-for' ) ) {
158
+ if ( hasOwn ( el . attrsMap , 'v-for' ) ) {
156
159
warnOnce (
157
160
'Cannot use v-for on stateful component root element because ' +
158
161
'it renders multiple elements.' ,
@@ -376,8 +379,7 @@ function processRawAttrs (el) {
376
379
377
380
export function processElement (
378
381
element : ASTElement ,
379
- options : CompilerOptions ,
380
- parent : ASTElement | undefined
382
+ options : CompilerOptions
381
383
) {
382
384
processKey ( element )
383
385
@@ -390,7 +392,7 @@ export function processElement (
390
392
)
391
393
392
394
processRef ( element )
393
- processSlot ( element , parent )
395
+ processSlot ( element )
394
396
processComponent ( element )
395
397
for ( let i = 0 ; i < transforms . length ; i ++ ) {
396
398
element = transforms [ i ] ( element , options ) || element
@@ -581,19 +583,86 @@ function processSlot (el) {
581
583
)
582
584
}
583
585
el . slotScope = slotScope
586
+ if ( process . env . NODE_ENV !== 'production' && nodeHas$Slot ( el ) ) {
587
+ warn ( 'Unepxected mixed usage of `slot-scope` and `$slot`.' , el )
588
+ }
589
+ } else {
590
+ // 2.6 $slot support
591
+ // Context: https://github.com/vuejs/vue/issues/9180
592
+ // Ideally, all slots should be compiled as functions (this is what we
593
+ // are doing in 3.x), but for 2.x e want to preserve complete backwards
594
+ // compatibility, and maintain the exact same compilation output for any
595
+ // code that does not use the new syntax.
596
+
597
+ // recursively check component children for presence of `$slot` in all
598
+ // expressions until running into a nested child component.
599
+ if ( maybeComponent ( el ) && childrenHas$Slot ( el ) ) {
600
+ processScopedSlots ( el )
601
+ }
584
602
}
585
603
const slotTarget = getBindingAttr ( el , 'slot' )
586
604
if ( slotTarget ) {
587
605
el . slotTarget = slotTarget === '""' ? '"default"' : slotTarget
588
606
// preserve slot as an attribute for native shadow DOM compat
589
607
// only for non-scoped slots.
590
- if ( el . tag !== 'template' && ! el . slotScope ) {
608
+ if ( el . tag !== 'template' && ! el . slotScope && ! nodeHas$Slot ( el ) ) {
591
609
addAttr ( el , 'slot' , slotTarget , getRawBindingAttr ( el , 'slot' ) )
592
610
}
593
611
}
594
612
}
595
613
}
596
614
615
+ function childrenHas$Slot ( el ) : boolean {
616
+ return el . children ? el . children . some ( nodeHas$Slot ) : false
617
+ }
618
+
619
+ const $slotRE = / \$ s l o t /
620
+ function nodeHas$Slot ( node ) : boolean {
621
+ // caching
622
+ if ( hasOwn ( node , 'has$Slot' ) ) {
623
+ return ( node . has$Slot : any )
624
+ }
625
+ if ( node . type === 1 ) { // element
626
+ for ( const key in node . attrsMap ) {
627
+ if ( dirRE . test ( key ) && $slotRE . test ( node . attrsMap [ key ] ) ) {
628
+ return ( node . has$Slot = true )
629
+ }
630
+ }
631
+ return ( node . has$Slot = childrenHas$Slot ( node ) )
632
+ } else if ( node . type === 2 ) { // expression
633
+ // TODO more robust logic for checking $slot usage
634
+ return ( node . has$Slot = $slotRE . test ( node . expression ) )
635
+ }
636
+ return false
637
+ }
638
+
639
+ function processScopedSlots ( el ) {
640
+ // 1. group children by slot target
641
+ const groups : any = { }
642
+ for ( let i = 0 ; i < el . children . length ; i ++ ) {
643
+ const child = el . children [ i ]
644
+ const target = child . slotTarget || '"default"'
645
+ if ( ! groups [ target ] ) {
646
+ groups [ target ] = [ ]
647
+ }
648
+ groups [ target ] . push ( child )
649
+ }
650
+ // 2. for each slot group, check if the group contains $slot
651
+ for ( const name in groups ) {
652
+ const group = groups [ name ]
653
+ if ( group . some ( nodeHas$Slot ) ) {
654
+ // 3. if a group contains $slot, all nodes in that group gets assigned
655
+ // as a scoped slot to el and removed from children
656
+ el . plain = false
657
+ const slots = el . scopedSlots || ( el . scopedSlots = { } )
658
+ const slotContainer = slots [ name ] = createASTElement ( 'template' , [ ] , el )
659
+ slotContainer . children = group
660
+ slotContainer . slotScope = '$slot'
661
+ el . children = el . children . filter ( c => group . indexOf ( c ) === - 1 )
662
+ }
663
+ }
664
+ }
665
+
597
666
function processComponent ( el ) {
598
667
let binding
599
668
if ( ( binding = getBindingAttr ( el , 'is' ) ) ) {
0 commit comments