Răsfoiți Sursa

apply scopeId to slot contents too

Evan You 10 ani în urmă
părinte
comite
cfe745e9d2

+ 3 - 10
src/core/instance/render.js

@@ -59,10 +59,10 @@ export function renderMixin (Vue: Class<Component>) {
       _parentVnode
     } = vm.$options
 
-    if (staticRenderFns && !vm._staticTrees) {
-      // render static sub-trees for once on initial render
-      renderStaticTrees(vm, staticRenderFns)
+    if (staticRenderFns && !this._staticTrees) {
+      this._staticTrees = []
     }
+
     // resolve slots. becaues slots are rendered in parent scope,
     // we set the activeInstance to parent.
     if (_renderChildren) {
@@ -154,13 +154,6 @@ export function renderMixin (Vue: Class<Component>) {
   }
 }
 
-function renderStaticTrees (vm: Component, fns: Array<Function>) {
-  const trees = vm._staticTrees = new Array(fns.length)
-  for (let i = 0; i < fns.length; i++) {
-    trees[i] = fns[i].call(vm._renderProxy)
-  }
-}
-
 function resolveSlots (
   vm: Component,
   renderChildren: Array<any> | () => Array<any> | string

+ 2 - 1
src/core/vdom/create-component.js

@@ -13,6 +13,7 @@ export function createComponent (
   data?: VNodeData,
   parent: Component,
   context: Component,
+  host: ?Component,
   tag?: string
 ): VNode | void {
   if (!Ctor) {
@@ -66,7 +67,7 @@ export function createComponent (
   const name = Ctor.options.name || tag
   const vnode = new VNode(
     `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
-    data, undefined, undefined, undefined, undefined, context,
+    data, undefined, undefined, undefined, undefined, context, host,
     { Ctor, propsData, listeners, parent, tag, children: undefined }
     // children to be set later by renderElementWithChildren,
     // but before the init hook

+ 10 - 5
src/core/vdom/create-element.js

@@ -37,6 +37,7 @@ export function renderElement (
   // make sure to expose real self instead of proxy
   const context: Component = this._self
   const parent: ?Component = renderState.activeInstance
+  const host = context !== parent ? parent : undefined
   if (!parent) {
     process.env.NODE_ENV !== 'production' && warn(
       'createElement cannot be called outside of component ' +
@@ -53,10 +54,10 @@ export function renderElement (
     if (config.isReservedTag(tag)) {
       return new VNode(
         tag, data, undefined,
-        undefined, undefined, namespace, context
+        undefined, undefined, namespace, context, host
       )
     } else if ((Ctor = resolveAsset(context.$options, 'components', tag))) {
-      return createComponent(Ctor, data, parent, context, tag)
+      return createComponent(Ctor, data, parent, context, host, tag)
     } else {
       if (process.env.NODE_ENV !== 'production') {
         if (!namespace && config.isUnknownElement(tag)) {
@@ -69,11 +70,11 @@ export function renderElement (
       }
       return new VNode(
         tag, data, undefined,
-        undefined, undefined, namespace, context
+        undefined, undefined, namespace, context, host
       )
     }
   } else {
-    return createComponent(tag, data, parent, context)
+    return createComponent(tag, data, parent, context, host)
   }
 }
 
@@ -82,5 +83,9 @@ export function renderText (str?: string): string {
 }
 
 export function renderStatic (index?: number): Object | void {
-  return this._staticTrees[index]
+  return this._staticTrees[index] || (
+    this._staticTrees[index] = this.$options.staticRenderFns[index].call(
+      this._renderProxy
+    )
+  )
 }

+ 3 - 0
src/core/vdom/patch.js

@@ -121,6 +121,9 @@ export function createPatchFunction (backend) {
   // of going through the normal attribute patching process.
   function setScope (vnode) {
     let i
+    if (isDef(i = vnode.host) && isDef(i = i.$options._scopeId)) {
+      nodeOps.setAttribute(vnode.elm, i, '')
+    }
     if (isDef(i = vnode.context) && isDef(i = i.$options._scopeId)) {
       nodeOps.setAttribute(vnode.elm, i, '')
     }

+ 4 - 0
src/core/vdom/vnode.js

@@ -8,6 +8,7 @@ export default class VNode {
   elm: Node | void;
   ns: string | void;
   context: Component | void;
+  host: ?Component;
   key: string | number | void;
   componentOptions: VNodeComponentOptions | void;
   child: Component | void;
@@ -22,6 +23,7 @@ export default class VNode {
     elm?: Node,
     ns?: string,
     context?: Component,
+    host?: ?Component,
     componentOptions?: VNodeComponentOptions
   ) {
     this.tag = tag
@@ -31,10 +33,12 @@ export default class VNode {
     this.elm = elm
     this.ns = ns
     this.context = context
+    this.host = host
     this.key = data && data.key
     this.componentOptions = componentOptions
     this.child = undefined
     this.parent = undefined
+    this.raw = false
     // apply construct hook.
     // this is applied during render, before patch happens.
     // unlike other hooks, this is applied on both client and server.

+ 5 - 2
src/server/render.js

@@ -93,9 +93,12 @@ export function createRenderFunction (
       }
     }
     // attach scoped CSS ID
+    let scopeId
+    if (node.host && (scopeId = node.host.$options._scopeId)) {
+      markup += ` ${scopeId}`
+    }
     while (node) {
-      const scopeId = node.context.$options._scopeId
-      if (scopeId) {
+      if ((scopeId = node.context.$options._scopeId)) {
         markup += ` ${scopeId}`
       }
       node = node.parent

+ 23 - 0
test/ssr/ssr-string.spec.js

@@ -439,6 +439,29 @@ describe('SSR: renderToString', () => {
     })
   })
 
+  it('_scopeId on slot content', done => {
+    renderVmWithOptions({
+      _scopeId: '_v-parent',
+      template: '<div><child><p>foo</p></child></div>',
+      components: {
+        child: {
+          _scopeId: '_v-child',
+          render () {
+            const h = this.$createElement
+            return h('div', null, this.$slots.default)
+          }
+        }
+      }
+    }, result => {
+      expect(result).toContain(
+        '<div server-rendered="true" _v-parent>' +
+          '<div _v-child _v-parent><p _v-child _v-parent>foo</p></div>' +
+        '</div>'
+      )
+      done()
+    })
+  })
+
   it('should catch error', done => {
     renderToString(new Vue({
       render () {

+ 43 - 0
test/unit/features/options/_scopeId.spec.js

@@ -0,0 +1,43 @@
+import Vue from 'vue'
+
+describe('Options _scopeId', () => {
+  it('should add scopeId attributes', () => {
+    const vm = new Vue({
+      _scopeId: 'foo',
+      template: '<div><p><span></span></p></div>'
+    }).$mount()
+    expect(vm.$el.hasAttribute('foo')).toBe(true)
+    expect(vm.$el.children[0].hasAttribute('foo')).toBe(true)
+    expect(vm.$el.children[0].children[0].hasAttribute('foo')).toBe(true)
+  })
+
+  it('should add scopedId attributes from both parent and child on child root', () => {
+    const vm = new Vue({
+      _scopeId: 'foo',
+      template: '<div><child></child></div>',
+      components: {
+        child: {
+          _scopeId: 'bar',
+          template: '<div></div>'
+        }
+      }
+    }).$mount()
+    expect(vm.$el.children[0].hasAttribute('foo')).toBe(true)
+    expect(vm.$el.children[0].hasAttribute('bar')).toBe(true)
+  })
+
+  it('should add scopedId attributes from both parent and child on slot contents', () => {
+    const vm = new Vue({
+      _scopeId: 'foo',
+      template: '<div><child><p>hi</p></child></div>',
+      components: {
+        child: {
+          _scopeId: 'bar',
+          template: '<div><slot></slot></div>'
+        }
+      }
+    }).$mount()
+    expect(vm.$el.children[0].children[0].hasAttribute('foo')).toBe(true)
+    expect(vm.$el.children[0].children[0].hasAttribute('bar')).toBe(true)
+  })
+})

+ 2 - 0
test/unit/modules/vdom/patch/element.spec.js

@@ -35,7 +35,9 @@ describe('element', () => {
   it('should create element with scope attribute', () => {
     const vnode = new VNode('div')
     vnode.context = new Vue({ _scopeId: 'foo' })
+    vnode.host = new Vue({ _scopeId: 'bar' })
     const elm = patch(null, vnode)
     expect(elm.hasAttribute('foo')).toBe(true)
+    expect(elm.hasAttribute('bar')).toBe(true)
   })
 })