Просмотр исходного кода

refactor component scope compilation

Evan You 11 лет назад
Родитель
Сommit
25d5dea9b9
7 измененных файлов с 167 добавлено и 143 удалено
  1. 3 76
      src/api/lifecycle.js
  2. 9 26
      src/compiler/transclude.js
  3. 1 3
      src/directive.js
  4. 3 21
      src/directives/component.js
  5. 14 9
      src/directives/repeat.js
  6. 116 8
      src/instance/compile.js
  7. 21 0
      src/util/dom.js

+ 3 - 76
src/api/lifecycle.js

@@ -51,85 +51,12 @@ function ready () {
 }
 
 /**
- * Teardown an instance, unobserves the data, unbind all the
- * directives, turn off all the event listeners, etc.
- *
- * @param {Boolean} remove - whether to remove the DOM node.
- * @param {Boolean} deferCleanup - if true, defer cleanup to
- *                                 be called later
- * @public
+ * Teardown the instance, simply delegate to the internal
+ * _destroy.
  */
 
 exports.$destroy = function (remove, deferCleanup) {
-  if (this._isBeingDestroyed) {
-    return
-  }
-  this._callHook('beforeDestroy')
-  this._isBeingDestroyed = true
-  var i
-  // remove self from parent. only necessary
-  // if parent is not being destroyed as well.
-  var parent = this.$parent
-  if (parent && !parent._isBeingDestroyed) {
-    i = parent._children.indexOf(this)
-    parent._children.splice(i, 1)
-  }
-  // destroy all children.
-  if (this._children) {
-    i = this._children.length
-    while (i--) {
-      this._children[i].$destroy()
-    }
-  }
-  // teardown all directives. this also tearsdown all
-  // directive-owned watchers. intentionally check for
-  // directives array length on every loop since directives
-  // that manages partial compilation can splice ones out
-  for (i = 0; i < this._directives.length; i++) {
-    this._directives[i]._teardown()
-  }
-  // teardown all user watchers.
-  for (i in this._userWatchers) {
-    this._userWatchers[i].teardown()
-  }
-  // remove reference to self on $el
-  if (this.$el) {
-    this.$el.__vue__ = null
-  }
-  // remove DOM element
-  var self = this
-  if (remove && this.$el) {
-    this.$remove(function () {
-      self._cleanup()
-    })
-  } else if (!deferCleanup) {
-    this._cleanup()
-  }
-}
-
-/**
- * Clean up to ensure garbage collection.
- * This is called after the leave transition if there
- * is any.
- */
-
-exports._cleanup = function () {
-  // remove reference from data ob
-  this._data.__ob__.removeVm(this)
-  this._data =
-  this._watchers =
-  this._userWatchers =
-  this._watcherList =
-  this.$el =
-  this.$parent =
-  this.$root =
-  this._children =
-  this._directives = null
-  // call the last hook...
-  this._isDestroyed = true
-  this._callHook('destroyed')
-  // turn off all instance listeners.
-  this.$off()
+  this._destroy(remove, deferCleanup)
 }
 
 /**

+ 9 - 26
src/compiler/transclude.js

@@ -44,43 +44,25 @@ function transcludeTemplate (el, options) {
   if (!frag) {
     _.warn('Invalid template option: ' + template)
   } else {
-    collectRawContent(el)
+    var rawContent = options._content || _.extractContent(el)
     if (options.replace) {
       if (frag.childNodes.length > 1) {
-        transcludeContent(frag)
+        transcludeContent(frag, rawContent)
         return frag
       } else {
         var replacer = frag.firstChild
         _.copyAttributes(el, replacer)
-        transcludeContent(replacer)
+        transcludeContent(replacer, rawContent)
         return replacer
       }
     } else {
       el.appendChild(frag)
-      transcludeContent(el)
+      transcludeContent(el, rawContent)
       return el
     }
   }
 }
 
-/**
- * Collect raw content inside $el before they are
- * replaced by template content.
- */
-
-var rawContent
-function collectRawContent (el) {
-  var child
-  rawContent = null
-  if (el.hasChildNodes()) {
-    rawContent = document.createElement('div')
-    /* jshint boss:true */
-    while (child = el.firstChild) {
-      rawContent.appendChild(child)
-    }
-  }
-}
-
 /**
  * Resolve <content> insertion points mimicking the behavior
  * of the Shadow DOM spec:
@@ -88,9 +70,10 @@ function collectRawContent (el) {
  *   http://w3c.github.io/webcomponents/spec/shadow/#insertion-points
  *
  * @param {Element|DocumentFragment} el
+ * @param {Element} raw
  */
 
