Skip to content

Commit 854ecd4

Browse files
committed
compile component <content> in parent scope (ref: vuejs#502)
1 parent bdb4dc4 commit 854ecd4

File tree

5 files changed

+105
-50
lines changed

5 files changed

+105
-50
lines changed

src/compile/compile.js

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,27 @@ var templateParser = require('../parse/template')
1010
* inside. This top level compile function should only be
1111
* called on instance root nodes.
1212
*
13+
* When the `asParent` flag is true, this means we are doing
14+
* a partial compile for a component's parent scope markup
15+
* (See #502). This could **only** be triggered during
16+
* compilation of `v-component`, and we need to skip v-with,
17+
* v-ref & v-component in this situation.
18+
*
1319
* @param {Element|DocumentFragment} el
1420
* @param {Object} options
1521
* @param {Boolean} partial
22+
* @param {Boolean} asParent
1623
* @return {Function}
1724
*/
1825

19-
module.exports = function compile (el, options, partial) {
26+
module.exports = function compile (el, options, partial, asParent) {
2027
var params = !partial && options.paramAttributes
2128
var paramsLinkFn = params
2229
? compileParamAttributes(el, params, options)
2330
: null
2431
var nodeLinkFn = el instanceof DocumentFragment
2532
? null
26-
: compileNode(el, options)
33+
: compileNode(el, options, asParent)
2734
var childLinkFn =
2835
!(nodeLinkFn && nodeLinkFn.terminal) &&
2936
el.tagName !== 'SCRIPT' &&
@@ -74,13 +81,14 @@ module.exports = function compile (el, options, partial) {
7481
*
7582
* @param {Node} node
7683
* @param {Object} options
84+
* @param {Boolean} asParent
7785
* @return {Function|undefined}
7886
*/
7987

80-
function compileNode (node, options) {
88+
function compileNode (node, options, asParent) {
8189
var type = node.nodeType
8290
if (type === 1 && node.tagName !== 'SCRIPT') {
83-
return compileElement(node, options)
91+
return compileElement(node, options, asParent)
8492
} else if (type === 3 && config.interpolate) {
8593
return compileTextNode(node, options)
8694
}
@@ -91,13 +99,14 @@ function compileNode (node, options) {
9199
*
92100
* @param {Element} el
93101
* @param {Object} options
102+
* @param {Boolean} asParent
94103
* @return {Function|null}
95104
*/
96105

97-
function compileElement (el, options) {
106+
function compileElement (el, options, asParent) {
98107
var linkFn, tag, component
99108
// check custom element component, but only on non-root
100-
if (!el.__vue__) {
109+
if (!asParent && !el.__vue__) {
101110
tag = el.tagName.toLowerCase()
102111
component =
103112
tag.indexOf('-') > 0 &&
@@ -108,12 +117,14 @@ function compileElement (el, options) {
108117
}
109118
if (component || el.hasAttributes()) {
110119
// check terminal direcitves
111-
linkFn = checkTerminalDirectives(el, options)
120+
if (!asParent) {
121+
linkFn = checkTerminalDirectives(el, options)
122+
}
112123
// if not terminal, build normal link function
113124
if (!linkFn) {
114-
var directives = collectDirectives(el, options)
115-
linkFn = directives.length
116-
? makeDirectivesLinkFn(directives)
125+
var dirs = collectDirectives(el, options, asParent)
126+
linkFn = dirs.length
127+
? makeDirectivesLinkFn(dirs)
117128
: null
118129
}
119130
}
@@ -432,16 +443,6 @@ function checkTerminalDirectives (el, options) {
432443
function makeTeriminalLinkFn (el, dirName, value, options) {
433444
var descriptor = dirParser.parse(value)[0]
434445
var def = options.directives[dirName]
435-
// special case: we need to collect directives found
436-
// on a component root node, but defined in the parent
437-
// template. These directives need to be compiled in
438-
// the parent scope.
439-
if (dirName === 'component') {
440-
var dirs = collectDirectives(el, options, true)
441-
el._parentLinker = dirs.length
442-
? makeDirectivesLinkFn(dirs)
443-
: null
444-
}
445446
var terminalLinkFn = function (vm, el) {
446447
vm._bindDir(dirName, el, descriptor, def)
447448
}
@@ -468,12 +469,11 @@ function collectDirectives (el, options, asParent) {
468469
attrName = attr.name
469470
if (attrName.indexOf(config.prefix) === 0) {
470471
dirName = attrName.slice(config.prefix.length)
471-
if (asParent) {
472-
if (dirName === 'with' || dirName === 'ref') {
473-
continue
474-
} else {
475-
el.removeAttribute(attrName)
476-
}
472+
if (asParent &&
473+
(dirName === 'with' ||
474+
dirName === 'ref' ||
475+
dirName === 'component')) {
476+
continue
477477
}
478478
dirDef = options.directives[dirName]
479479
_.assertAsset(dirDef, 'directive', dirName)

src/directives/component.js

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
var _ = require('../util')
2+
var compile = require('../compile/compile')
23
var templateParser = require('../parse/template')
34

45
module.exports = {
@@ -22,8 +23,12 @@ module.exports = {
2223
_.replace(this.el, this.ref)
2324
// check keep-alive options
2425
this.checkKeepAlive()
25-
// check parent directives
26-
this.parentLinker = this.el._parentLinker
26+
// compile parent scope content
27+
this.parentLinkFn = compile(
28+
this.el, this.vm.$options,
29+
true, // partial
30+
true // asParent
31+
)
2732
// if static, build right now.
2833
if (!this._isDynamicLiteral) {
2934
this.resolveCtor(this.expression)
@@ -81,18 +86,14 @@ module.exports = {
8186
}
8287
}
8388
var vm = this.vm
89+
var el = templateParser.clone(this.el)
8490
if (this.Ctor && !this.childVM) {
91+
if (this.parentLinkFn) {
92+
this.parentUnlinkFn = this.parentLinkFn(vm, el)
93+
}
8594
this.childVM = vm.$addChild({
86-
el: templateParser.clone(this.el)
95+
el: el
8796
}, this.Ctor)
88-
if (this.parentLinker) {
89-
var dirCount = vm._directives.length
90-
var targetVM = this.childVM.$options.inherit
91-
? this.childVM
92-
: vm
93-
this.parentLinker(targetVM, this.childVM.$el)
94-
this.parentDirs = vm._directives.slice(dirCount)
95-
}
9697
if (this.keepAlive) {
9798
this.cache[this.ctorId] = this.childVM
9899
}
@@ -118,12 +119,8 @@ module.exports = {
118119
}
119120
} else {
120121
child.$destroy(remove)
121-
var parentDirs = this.parentDirs
122-
if (parentDirs) {
123-
var i = parentDirs.length
124-
while (i--) {
125-
parentDirs[i]._teardown()
126-
}
122+
if (this.parentUnlinkFn) {
123+
this.parentUnlinkFn()
127124
}
128125
}
129126
this.childVM = null

test/unit/specs/compile/compile_spec.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ if (_.inBrowser) {
1616
directiveTeardown = jasmine.createSpy()
1717
vm = {
1818
_directives: [],
19-
_bindDir: function () {
19+
_bindDir: function (name) {
2020
this._directives.push({
21+
name: name,
2122
_teardown: directiveTeardown
2223
})
2324
},
@@ -203,5 +204,14 @@ if (_.inBrowser) {
203204
expect(vm._bindDir.calls.count()).toBe(0)
204205
})
205206

207+
it('component parent scope compilation should skip v-ref, v-with & v-component', function () {
208+
el.innerHTML = '<div v-component v-ref="test" v-with="test"></div>'
209+
el = el.firstChild
210+
var linker = compile(el, Vue.options, true, true)
211+
linker(vm, el)
212+
expect(vm._directives.length).toBe(0)
213+
expect(el.attributes.length).toBe(3)
214+
})
215+
206216
})
207217
}

test/unit/specs/directives/component_spec.js

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,19 +134,63 @@ if (_.inBrowser) {
134134
})
135135
})
136136

137-
it('should compile parent template directives in parent scope', function (done) {
137+
it('should compile parent template directives & content in parent scope', function (done) {
138138
var vm = new Vue({
139139
el: el,
140-
data: { ok: false },
141-
template: '<div v-component="test" v-show="ok"></div>',
140+
data: {
141+
ok: false,
142+
message: 'hello'
143+
},
144+
template: '<div v-component="test" v-show="ok">{{message}}</div>',
142145
components: {
143-
test: {}
146+
test: {
147+
template: '<content></content> {{message}}',
148+
data: function () {
149+
return {
150+
message: 'world'
151+
}
152+
}
153+
}
144154
}
145155
})
146156
expect(el.firstChild.style.display).toBe('none')
157+
expect(el.firstChild.textContent).toBe('hello world')
147158
vm.ok = true
159+
vm.message = 'bye'
148160
_.nextTick(function () {
149161
expect(el.firstChild.style.display).toBe('')
162+
expect(el.firstChild.textContent).toBe('bye world')
163+
done()
164+
})
165+
})
166+
167+
it('parent content + v-if', function (done) {
168+
var vm = new Vue({
169+
el: el,
170+
data: {
171+
ok: false,
172+
message: 'hello'
173+
},
174+
template: '<div v-component="test" v-if="ok">{{message}}</div>',
175+
components: {
176+
test: {
177+
template: '<content></content> {{message}}',
178+
data: function () {
179+
return {
180+
message: 'world'
181+
}
182+
}
183+
}
184+
}
185+
})
186+
expect(el.textContent).toBe('')
187+
expect(vm._children).toBeNull()
188+
expect(vm._directives.length).toBe(1) // v-if
189+
vm.ok = true
190+
_.nextTick(function () {
191+
expect(vm._children.length).toBe(1)
192+
expect(vm._directives.length).toBe(3) // v-if, v-component, v-text
193+
expect(el.textContent).toBe('hello world')
150194
done()
151195
})
152196
})

test/unit/specs/directives/ref_spec.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@ if (_.inBrowser) {
5050
it('nested v-repeat', function () {
5151
var vm = new Vue({
5252
el: el,
53-
template: '<div v-component="c1" v-ref="c1"><div v-repeat="2" v-ref="c2"></div></div>',
54-
components: { c1: {} }
53+
template: '<div v-component="c1" v-ref="c1"></div>',
54+
components: {
55+
c1: {
56+
template: '<div v-repeat="2" v-ref="c2"></div>'
57+
}
58+
}
5559
})
5660
expect(vm.$.c1 instanceof Vue).toBe(true)
5761
expect(vm.$.c2).toBeUndefined()

0 commit comments

Comments
 (0)