wip(ssr): handle <textarea> with dynamic key v-bind

This commit is contained in:
Evan You 2020-02-04 22:49:47 -05:00
parent 1958314976
commit 8da6df7235
9 changed files with 239 additions and 13 deletions

View File

@ -189,3 +189,13 @@ return function render() {
}
}"
`;
exports[`compiler: codegen temps 1`] = `
"
return function render() {
with (this) {
let _temp0, _temp1, _temp2
return null
}
}"
`;

View File

@ -65,6 +65,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -134,6 +135,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -203,6 +205,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -289,6 +292,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -375,6 +379,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -461,6 +466,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -547,6 +553,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -616,6 +623,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -685,6 +693,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -754,6 +763,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -823,6 +833,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -892,6 +903,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -985,6 +997,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -1054,6 +1067,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -1123,6 +1137,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -1192,6 +1207,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -1336,6 +1352,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -1411,6 +1428,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -1486,6 +1504,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -1555,6 +1574,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -1624,6 +1644,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -1699,6 +1720,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -1792,6 +1814,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -1861,6 +1884,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -1930,6 +1954,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -1999,6 +2024,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -2068,6 +2094,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -2137,6 +2164,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -2206,6 +2234,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -2275,6 +2304,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -2350,6 +2380,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -2425,6 +2456,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -2519,6 +2551,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -2613,6 +2646,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -2707,6 +2741,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -2817,6 +2852,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -2927,6 +2963,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -3037,6 +3074,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -3147,6 +3185,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -3257,6 +3296,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -3367,6 +3407,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -3477,6 +3518,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -3587,6 +3629,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -3656,6 +3699,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -3701,6 +3745,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -3770,6 +3815,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -3839,6 +3885,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -3908,6 +3955,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -3977,6 +4025,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -4046,6 +4095,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -4133,6 +4183,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -4202,6 +4253,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -4288,6 +4340,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -4398,6 +4451,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -4492,6 +4546,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -4586,6 +4641,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -4637,6 +4693,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -4706,6 +4763,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -4775,6 +4833,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -4844,6 +4903,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -4991,6 +5051,7 @@ class=\\"bar\\"></div></template>",
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -5135,6 +5196,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -5204,6 +5266,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -5273,6 +5336,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -5342,6 +5406,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -5411,6 +5476,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -5480,6 +5546,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -5549,6 +5616,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -5618,6 +5686,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -5687,6 +5756,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -5797,6 +5867,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -5907,6 +5978,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -6017,6 +6089,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -6127,6 +6200,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -6237,6 +6311,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -6347,6 +6422,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -6457,6 +6533,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -6567,6 +6644,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -6661,6 +6739,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -6771,6 +6850,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -6840,6 +6920,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -6952,6 +7033,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -7021,6 +7103,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -7090,6 +7173,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -7141,6 +7225,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -7192,6 +7277,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -7279,6 +7365,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -7348,6 +7435,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -7456,6 +7544,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -7531,6 +7620,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -7606,6 +7696,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -7651,6 +7742,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -7696,6 +7788,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -7759,6 +7852,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -7840,6 +7934,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -8029,6 +8124,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;
@ -8241,6 +8337,7 @@ Object {
"offset": 0,
},
},
"temps": 0,
"type": 0,
}
`;

View File

