Browse Source

Generate style on custom component in SSR (fix #4055) (#4076)

* fix #4055, generate style on custom component

* add test for custom component style

* add synthetic data for module processing
(´・ω・`) 9 years ago
parent
commit
240df147f1
3 changed files with 126 additions and 15 deletions
  1. 34 15
      src/platforms/web/server/modules/style.js
  2. 11 0
      src/server/render.js
  3. 81 0
      test/ssr/ssr-string.spec.js

+ 34 - 15
src/platforms/web/server/modules/style.js

@@ -2,24 +2,43 @@
 
 import { hyphenate, toObject } from 'shared/util'
 
-export default function renderStyle (node: VNodeWithData): ?string {
+function concatStyleString (former: string, latter: string) {
+  if (former === '' || latter === '' || former.charAt(former.length - 1) === ';') {
+    return former + latter
+  }
+  return former + ';' + latter
+}
+
+function generateStyleText (node) {
   const staticStyle = node.data.attrs && node.data.attrs.style
-  if (node.data.style || staticStyle) {
-    let styles = node.data.style
-    let res = ''
-    if (styles) {
-      if (typeof styles === 'string') {
-        res += styles
-      } else {
-        if (Array.isArray(styles)) {
-          styles = toObject(styles)
-        }
-        for (const key in styles) {
-          res += `${hyphenate(key)}:${styles[key]};`
-        }
-        res += staticStyle || ''
+  let styles = node.data.style
+  const parentStyle = node.parent ? generateStyleText(node.parent) : ''
+
+  if (!styles && !staticStyle) {
+    return parentStyle
+  }
+
+  let dynamicStyle = ''
+  if (styles) {
+    if (typeof styles === 'string') {
+      dynamicStyle += styles
+    } else {
+      if (Array.isArray(styles)) {
+        styles = toObject(styles)
+      }
+      for (const key in styles) {
+        dynamicStyle += `${hyphenate(key)}:${styles[key]};`
       }
     }
+  }
+
+  dynamicStyle = concatStyleString(parentStyle, dynamicStyle)
+  return concatStyleString(dynamicStyle, staticStyle || '')
+}
+
+export default function renderStyle (node: VNodeWithData): ?string {
+  const res = generateStyleText(node)
+  if (res) {
     return ` style=${JSON.stringify(res)}`
   }
 }

+ 11 - 0
src/server/render.js

@@ -150,9 +150,20 @@ function renderElement (el, isRoot, context) {
   }
 }
 
+function hasAncestorData (node: VNode) {
+  const parentNode = node.parent
+  return parentNode && (parentNode.data || hasAncestorData(parentNode))
+}
+
 function renderStartingTag (node: VNode, context) {
   let markup = `<${node.tag}`
   const { directives, modules } = context
+
+  // construct synthetic data for module processing
+  // because modules like style also produce code by parent VNode data
+  if (!node.data && hasAncestorData(node)) {
+    node.data = {}
+  }
   if (node.data) {
     // check directives
     const dirs = node.data.directives

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

@@ -86,6 +86,87 @@ describe('SSR: renderToString', () => {
     })
   })
 
+  it('custom component style', done => {
+    renderVmWithOptions({
+      template: '<section><comp :style="style"></comp></section>',
+      data: {
+        style: 'color:red'
+      },
+      components: {
+        comp: {
+          template: '<div></div>'
+        }
+      }
+    }, result => {
+      expect(result).toContain(
+        '<section server-rendered="true"><div style="color:red"></div></section>'
+      )
+      done()
+    })
+  })
+
+  it('nested custom component style', done => {
+    renderVmWithOptions({
+      template: '<comp :style="style"></comp>',
+      data: {
+        style: 'color:red'
+      },
+      components: {
+        comp: {
+          template: '<nested style="font-size:520rem"></nested>',
+          components: {
+            nested: {
+              template: '<div></div>'
+            }
+          }
+        }
+      }
+    }, result => {
+      expect(result).toContain(
+        '<div server-rendered="true" style="color:red;font-size:520rem"></div>'
+      )
+      done()
+    })
+  })
+
+  it('component style not passed to child', done => {
+    renderVmWithOptions({
+      template: '<comp :style="style"></comp>',
+      data: {
+        style: 'color:red'
+      },
+      components: {
+        comp: {
+          template: '<div><div></div></div>'
+        }
+      }
+    }, result => {
+      expect(result).toContain(
+        '<div server-rendered="true" style="color:red"><div></div></div>'
+      )
+      done()
+    })
+  })
+
+  it('component style not passed to slot', done => {
+    renderVmWithOptions({
+      template: '<comp :style="style"><span style="color:black"></span></comp>',
+      data: {
+        style: 'color:red'
+      },
+      components: {
+        comp: {
+          template: '<div><slot></slot></div>'
+        }
+      }
+    }, result => {
+      expect(result).toContain(
+        '<div server-rendered="true" style="color:red"><span style="color:black"></span></div>'
+      )
+      done()
+    })
+  })
+
   it('text interpolation', done => {
     renderVmWithOptions({
       template: '<div>{{ foo }} side {{ bar }}</div>',