Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions packages/compiler-ssr/__tests__/ssrVModel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,133 @@ describe('ssr: v-model', () => {
_push(\`<!--]--></optgroup></select></div>\`)
}"
`)

expect(
compileWithWrapper(
`<select v-model="model"><option>foo</option></select>`,
).code,
).toMatchInlineSnapshot(`
"const { ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")

return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${
_ssrRenderAttrs(_attrs)
}><select><option\${
(_ssrIncludeBooleanAttr(_ssrLooseEqual(_ctx.model, \`\${"foo"}\`))) ? " selected" : ""
}>foo</option></select></div>\`)
}"
`)

expect(
compileWithWrapper(
`<select v-model="model"><option> foo </option></select>`,
).code,
).toMatchInlineSnapshot(`
"const { ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")

return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${
_ssrRenderAttrs(_attrs)
}><select><option\${
(_ssrIncludeBooleanAttr(_ssrLooseEqual(_ctx.model, \`\${"foo"}\`))) ? " selected" : ""
}> foo </option></select></div>\`)
}"
`)

expect(
compileWithWrapper(
`<select v-model="model"><option>{{ myValue }}</option></select>`,
).code,
).toMatchInlineSnapshot(`
"const { ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("vue/server-renderer")

return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${
_ssrRenderAttrs(_attrs)
}><select><option\${
(_ssrIncludeBooleanAttr(_ssrLooseEqual(_ctx.model, \`\${_ctx.myValue}\`))) ? " selected" : ""
}>\${
_ssrInterpolate(_ctx.myValue)
}</option></select></div>\`)
}"
`)

expect(
compileWithWrapper(
`<select v-model="model">
<option>
A
B
<!--comment-->
<!--comment-->
C
{{ myValue1 }}
D
<!--comment-->
{{ myValue2 }}
<!--comment-->E
<!--comment-->
</option>
</select>`,
).code,
).toMatchInlineSnapshot(`
"const { ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("vue/server-renderer")

return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${
_ssrRenderAttrs(_attrs)
}><select><option\${
(_ssrIncludeBooleanAttr(_ssrLooseEqual(_ctx.model, \`\${
"A B C "
}\${
_ctx.myValue1
}\${
" D "
}\${
_ctx.myValue2
}\${
" E"
}\`))) ? " selected" : ""
}> A B <!--comment--><!--comment--> C \${
_ssrInterpolate(_ctx.myValue1)
} D <!--comment--> \${
_ssrInterpolate(_ctx.myValue2)
} <!--comment-->E <!--comment--></option></select></div>\`)
}"
`)

expect(
compileWithWrapper(
`<select v-model="model"><option v-text="'foo'"></option></select>`,
).code,
).toMatchInlineSnapshot(`
"const { ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("vue/server-renderer")

return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${
_ssrRenderAttrs(_attrs)
}><select><option\${
(_ssrIncludeBooleanAttr(_ssrLooseEqual(_ctx.model, 'foo'))) ? " selected" : ""
}>\${
_ssrInterpolate('foo')
}</option></select></div>\`)
}"
`)

expect(
compileWithWrapper(`<select v-model="model"><option></option></select>`)
.code,
).toMatchInlineSnapshot(`
"const { ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")

return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${
_ssrRenderAttrs(_attrs)
}><select><option\${
(_ssrIncludeBooleanAttr(_ssrLooseEqual(_ctx.model, ""))) ? " selected" : ""
}></option></select></div>\`)
}"
`)
})

