Skip to content

Commit 21b4b6a

Browse files
committed
support "is" attribute on normal elements
1 parent a86404b commit 21b4b6a

File tree

8 files changed

+108
-39
lines changed

8 files changed

+108
-39
lines changed

src/compiler/compile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ function compileElement (el, options) {
284284
}
285285
// check component
286286
if (!linkFn) {
287-
linkFn = checkComponent(el, options)
287+
linkFn = checkComponent(el, options, hasAttrs)
288288
}
289289
// normal directives
290290
if (!linkFn && hasAttrs) {

src/compiler/transclude.js

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,11 @@ function transcludeTemplate (el, options) {
8181
// non-element template
8282
replacer.nodeType !== 1 ||
8383
// single nested component
84-
tag === 'component' ||
8584
_.resolveAsset(options, 'components', tag) ||
86-
replacer.hasAttribute(config.prefix + 'component') ||
8785
// element directive
8886
_.resolveAsset(options, 'elementDirectives', tag) ||
89-
// repeat block
90-
replacer.hasAttribute(config.prefix + 'repeat') ||
91-
// for block
92-
replacer.hasAttribute(config.prefix + 'for')
87+
// attribue based fragment cases
88+
isFragment(replacer)
9389
) {
9490
return frag
9591
} else {
@@ -145,3 +141,27 @@ function mergeAttrs (from, to) {
145141
}
146142
}
147143
}
144+
145+
/**
146+
* Check if a replacer element needs to force the instance
147+
* into fragment mode.
148+
*
149+
* @param {Element} el
150+
* @return {Boolean}
151+
*/
152+
153+
function isFragment (el) {
154+
return el.hasAttributes() && (
155+
// alternative component syntax
156+
el.hasAttribute('is') ||
157+
el.hasAttribute(':is') ||
158+
el.hasAttribute('bind-is') ||
159+
el.hasAttribute(config.prefix + 'component') ||
160+
// repeat block
161+
el.hasAttribute(config.prefix + 'repeat') ||
162+
// for block
163+
el.hasAttribute(config.prefix + 'for') ||
164+
// if block
165+
el.hasAttribute(config.prefix + 'if')
166+
)
167+
}

src/deprecations.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,12 @@ if (process.env.NODE_ENV !== 'production') {
226226
'The "prefix" global config will be deprecated in 1.0.0. All directives ' +
227227
'will consistently use the v- or v. prefixes.'
228228
)
229+
},
230+
231+
V_COMPONENT: function () {
232+
warn(
233+
'v-component will be deprecated in 1.0.0. Use "is" attribute instead.'
234+
)
229235
}
230236

231237
}

