8000 ensure slot compilation order inside conditional fragments (fix #1965) · htylab/vue@2b9e072 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2b9e072

Browse files
committed
ensure slot compilation order inside conditional fragments (fix vuejs#1965)
1 parent 62229ef commit 2b9e072

File tree

6 files changed

+60
-43
lines changed

6 files changed

+60
-43
lines changed

src/compiler/compile.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import {
1515
findRef,
1616
defineReactive,
1717
assertAsset,
18-
getAttr
18+
getAttr,
19+
hasBindAttr
1920
} from '../util/index'
2021

2122
// special binding prefixes
@@ -514,6 +515,11 @@ function makeChildLinkFn (linkFns) {
514515
function checkElementDirectives (el, options) {
515516
var tag = el.tagName.toLowerCase()
516517
if (commonTagRE.test(tag)) return
518+
// special case: give named slot a higher priority
519+
// than unnamed slots
520+
if (tag === 'slot' && hasBindAttr(el, 'name')) {
521+
tag = '_namedSlot'
522+
}
517523
var def = resolveAsset(options, 'elementDirectives', tag)
518524
if (def) {
519525
return makeTerminalNodeLinkFn(el, tag, '', options, def)

src/compiler/transclude.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
createAnchor,
88
resolveAsset,
99
toArray,
10-
addClass
10+
addClass,
11+
hasBindAttr
1112
} from '../util/index'
1213

1314
const specialCharRE = /[^\w\-:\.]/
@@ -92,9 +93,7 @@ function transcludeTemplate (el, options) {
9293
// single nested component
9394
tag === 'component' ||
9495
resolveAsset(options, 'components', tag) ||
95-
replacer.hasAttribute('is') ||
96-
replacer.hasAttribute(':is') ||
97-
replacer.hasAttribute('v-bind:is') ||
96+
hasBindAttr(replacer, 'is') ||
9897
// element directive
9998
resolveAsset(options, 'elementDirectives', tag) ||
10099
// for block

src/directives/element/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import slot from './slot'
1+
import { slot, namedSlot as _namedSlot } from './slot'
22
import partial from './partial'
33

44
export default {
55
slot,
6+
_namedSlot, // same as slot but with higher priority
67
partial
78
}

src/directives/element/slot.js

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
} from '../../parsers/template'
55

66
import {
7+
extend,
78
extractContent,
89
replace,
910
remove,
@@ -15,60 +16,46 @@ import {
1516
// instance being stored as `$options._content` during
1617
// the transclude phase.
1718

18-
export default {
19+
// We are exporting two versions, one for named and one
20+
// for unnamed, because the unnamed slots must be compiled
21+
// AFTER all named slots have selected their content. So
22+
// we need to give them different priorities in the compilation
23+
// process. (See #1965)
1924

20-
priority: 1750,
25+
export const slot = {
2126

22-
params: ['name'],
27+
priority: 1750,
2328

2429
bind () {
2530
var host = this.vm
2631
var raw = host.$options._content
27-
var content
2832
if (!raw) {
2933
this.fallback()
3034
return
3135
}
3236
var context = host._context
33-
var slotName = this.params.name
37+
var slotName = this.params && this.params.name
3438
if (!slotName) {
35-
// Default content
36-
var self = this
37-
var compileDefaultContent = function () {
38-
var content = extractFragment(raw.childNodes, raw, true)
39-
if (content.hasChildNodes()) {
40-
self.compile(content, context, host)
41-
} else {
42-
self.fallback()
43-
}
44-
}
45-
if (!host._isCompiled) {
46-
// defer until the end of instance compilation,
47-
// because the default outlet must wait until all
48-
// other possible outlets with selectors have picked
49-
// out their contents.
50-
host.$once('hook:compiled', compileDefaultContent)
51-
} else {
52-
compileDefaultContent()
53-
}
39+
// Default slot
40+
this.tryCompile(extractFragment(raw.childNodes, raw, true), context, host)
5441
} else {
42+
// Named slot
5543
var selector = '[slot="' + slotName + '"]'
5644
var nodes = raw.querySelectorAll(selector)
5745
if (nodes.length) {
58-
content = extractFragment(nodes, raw)
59-
if (content.hasChildNodes()) {
60-
this.compile(content, context, host)
61-
} else {
62-
this.fallback()
63-
}
46+
this.tryCompile(extractFragment(nodes, raw), context, host)
6447
} else {
6548
this.fallback()
6649
}
6750
}
6851
},
6952

70-
fallback () {
71-
this.compile(extractContent(this.el, true), this.vm)
53+
tryCompile (content, context, host) {
54+
if (content.hasChildNodes()) {
55+
this.compile(content, context, host)
56+
} else {
57+
this.fallback()
58+
}
7259
},
7360

7461
compile (content, context, host) {
@@ -87,13 +74,22 @@ export default {
8774
}
8875
},
8976

77+
fallback () {
78+
this.compile(extractContent(this.el, true), this.vm)
79+
},
80+
9081
unbind () {
9182
if (this.unlink) {
9283
this.unlink()
9384
}
9485
}
9586
}
9687

88+
export const namedSlot = extend(extend({}, slot), {
89+
priority: slot.priority + 1,
90+
params: ['name']
91+
})
92+
9793
/**
9894
* Extract qualified content nodes from a node list.
9995
*

src/util/dom.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,20 @@ export function getBindAttr (node, name) {
7575
return val
7676
}
7777

78+
/**
79+
* Check the presence of a bind attribute.
80+
*
81+
* @param {Node} node
82+
* @param {String} name
83+
* @return {Boolean}
84+
*/
85+
86+
export function hasBindAttr (node, name) {
87+
return node.hasAttribute(name) ||
88+
node.hasAttribute(':' + name) ||
89+
node.hasAttribute('v-bind:' + name)
90+
}
91+
7892
/**
7993
* Insert el before target
8094
*

test/unit/specs/directives/element/slot_spec.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,27 +169,28 @@ describe('Slot Distribution', function () {
169169
el: el,
170170
data: {
171171
a: 1,
172+
b: 2,
172173
show: true
173174
},
174-
template: '<test :show="show">{{a}}</test>',
175+
template: '<test :show="show"><p slot="b">{{b}}</a><p>{{a}}</p></test>',
175176
components: {
176177
test: {
177178
props: ['show'],
178-
template: '<div v-if="show"><slot></cotent></div>'
179+
template: '<div v-if="show"><slot></slot><slot name="b"></slot></div>'
179180
}
180181
}
181182
})
182-
expect(el.textContent).toBe('1')
183+
expect(el.textContent).toBe('12')
183184
vm.a = 2
184185
_.nextTick(function () {
185-
expect(el.textContent).toBe('2')
186+
expect(el.textContent).toBe('22')
186187
vm.show = false
187188
_.nextTick(function () {
188189
expect(el.textContent).toBe('')
189190
vm.show = true
190191
vm.a = 3
191192
_.nextTick(function () {
192-
expect(el.textContent).toBe('3')
193+
expect(el.textContent).toBe('32')
193194
done()
194195
})
195196
})

0 commit comments

Comments
 (0)
0