Skip to content

Commit

Permalink
fix(compiler-dom): properly stringify class/style bindings when hoist…
Browse files Browse the repository at this point in the history
…ing static strings
  • Loading branch information
yyx990803 committed Feb 21, 2020
1 parent 189a0a3 commit 1b9b235
Show file tree
Hide file tree
Showing 11 changed files with 57 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -646,7 +646,7 @@ describe('compiler: element transform', () => {
elements: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`,
content: `{"color":"green"}`,
isStatic: false
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 */))
}
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-dom/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>`
)
Expand All @@ -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>`
)
Expand Down
17 changes: 4 additions & 13 deletions packages/compiler-dom/__tests__/transforms/transformStyle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand All @@ -47,7 +38,7 @@ describe('compiler: style transform', () => {
},
exp: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`,
content: `{"color":"red"}`,
isStatic: false
}
})
Expand All @@ -71,7 +62,7 @@ describe('compiler: style transform', () => {
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`,
content: `{"color":"red"}`,
isStatic: false
}
}
Expand Down
16 changes: 13 additions & 3 deletions packages/compiler-dom/src/transforms/stringifyStatic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
)}"`
}
}
Expand Down Expand Up @@ -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)
}
Expand Down
5 changes: 2 additions & 3 deletions packages/compiler-dom/src/transforms/transformStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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)
}
4 changes: 2 additions & 2 deletions packages/compiler-ssr/__tests__/ssrElement.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>\`"`
)
})

Expand Down Expand Up @@ -184,7 +184,7 @@ describe('ssr: element', () => {
)
).toMatchInlineSnapshot(`
"\`<div\${_ssrRenderAttrs(_mergeProps({
style: [_hoisted_1, _ctx.b]
style: [{\\"color\\":\\"red\\"}, _ctx.b]
}, _ctx.obj))}></div>\`"
`)
})
Expand Down
12 changes: 3 additions & 9 deletions packages/compiler-ssr/__tests__/ssrVShow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>\`)
}"
Expand Down Expand Up @@ -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>\`)
Expand All @@ -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\\" }
]
Expand Down
18 changes: 2 additions & 16 deletions packages/server-renderer/src/helpers/ssrRenderAttrs.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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))
}
24 changes: 23 additions & 1 deletion packages/shared/src/normalizeProp.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { isArray, isString, isObject } from './'
import { isArray, isString, isObject, hyphenate } from './'
import { isNoUnitNumericStyleProp } from './domAttrConfig'

export function normalizeStyle(
value: unknown
Expand All @@ -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)) {
Expand Down

0 comments on commit 1b9b235

Please sign in to comment.