Skip to content

Commit 5566f75

Browse files
committed
use empty textNodes as anchors for cleaner HTML output
1 parent cef724a commit 5566f75

File tree

15 files changed

+135
-113
lines changed

15 files changed

+135
-113
lines changed

src/compiler/transclude.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,11 @@ module.exports = function transclude (el, options) {
5151
el = transcludeTemplate(el, options)
5252
}
5353
if (el instanceof DocumentFragment) {
54-
_.prepend(document.createComment('v-start'), el)
55-
el.appendChild(document.createComment('v-end'))
54+
// anchors for block instance
55+
// passing in `persist: true` to avoid them being
56+
// discarded by IE during template cloning
57+
_.prepend(_.createAnchor('v-start', true), el)
58+
el.appendChild(_.createAnchor('v-end', true))
5659
}
5760
return el
5861
}

src/directives/component.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ module.exports = {
1818
bind: function () {
1919
if (!this.el.__vue__) {
2020
// create a ref anchor
21-
this.ref = document.createComment('v-component')
22-
_.replace(this.el, this.ref)
21+
this.anchor = _.createAnchor('v-component')
22+
_.replace(this.el, this.anchor)
2323
// check keep-alive options.
2424
// If yes, instead of destroying the active vm when
2525
// hiding (v-if) or switching (dynamic literal) it,
@@ -44,7 +44,7 @@ module.exports = {
4444
if (!this._isDynamicLiteral) {
4545
this.resolveCtor(this.expression, _.bind(function () {
4646
var child = this.build()
47-
child.$before(this.ref)
47+
child.$before(this.anchor)
4848
this.setCurrent(child)
4949
}, this))
5050
} else {
@@ -214,18 +214,18 @@ module.exports = {
214214
this.setCurrent(target)
215215
switch (self.transMode) {
216216
case 'in-out':
217-
target.$before(self.ref, function () {
217+
target.$before(self.anchor, function () {
218218
self.remove(current, cb)
219219
})
220220
break
221221
case 'out-in':
222222
self.remove(current, function () {
223-
target.$before(self.ref, cb)
223+
target.$before(self.anchor, cb)
224224
})
225225
break
226226
default:
227227
self.remove(current)
228-
target.$before(self.ref, cb)
228+
target.$before(self.anchor, cb)
229229
}
230230
},
231231

src/directives/html.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ module.exports = {
99
if (this.el.nodeType === 8) {
1010
// hold nodes
1111
this.nodes = []
12+
// replace the placeholder with proper anchor
13+
this.anchor = _.createAnchor('v-html')
14+
_.replace(this.el, this.anchor)
1215
}
1316
},
1417

@@ -32,7 +35,7 @@ module.exports = {
3235
var frag = templateParser.parse(value, true, true)
3336
// save a reference to these nodes so we can remove later
3437
this.nodes = _.toArray(frag.childNodes)
35-
_.before(frag, this.el)
38+
_.before(frag, this.anchor)
3639
}
3740

3841
}

src/directives/if.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ module.exports = {
88
bind: function () {
99
var el = this.el
1010
if (!el.__vue__) {
11-
this.start = document.createComment('v-if-start')
12-
this.end = document.createComment('v-if-end')
11+
this.start = _.createAnchor('v-if-start')
12+
this.end = _.createAnchor('v-if-end')
1313
_.replace(el, this.end)
1414
_.before(this.start, this.end)
1515
if (el.tagName === 'TEMPLATE') {

src/directives/repeat.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ module.exports = {
3737
} else {
3838
this.filters.read.unshift(objectConverter)
3939
}
40-
// setup ref node
41-
this.ref = document.createComment('v-repeat')
42-
_.replace(this.el, this.ref)
40+
// setup anchor node
41+
this.anchor = _.createAnchor('v-repeat')
42+
_.replace(this.el, this.anchor)
4343
// check if this is a block repeat
4444
this.template = this.el.tagName === 'TEMPLATE'
4545
? templateParser.parse(this.el, true)
@@ -258,7 +258,7 @@ module.exports = {
258258
diff: function (data, oldVms) {
259259
var idKey = this.idKey
260260
var converted = this.converted
261-
var ref = this.ref
261+
var anchor = this.anchor
262262
var alias = this.arg
263263
var init = !oldVms
264264
var vms = new Array(data.length)
@@ -297,7 +297,7 @@ module.exports = {
297297
vms[i] = vm
298298
// insert if this is first run
299299
if (init) {
300-
vm.$before(ref)
300+
vm.$before(anchor)
301301
}
302302
}
303303
// if this is the first run, we're done.
@@ -330,13 +330,13 @@ module.exports = {
330330
// place, so no need to touch it. Otherwise, insert
331331
// it.
332332
if (!vm._reused) {
333-
vm.$before(ref)
333+
vm.$before(anchor)
334334
}
335335
} else {
336336
var nextEl = targetNext.$el
337337
if (vm._reused) {
338338
// this is the vm we are actually in front of
339-
currentNext = findNextVm(vm, ref)
339+
currentNext = findNextVm(vm, anchor)
340340
// we only need to move if we are not in the right
341341
// place already.
342342
if (currentNext !== targetNext) {
@@ -544,13 +544,13 @@ module.exports = {
544544
* should have been removed so we can skip them.
545545
*
546546
* @param {Vue} vm
547-
* @param {CommentNode} ref
547+
* @param {Comment|Text} anchor
548548
* @return {Vue}
549549
*/
550550

551-
function findNextVm (vm, ref) {
551+
function findNextVm (vm, anchor) {
552552
var el = (vm._blockEnd || vm.$el).nextSibling
553-
while (!el.__vue__ && el !== ref) {
553+
while (!el.__vue__ && el !== anchor) {
554554
el = el.nextSibling
555555
}
556556
return el.__vue__

src/instance/compile.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ exports._initElement = function (el) {
5151
this._isBlock = true
5252
this.$el = this._blockStart = el.firstChild
5353
this._blockEnd = el.lastChild
54+
// set persisted text anchors to empty
55+
if (this._blockStart.nodeType === 3) {
56+
this._blockStart.data = this._blockEnd.data = ''
57+
}
5458
this._blockFragment = el
5559
} else {
5660
this.$el = el

src/util/misc.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
var config = require('../config')
2+
13
/**
24
* Check if an element is a component, if yes return its
35
* component id.
@@ -17,4 +19,28 @@ exports.checkComponent = function (el, options) {
1719
} else if (options.components[tag]) {
1820
return tag
1921
}
22+
}
23+
24+
/**
25+
* Create an "anchor" for performing dom insertion/removals.
26+
* This is used in a number of scenarios:
27+
* - block instance
28+
* - v-html
29+
* - v-if
30+
* - component
31+
* - repeat
32+
*
33+
* @param {String} content
34+
* @param {Boolean} persist - IE trashes empty textNodes on
35+
* cloneNode(true), so in certain
36+
* cases the anchor needs to be
37+
* non-empty to be persisted in
38+
* templates.
39+
* @return {Comment|Text}
40+
*/
41+
42+
exports.createAnchor = function (content, persist) {
43+
return config.debug
44+
? document.createComment(content)
45+
: document.createTextNode(persist ? ' ' : '')
2046
}

test/unit/specs/compiler/compile_spec.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,17 +257,15 @@ if (_.inBrowser) {
257257
})
258258
expect(el.innerHTML).toBe(
259259
'<testa><testb>' +
260-
'<div>1</div><div>2</div><!--v-repeat-->' +
261-
'</testb><!--v-component-->' +
262-
'</testa><!--v-component-->'
260+
'<div>1</div><div>2</div>' +
261+
'</testb></testa>'
263262
)
264263
vm.list.push(3)
265264
_.nextTick(function () {
266265
expect(el.innerHTML).toBe(
267266
'<testa><testb>' +
268-
'<div>1</div><div>2</div><div>3</div><!--v-repeat-->' +
269-
'</testb><!--v-component-->' +
270-
'</testa><!--v-component-->'
267+
'<div>1</div><div>2</div><div>3</div>' +
268+
'</testb></testa>'
271269
)
272270
done()
273271
})

test/unit/specs/compiler/transclude_spec.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ if (_.inBrowser) {
4747
var res = transclude(frag, options)
4848
expect(res).toBe(frag)
4949
expect(res.childNodes.length).toBe(3)
50-
expect(res.childNodes[0].nodeType).toBe(8)
50+
expect(res.childNodes[0].nodeType).toBe(3)
5151
expect(res.childNodes[1]).toBe(el)
52-
expect(res.childNodes[2].nodeType).toBe(8)
52+
expect(res.childNodes[2].nodeType).toBe(3)
5353
})
5454

5555
it('template element', function () {
@@ -58,9 +58,9 @@ if (_.inBrowser) {
5858
var res = transclude(tpl, options)
5959
expect(res instanceof DocumentFragment).toBe(true)
6060
expect(res.childNodes.length).toBe(3)
61-
expect(res.childNodes[0].nodeType).toBe(8)
61+
expect(res.childNodes[0].nodeType).toBe(3)
6262
expect(res.childNodes[1].textContent).toBe('123')
63-
expect(res.childNodes[2].nodeType).toBe(8)
63+
expect(res.childNodes[2].nodeType).toBe(3)
6464
})
6565

6666
it('content transclusion', function () {

test/unit/specs/directives/component_spec.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ if (_.inBrowser) {
2828
}
2929
}
3030
})
31-
expect(el.innerHTML).toBe('<test>123</test><!--v-component-->')
31+
expect(el.innerHTML).toBe('<test>123</test>')
3232
})
3333

3434
it('replace', function () {
@@ -45,7 +45,7 @@ if (_.inBrowser) {
4545
}
4646
}
4747
})
48-
expect(el.innerHTML).toBe('<p>123</p><!--v-component-->')
48+
expect(el.innerHTML).toBe('<p>123</p>')
4949
})
5050

5151
it('inline-template', function () {
@@ -64,7 +64,7 @@ if (_.inBrowser) {
6464
}
6565
}
6666
})
67-
expect(el.innerHTML).toBe('<test>child</test><!--v-component-->')
67+
expect(el.innerHTML).toBe('<test>child</test>')
6868
})
6969

7070
it('block replace', function () {
@@ -81,7 +81,7 @@ if (_.inBrowser) {
8181
}
8282
}
8383
})
84-
expect(el.innerHTML).toBe('<!--v-start--><p>123</p><p>234</p><!--v-end--><!--v-component-->')
84+
expect(el.innerHTML).toBe('<p>123</p><p>234</p>')
8585
})
8686

8787
it('dynamic', function (done) {
@@ -108,13 +108,13 @@ if (_.inBrowser) {
108108
}
109109
}
110110
})
111-
expect(el.innerHTML).toBe('<div view="a">AAA</div><!--v-component-->')
111+
expect(el.innerHTML).toBe('<div view="a">AAA</div>')
112112
vm.view = 'b'
113113
_.nextTick(function () {
114-
expect(el.innerHTML).toBe('<div view="b">BBB</div><!--v-component-->')
114+
expect(el.innerHTML).toBe('<div view="b">BBB</div>')
115115
vm.view = ''
116116
_.nextTick(function () {
117-
expect(el.innerHTML).toBe('<!--v-component-->')
117+
expect(el.innerHTML).toBe('')
118118
done()
119119
})
120120
})
@@ -142,22 +142,22 @@ if (_.inBrowser) {
142142
}
143143
}
144144
})
145-
expect(el.innerHTML).toBe('<div>AAA</div><!--v-component-->')
145+
expect(el.innerHTML).toBe('<div>AAA</div>')
146146
expect(spyA.calls.count()).toBe(1)
147147
expect(spyB.calls.count()).toBe(0)
148148
vm.view = 'b'
149149
_.nextTick(function () {
150-
expect(el.innerHTML).toBe('<div>BBB</div><!--v-component-->')
150+
expect(el.innerHTML).toBe('<div>BBB</div>')
151151
expect(spyA.calls.count()).toBe(1)
152152
expect(spyB.calls.count()).toBe(1)
153153
vm.view = 'a'
154154
_.nextTick(function () {
155-
expect(el.innerHTML).toBe('<div>AAA</div><!--v-component-->')
155+
expect(el.innerHTML).toBe('<div>AAA</div>')
156156
expect(spyA.calls.count()).toBe(1)
157157
expect(spyB.calls.count()).toBe(1)
158158
vm.view = 'b'
159159
_.nextTick(function () {
160-
expect(el.innerHTML).toBe('<div>BBB</div><!--v-component-->')
160+
expect(el.innerHTML).toBe('<div>BBB</div>')
161161
expect(spyA.calls.count()).toBe(1)
162162
expect(spyB.calls.count()).toBe(1)
163163
done()
@@ -243,7 +243,7 @@ if (_.inBrowser) {
243243
}
244244
}
245245
})
246-
expect(el.innerHTML).toBe('<ul><li>1</li><li>2</li><!--v-repeat--></ul><!--v-component-->')
246+
expect(el.innerHTML).toBe('<ul><li>1</li><li>2</li></ul>')
247247
})
248248

249249
it('wait-for', function (done) {

0 commit comments

Comments
 (0)