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: {
elements: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_1`,
+ content: `{"color":"green"}`,
isStatic: false
},
{
exports[`compile should contain standard transforms 1`] = `
"const _Vue = Vue
-const { createVNode: _createVNode } = _Vue
-
-const _hoisted_1 = {}
return function render(_ctx, _cache) {
with (_ctx) {
_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 */))
}
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()
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>`
)
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>`
)
}
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`,
},
exp: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_1`,
+ content: `{"color":"red"}`,
isStatic: false
}
})
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_1`,
+ content: `{"color":"red"}`,
isStatic: false
}
}
isString,
isSymbol,
escapeHtml,
- toDisplayString
+ toDisplayString,
+ normalizeClass,
+ normalizeStyle,
+ stringifyStyle
} from '@vue/shared'
// Turn eligible hoisted static trees into stringied static nodes, e.g.
}
} 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
)}"`
}
}
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)
}
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
}
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)
}
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>\`"`
)
})
)
).toMatchInlineSnapshot(`
"\`<div\${_ssrRenderAttrs(_mergeProps({
- style: [_hoisted_1, _ctx.b]
+ style: [{\\"color\\":\\"red\\"}, _ctx.b]
}, _ctx.obj))}></div>\`"
`)
})
.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>\`)
}"
).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>\`)
"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\\" }
]
-import { escapeHtml } from '@vue/shared'
+import { escapeHtml, stringifyStyle } from '@vue/shared'
import {
normalizeClass,
normalizeStyle,
propsToAttrMap,
- hyphenate,
isString,
- isNoUnitNumericStyleProp,
isOn,
isSSRSafeAttrName,
isBooleanAttr,
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))
}
-import { isArray, isString, isObject } from './'
+import { isArray, isString, isObject, hyphenate } from './'
+import { isNoUnitNumericStyleProp } from './domAttrConfig'
export function normalizeStyle(
value: unknown
}
}
+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)) {