for (let i = 0; i < ast.temps; i++) {
push(`${i > 0 ? `, ` : ``}_temp${i}`)
}
- newline()
+ push(`\n`)
}
if (ast.components.length || ast.directives.length || ast.temps) {
newline()
export { transformBind } from './transforms/vBind'
// exported for compiler-ssr
+export { MERGE_PROPS } from './runtimeHelpers'
export { processIfBranches } from './transforms/vIf'
export { processForNode, createForLoopParams } from './transforms/vFor'
export {
ComponentCodegenNode,
createCallExpression,
CacheExpression,
- createCacheExpression
+ createCacheExpression,
+ TemplateLiteral
} from './ast'
import {
isString,
export interface DirectiveTransformResult {
props: Property[]
needRuntime?: boolean | symbol
+ ssrTagParts?: TemplateLiteral['elements']
}
// A structural directive transform is a technically a NodeTransform;
return function ssrRender(_ctx, _push, _parent) {
let _temp0
-
+
_push(\`<textarea\${
_renderAttrs(_temp0 = _ctx.obj)
}>\${
}"
`)
})
+
+ test('<input :type="x">', () => {
+ expect(compile(`<input :type="x" v-model="foo">`).code)
+ .toMatchInlineSnapshot(`
+ "const { _renderAttr, _renderDynamicModel } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ _push(\`<input\${
+ _renderAttr(\\"type\\", _ctx.x)
+ }\${
+ _renderDynamicModel(_ctx.x, _ctx.foo, null)
+ }>\`)
+ }"
+ `)
+
+ expect(compile(`<input :type="x" v-model="foo" value="bar">`).code)
+ .toMatchInlineSnapshot(`
+ "const { _renderAttr, _renderDynamicModel } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ _push(\`<input\${
+ _renderAttr(\\"type\\", _ctx.x)
+ }\${
+ _renderDynamicModel(_ctx.x, _ctx.foo, \\"bar\\")
+ } value=\\"bar\\">\`)
+ }"
+ `)
+
+ expect(compile(`<input :type="x" v-model="foo" :value="bar">`).code)
+ .toMatchInlineSnapshot(`
+ "const { _renderAttr, _renderDynamicModel } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ _push(\`<input\${
+ _renderAttr(\\"type\\", _ctx.x)
+ }\${
+ _renderDynamicModel(_ctx.x, _ctx.foo, _ctx.bar)
+ }\${
+ _renderAttr(\\"value\\", _ctx.bar)
+ }>\`)
+ }"
+ `)
+ })
+
+ test('<input v-bind="obj">', () => {
+ expect(compile(`<input v-bind="obj" v-model="foo">`).code)
+ .toMatchInlineSnapshot(`
+ "const { mergeProps } = require(\\"vue\\")
+ const { _renderAttrs, _getDynamicModelProps } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ let _temp0
+
+ _push(\`<input\${_renderAttrs(_temp0 = _ctx.obj, mergeProps(_temp0, _getDynamicModelProps(_temp0, _ctx.foo)))}>\`)
+ }"
+ `)
+
+ expect(compile(`<input id="x" v-bind="obj" v-model="foo" class="y">`).code)
+ .toMatchInlineSnapshot(`
+ "const { mergeProps } = require(\\"vue\\")
+ const { _renderAttrs, _getDynamicModelProps } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ let _temp0
+
+ _push(\`<input\${_renderAttrs(_temp0 = mergeProps({ id: \\"x\\" }, _ctx.obj, { class: \\"y\\" }), mergeProps(_temp0, _getDynamicModelProps(_temp0, _ctx.foo)))}>\`)
+ }"
+ `)
+ })
})
export const SSR_RENDER_LIST = Symbol(`renderList`)
export const SSR_LOOSE_EQUAL = Symbol(`looseEqual`)
export const SSR_LOOSE_CONTAIN = Symbol(`looseContain`)
+export const SSR_RENDER_DYNAMIC_MODEL = Symbol(`renderDynamicModel`)
+export const SSR_GET_DYNAMIC_MODEL_PROPS = Symbol(`getDynamicModelProps`)
export const ssrHelpers = {
[SSR_INTERPOLATE]: `_interpolate`,
[SSR_RENDER_DYNAMIC_ATTR]: `_renderDynamicAttr`,
[SSR_RENDER_LIST]: `_renderList`,
[SSR_LOOSE_EQUAL]: `_looseEqual`,
- [SSR_LOOSE_CONTAIN]: `_looseContain`
+ [SSR_LOOSE_CONTAIN]: `_looseContain`,
+ [SSR_RENDER_DYNAMIC_MODEL]: `_renderDynamicModel`,
+ [SSR_GET_DYNAMIC_MODEL_PROPS]: `_getDynamicModelProps`
}
// Note: these are helpers imported from @vue/server-renderer
ArrayExpression,
createAssignmentExpression,
TextNode,
- hasDynamicKeyVBind
+ hasDynamicKeyVBind,
+ MERGE_PROPS
} from '@vue/compiler-dom'
import { escapeHtml, isBooleanAttr, isSSRSafeAttrName } from '@vue/shared'
import { createSSRCompilerError, SSRErrorCodes } from '../errors'
SSR_RENDER_STYLE,
SSR_RENDER_DYNAMIC_ATTR,
SSR_RENDER_ATTRS,
- SSR_INTERPOLATE
+ SSR_INTERPOLATE,
+ SSR_GET_DYNAMIC_MODEL_PROPS
} from '../runtimeHelpers'
export const ssrTransformElement: NodeTransform = (node, context) => {
context.helper(SSR_RENDER_ATTRS),
[props]
)
+
if (node.tag === 'textarea') {
// <textarea> with dynamic v-bind. We don't know if the final props
// will contain .value, so we will have to do something special:
)
]
)
+ } else if (node.tag === 'input') {
+ // <input v-bind="obj" v-model>
+ // we need to determine the props to render for the dynamic v-model
+ // and merge it with the v-bind expression.
+ const vModel = findVModel(node)
+ if (vModel) {
+ // 1. save the props (san v-model) in a temp variable
+ const tempId = `_temp${context.temps++}`
+ const tempExp = createSimpleExpression(tempId, false)
+ propsExp.arguments = [
+ createAssignmentExpression(tempExp, props),
+ createCallExpression(context.helper(MERGE_PROPS), [
+ tempExp,
+ createCallExpression(
+ context.helper(SSR_GET_DYNAMIC_MODEL_PROPS),
+ [
+ tempExp, // existing props
+ vModel.exp! // model
+ ]
+ )
+ ])
+ ]
+ }
}
+
openTag.push(propsExp)
}
}
)
)
} else if (!hasDynamicVBind) {
- const { props } = directiveTransform(prop, node, context)
+ const { props, ssrTagParts } = directiveTransform(
+ prop,
+ node,
+ context
+ )
+ if (ssrTagParts) {
+ openTag.push(...ssrTagParts)
+ }
for (let j = 0; j < props.length; j++) {
const { key, value } = props[j]
if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
tag.splice(i, 1)
}
}
+
+function findVModel(node: PlainElementNode): DirectiveNode | undefined {
+ return node.props.find(
+ p => p.type === NodeTypes.DIRECTIVE && p.name === 'model' && p.exp
+ ) as DirectiveNode | undefined
+}
NodeTypes,
createDOMCompilerError,
DOMErrorCodes,
- Property,
createObjectProperty,
createSimpleExpression,
createCallExpression,
PlainElementNode,
ExpressionNode,
createConditionalExpression,
- createInterpolation
+ createInterpolation,
+ hasDynamicKeyVBind
} from '@vue/compiler-dom'
-import { SSR_LOOSE_EQUAL, SSR_LOOSE_CONTAIN } from '../runtimeHelpers'
+import {
+ SSR_LOOSE_EQUAL,
+ SSR_LOOSE_CONTAIN,
+ SSR_RENDER_DYNAMIC_MODEL
+} from '../runtimeHelpers'
+import { DirectiveTransformResult } from 'packages/compiler-core/src/transform'
export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
const model = dir.exp!
}
if (node.tagType === ElementTypes.ELEMENT) {
- let props: Property[] = []
+ const res: DirectiveTransformResult = { props: [] }
const defaultProps = [
// default value binding for text type inputs
createObjectProperty(createSimpleExpression(`value`, true), model)
if (node.tag === 'input') {
const type = findProp(node, 'type')
if (type) {
+ const value = findValueBinding(node)
if (type.type === NodeTypes.DIRECTIVE) {
// dynamic type
- // TODO
+ res.ssrTagParts = [
+ createCallExpression(context.helper(SSR_RENDER_DYNAMIC_MODEL), [
+ type.exp!,
+ model,
+ value
+ ])
+ ]
} else if (type.value) {
// static type
switch (type.value.content) {
case 'radio':
- props = [
+ res.props = [
createObjectProperty(
createSimpleExpression(`checked`, true),
createCallExpression(context.helper(SSR_LOOSE_EQUAL), [
model,
- findValueBinding(node)
+ value
])
)
]
break
case 'checkbox':
- const value = findValueBinding(node)
- props = [
+ res.props = [
createObjectProperty(
createSimpleExpression(`checked`, true),
createConditionalExpression(
break
default:
checkDuplicatedValue()
- props = defaultProps
+ res.props = defaultProps
break
}
}
+ } else if (hasDynamicKeyVBind(node)) {
+ // dynamic type due to dynamic v-bind
+ // NOOP, handled in ssrTransformElement due to need to rewrite
+ // the entire props expression
} else {
+ // text type
checkDuplicatedValue()
- props = defaultProps
+ res.props = defaultProps
}
} else if (node.tag === 'textarea') {
checkDuplicatedValue()
)
}
- return { props }
+ return res
} else {
// component v-model
return transformModel(dir, node, context)
--- /dev/null
+import { looseEqual as _looseEqual, looseIndexOf } from '@vue/shared'
+import { renderAttr } from './renderAttrs'
+
+export const looseEqual = _looseEqual as (a: unknown, b: unknown) => boolean
+
+export function looseContain(arr: unknown[], value: unknown): boolean {
+ return looseIndexOf(arr, value) > -1
+}
+
+// for <input :type="type" v-model="model" value="value">
+export function renderDynamicModel(
+ type: unknown,
+ model: unknown,
+ value: unknown
+) {
+ switch (type) {
+ case 'radio':
+ return _looseEqual(model, value) ? ' checked' : ''
+ case 'checkbox':
+ return (Array.isArray(model)
+ ? looseContain(model, value)
+ : model)
+ ? ' checked'
+ : ''
+ default:
+ // text types
+ return renderAttr('value', model)
+ }
+}
+
+// for <input v-bind="obj" v-model="model">
+export function getDynamicModelProps(existingProps: any = {}, model: unknown) {
+ const { type, value } = existingProps
+ switch (type) {
+ case 'radio':
+ return _looseEqual(model, value) ? { checked: true } : null
+ case 'checkbox':
+ return (Array.isArray(model)
+ ? looseContain(model, value)
+ : model)
+ ? { checked: true }
+ : null
+ default:
+ // text types
+ return { value: model }
+ }
+}
export { renderList as _renderList } from './helpers/renderList'
// v-model helpers
-import { looseEqual, looseIndexOf } from '@vue/shared'
-export const _looseEqual = looseEqual as (a: unknown, b: unknown) => boolean
-export const _looseContain = (arr: unknown[], value: unknown): boolean =>
- looseIndexOf(arr, value) > -1
+export {
+ looseEqual as _looseEqual,
+ looseContain as _looseContain,
+ renderDynamicModel as _renderDynamicModel,
+ getDynamicModelProps as _getDynamicModelProps
+} from './helpers/vModelHelpers'