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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy