Skip to content

Commit 792c139

Browse files
committed
refactor transclusion logic
transcluded contents are now marked with a "transcluded" attribute so that the compiler knows to compile them in parent scope. this allows proper re-compile of transcluded blocks in conditionals (v-if and v-partial).
1 parent d39fa12 commit 792c139

File tree

5 files changed

+124
-137
lines changed

5 files changed

+124
-137
lines changed

src/compiler/compile.js

Lines changed: 66 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,22 @@ var textParser = require('../parsers/text')
44
var dirParser = require('../parsers/directive')
55
var templateParser = require('../parsers/template')
66

7+
module.exports = compile
8+
79
/**
810
* Compile a template and return a reusable composite link
911
* function, which recursively contains more link functions
1012
* inside. This top level compile function should only be
1113
* called on instance root nodes.
1214
*
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-
*
1915
* @param {Element|DocumentFragment} el
2016
* @param {Object} options
2117
* @param {Boolean} partial
22-
* @param {Boolean} asParent - compiling a component
23-
* container as its parent.
18+
* @param {Boolean} transcluded
2419
* @return {Function}
2520
*/
2621

27-
module.exports = function compile (el, options, partial, asParent) {
22+
function compile (el, options, partial, transcluded) {
2823
var isBlock = el.nodeType === 11
2924
var params = !partial && options.paramAttributes
3025
// if el is a fragment, this is a block instance
@@ -37,7 +32,7 @@ module.exports = function compile (el, options, partial, asParent) {
3732
: null
3833
var nodeLinkFn = isBlock
3934
? null
40-
: compileNode(el, options, asParent)
35+
: compileNode(el, options)
4136
var childLinkFn =
4237
!(nodeLinkFn && nodeLinkFn.terminal) &&
4338
el.tagName !== 'SCRIPT' &&
@@ -57,12 +52,16 @@ module.exports = function compile (el, options, partial, asParent) {
5752

5853
return function link (vm, el) {
5954
var originalDirCount = vm._directives.length
55+
var parentOriginalDirCount =
56+
vm.$parent && vm.$parent._directives.length
6057
if (paramsLinkFn) {
6158
var paramsEl = isBlock ? el.childNodes[1] : el
6259
paramsLinkFn(vm, paramsEl)
6360
}
6461
// cache childNodes before linking parent, fix #657
6562
var childNodes = _.toArray(el.childNodes)
63+
// if transcluded, link in parent scope
64+
if (transcluded) vm = vm.$parent
6665
if (nodeLinkFn) nodeLinkFn(vm, el)
6766
if (childLinkFn) childLinkFn(vm, childNodes)
6867

@@ -73,16 +72,26 @@ module.exports = function compile (el, options, partial, asParent) {
7372
* linking.
7473
*/
7574

76-
if (partial) {
77-
var dirs = vm._directives.slice(originalDirCount)
78-
return function unlink () {
75+
if (partial && !transcluded) {
76+
var selfDirs = vm._directives.slice(originalDirCount)
77+
var parentDirs = vm.$parent &&
78+
vm.$parent._directives.slice(parentOriginalDirCount)
79+
80+
var teardownDirs = function (vm, dirs) {
7981
var i = dirs.length
8082
while (i--) {
8183
dirs[i]._teardown()
8284
}
8385
i = vm._directives.indexOf(dirs[0])
8486
vm._directives.splice(i, dirs.length)
8587
}
88+
89+
return function unlink () {
90+
teardownDirs(vm, selfDirs)
91+
if (parentDirs) {
92+
teardownDirs(vm.$parent, parentDirs)
93+
}
94+
}
8695
}
8796
}
8897
}
@@ -93,14 +102,13 @@ module.exports = function compile (el, options, partial, asParent) {
93102
*
94103
* @param {Node} node
95104
* @param {Object} options
96-
* @param {Boolean} asParent
97105
* @return {Function|null}
98106
*/
99107

100-
function compileNode (node, options, asParent) {
108+
function compileNode (node, options) {
101109
var type = node.nodeType
102110
if (type === 1 && node.tagName !== 'SCRIPT') {
103-
return compileElement(node, options, asParent)
111+
return compileElement(node, options)
104112
} else if (type === 3 && config.interpolate && node.data.trim()) {
105113
return compileTextNode(node, options)
106114
} else {
@@ -113,14 +121,20 @@ function compileNode (node, options, asParent) {
113121
*
114122
* @param {Element} el
115123
* @param {Object} options
116-
* @param {Boolean} asParent
117124
* @return {Function|null}
118125
*/
119126

120-
function compileElement (el, options, asParent) {
127+
function compileElement (el, options) {
128+
if (checkTransclusion(el)) {
129+
// unwrap textNode
130+
if (el.hasAttribute('__vue__wrap')) {
131+
el = el.firstChild
132+
}
133+
return compile(el, options._parent.$options, true, true)
134+
}
121135
var linkFn, tag, component
122136
// check custom element component, but only on non-root
123-
if (!asParent && !el.__vue__) {
137+
if (!el.__vue__) {
124138
tag = el.tagName.toLowerCase()
125139
component =
126140
tag.indexOf('-') > 0 &&
@@ -131,12 +145,10 @@ function compileElement (el, options, asParent) {
131145
}
132146
if (component || el.hasAttributes()) {
133147
// check terminal direcitves
134-
if (!asParent) {
135-
linkFn = checkTerminalDirectives(el, options)
136-
}
148+
linkFn = checkTerminalDirectives(el, options)
137149
// if not terminal, build normal link function
138150
if (!linkFn) {
139-
var dirs = collectDirectives(el, options, asParent)
151+
var dirs = collectDirectives(el, options)
140152
linkFn = dirs.length
141153
? makeDirectivesLinkFn(dirs)
142154
: null
@@ -166,16 +178,21 @@ function makeDirectivesLinkFn (directives) {
166178
return function directivesLinkFn (vm, el) {
167179
// reverse apply because it's sorted low to high
168180
var i = directives.length
169-
var dir, j, k
181+
var dir, j, k, target
170182
while (i--) {
171183
dir = directives[i]
184+
// a directive can be transcluded if it's written
185+
// on a component's container in its parent tempalte.
186+
target = dir.transcluded
187+
? vm.$parent
188+
: vm
172189
if (dir._link) {
173190
// custom link fn
174-
dir._link(vm, el)
191+
dir._link(target, el)
175192
} else {
176193
k = dir.descriptors.length
177194
for (j = 0; j < k; j++) {
178-
vm._bindDir(dir.name, el,
195+
target._bindDir(dir.name, el,
179196
dir.descriptors[j], dir.def)
180197
}
181198
}
@@ -478,38 +495,37 @@ function makeTeriminalLinkFn (el, dirName, value, options) {
478495
*
479496
* @param {Element} el
480497
* @param {Object} options
481-
* @param {Boolean} asParent
482498
* @return {Array}
483499
*/
484500

485-
function collectDirectives (el, options, asParent) {
501+
function collectDirectives (el, options) {
486502
var attrs = _.toArray(el.attributes)
487503
var i = attrs.length
488504
var dirs = []
489-
var attr, attrName, dir, dirName, dirDef
505+
var attr, attrName, dir, dirName, dirDef, transcluded
490506
while (i--) {
491507
attr = attrs[i]
492508
attrName = attr.name
509+
transcluded =
510+
options._transcludedAttrs &&
511+
options._transcludedAttrs[attrName]
493512
if (attrName.indexOf(config.prefix) === 0) {
494513
dirName = attrName.slice(config.prefix.length)
495-
if (asParent &&
496-
(dirName === 'with' ||
497-
dirName === 'component')) {
498-
continue
499-
}
500514
dirDef = options.directives[dirName]
501515
_.assertAsset(dirDef, 'directive', dirName)
502516
if (dirDef) {
503517
dirs.push({
504518
name: dirName,
505519
descriptors: dirParser.parse(attr.value),
506-
def: dirDef
520+
def: dirDef,
521+
transcluded: transcluded
507522
})
508523
}
509524
} else if (config.interpolate) {
510525
dir = collectAttrDirective(el, attrName, attr.value,
511526
options)
512527
if (dir) {
528+
dir.transcluded = transcluded
513529
dirs.push(dir)
514530
}
515531
}
@@ -531,10 +547,6 @@ function collectDirectives (el, options, asParent) {
531547
*/
532548

533549
function collectAttrDirective (el, name, value, options) {
534-
if (options._skipAttrs &&
535-
options._skipAttrs.indexOf(name) > -1) {
536-
return
537-
}
538550
var tokens = textParser.parse(value)
539551
if (tokens) {
540552
var def = options.directives.attr
@@ -572,4 +584,19 @@ function directiveComparator (a, b) {
572584
a = a.def.priority || 0
573585
b = b.def.priority || 0
574586
return a > b ? 1 : -1
587+
}
588+
589+
/**
590+
* Check whether an element is transcluded
591+
*
592+
* @param {Element} el
593+
* @return {Boolean}
594+
*/
595+
596+
var transcludedFlagAttr = '__vue__transcluded'
597+
function checkTransclusion (el) {
598+
if (el.nodeType === 1 && el.hasAttribute(transcludedFlagAttr)) {
599+
el.removeAttribute(transcludedFlagAttr)
600+
return true
601+
}
575602
}

src/directives/if.js

Lines changed: 8 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,11 @@ module.exports = {
1212
this.end = document.createComment('v-if-end')
1313
_.replace(el, this.end)
1414
_.before(this.start, this.end)
15-
16-
// Note: content transclusion is not available for
17-
// <template> blocks
1815
if (el.tagName === 'TEMPLATE') {
1916
this.template = templateParser.parse(el, true)
2017
} else {
2118
this.template = document.createDocumentFragment()
2219
this.template.appendChild(templateParser.clone(el))
23-
this.checkContent()
2420
}
2521
// compile the nested partial
2622
this.linker = compile(
@@ -37,48 +33,13 @@ module.exports = {
3733
}
3834
},
3935

40-
// check if there are any content nodes from parent.
41-
// these nodes are compiled by the parent and should
42-
// not be cloned during a re-compilation - otherwise the
43-
// parent directives bound to them will no longer work.
44-
// (see #736)
45-
checkContent: function () {
46-
var el = this.el
47-
for (var i = 0; i < el.childNodes.length; i++) {
48-
var node = el.childNodes[i]
49-
// _isContent is a flag set in instance/compile
50-
// after the raw content has been compiled by parent
51-
if (node._isContent) {
52-
;(this.contentNodes = this.contentNodes || []).push(node)
53-
;(this.contentPositions = this.contentPositions || []).push(i)
54-
}
55-
}
56-
// keep track of any transcluded components contained within
57-
// the conditional block. we need to call attach/detach hooks
58-
// for them.
59-
this.transCpnts =
60-
this.vm._transCpnts &&
61-
this.vm._transCpnts.filter(function (c) {
62-
return el.contains(c.$el)
63-
})
64-
},
65-
6636
update: function (value) {
6737
if (this.invalid) return
6838
if (value) {
6939
// avoid duplicate compiles, since update() can be
7040
// called with different truthy values
7141
if (!this.unlink) {
7242
var frag = templateParser.clone(this.template)
73-
// persist content nodes from parent.
74-
if (this.contentNodes) {
75-
var el = frag.childNodes[0]
76-
for (var i = 0, l = this.contentNodes.length; i < l; i++) {
77-
var node = this.contentNodes[i]
78-
var j = this.contentPositions[i]
79-
el.replaceChild(node, el.childNodes[j])
80-
}
81-
}
8243
this.compile(frag)
8344
}
8445
} else {
@@ -90,13 +51,19 @@ module.exports = {
9051
compile: function (frag) {
9152
var vm = this.vm
9253
var originalChildLength = vm._children.length
54+
var originalParentChildLength = vm.$parent &&
55+
vm.$parent._children.length
56+
// the linker is not guaranteed to be present because
57+
// this function might get called by v-partial
9358
this.unlink = this.linker
9459
? this.linker(vm, frag)
9560
: vm.$compile(frag)
9661
transition.blockAppend(frag, this.end, vm)
9762
this.children = vm._children.slice(originalChildLength)
98-
if (this.transCpnts) {
99-
this.children = this.children.concat(this.transCpnts)
63+
if (vm.$parent) {
64+
this.children = this.children.concat(
65+
vm.$parent._children.slice(originalParentChildLength)
66+
)
10067
}
10168
if (this.children.length && _.inDoc(vm.$el)) {
10269
this.children.forEach(function (child) {

src/directives/repeat.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ module.exports = {
120120
})
121121
merged.template = this.inlineTempalte || merged.template
122122
this.template = transclude(this.template, merged)
123-
this._linkFn = compile(this.template, merged, false, true)
123+
this._linkFn = compile(this.template, merged)
124124
}
125125
} else {
126126
// to be resolved later

0 commit comments

Comments
 (0)