From 70d5280faa1955d2b31e307d66a39190bb1976bc Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 15 May 2015 11:48:31 -0400 Subject: [PATCH] remove v-component --- examples/svg/index.html | 10 +- examples/svg/svg.js | 1 + examples/tree/index.html | 30 ++--- examples/tree/tree.js | 1 + src/compiler/compile.js | 53 +++++--- src/compiler/transclude.js | 6 +- src/config.js | 3 +- src/directives/index.js | 2 +- src/directives/repeat.js | 2 +- src/util/index.js | 24 +++- test/unit/specs/async_component_spec.js | 24 ++-- test/unit/specs/compiler/compile_spec.js | 36 +++--- test/unit/specs/directives/component_spec.js | 47 +++---- test/unit/specs/directives/events_spec.js | 10 +- test/unit/specs/directives/if_spec.js | 38 +++--- test/unit/specs/directives/prop_spec.js | 6 +- test/unit/specs/directives/ref_spec.js | 8 +- test/unit/specs/directives/repeat_spec.js | 125 +++---------------- test/unit/specs/misc_spec.js | 6 +- 19 files changed, 195 insertions(+), 237 deletions(-) diff --git a/examples/svg/index.html b/examples/svg/index.html index de7871195..da6ec3318 100644 --- a/examples/svg/index.html +++ b/examples/svg/index.html @@ -10,9 +10,11 @@ @@ -24,7 +26,7 @@
- +
diff --git a/examples/svg/svg.js b/examples/svg/svg.js index 02e7ce5bb..3458d0f7b 100644 --- a/examples/svg/svg.js +++ b/examples/svg/svg.js @@ -12,6 +12,7 @@ var stats = [ Vue.component('polygraph', { props: ['stats'], template: '#polygraph-template', + replace: true, computed: { // a computed property for the polygon's points points: function () { diff --git a/examples/tree/index.html b/examples/tree/index.html index 2fe45c3f1..89a2bb733 100644 --- a/examples/tree/index.html +++ b/examples/tree/index.html @@ -26,28 +26,28 @@

(You can double click on an item to turn it into a folder.)

    -
  • -
  • +
diff --git a/examples/tree/tree.js b/examples/tree/tree.js index f5b6eb055..7034301fa 100644 --- a/examples/tree/tree.js +++ b/examples/tree/tree.js @@ -32,6 +32,7 @@ var data = { Vue.component('item', { props: ['model'], template: '#item-template', + replace: true, data: function () { return { open: false diff --git a/src/compiler/compile.js b/src/compiler/compile.js index 342e527b5..2224192c2 100644 --- a/src/compiler/compile.js +++ b/src/compiler/compile.js @@ -6,7 +6,7 @@ var templateParser = require('../parsers/template') // internal directives var propDef = require('../directives/prop') -// var componentDef = require('../directives/component') +var componentDef = require('../directives/component') module.exports = compile @@ -192,24 +192,19 @@ function compileElement (el, options) { } return compile(el, options._parent.$options, true, true) } - var linkFn, tag, component - // check custom element component, but only on non-root - if (!el.__vue__) { - tag = el.tagName.toLowerCase() - component = - tag.indexOf('-') > 0 && - options.components[tag] - if (component) { - el.setAttribute(config.prefix + 'component', tag) - } - } - if (component || el.hasAttributes()) { - // check terminal direcitves + var linkFn + var hasAttrs = el.hasAttributes() + // check terminal direcitves (repeat & if) + if (hasAttrs) { linkFn = checkTerminalDirectives(el, options) - // if not terminal, build normal link function - if (!linkFn) { - linkFn = compileDirectives(el, options) - } + } + // check component + if (!linkFn) { + linkFn = checkComponent(el, options) + } + // normal directives + if (!linkFn && hasAttrs) { + linkFn = compileDirectives(el, options) } // if the element is a textarea, we need to interpolate // its content on initial render. @@ -449,6 +444,28 @@ function makePropsLinkFn (props) { } } +/** + * Check if an element is a component. If yes, return + * a component link function. + * + * @param {Element} el + * @param {Object} options + * @return {Function|undefined} + */ + +function checkComponent (el, options) { + var componentId = _.checkComponent(el, options) + if (componentId) { + var componentLinkFn = function (vm, el, host) { + vm._bindDir('component', el, { + expression: componentId + }, componentDef, host) + } + componentLinkFn.terminal = true + return componentLinkFn + } +} + /** * Check an element for terminal directives in fixed order. * If it finds one, return a terminal link function. diff --git a/src/compiler/transclude.js b/src/compiler/transclude.js index 8574119e2..e5f80638a 100644 --- a/src/compiler/transclude.js +++ b/src/compiler/transclude.js @@ -73,18 +73,19 @@ function transcludeTemplate (el, options) { _.warn('Invalid template option: ' + template) } else { var rawContent = options._content || _.extractContent(el) + var replacer = frag.firstChild if (options.replace) { if ( frag.childNodes.length > 1 || + replacer.nodeType !== 1 || // when root node has v-repeat, the instance ends up // having multiple top-level nodes, thus becoming a // block instance. (#835) - frag.firstChild.hasAttribute(config.prefix + 'repeat') + replacer.hasAttribute(config.prefix + 'repeat') ) { transcludeContent(frag, rawContent) return frag } else { - var replacer = frag.firstChild options._replacerAttrs = extractAttrs(replacer) mergeAttrs(el, replacer) transcludeContent(replacer, rawContent) @@ -199,7 +200,6 @@ function insertContentAt (outlet, contents) { function extractAttrs (el) { var attrs = el.attributes - if (!attrs) return var res = {} var i = attrs.length while (i--) { diff --git a/src/config.js b/src/config.js index 9175b1fca..53e5f5d16 100644 --- a/src/config.js +++ b/src/config.js @@ -71,8 +71,7 @@ module.exports = { _terminalDirectives: [ 'repeat', - 'if', - 'component' + 'if' ] } diff --git a/src/directives/index.js b/src/directives/index.js index bf167f50e..770addc89 100644 --- a/src/directives/index.js +++ b/src/directives/index.js @@ -23,5 +23,5 @@ exports.events = require('./events') // internal directives that should not be used directly // but we still want to expose them for advanced usage. -exports.component = require('./component') +exports._component = require('./component') exports._prop = require('./prop') \ No newline at end of file diff --git a/src/directives/repeat.js b/src/directives/repeat.js index 999605c40..b30c86743 100644 --- a/src/directives/repeat.js +++ b/src/directives/repeat.js @@ -92,8 +92,8 @@ module.exports = { checkComponent: function () { this.componentState = UNRESOLVED - var id = _.attr(this.el, 'component') var options = this.vm.$options + var id = _.checkComponent(this.el, options) if (!id) { // default constructor this.Ctor = _.Vue diff --git a/src/util/index.js b/src/util/index.js index 2b96b4dd3..752507b6a 100644 --- a/src/util/index.js +++ b/src/util/index.js @@ -5,4 +5,26 @@ extend(exports, lang) extend(exports, require('./env')) extend(exports, require('./dom')) extend(exports, require('./filter')) -extend(exports, require('./debug')) \ No newline at end of file +extend(exports, require('./debug')) + +/** + * Check if an element is a component, if yes return its + * component id. + * + * @param {Element} el + * @param {Object} options + * @return {String|undefined} + */ + +exports.checkComponent = function (el, options) { + var tag = el.tagName.toLowerCase() + if (options.components[tag]) { + return tag + } + // dynamic syntax + if (tag === 'component') { + var exp = el.getAttribute('type') + el.removeAttribute('type') + return exp + } +} \ No newline at end of file diff --git a/test/unit/specs/async_component_spec.js b/test/unit/specs/async_component_spec.js index ab6f4f63f..40cbce405 100644 --- a/test/unit/specs/async_component_spec.js +++ b/test/unit/specs/async_component_spec.js @@ -19,7 +19,7 @@ describe('Async components', function () { it('normal', function (done) { var vm = new Vue({ el: el, - template: '
', + template: '', components: { test: function (resolve) { setTimeout(function () { @@ -40,7 +40,7 @@ describe('Async components', function () { it('dynamic', function (done) { var vm = new Vue({ el: el, - template: '
', + template: '', data: { view: 'a' }, @@ -84,7 +84,7 @@ describe('Async components', function () { it('invalidate pending on dynamic switch', function (done) { var vm = new Vue({ el: el, - template: '
', + template: '', data: { view: 'a' }, @@ -124,12 +124,12 @@ describe('Async components', function () { it('invalidate pending on teardown', function (done) { var vm = new Vue({ el: el, - template: '
', + template: '', data: { view: 'a' }, components: { - a: function (resolve) { + test: function (resolve) { setTimeout(function () { resolve({ template: 'A' @@ -157,10 +157,10 @@ describe('Async components', function () { var vm = new Vue({ el: el, template: - '
' + - '
', + '' + + '', components: { - a: factory + test: factory } }) function factory (resolve) { @@ -192,7 +192,7 @@ describe('Async components', function () { it('normal', function (done) { var vm = new Vue({ el: el, - template: '
', + template: '', data: { list: [1, 2, 3] }, @@ -216,7 +216,7 @@ describe('Async components', function () { it('only resolve once', function (done) { var vm = new Vue({ el: el, - template: '
', + template: '', data: { list: [1, 2, 3] }, @@ -250,7 +250,7 @@ describe('Async components', function () { it('invalidate on teardown', function (done) { var vm = new Vue({ el: el, - template: '
', + template: '', data: { list: [1, 2, 3] }, @@ -277,7 +277,7 @@ describe('Async components', function () { it('warn when used with dynamic v-repeat', function () { var vm = new Vue({ el: el, - template: '
', + template: '', data: { list: [1, 2, 3], c: 'test' diff --git a/test/unit/specs/compiler/compile_spec.js b/test/unit/specs/compiler/compile_spec.js index 4df3fc60f..9ea17566f 100644 --- a/test/unit/specs/compiler/compile_spec.js +++ b/test/unit/specs/compiler/compile_spec.js @@ -126,12 +126,10 @@ if (_.inBrowser) { } }) el.innerHTML = '
' - var def = Vue.options.directives.component - var descriptor = dirParser.parse('my-component')[0] var linker = compile(el, options) linker(vm, el) expect(vm._bindDir.calls.count()).toBe(1) - expect(vm._bindDir).toHaveBeenCalledWith('component', el.firstChild, descriptor, def, undefined) + expect(vm._bindDir.calls.argsFor(0)[0]).toBe('component') expect(_.warn).not.toHaveBeenCalled() }) @@ -244,32 +242,32 @@ if (_.inBrowser) { vm = new Vue({ el: el, template: - '
' + - '
' + + '' + + '' + '
{{$value}}
' + - '
' + - '
', + '' + + '', data: { list: [1,2] }, components: { - a: { template: '' }, - b: { template: '' } + testa: { template: '' }, + testb: { template: '' } } }) expect(el.innerHTML).toBe( - '
' + + '' + '
1
2
' + - '
' + - '
' + '' + + '' ) vm.list.push(3) _.nextTick(function () { expect(el.innerHTML).toBe( - '
' + + '' + '
1
2
3
' + - '
' + - '
' + '' + + '' ) done() }) @@ -281,12 +279,12 @@ if (_.inBrowser) { vm = new Vue({ el: el, template: - '
', + '', methods: { test: parentSpy }, components: { - a: { + test: { template: '
', replace: true, methods: { @@ -307,7 +305,7 @@ if (_.inBrowser) { var vm = new Vue({ el: el, template: - '
{{test}}
', + '{{test}}', data: { test: 'parent' }, @@ -330,7 +328,7 @@ if (_.inBrowser) { el: el, template: '
' + - '
{{test}}
' + + '{{test}}' + '
', data: { test: 'parent', diff --git a/test/unit/specs/directives/component_spec.js b/test/unit/specs/directives/component_spec.js index a7847c4fb..fc2e576f7 100644 --- a/test/unit/specs/directives/component_spec.js +++ b/test/unit/specs/directives/component_spec.js @@ -18,7 +18,7 @@ if (_.inBrowser) { it('static', function () { var vm = new Vue({ el: el, - template: '
', + template: '', components: { test: { data: function () { @@ -28,13 +28,13 @@ if (_.inBrowser) { } } }) - expect(el.innerHTML).toBe('
123
') + expect(el.innerHTML).toBe('123') }) it('replace', function () { var vm = new Vue({ el: el, - template: '
', + template: '', components: { test: { replace: true, @@ -51,7 +51,7 @@ if (_.inBrowser) { it('inline-template', function () { var vm = new Vue({ el: el, - template: '
{{a}}
', + template: '{{a}}', data: { a: 'parent' }, @@ -64,13 +64,13 @@ if (_.inBrowser) { } } }) - expect(el.innerHTML).toBe('
child
') + expect(el.innerHTML).toBe('child') }) it('block replace', function () { var vm = new Vue({ el: el, - template: '
', + template: '', components: { test: { replace: true, @@ -87,19 +87,21 @@ if (_.inBrowser) { it('dynamic', function (done) { var vm = new Vue({ el: el, - template: '
', + template: '', data: { view: 'a' }, components: { a: { - template: 'AAA', + template: '
AAA
', + replace: true, data: function () { return { view: 'a' } } }, b: { - template: 'BBB', + template: '
BBB
', + replace: true, data: function () { return { view: 'b' } } @@ -123,18 +125,20 @@ if (_.inBrowser) { var spyB = jasmine.createSpy() var vm = new Vue({ el: el, - template: '
', + template: '', data: { view: 'a' }, components: { a: { created: spyA, - template: 'AAA' + template: '
AAA
', + replace: true }, b: { created: spyB, - template: 'BBB' + template: '
BBB
', + replace: true } } }) @@ -169,7 +173,7 @@ if (_.inBrowser) { ok: false, message: 'hello' }, - template: '
{{message}}
', + template: '{{message}}', components: { test: { template: '
{{message}}
', @@ -200,7 +204,7 @@ if (_.inBrowser) { ok: false, message: 'hello' }, - template: '
{{message}}
', + template: '{{message}}', components: { test: { template: ' {{message}}', @@ -230,10 +234,11 @@ if (_.inBrowser) { data: { list: [{a:1}, {a:2}] }, - template: '
    ', + template: '', components: { test: { - template: '
  • {{a}}
  • ', + template: '
    • {{a}}
    ', + replace: true, props: ['collection'] } } @@ -247,7 +252,7 @@ if (_.inBrowser) { data: { view: 'a' }, - template: '
    ', + template: '', components: { a: { template: 'AAA' @@ -277,7 +282,7 @@ if (_.inBrowser) { data: { view: 'a' }, - template: '
    ', + template: '', components: { a: { template: 'AAA' }, b: { template: 'BBB' } @@ -317,7 +322,7 @@ if (_.inBrowser) { data: { view: 'a' }, - template: '
    ', + template: '', components: { a: { template: 'AAA' }, b: { template: 'BBB' } @@ -351,7 +356,7 @@ if (_.inBrowser) { it('teardown', function (done) { var vm = new Vue({ el: el, - template: '
    ', + template: '', data: { view: 'test' }, @@ -375,7 +380,7 @@ if (_.inBrowser) { }) it('already mounted warn', function () { - el.setAttribute('v-component', 'test') + el.setAttribute('v-_component', 'test') var vm = new Vue({ el: el }) diff --git a/test/unit/specs/directives/events_spec.js b/test/unit/specs/directives/events_spec.js index 67f4ea43e..1caa95019 100644 --- a/test/unit/specs/directives/events_spec.js +++ b/test/unit/specs/directives/events_spec.js @@ -14,7 +14,7 @@ if (_.inBrowser) { var spy = jasmine.createSpy('v-events') new Vue({ el: el, - template: '
    ', + template: '', methods: { test: spy }, @@ -43,7 +43,7 @@ if (_.inBrowser) { var spy = jasmine.createSpy('v-events') new Vue({ el: el, - template: '
    ', + template: '', methods: { test: spy }, @@ -67,7 +67,7 @@ if (_.inBrowser) { var vm = new Vue({ el: el, data: { test: 123 }, - template: '
    ', + template: '', components: { test: {} } @@ -79,7 +79,7 @@ if (_.inBrowser) { var vm = new Vue({ el: el, data: {a:1}, - template: '
    ', + template: '', components: { test: { compiled: function () { @@ -104,7 +104,7 @@ if (_.inBrowser) { a++ } }, - template: '
    ', + template: '', components: { test: { compiled: function () { diff --git a/test/unit/specs/directives/if_spec.js b/test/unit/specs/directives/if_spec.js index a3f75a907..6ecb6c56a 100644 --- a/test/unit/specs/directives/if_spec.js +++ b/test/unit/specs/directives/if_spec.js @@ -18,7 +18,7 @@ if (_.inBrowser) { var vm = new Vue({ el: el, data: { test: false, a: 'A' }, - template: '
    ', + template: '
    ', components: { test: { inherit: true, @@ -31,7 +31,7 @@ if (_.inBrowser) { expect(vm._children.length).toBe(0) vm.test = true _.nextTick(function () { - expect(el.innerHTML).toBe(wrap('
    A
    ')) + expect(el.innerHTML).toBe(wrap('
    A
    ')) expect(vm._children.length).toBe(1) vm.test = false _.nextTick(function () { @@ -39,7 +39,7 @@ if (_.inBrowser) { expect(vm._children.length).toBe(0) vm.test = true _.nextTick(function () { - expect(el.innerHTML).toBe(wrap('
    A
    ')) + expect(el.innerHTML).toBe(wrap('
    A
    ')) expect(vm._children.length).toBe(1) var child = vm._children[0] vm.$destroy() @@ -76,7 +76,7 @@ if (_.inBrowser) { var vm = new Vue({ el: el, data: { ok: false }, - template: '
    ', + template: '', components: { test: { data: function () { @@ -94,7 +94,7 @@ if (_.inBrowser) { expect(vm._children.length).toBe(0) vm.ok = true _.nextTick(function () { - expect(el.innerHTML).toBe(wrap('
    123
    ')) + expect(el.innerHTML).toBe(wrap('123')) expect(vm._children.length).toBe(1) expect(attachSpy).toHaveBeenCalled() expect(readySpy).toHaveBeenCalled() @@ -116,7 +116,7 @@ if (_.inBrowser) { ok: false, view: 'a' }, - template: '
    ', + template: '', components: { a: { template: 'AAA' @@ -131,12 +131,12 @@ if (_.inBrowser) { // toggle if with lazy instantiation vm.ok = true _.nextTick(function () { - expect(el.innerHTML).toBe(wrap('
    AAA
    ')) + expect(el.innerHTML).toBe(wrap('AAA')) expect(vm._children.length).toBe(1) // switch view when if=true vm.view = 'b' _.nextTick(function () { - expect(el.innerHTML).toBe(wrap('
    BBB
    ')) + expect(el.innerHTML).toBe(wrap('BBB')) expect(vm._children.length).toBe(1) // toggle if when already instantiated vm.ok = false @@ -147,7 +147,7 @@ if (_.inBrowser) { vm.view = 'a' vm.ok = true _.nextTick(function () { - expect(el.innerHTML).toBe(wrap('
    AAA
    ')) + expect(el.innerHTML).toBe(wrap('AAA')) expect(vm._children.length).toBe(1) done() }) @@ -187,7 +187,7 @@ if (_.inBrowser) { a: 1, show: true }, - template: '
    {{a}}
    ', + template: '{{a}}', components: { test: { props: ['show'], @@ -219,7 +219,7 @@ if (_.inBrowser) { var vm = new Vue({ el: el, data: { show: true }, - template: '
    ', + template: '', components: { outer: { template: '
    ' @@ -251,11 +251,11 @@ if (_.inBrowser) { list: [{a:0}] }, template: - '
    ' + + '' + '
    ' + // an extra layer to test components deep inside the tree - '
    ' + + '' + '
    ' + - '
    ', + '', components: { outer: { template: @@ -264,7 +264,7 @@ if (_.inBrowser) { '
    ' + // this is to test that compnents that are not in the if block // should not fire attach/detach when v-if toggles - '
    ' + '' }, transcluded: { template: '{{a}}', @@ -300,16 +300,16 @@ if (_.inBrowser) { var showBlock = vm.show ? '
    ' + vm.list.map(function (o) { - return '
    ' + o.a + '
    ' + return '' + o.a + '' }).join('') + '' + '
    ' : '' - var markup = '
    ' + + var markup = '' + '' + showBlock + '' + - '
    ' + - '
    ' + '' + + '' expect(el.innerHTML).toBe(markup) } }) diff --git a/test/unit/specs/directives/prop_spec.js b/test/unit/specs/directives/prop_spec.js index b1d75c8b9..8e86b24a5 100644 --- a/test/unit/specs/directives/prop_spec.js +++ b/test/unit/specs/directives/prop_spec.js @@ -19,7 +19,7 @@ if (_.inBrowser) { a: 'A' } }, - template: '
    ', + template: '', components: { test: { props: ['testt', 'bb'], @@ -64,7 +64,7 @@ if (_.inBrowser) { data: { b: 'B' }, - template: '
    ', + template: '', components: { test: { props: ['bb'], @@ -88,7 +88,7 @@ if (_.inBrowser) { it('block instance with replace:true', function () { var vm = new Vue({ el: el, - template: '
    ', + template: '', data: { a: 'AAA', d: 'DDD' diff --git a/test/unit/specs/directives/ref_spec.js b/test/unit/specs/directives/ref_spec.js index ca1bfa716..ddbce2f92 100644 --- a/test/unit/specs/directives/ref_spec.js +++ b/test/unit/specs/directives/ref_spec.js @@ -23,7 +23,7 @@ if (_.inBrowser) { var vm = new Vue({ el: el, components: components, - template: '
    ' + template: '' }) expect(vm.$.test).toBeTruthy() expect(vm.$.test.$options.id).toBe('test') @@ -34,7 +34,7 @@ if (_.inBrowser) { el: el, components: components, data: { test: 'test' }, - template: '
    ' + template: '' }) expect(vm.$.test.$options.id).toBe('test') vm.test = 'test2' @@ -52,7 +52,7 @@ if (_.inBrowser) { var vm = new Vue({ el: el, data: { view: 'test1' }, - template: '
    ', + template: '', components: { test1: { id: 'test1', @@ -97,7 +97,7 @@ if (_.inBrowser) { it('nested v-repeat', function () { var vm = new Vue({ el: el, - template: '
    ', + template: '', components: { c1: { template: '
    ' diff --git a/test/unit/specs/directives/repeat_spec.js b/test/unit/specs/directives/repeat_spec.js index 9dc82f75e..d2ed3057f 100644 --- a/test/unit/specs/directives/repeat_spec.js +++ b/test/unit/specs/directives/repeat_spec.js @@ -138,13 +138,13 @@ if (_.inBrowser) { expect(el.innerHTML).toBe('
    aaa
    ') }) - it('v-component', function (done) { + it('component', function (done) { var vm = new Vue({ el: el, data: { items: [{a:1}, {a:2}] }, - template: '

    ', + template: '', components: { test: { template: '
    {{$index}} {{a}}
    ', @@ -155,16 +155,16 @@ if (_.inBrowser) { assertMutations(vm, el, done) }) - it('v-component with inline-template', function (done) { + it('component with inline-template', function (done) { var vm = new Vue({ el: el, data: { items: [{a:1}, {a:2}] }, template: - '
    ' + + '' + '{{$index}} {{a}}' + - '
    ', + '', components: { test: {} } @@ -172,30 +172,13 @@ if (_.inBrowser) { assertMutations(vm, el, done) }) - it('v-component with inline-template on