Browse Source

improve implicit fragment instance handling (fix #1084)

Evan You 10 years ago
parent
commit
99f3774852

+ 8 - 4
src/compiler/transclude.js

@@ -71,17 +71,21 @@ function transcludeTemplate (el, options) {
           'should probably use `replace: false` here.'
         )
       }
+      // there are many cases where the instance must
+      // become a fragment instance: basically anything that
+      // can create more than 1 root nodes.
       if (
         // multi-children template
         frag.childNodes.length > 1 ||
         // non-element template
         replacer.nodeType !== 1 ||
-        // when root node is <component>, is an element
-        // directive, or has v-repeat, the instance could
-        // end up having multiple top-level nodes, thus
-        // becoming a block instance.
+        // single nested component
         tag === 'component' ||
+        _.resolveAsset(options, 'components', tag) ||
+        replacer.hasAttribute(config.prefix + 'component') ||
+        // element directive
         _.resolveAsset(options, 'elementDirectives', tag) ||
+        // repeat block
         replacer.hasAttribute(config.prefix + 'repeat')
       ) {
         return frag

+ 2 - 4
src/directives/component.js

@@ -51,10 +51,8 @@ module.exports = {
       }
     } else {
       process.env.NODE_ENV !== 'production' && _.warn(
-        'Do not create a component that only contains ' +
-        'a single other component - they will be mounted to ' +
-        'the same element and cause conflict. Wrap it with ' +
-        'an outer element.'
+        'cannot mount component "' + this.expression + '" ' +
+        'on already mounted element: ' + this.el
       )
     }
   },

+ 43 - 1
test/unit/specs/compiler/transclude_spec.js

@@ -42,7 +42,49 @@ if (_.inBrowser) {
       expect(res.innerHTML).toBe('{{hi}}')
     })
 
-    it('block instance', function () {
+    it('template replace -> fragment instance', function () {
+      var res
+      options.replace = true
+
+      // multiple root
+      options.template = '<div></div><div></div>'
+      res = transclude(el, options)
+      expect(res instanceof DocumentFragment).toBe(true)
+
+      // non-element
+      options.template = '{{hi}}'
+      res = transclude(el, options)
+      expect(res instanceof DocumentFragment).toBe(true)
+
+      // single component: <component>
+      options.template = '<component is="{{hi}}"></component>'
+      res = transclude(el, options)
+      expect(res instanceof DocumentFragment).toBe(true)
+
+      // single component: custom element
+      options.template = '<test></test>'
+      options.components = { test: {}}
+      res = transclude(el, options)
+      expect(res instanceof DocumentFragment).toBe(true)
+
+      // single component: v-component
+      options.template = '<div v-component="test"></div>'
+      res = transclude(el, options)
+      expect(res instanceof DocumentFragment).toBe(true)
+
+      // element directive
+      options.template = '<el-dir></el-dir>'
+      options.elementDirectives = { 'el-dir': {}}
+      res = transclude(el, options)
+      expect(res instanceof DocumentFragment).toBe(true)
+
+      // v-repeat
+      options.template = '<div v-repeat="list"></div>'
+      res = transclude(el, options)
+      expect(res instanceof DocumentFragment).toBe(true)
+    })
+
+    it('direct fragment instance', function () {
       var frag = document.createDocumentFragment()
       frag.appendChild(el)
       var res = transclude(frag, options)

+ 1 - 1
test/unit/specs/directives/component_spec.js

@@ -420,7 +420,7 @@ if (_.inBrowser) {
       new Vue({
         el: el
       })
-      expect(hasWarned(_, 'Do not create a component that only contains a single other component')).toBe(true)
+      expect(hasWarned(_, 'cannot mount component "test" on already mounted element')).toBe(true)
     })
 
   })