-function transcludeContent (el) {
+function transcludeContent (el, raw) {
   var outlets = getOutlets(el)
   var i = outlets.length
   if (!i) return
@@ -99,10 +82,10 @@ function transcludeContent (el) {
   // for each outlet.
   while (i--) {
     outlet = outlets[i]
-    if (rawContent) {
+    if (raw) {
       select = outlet.getAttribute('select')
       if (select) {  // select content
-        selected = rawContent.querySelectorAll(select)
+        selected = raw.querySelectorAll(select)
         outlet.content = _.toArray(
           selected.length
             ? selected
@@ -124,7 +107,7 @@ function transcludeContent (el) {
   }
   // finally insert the main content
   if (main) {
-    insertContentAt(main, _.toArray(rawContent.childNodes))
+    insertContentAt(main, _.toArray(raw.childNodes))
   }
 }
 

+ 1 - 3
src/directive.js

@@ -18,11 +18,10 @@ var expParser = require('./parsers/expression')
  *                 - {String} [arg]
  *                 - {Array<Object>} [filters]
  * @param {Object} def - directive definition object
- * @param {Function} [linker] - pre-compiled linker function
  * @constructor
  */
 
-function Directive (name, el, vm, descriptor, def, linker) {
+function Directive (name, el, vm, descriptor, def) {
   // public
   this.name = name
   this.el = el
@@ -33,7 +32,6 @@ function Directive (name, el, vm, descriptor, def, linker) {
   this.arg = descriptor.arg
   this.filters = _.resolveFilters(vm, descriptor.filters)
   // private
-  this._linker = linker
   this._locked = false
   this._bound = false
   // init

+ 3 - 21
src/directives/component.js

@@ -1,5 +1,4 @@
 var _ = require('../util')
-var compile = require('../compiler/compile')
 var templateParser = require('../parsers/template')
 
 module.exports = {
@@ -30,12 +29,6 @@ module.exports = {
       if (this.keepAlive) {
         this.cache = {}
       }
-      // 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)
@@ -83,14 +76,10 @@ module.exports = {
     var vm = this.vm
     var el = templateParser.clone(this.el)
     if (this.Ctor) {
-      var parentUnlinkFn
-      if (this.parentLinkFn) {
-        parentUnlinkFn = this.parentLinkFn(vm, el)
-      }
       var child = vm.$addChild({
-        el: el
+        el: el,
+        _asComponent: true
       }, this.Ctor)
-      child._parentUnlinkFn = parentUnlinkFn
       if (this.keepAlive) {
         this.cache[this.ctorId] = child
       }
@@ -108,9 +97,6 @@ module.exports = {
     if (!child || this.keepAlive) {
       return
     }
-    if (child._parentUnlinkFn) {
-      child._parentUnlinkFn()
-    }
     // the sole purpose of `deferCleanup` is so that we can
     // "deactivate" the vm right now and perform DOM removal
     // later.
@@ -201,11 +187,7 @@ module.exports = {
     // destroy all keep-alive cached instances
     if (this.cache) {
       for (var key in this.cache) {
-        var child = this.cache[key]
-        if (child._parentUnlinkFn) {
-          child._parentUnlinkFn()
-        }
-        child.$destroy()
+        this.cache[key].$destroy()
       }
       this.cache = null
     }

+ 14 - 9
src/directives/repeat.js

@@ -92,22 +92,26 @@ module.exports = {
       // important: transclude with no options, just
       // to ensure block start and block end
       this.template = transclude(this.template)
-      this._linker = compile(this.template, options)
+      this._linkFn = compile(this.template, options)
     } else {
+      this._asComponent = true
       var tokens = textParser.parse(id)
       if (!tokens) { // static component
         var Ctor = this.Ctor = options.components[id]
         _.assertAsset(Ctor, 'component', id)
-        if (Ctor) {
+        // If there's no parent scope directives and no
+        // content to be transcluded, we can optimize the
+        // rendering by pre-transcluding + compiling here
+        // and provide a link function to every instance.
+        if (!this.el.hasChildNodes() &&
+            !this.el.hasAttributes()) {
           // merge an empty object with owner vm as parent
           // so child vms can access parent assets.
-          var merged = mergeOptions(
-            Ctor.options,
-            {},
-            { $parent: this.vm }
-          )
+          var merged = mergeOptions(Ctor.options, {}, {
+            $parent: this.vm
+          })
           this.template = transclude(this.template, merged)
-          this._linker = compile(this.template, merged)
+          this._linkFn = compile(this.template, merged)
         }
       } else {
         // to be resolved later
@@ -274,7 +278,8 @@ module.exports = {
     var Ctor = this.Ctor || this.resolveCtor(data, meta)
     var vm = this.vm.$addChild({
       el: templateParser.clone(this.template),
-      _linker: this._linker,
+      _asComponent: this._asComponent,
+      _linkFn: this._linkFn,
       _meta: meta,
       data: data,
       inherit: this.inherit

+ 116 - 8
src/instance/compile.js

@@ -18,15 +18,36 @@ var transclude = require('../compiler/transclude')
 
 exports._compile = function (el) {
   var options = this.$options
-  if (options._linker) {
+  if (options._linkFn) {
     this._initElement(el)
-    options._linker(this, el)
+    options._linkFn(this, el)
   } else {
     var raw = el
-    el = transclude(el, options)
+    if (options._asComponent) {
+      // separate container element and content
+      var content = options._content = _.extractContent(raw)
+      // create two separate linekrs for container and content
+      var containerLinkFn =
+        compile(raw, options, true, true)
+      if (content) {
+        var contentLinkFn =
+          compile(content, options, true, true)
+        // call content linker now, before transclusion
+        this._contentUnlinkFn =
+          contentLinkFn(options._parent, content)
+      }
+      // tranclude, this possibly replaces original
+      el = transclude(el, options)
+      // now call the container linker on the resolved el
+      this._containerUnlinkFn =
+        containerLinkFn(options._parent, el)
+    } else {
+      // simply transclude
+      el = transclude(el, options)
+    }
     this._initElement(el)
-    var linker = compile(el, options)
-    linker(this, el)
+    var linkFn = compile(el, options)
+    linkFn(this, el)
     if (options.replace) {
       _.replace(raw, el)
     }
@@ -61,11 +82,98 @@ exports._initElement = function (el) {
  * @param {Node} node   - target node
  * @param {Object} desc - parsed directive descriptor
  * @param {Object} def  - directive definition object
- * @param {Function} [linker] - pre-compiled linker fn
  */
 
-exports._bindDir = function (name, node, desc, def, linker) {
+exports._bindDir = function (name, node, desc, def) {
   this._directives.push(
-    new Directive(name, node, this, desc, def, linker)
+    new Directive(name, node, this, desc, def)
   )
+}
+
+/**
+ * Teardown an instance, unobserves the data, unbind all the
+ * directives, turn off all the event listeners, etc.
+ *
+ * @param {Boolean} remove - whether to remove the DOM node.
+ * @param {Boolean} deferCleanup - if true, defer cleanup to
+ *                                 be called later
+ */
+
+exports._destroy = function (remove, deferCleanup) {
+  if (this._isBeingDestroyed) {
+    return
+  }
+  this._callHook('beforeDestroy')
+  this._isBeingDestroyed = true
+  var i
+  // remove self from parent. only necessary
+  // if parent is not being destroyed as well.
+  var parent = this.$parent
+  if (parent && !parent._isBeingDestroyed) {
+    i = parent._children.indexOf(this)
+    parent._children.splice(i, 1)
+  }
+  // destroy all children.
+  if (this._children) {
+    i = this._children.length
+    while (i--) {
+      this._children[i].$destroy()
+    }
+  }
+  // teardown parent linkers
+  if (this._containerUnlinkFn) {
+    this._containerUnlinkFn()
+  }
+  if (this._contentUnlinkFn) {
+    this._contentUnlinkFn()
+  }
+  // teardown all directives. this also tearsdown all
+  // directive-owned watchers. intentionally check for
+  // directives array length on every loop since directives
+  // that manages partial compilation can splice ones out
+  for (i = 0; i < this._directives.length; i++) {
+    this._directives[i]._teardown()
+  }
+  // teardown all user watchers.
+  for (i in this._userWatchers) {
+    this._userWatchers[i].teardown()
+  }
+  // remove reference to self on $el
+  if (this.$el) {
+    this.$el.__vue__ = null
+  }
+  // remove DOM element
+  var self = this
+  if (remove && this.$el) {
+    this.$remove(function () {
+      self._cleanup()
+    })
+  } else if (!deferCleanup) {
+    this._cleanup()
+  }
+}
+
+/**
+ * Clean up to ensure garbage collection.
+ * This is called after the leave transition if there
+ * is any.
+ */
+
+exports._cleanup = function () {
+  // remove reference from data ob
+  this._data.__ob__.removeVm(this)
+  this._data =
+  this._watchers =
+  this._userWatchers =
+  this._watcherList =
+  this.$el =
+  this.$parent =
+  this.$root =
+  this._children =
+  this._directives = null
+  // call the last hook...
+  this._isDestroyed = true
+  this._callHook('destroyed')
+  // turn off all instance listeners.
+  this.$off()
 }

+ 21 - 0
src/util/dom.js

@@ -173,4 +173,25 @@ exports.removeClass = function (el, cls) {
     }
     el.setAttribute('class', cur.trim())
   }
+}
+
+/**
+ * Extract raw content inside an element into a temporary
+ * container div
+ *
+ * @param {Element} el
+ * @return {Element}
+ */
+
+exports.extractContent = function (el) {
+  var child
+  var rawContent
+  if (el.hasChildNodes()) {
+    rawContent = document.createElement('div')
+    /* jshint boss:true */
+    while (child = el.firstChild) {
+      rawContent.appendChild(child)
+    }
+  }
+  return rawContent
 }