]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-dom): properly stringify class/style bindings when hoisting static strings
authorEvan You <yyx990803@gmail.com>
Fri, 21 Feb 2020 12:10:13 +0000 (13:10 +0100)
committerEvan You <yyx990803@gmail.com>
Fri, 21 Feb 2020 12:10:13 +0000 (13:10 +0100)
packages/compiler-core/__tests__/transforms/transformElement.spec.ts
packages/compiler-dom/__tests__/__snapshots__/index.spec.ts.snap
packages/compiler-dom/__tests__/index.spec.ts
packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts
packages/compiler-dom/__tests__/transforms/transformStyle.spec.ts
packages/compiler-dom/src/transforms/stringifyStatic.ts
packages/compiler-dom/src/transforms/transformStyle.ts
packages/compiler-ssr/__tests__/ssrElement.spec.ts
packages/compiler-ssr/__tests__/ssrVShow.spec.ts
packages/server-renderer/src/helpers/ssrRenderAttrs.ts
packages/shared/src/normalizeProp.ts

index b62a27a7557fa94ed7ad0d6df5227c139b94846f..cc3cbd480a5342274746f5b1b2a837e4b9943c45 100644 (file)
@@ -623,7 +623,7 @@ describe('compiler: element transform', () => {
 
   test(`props merging: style`, () => {
     const { node } = parseWithElementTransform(
-      `<div style="color: red" :style="{ color: 'red' }" />`,
+      `<div style="color: green" :style="{ color: 'red' }" />`,
       {
         nodeTransforms: [transformStyle, transformElement],
         directiveTransforms: {
@@ -646,7 +646,7 @@ describe('compiler: element transform', () => {
             elements: [
               {
                 type: NodeTypes.SIMPLE_EXPRESSION,
-                content: `_hoisted_1`,
+                content: `{"color":"green"}`,
                 isStatic: false
               },
               {
index b429387fd6d4e435207c00ce58a8bb8ecbb640b2..40e14d51f1857e2a1db4d4df7c2fb39be2765e03 100644 (file)
@@ -2,9 +2,6 @@
 
 exports[`compile should contain standard transforms 1`] = `
 "const _Vue = Vue
-const { createVNode: _createVNode } = _Vue
-
-const _hoisted_1 = {}
 
 return function render(_ctx, _cache) {
   with (_ctx) {
@@ -14,7 +11,7 @@ return function render(_ctx, _cache) {
       _createVNode(\\"div\\", { textContent: text }, null, 8 /* PROPS */, [\\"textContent\\"]),
       _createVNode(\\"div\\", { innerHTML: html }, null, 8 /* PROPS */, [\\"innerHTML\\"]),
       _createVNode(\\"div\\", null, \\"test\\"),
-      _createVNode(\\"div\\", { style: _hoisted_1 }, \\"red\\"),
+      _createVNode(\\"div\\", { style: {\\"color\\":\\"red\\"} }, \\"red\\"),
       _createVNode(\\"div\\", { style: {color: 'green'} }, null, 4 /* STYLE */)
     ], 64 /* STABLE_FRAGMENT */))
   }
index d40bf36fe2c7673300f3225ab136258aa1a35067..db43ce905cfd2e47aff709b30d396a5892956867 100644 (file)
@@ -5,7 +5,7 @@ describe('compile', () => {
     const { code } = compile(`<div v-text="text"></div>
         <div v-html="html"></div>
         <div v-cloak>test</div>
-        <div style="color=red">red</div>
+        <div style="color:red">red</div>
         <div :style="{color: 'green'}"></div>`)
 
     expect(code).toMatchSnapshot()
index d1095d3f4256fa00de3b920857ea5fb56bcf0363..cc8b53f245d32d8f29d1e6c4468fd71bf3c567b5 100644 (file)
@@ -77,8 +77,8 @@ describe('stringify static html', () => {
 
   test('serliazing constant bindings', () => {
     const { ast } = compileWithStringify(
-      `<div><div>${repeat(
-        `<span :class="'foo' + 'bar'">{{ 1 }} + {{ false }}</span>`,
+      `<div><div :style="{ color: 'red' }">${repeat(
+        `<span :class="[{ foo: true }, { bar: true }]">{{ 1 }} + {{ false }}</span>`,
         StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
       )}</div></div>`
     )
@@ -89,8 +89,8 @@ describe('stringify static html', () => {
       callee: CREATE_STATIC,
       arguments: [
         JSON.stringify(
-          `<div>${repeat(
-            `<span class="foobar">1 + false</span>`,
+          `<div style="color:red;">${repeat(
+            `<span class="foo bar">1 + false</span>`,
             StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
           )}</div>`
         )
index 89121c10a26e2bb2a2ba62b5acbd6609c97e216d..6eee9f8ba3821ebfb66e32401bd59bd5f8602431 100644 (file)
@@ -26,17 +26,8 @@ function transformWithStyleTransform(
 }
 
 describe('compiler: style transform', () => {
-  test('should transform into directive node and hoist value', () => {
-    const { root, node } = transformWithStyleTransform(
-      `<div style="color: red"/>`
-    )
-    expect(root.hoists).toMatchObject([
-      {
-        type: NodeTypes.SIMPLE_EXPRESSION,
-        content: `{"color":"red"}`,
-        isStatic: false
-      }
-    ])
+  test('should transform into directive node', () => {
+    const { node } = transformWithStyleTransform(`<div style="color: red"/>`)
     expect(node.props[0]).toMatchObject({
       type: NodeTypes.DIRECTIVE,
       name: `bind`,
@@ -47,7 +38,7 @@ describe('compiler: style transform', () => {
       },
       exp: {
         type: NodeTypes.SIMPLE_EXPRESSION,
-        content: `_hoisted_1`,
+        content: `{"color":"red"}`,
         isStatic: false
       }
     })
@@ -71,7 +62,7 @@ describe('compiler: style transform', () => {
           },
           value: {
             type: NodeTypes.SIMPLE_EXPRESSION,
-            content: `_hoisted_1`,
+            content: `{"color":"red"}`,
             isStatic: false
           }
         }
index a2efa56eec0c0cd72abed76302cdd4c7802c081c..a4aa7745f8ed22cac15f1dd4077e5571b3cbf700 100644 (file)
@@ -14,7 +14,10 @@ import {
   isString,
   isSymbol,
   escapeHtml,
-  toDisplayString
+  toDisplayString,
+  normalizeClass,
+  normalizeStyle,
+  stringifyStyle
 } from '@vue/shared'
 
 // Turn eligible hoisted static trees into stringied static nodes, e.g.
@@ -84,8 +87,15 @@ function stringifyElement(
       }
     } else if (p.type === NodeTypes.DIRECTIVE && p.name === 'bind') {
       // constant v-bind, e.g. :foo="1"
+      let evaluated = evaluateConstant(p.exp as SimpleExpressionNode)
+      const arg = p.arg && (p.arg as SimpleExpressionNode).content
+      if (arg === 'class') {
+        evaluated = normalizeClass(evaluated)
+      } else if (arg === 'style') {
+        evaluated = stringifyStyle(normalizeStyle(evaluated))
+      }
       res += ` ${(p.arg as SimpleExpressionNode).content}="${escapeHtml(
-        evaluateConstant(p.exp as ExpressionNode)
+        evaluated
       )}"`
     }
   }
@@ -151,7 +161,7 @@ function evaluateConstant(exp: ExpressionNode): string {
       if (c.type === NodeTypes.TEXT) {
         res += c.content
       } else if (c.type === NodeTypes.INTERPOLATION) {
-        res += evaluateConstant(c.content)
+        res += toDisplayString(evaluateConstant(c.content))
       } else {
         res += evaluateConstant(c)
       }
index 3c232db4357447239fe580a79b5bacd83e4374ca..765fab778e9a92dee581c1bdfab415d73314c325 100644 (file)
@@ -17,12 +17,11 @@ export const transformStyle: NodeTransform = (node, context) => {
     node.props.forEach((p, i) => {
       if (p.type === NodeTypes.ATTRIBUTE && p.name === 'style' && p.value) {
         // replace p with an expression node
-        const exp = context.hoist(parseInlineCSS(p.value.content, p.loc))
         node.props[i] = {
           type: NodeTypes.DIRECTIVE,
           name: `bind`,
           arg: createSimpleExpression(`style`, true, p.loc),
-          exp,
+          exp: parseInlineCSS(p.value.content, p.loc),
           modifiers: [],
           loc: p.loc
         }
@@ -45,5 +44,5 @@ function parseInlineCSS(
       tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim())
     }
   })
-  return createSimpleExpression(JSON.stringify(res), false, loc)
+  return createSimpleExpression(JSON.stringify(res), false, loc, true)
 }
index c99d13c543a4b55e42c4ccd3b164dd75b0bb4fc7..c81d6ccb10e71ac18446cbc9cdd8c007460c0c3c 100644 (file)
@@ -101,7 +101,7 @@ describe('ssr: element', () => {
       expect(
         getCompiledString(`<div style="color:red;" :style="bar"></div>`)
       ).toMatchInlineSnapshot(
-        `"\`<div style=\\"\${_ssrRenderStyle([_hoisted_1, _ctx.bar])}\\"></div>\`"`
+        `"\`<div style=\\"\${_ssrRenderStyle([{\\"color\\":\\"red\\"}, _ctx.bar])}\\"></div>\`"`
       )
     })
 
@@ -184,7 +184,7 @@ describe('ssr: element', () => {
         )
       ).toMatchInlineSnapshot(`
         "\`<div\${_ssrRenderAttrs(_mergeProps({
-            style: [_hoisted_1, _ctx.b]
+            style: [{\\"color\\":\\"red\\"}, _ctx.b]
           }, _ctx.obj))}></div>\`"
       `)
     })
index 2738bc2261780848dae6ef55e728de341923e02e..8a99a52c7d34523cbedd09a8db80d1ff8f3b81e6 100644 (file)
@@ -16,11 +16,9 @@ describe('ssr: v-show', () => {
       .toMatchInlineSnapshot(`
       "const { ssrRenderStyle: _ssrRenderStyle } = require(\\"@vue/server-renderer\\")
 
-      const _hoisted_1 = {\\"color\\":\\"red\\"}
-
       return function ssrRender(_ctx, _push, _parent) {
         _push(\`<div style=\\"\${_ssrRenderStyle([
-          _hoisted_1,
+          {\\"color\\":\\"red\\"},
           (_ctx.foo) ? null : { display: \\"none\\" }
         ])}\\"></div>\`)
       }"
@@ -48,11 +46,9 @@ describe('ssr: v-show', () => {
     ).toMatchInlineSnapshot(`
       "const { ssrRenderStyle: _ssrRenderStyle } = require(\\"@vue/server-renderer\\")
 
-      const _hoisted_1 = {\\"color\\":\\"red\\"}
-
       return function ssrRender(_ctx, _push, _parent) {
         _push(\`<div style=\\"\${_ssrRenderStyle([
-          _hoisted_1,
+          {\\"color\\":\\"red\\"},
           { fontSize: 14 },
           (_ctx.foo) ? null : { display: \\"none\\" }
         ])}\\"></div>\`)
@@ -69,12 +65,10 @@ describe('ssr: v-show', () => {
       "const { mergeProps: _mergeProps } = require(\\"vue\\")
       const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
 
-      const _hoisted_1 = {\\"color\\":\\"red\\"}
-
       return function ssrRender(_ctx, _push, _parent) {
         _push(\`<div\${_ssrRenderAttrs(_mergeProps(_ctx.baz, {
           style: [
-            _hoisted_1,
+            {\\"color\\":\\"red\\"},
             { fontSize: 14 },
             (_ctx.foo) ? null : { display: \\"none\\" }
           ]
index 4a1dcb8df5f9ad68906504da29b68ac0de4c17ac..ef07bac30f1bbf2d15d3d5519262ae403c4f0ee3 100644 (file)
@@ -1,11 +1,9 @@
-import { escapeHtml } from '@vue/shared'
+import { escapeHtml, stringifyStyle } from '@vue/shared'
 import {
   normalizeClass,
   normalizeStyle,
   propsToAttrMap,
-  hyphenate,
   isString,
-  isNoUnitNumericStyleProp,
   isOn,
   isSSRSafeAttrName,
   isBooleanAttr,
@@ -93,17 +91,5 @@ export function ssrRenderStyle(raw: unknown): string {
     return escapeHtml(raw)
   }
   const styles = normalizeStyle(raw)
-  let ret = ''
-  for (const key in styles) {
-    const value = styles[key]
-    const normalizedKey = key.indexOf(`--`) === 0 ? key : hyphenate(key)
-    if (
-      isString(value) ||
-      (typeof value === 'number' && isNoUnitNumericStyleProp(normalizedKey))
-    ) {
-      // only render valid values
-      ret += `${normalizedKey}:${value};`
-    }
-  }
-  return escapeHtml(ret)
+  return escapeHtml(stringifyStyle(styles))
 }
index 96678e8bc94a20d908f3d3a2825535599b874522..201e49806d3c15dbb90dee0282ad0d2fb6d17d22 100644 (file)
@@ -1,4 +1,5 @@
-import { isArray, isString, isObject } from './'
+import { isArray, isString, isObject, hyphenate } from './'
+import { isNoUnitNumericStyleProp } from './domAttrConfig'
 
 export function normalizeStyle(
   value: unknown
@@ -19,6 +20,27 @@ export function normalizeStyle(
   }
 }
 
+export function stringifyStyle(
+  styles: Record<string, string | number> | undefined
+): string {
+  let ret = ''
+  if (!styles) {
+    return ret
+  }
+  for (const key in styles) {
+    const value = styles[key]
+    const normalizedKey = key.indexOf(`--`) === 0 ? key : hyphenate(key)
+    if (
+      isString(value) ||
+      (typeof value === 'number' && isNoUnitNumericStyleProp(normalizedKey))
+    ) {
+      // only render valid values
+      ret += `${normalizedKey}:${value};`
+    }
+  }
+  return ret
+}
+
 export function normalizeClass(value: unknown): string {
   let res = ''
   if (isString(value)) {