Quellcode durchsuchen

compile component <content> in parent scope (ref: #502)

Evan You vor 11 Jahren
Ursprung
Commit
854ecd4fad

+ 26 - 26
src/compile/compile.js

@@ -10,20 +10,27 @@ var templateParser = require('../parse/template')
  * inside. This top level compile function should only be
  * called on instance root nodes.
  *
+ * When the `asParent` flag is true, this means we are doing
+ * a partial compile for a component's parent scope markup
+ * (See #502). This could **only** be triggered during
+ * compilation of `v-component`, and we need to skip v-with,
+ * v-ref & v-component in this situation.
+ *
  * @param {Element|DocumentFragment} el
  * @param {Object} options
  * @param {Boolean} partial
+ * @param {Boolean} asParent
  * @return {Function}
  */
 
-module.exports = function compile (el, options, partial) {
+module.exports = function compile (el, options, partial, asParent) {
   var params = !partial && options.paramAttributes
   var paramsLinkFn = params
     ? compileParamAttributes(el, params, options)
     : null
   var nodeLinkFn = el instanceof DocumentFragment
     ? null
-    : compileNode(el, options)
+    : compileNode(el, options, asParent)
   var childLinkFn =
     !(nodeLinkFn && nodeLinkFn.terminal) &&
     el.tagName !== 'SCRIPT' &&
@@ -74,13 +81,14 @@ module.exports = function compile (el, options, partial) {
  *
  * @param {Node} node
  * @param {Object} options
+ * @param {Boolean} asParent
  * @return {Function|undefined}
  */
 
-function compileNode (node, options) {
+function compileNode (node, options, asParent) {
   var type = node.nodeType
   if (type === 1 && node.tagName !== 'SCRIPT') {
-    return compileElement(node, options)
+    return compileElement(node, options, asParent)
   } else if (type === 3 && config.interpolate) {
     return compileTextNode(node, options)
   }
@@ -91,13 +99,14 @@ function compileNode (node, options) {
  *
  * @param {Element} el
  * @param {Object} options
+ * @param {Boolean} asParent
  * @return {Function|null}
  */
 
-function compileElement (el, options) {
+function compileElement (el, options, asParent) {
   var linkFn, tag, component
   // check custom element component, but only on non-root
-  if (!el.__vue__) {
+  if (!asParent && !el.__vue__) {
     tag = el.tagName.toLowerCase()
     component =
       tag.indexOf('-') > 0 &&
@@ -108,12 +117,14 @@ function compileElement (el, options) {
   }
   if (component || el.hasAttributes()) {
     // check terminal direcitves
-    linkFn = checkTerminalDirectives(el, options)
+    if (!asParent) {
+      linkFn = checkTerminalDirectives(el, options)
+    }
     // if not terminal, build normal link function
     if (!linkFn) {
-      var directives = collectDirectives(el, options)
-      linkFn = directives.length
-        ? makeDirectivesLinkFn(directives)
+      var dirs = collectDirectives(el, options, asParent)
+      linkFn = dirs.length
+        ? makeDirectivesLinkFn(dirs)
         : null
     }
   }
@@ -432,16 +443,6 @@ function checkTerminalDirectives (el, options) {
 function makeTeriminalLinkFn (el, dirName, value, options) {
   var descriptor = dirParser.parse(value)[0]
   var def = options.directives[dirName]
-  // special case: we need to collect directives found
-  // on a component root node, but defined in the parent
-  // template. These directives need to be compiled in
-  // the parent scope.
-  if (dirName === 'component') {
-    var dirs = collectDirectives(el, options, true)
-    el._parentLinker = dirs.length
-      ? makeDirectivesLinkFn(dirs)
-      : null
-  }
   var terminalLinkFn = function (vm, el) {
     vm._bindDir(dirName, el, descriptor, def)
   }
@@ -468,12 +469,11 @@ function collectDirectives (el, options, asParent) {
     attrName = attr.name
     if (attrName.indexOf(config.prefix) === 0) {
       dirName = attrName.slice(config.prefix.length)
-      if (asParent) {
-        if (dirName === 'with' || dirName === 'ref') {
-          continue
-        } else {
-          el.removeAttribute(attrName)
-        }
+      if (asParent &&
+          (dirName === 'with' ||
+           dirName === 'ref' ||
+           dirName === 'component')) {
+        continue
       }
       dirDef = options.directives[dirName]
       _.assertAsset(dirDef, 'directive', dirName)

+ 14 - 17
src/directives/component.js

@@ -1,4 +1,5 @@
 var _ = require('../util')
+var compile = require('../compile/compile')
 var templateParser = require('../parse/template')
 
 module.exports = {
@@ -22,8 +23,12 @@ module.exports = {
       _.replace(this.el, this.ref)
       // check keep-alive options
       this.checkKeepAlive()
-      // check parent directives
-      this.parentLinker = this.el._parentLinker
+      // compile parent scope content
+      this.parentLinkFn = compile(
+        this.el, this.vm.$options,
+        true, // partial
+        true  // asParent
+      )
       // if static, build right now.
       if (!this._isDynamicLiteral) {
         this.resolveCtor(this.expression)
@@ -81,18 +86,14 @@ module.exports = {
       }
     }
     var vm = this.vm
+    var el = templateParser.clone(this.el)
     if (this.Ctor && !this.childVM) {
+      if (this.parentLinkFn) {
+        this.parentUnlinkFn = this.parentLinkFn(vm, el)
+      }
       this.childVM = vm.$addChild({
-        el: templateParser.clone(this.el)
+        el: el
       }, this.Ctor)
-      if (this.parentLinker) {
-        var dirCount = vm._directives.length
-        var targetVM = this.childVM.$options.inherit
-          ? this.childVM
-          : vm
-        this.parentLinker(targetVM, this.childVM.$el)
-        this.parentDirs = vm._directives.slice(dirCount)
-      }
       if (this.keepAlive) {
         this.cache[this.ctorId] = this.childVM
       }
@@ -118,12 +119,8 @@ module.exports = {
       }
     } else {
       child.$destroy(remove)
-      var parentDirs = this.parentDirs
-      if (parentDirs) {
-        var i = parentDirs.length
-        while (i--) {
-          parentDirs[i]._teardown()
-        }
+      if (this.parentUnlinkFn) {
+        this.parentUnlinkFn()
       }
     }
     this.childVM = null

+ 11 - 1
test/unit/specs/compile/compile_spec.js

@@ -16,8 +16,9 @@ if (_.inBrowser) {
       directiveTeardown = jasmine.createSpy()
       vm = {
         _directives: [],
-        _bindDir: function () {
+        _bindDir: function (name) {
           this._directives.push({
+            name: name,
             _teardown: directiveTeardown
           })
         },
@@ -203,5 +204,14 @@ if (_.inBrowser) {
       expect(vm._bindDir.calls.count()).toBe(0)
     })
 
+    it('component parent scope compilation should skip v-ref, v-with & v-component', function () {
+      el.innerHTML = '<div v-component v-ref="test" v-with="test"></div>'
+      el = el.firstChild
+      var linker = compile(el, Vue.options, true, true)
+      linker(vm, el)
+      expect(vm._directives.length).toBe(0)
+      expect(el.attributes.length).toBe(3)
+    })
+
   })
 }

+ 48 - 4
test/unit/specs/directives/component_spec.js

@@ -134,19 +134,63 @@ if (_.inBrowser) {
       })
     })
 
-    it('should compile parent template directives in parent scope', function (done) {
+    it('should compile parent template directives & content in parent scope', function (done) {
       var vm = new Vue({
         el: el,
-        data: { ok: false },
-        template: '<div v-component="test" v-show="ok"></div>',
+        data: {
+          ok: false,
+          message: 'hello'
+        },
+        template: '<div v-component="test" v-show="ok">{{message}}</div>',
         components: {
-          test: {}
+          test: {
+            template: '<content></content> {{message}}',
+            data: function () {
+              return {
+                message: 'world'
+              }
+            }
+          }
         }
       })
       expect(el.firstChild.style.display).toBe('none')
+      expect(el.firstChild.textContent).toBe('hello world')
       vm.ok = true
+      vm.message = 'bye'
       _.nextTick(function () {
         expect(el.firstChild.style.display).toBe('')
+        expect(el.firstChild.textContent).toBe('bye world')
+        done()
+      })
+    })
+
+    it('parent content + v-if', function (done) {
+      var vm = new Vue({
+        el: el,
+        data: {
+          ok: false,
+          message: 'hello'
+        },
+        template: '<div v-component="test" v-if="ok">{{message}}</div>',
+        components: {
+          test: {
+            template: '<content></content> {{message}}',
+            data: function () {
+              return {
+                message: 'world'
+              }
+            }
+          }
+        }
+      })
+      expect(el.textContent).toBe('')
+      expect(vm._children).toBeNull()
+      expect(vm._directives.length).toBe(1) // v-if
+      vm.ok = true
+      _.nextTick(function () {
+        expect(vm._children.length).toBe(1)
+        expect(vm._directives.length).toBe(3) // v-if, v-component, v-text
+        expect(el.textContent).toBe('hello world')
         done()
       })
     })

+ 6 - 2
test/unit/specs/directives/ref_spec.js

@@ -50,8 +50,12 @@ if (_.inBrowser) {
     it('nested v-repeat', function () {
       var vm = new Vue({
         el: el,
-        template: '<div v-component="c1" v-ref="c1"><div v-repeat="2" v-ref="c2"></div></div>',
-        components: { c1: {} }
+        template: '<div v-component="c1" v-ref="c1"></div>',
+        components: {
+          c1: {
+            template: '<div v-repeat="2" v-ref="c2"></div>'
+          }
+        }
       })
       expect(vm.$.c1 instanceof Vue).toBe(true)
       expect(vm.$.c2).toBeUndefined()