src/directives/repeat.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ module.exports = {
108108
checkComponent: function () {
109109
this.componentState = UNRESOLVED
110110
var options = this.vm.$options
111-
var id = _.checkComponent(this.el, options)
111+
var id = _.checkComponent(this.el, options, this.el.hasAttributes())
112112
if (!id) {
113113
// default constructor
114114
this.Component = _.Vue

src/util/component.js

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,64 @@ var _ = require('./index')
1010
*/
1111

1212
exports.commonTagRE = /^(div|p|span|img|a|b|i|br|ul|ol|li|h1|h2|h3|h4|h5|h6|code|pre|table|th|td|tr|form|label|input|select|option|nav|article|section|header|footer)$/
13-
exports.checkComponent = function (el, options) {
13+
exports.checkComponent = function (el, options, hasAttrs) {
1414
var tag = el.tagName.toLowerCase()
15-
if (tag === 'component') {
16-
// dynamic syntax
17-
var exp = el.getAttribute('is')
18-
if (exp != null) {
19-
if (process.env.NODE_ENV !== 'production' && /{{.*}}/.test(exp)) {
20-
_.deprecation.BIND_IS()
21-
}
22-
el.removeAttribute('is')
23-
} else {
24-
exp = _.getBindAttr(el, 'is')
25-
if (exp != null) {
26-
// leverage literal dynamic for now.
27-
// TODO: make this cleaner
28-
exp = '{{' + exp + '}}'
29-
}
30-
}
31-
return exp
32-
} else if (!exports.commonTagRE.test(tag)) {
15+
if (!exports.commonTagRE.test(tag) && tag !== 'component') {
3316
if (_.resolveAsset(options, 'components', tag)) {
17+
// custom element component
3418
return tag
35-
} else if (process.env.NODE_ENV !== 'production') {
36-
if (tag.indexOf('-') > -1 ||
37-
/HTMLUnknownElement/.test(Object.prototype.toString.call(el))) {
38-
_.warn(
39-
'Unknown custom element: <' + tag + '> - did you ' +
40-
'register the component correctly?'
41-
)
19+
} else {
20+
var exp = hasAttrs && checkComponentAttribute(el)
21+
if (exp) return exp
22+
if (process.env.NODE_ENV !== 'production') {
23+
if (tag.indexOf('-') > -1 ||
24+
/HTMLUnknownElement/.test(Object.prototype.toString.call(el))) {
25+
_.warn(
26+
'Unknown custom element: <' + tag + '> - did you ' +
27+
'register the component correctly?'
28+
)
29+
}
4230
}
4331
}
32+
} else if (hasAttrs) {
33+
return checkComponentAttribute(el)
4434
}
35+
}
36+
37+
/**
38+
* Check possible component denoting attributes, e.g.
39+
* is, bind-is and v-component.
40+
*
41+
* @param {Elemnent} el
42+
* @return {String|null}
43+
*/
44+
45+
function checkComponentAttribute (el) {
46+
var exp
4547
/* eslint-disable no-cond-assign */
46-
if (tag = _.attr(el, 'component')) {
48+
if (exp = _.attr(el, 'component')) {
4749
/* eslint-enable no-cond-assign */
48-
return tag
50+
if (process.env.NODE_ENV !== 'production') {
51+
_.deprecation.V_COMPONENT()
52+
}
53+
return exp
54+
}
55+
// dynamic syntax
56+
exp = el.getAttribute('is')
57+
if (exp != null) {
58+
if (process.env.NODE_ENV !== 'production' && /{{.*}}/.test(exp)) {
59+
_.deprecation.BIND_IS()
60+
}
61+
el.removeAttribute('is')
62+
} else {
63+
exp = _.getBindAttr(el, 'is')
64+
if (exp != null) {
65+
// leverage literal dynamic for now.
66+
// TODO: make this cleaner
67+
exp = '{{' + exp + '}}'
68+
}
4969
}
70+
return exp
5071
}
5172

5273
/**

test/unit/specs/compiler/transclude_spec.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ if (_.inBrowser) {
6161
res = transclude(el, options)
6262
expect(res instanceof DocumentFragment).toBe(true)
6363

64+
// single component: is
65+
options.template = '<div is="test"></div>'
66+
res = transclude(el, options)
67+
expect(res instanceof DocumentFragment).toBe(true)
68+
6469
// single component: custom element
6570
options.template = '<test></test>'
6671
options.components = { test: {}}

test/unit/specs/directives/component_spec.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,23 @@ if (_.inBrowser) {
6565
expect(_.warn).not.toHaveBeenCalled()
6666
})
6767

68+
it('allow "is" attribute on normal elements', function () {
69+
var vm = new Vue({
70+
el: el,
71+
template: '<table><tbody><tr is="test"></tr></tbody></table>',
72+
components: {
73+
test: {
74+
data: function () {
75+
return { a: 123 }
76+
},
77+
template: '<td>{{a}}</td>'
78+
}
79+
}
80+
})
81+
expect(el.innerHTML).toBe(vm.$options.template.replace(/<tr.*\/tr>/, '<tr><td>123</td></tr>'))
82+
expect(_.warn).not.toHaveBeenCalled()
83+
})
84+
6885
it('inline-template', function () {
6986
new Vue({
7087
el: el,

test/unit/specs/util/component_spec.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
var _ = require('../../../../src/util')
22

3-
describe('Util - Misc', function () {
3+
describe('Util - Component', function () {
44

55
it('checkComponent', function () {
66
var el = document.createElement('component')
77
// <component> with no is attr
88
var res = _.checkComponent(el)
9-
expect(res).toBe(null)
9+
expect(res).toBeUndefined()
1010
// <component is="...">
1111
el.setAttribute('is', '{{what}}')
12-
res = _.checkComponent(el)
12+
res = _.checkComponent(el, {}, true)
1313
expect(res).toBe('{{what}}')
1414
// custom element, not defined
1515
el = document.createElement('test')

0 commit comments

Comments
 (0)