test('<input type="radio">', () => {
Expand Down
130 changes: 118 additions & 12 deletions packages/compiler-ssr/src/transforms/ssrVModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ import {
NodeTypes,
type PlainElementNode,
type TemplateChildNode,
type TemplateLiteral,
type TextNode,
createCallExpression,
createConditionalExpression,
createDOMCompilerError,
createInterpolation,
createObjectProperty,
createSimpleExpression,
createTemplateLiteral,
findDir,
findProp,
hasDynamicKeyVBind,
transformModel,
Expand Down Expand Up @@ -54,21 +58,26 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
function processOption(plainNode: PlainElementNode) {
if (plainNode.tag === 'option') {
if (plainNode.props.findIndex(p => p.name === 'selected') === -1) {
const value = findValueBinding(plainNode)
const value = findOptionValue(plainNode)
plainNode.ssrCodegenNode!.elements.push(
createConditionalExpression(
createCallExpression(context.helper(SSR_INCLUDE_BOOLEAN_ATTR), [
createConditionalExpression(
createCallExpression(`Array.isArray`, [model]),
createCallExpression(context.helper(SSR_LOOSE_CONTAIN), [
model,
value,
]),
createCallExpression(context.helper(SSR_LOOSE_EQUAL), [
model,
value,
]),
),
value.maybeArray
? createConditionalExpression(
createCallExpression(`Array.isArray`, [model]),
createCallExpression(context.helper(SSR_LOOSE_CONTAIN), [
model,
value.node,
]),
createCallExpression(context.helper(SSR_LOOSE_EQUAL), [
model,
value.node,
]),
)
: createCallExpression(context.helper(SSR_LOOSE_EQUAL), [
model,
value.node,
]),
]),
createSimpleExpression(' selected', true),
createSimpleExpression('', true),
Expand Down Expand Up @@ -190,6 +199,65 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
}
}

interface OptionValue {
node: ExpressionNode | TemplateLiteral
maybeArray: boolean
}

function findOptionValue(node: PlainElementNode): OptionValue {
const valueBinding = findProp(node, 'value')
if (valueBinding) {
return {
node:
valueBinding.type === NodeTypes.DIRECTIVE
? valueBinding.exp!
: createSimpleExpression(valueBinding.value!.content, true),
maybeArray: true,
}
}

const textDir = findDir(node, 'text')
if (textDir) {
return { node: textDir.exp!, maybeArray: false }
}

if (
node.children.every(
x =>
x.type === NodeTypes.TEXT ||
x.type === NodeTypes.COMMENT ||
x.type === NodeTypes.INTERPOLATION,
)
) {
const relevantNodes = collapseTextBetweenComments(node.children).filter(
x => x.type !== NodeTypes.COMMENT,
)
if (relevantNodes.length) {
const textContentValue = createTemplateLiteral(
relevantNodes.map((x, i) => {
if (x.type === NodeTypes.TEXT) {
let content = x.content
if (i === 0) {
content = content.trimStart()
}
if (i === relevantNodes.length - 1) {
content = content.trimEnd()
}
return createSimpleExpression(content, true)
} else {
return x.content
}
}),
)
if (textContentValue) {
return { node: textContentValue, maybeArray: false }
}
}
}

return { node: createSimpleExpression(``, true), maybeArray: false }
}

function findValueBinding(node: PlainElementNode): ExpressionNode {
const valueBinding = findProp(node, 'value')
return valueBinding
Expand All @@ -198,3 +266,41 @@ function findValueBinding(node: PlainElementNode): ExpressionNode {
: createSimpleExpression(valueBinding.value!.content, true)
: createSimpleExpression(`null`, false)
}

function collapseTextBetweenComments<T extends TemplateChildNode>(
children: T[],
) {
const result: (T | TextNode)[] = []
let prevTextNode: TextNode | undefined
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (child.type === NodeTypes.TEXT) {
if (prevTextNode) {
const prevContent = prevTextNode.content
let thisContent = child.content
if (prevContent.endsWith(' ') && thisContent.startsWith(' ')) {
thisContent = thisContent.slice(1)
}
const combined: TextNode = {
...prevTextNode,
content: prevContent + thisContent,
}
prevTextNode = combined
} else {
prevTextNode = child
}
} else if (child.type === NodeTypes.COMMENT) {
continue
} else {
if (prevTextNode) {
result.push(prevTextNode)
prevTextNode = undefined
}
result.push(child)
}
}
if (prevTextNode) {
result.push(prevTextNode)
}
return result
}
Loading