@ -17,7 +17,8 @@ import {
createCacheExpression,
createTemplateLiteral,
createBlockStatement,
createIfStatement
createIfStatement,
createAssignmentExpression
} from '../src'
import {
CREATE_VNODE,
@ -40,6 +41,7 @@ function createRoot(options: Partial<RootNode> = {}): RootNode {
imports: [],
hoists: [],
cached: 0,
temps: 0,
codegenNode: createSimpleExpression(`null`, false),
loc: locStub,
...options
@ -141,6 +143,15 @@ describe('compiler: codegen', () => {
expect(code).toMatchSnapshot()
})
test('temps', () => {
const root = createRoot({
temps: 3
})
const { code } = generate(root)
expect(code).toMatch(`let _temp0, _temp1, _temp2`)
expect(code).toMatchSnapshot()
})
test('prefixIdentifiers: true should inject _ctx statement', () => {
const { code } = generate(createRoot(), { prefixIdentifiers: true })
expect(code).toMatch(`const _ctx = this\n`)
@ -540,4 +551,23 @@ describe('compiler: codegen', () => {
`)
})
})
test('AssignmentExpression', () => {
const { code } = generate(
createRoot({
codegenNode: createAssignmentExpression(
createSimpleExpression(`foo`, false),
createSimpleExpression(`bar`, false)
)
})
)
expect(code).toMatchInlineSnapshot(`
"
return function render() {
with (this) {
return (foo = bar)
}
}"
`)
})
})

View File

@ -50,7 +50,8 @@ export const enum NodeTypes {
// ssr codegen
JS_BLOCK_STATEMENT,
JS_TEMPLATE_LITERAL,
JS_IF_STATEMENT
JS_IF_STATEMENT,
JS_ASSIGNMENT_EXPRESSION
}
export const enum ElementTypes {
@ -102,8 +103,9 @@ export interface RootNode extends Node {
hoists: JSChildNode[]
imports: ImportItem[]
cached: number
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement | undefined
temps: number
ssrHelpers?: symbol[]
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement | undefined
}
export type ElementNode =
@ -255,6 +257,7 @@ export type JSChildNode =
| ConditionalExpression
| SequenceExpression
| CacheExpression
| AssignmentExpression
export interface CallExpression extends Node {
type: NodeTypes.JS_CALL_EXPRESSION
@ -335,6 +338,12 @@ export interface IfStatement extends Node {
alternate: IfStatement | BlockStatement | undefined
}
export interface AssignmentExpression extends Node {
type: NodeTypes.JS_ASSIGNMENT_EXPRESSION
left: SimpleExpressionNode
right: JSChildNode
}
// Codegen Node Types ----------------------------------------------------------
// createVNode(...)
@ -709,3 +718,15 @@ export function createIfStatement(
loc: locStub
}
}
export function createAssignmentExpression(
left: AssignmentExpression['left'],
right: AssignmentExpression['right']
): AssignmentExpression {
return {
type: NodeTypes.JS_ASSIGNMENT_EXPRESSION,
left,
right,
loc: locStub
}
}

View File

@ -21,7 +21,8 @@ import {
locStub,
SSRCodegenNode,
TemplateLiteral,
IfStatement
IfStatement,
AssignmentExpression
} from './ast'
import { SourceMapGenerator, RawSourceMap } from 'source-map'
import {
@ -232,7 +233,14 @@ export function generate(
if (ast.directives.length) {
genAssets(ast.directives, 'directive', context)
}
if (ast.components.length || ast.directives.length) {
if (ast.temps > 0) {
push(`let `)
for (let i = 0; i < ast.temps; i++) {
push(`${i > 0 ? `, ` : ``}_temp${i}`)
}
newline()
}
if (ast.components.length || ast.directives.length || ast.temps) {
newline()
}
@ -520,6 +528,9 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
case NodeTypes.JS_IF_STATEMENT:
!__BROWSER__ && genIfStatement(node, context)
break
case NodeTypes.JS_ASSIGNMENT_EXPRESSION:
!__BROWSER__ && genAssignmentExpression(node, context)
break
/* istanbul ignore next */
default:
@ -790,3 +801,12 @@ function genIfStatement(node: IfStatement, context: CodegenContext) {
}
}
}
function genAssignmentExpression(
node: AssignmentExpression,
context: CodegenContext
) {
genNode(node.left, context)
context.push(` = `)
genNode(node.right, context)
}

View File

@ -82,6 +82,7 @@ export function baseParse(
hoists: [],
imports: [],
cached: 0,
temps: 0,
codegenNode: undefined,
loc: getSelection(context, start)
}

View File

@ -83,6 +83,7 @@ export interface TransformContext extends Required<TransformOptions> {
components: Set<string>
directives: Set<string>
hoists: JSChildNode[]
temps: number
imports: Set<ImportItem>
cached: number
identifiers: { [name: string]: number | undefined }
@ -136,6 +137,7 @@ function createTransformContext(
components: new Set(),
directives: new Set(),
hoists: [],
temps: 0,
imports: new Set(),
cached: 0,
identifiers: {},
@ -267,6 +269,7 @@ export function transform(root: RootNode, options: TransformOptions) {
root.directives = [...context.directives]
root.imports = [...context.imports]
root.hoists = context.hoists
root.temps = context.temps
root.cached = context.cached
}

View File

@ -1,4 +1,5 @@
import { getCompiledString } from './utils'
import { compile } from '../src'
describe('ssr: element', () => {
test('basic elements', () => {
@ -48,7 +49,20 @@ describe('ssr: element', () => {
})
test('<textarea> with dynamic v-bind', () => {
// TODO
expect(compile(`<textarea v-bind="obj">fallback</textarea>`).code)
.toMatchInlineSnapshot(`
"const { _renderAttrs, _interpolate } = require(\\"vue\\")
return function ssrRender(_ctx, _push, _parent) {
let _temp0
_push(\`<textarea\${
_renderAttrs(_temp0 = _ctx.obj)
}>\${
_interpolate((\\"value\\" in _temp0) ? _temp0.value : \\"fallback\\")
}</textarea>\`)
}"
`)
})
})
@ -95,7 +109,7 @@ describe('ssr: element', () => {
expect(
getCompiledString(`<input type="checkbox" :checked="checked">`)
).toMatchInlineSnapshot(
`"\`<input type=\\"checkbox\\"\${(_ctx.checked)? \\" checked\\": \\"\\"}>\`"`
`"\`<input type=\\"checkbox\\"\${(_ctx.checked) ? \\" checked\\" : \\"\\"}>\`"`
)
})

View File

@ -17,7 +17,9 @@ import {
createArrayExpression,
ExpressionNode,
JSChildNode,
ArrayExpression
ArrayExpression,
createAssignmentExpression,
TextNode
} from '@vue/compiler-dom'
import { escapeHtml, isBooleanAttr, isSSRSafeAttrName } from '@vue/shared'
import { createSSRCompilerError, SSRErrorCodes } from '../errors'
@ -26,7 +28,8 @@ import {
SSR_RENDER_CLASS,
SSR_RENDER_STYLE,
SSR_RENDER_DYNAMIC_ATTR,
SSR_RENDER_ATTRS
SSR_RENDER_ATTRS,
SSR_INTERPOLATE
} from '../runtimeHelpers'
export const ssrTransformElement: NodeTransform = (node, context) => {
@ -54,9 +57,38 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
if (hasDynamicVBind) {
const { props } = buildProps(node, context, node.props, true /* ssr */)
if (props) {
openTag.push(
createCallExpression(context.helper(SSR_RENDER_ATTRS), [props])
const propsExp = createCallExpression(
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:
// assign the merged props to a temp variable, and check whether
// it contains value (if yes, render is as children).
const tempId = `_temp${context.temps++}`
propsExp.arguments[0] = createAssignmentExpression(
createSimpleExpression(tempId, false),
props
)
const existingText = node.children[0] as TextNode | undefined
node.children = []
rawChildren = createCallExpression(
context.helper(SSR_INTERPOLATE),
[
createConditionalExpression(
createSimpleExpression(`"value" in ${tempId}`, false),
createSimpleExpression(`${tempId}.value`, false),
createSimpleExpression(
existingText ? existingText.content : ``,
true
),
false
)
]
)
}
openTag.push(propsExp)
}
}
@ -83,8 +115,6 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
} else if (isTextareaWithValue(node, prop) && prop.exp) {
if (!hasDynamicVBind) {
node.children = [createInterpolation(prop.exp, prop.loc)]
} else {
// TODO handle <textrea> with dynamic v-bind
}
} else {
// Directive transforms.