|
|
@@ -23,7 +23,8 @@ import {
|
|
|
hasDynamicKeyVBind,
|
|
|
MERGE_PROPS,
|
|
|
isBindKey,
|
|
|
- createSequenceExpression
|
|
|
+ createSequenceExpression,
|
|
|
+ InterpolationNode
|
|
|
} from '@vue/compiler-dom'
|
|
|
import {
|
|
|
escapeHtml,
|
|
|
@@ -53,30 +54,40 @@ const rawChildrenMap = new WeakMap<
|
|
|
|
|
|
export const ssrTransformElement: NodeTransform = (node, context) => {
|
|
|
if (
|
|
|
- node.type === NodeTypes.ELEMENT &&
|
|
|
- node.tagType === ElementTypes.ELEMENT
|
|
|
+ node.type !== NodeTypes.ELEMENT ||
|
|
|
+ node.tagType !== ElementTypes.ELEMENT
|
|
|
) {
|
|
|
- return function ssrPostTransformElement() {
|
|
|
- // element
|
|
|
- // generate the template literal representing the open tag.
|
|
|
- const openTag: TemplateLiteral['elements'] = [`<${node.tag}`]
|
|
|
- // some tags need to be pasesd to runtime for special checks
|
|
|
- const needTagForRuntime =
|
|
|
- node.tag === 'textarea' || node.tag.indexOf('-') > 0
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- // v-bind="obj" or v-bind:[key] can potentially overwrite other static
|
|
|
- // attrs and can affect final rendering result, so when they are present
|
|
|
- // we need to bail out to full `renderAttrs`
|
|
|
- const hasDynamicVBind = hasDynamicKeyVBind(node)
|
|
|
- if (hasDynamicVBind) {
|
|
|
- const { props } = buildProps(node, context, node.props, true /* ssr */)
|
|
|
- if (props) {
|
|
|
- const propsExp = createCallExpression(
|
|
|
- context.helper(SSR_RENDER_ATTRS),
|
|
|
- [props]
|
|
|
- )
|
|
|
+ return function ssrPostTransformElement() {
|
|
|
+ // element
|
|
|
+ // generate the template literal representing the open tag.
|
|
|
+ const openTag: TemplateLiteral['elements'] = [`<${node.tag}`]
|
|
|
+ // some tags need to be pasesd to runtime for special checks
|
|
|
+ const needTagForRuntime =
|
|
|
+ node.tag === 'textarea' || node.tag.indexOf('-') > 0
|
|
|
|
|
|
- if (node.tag === 'textarea') {
|
|
|
+ // v-bind="obj" or v-bind:[key] can potentially overwrite other static
|
|
|
+ // attrs and can affect final rendering result, so when they are present
|
|
|
+ // we need to bail out to full `renderAttrs`
|
|
|
+ const hasDynamicVBind = hasDynamicKeyVBind(node)
|
|
|
+ if (hasDynamicVBind) {
|
|
|
+ const { props } = buildProps(node, context, node.props, true /* ssr */)
|
|
|
+ if (props) {
|
|
|
+ const propsExp = createCallExpression(
|
|
|
+ context.helper(SSR_RENDER_ATTRS),
|
|
|
+ [props]
|
|
|
+ )
|
|
|
+
|
|
|
+ if (node.tag === 'textarea') {
|
|
|
+ const existingText = node.children[0] as
|
|
|
+ | TextNode
|
|
|
+ | InterpolationNode
|
|
|
+ | undefined
|
|
|
+ // If interpolation, this is dynamic <textarea> content, potentially
|
|
|
+ // injected by v-model and takes higher priority than v-bind value
|
|
|
+ if (!existingText || existingText.type !== NodeTypes.INTERPOLATION) {
|
|
|
// <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
|
|
|
@@ -88,7 +99,6 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
|
|
props
|
|
|
)
|
|
|
]
|
|
|
- const existingText = node.children[0] as TextNode | undefined
|
|
|
rawChildrenMap.set(
|
|
|
node,
|
|
|
createCallExpression(context.helper(SSR_INTERPOLATE), [
|
|
|
@@ -103,189 +113,189 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
|
|
)
|
|
|
])
|
|
|
)
|
|
|
- } 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 = [
|
|
|
- createSequenceExpression([
|
|
|
- createAssignmentExpression(tempExp, props),
|
|
|
- createCallExpression(context.helper(MERGE_PROPS), [
|
|
|
- tempExp,
|
|
|
- createCallExpression(
|
|
|
- context.helper(SSR_GET_DYNAMIC_MODEL_PROPS),
|
|
|
- [
|
|
|
- tempExp, // existing props
|
|
|
- vModel.exp! // model
|
|
|
- ]
|
|
|
- )
|
|
|
- ])
|
|
|
- ])
|
|
|
- ]
|
|
|
- }
|
|
|
}
|
|
|
-
|
|
|
- if (needTagForRuntime) {
|
|
|
- propsExp.arguments.push(`"${node.tag}"`)
|
|
|
+ } 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 = [
|
|
|
+ createSequenceExpression([
|
|
|
+ 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)
|
|
|
+ if (needTagForRuntime) {
|
|
|
+ propsExp.arguments.push(`"${node.tag}"`)
|
|
|
}
|
|
|
+
|
|
|
+ openTag.push(propsExp)
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // book keeping static/dynamic class merging.
|
|
|
- let dynamicClassBinding: CallExpression | undefined = undefined
|
|
|
- let staticClassBinding: string | undefined = undefined
|
|
|
- // all style bindings are converted to dynamic by transformStyle.
|
|
|
- // but we need to make sure to merge them.
|
|
|
- let dynamicStyleBinding: CallExpression | undefined = undefined
|
|
|
+ // book keeping static/dynamic class merging.
|
|
|
+ let dynamicClassBinding: CallExpression | undefined = undefined
|
|
|
+ let staticClassBinding: string | undefined = undefined
|
|
|
+ // all style bindings are converted to dynamic by transformStyle.
|
|
|
+ // but we need to make sure to merge them.
|
|
|
+ let dynamicStyleBinding: CallExpression | undefined = undefined
|
|
|
|
|
|
- for (let i = 0; i < node.props.length; i++) {
|
|
|
- const prop = node.props[i]
|
|
|
- // special cases with children override
|
|
|
- if (prop.type === NodeTypes.DIRECTIVE) {
|
|
|
- if (prop.name === 'html' && prop.exp) {
|
|
|
- rawChildrenMap.set(node, prop.exp)
|
|
|
- } else if (prop.name === 'text' && prop.exp) {
|
|
|
+ for (let i = 0; i < node.props.length; i++) {
|
|
|
+ const prop = node.props[i]
|
|
|
+ // special cases with children override
|
|
|
+ if (prop.type === NodeTypes.DIRECTIVE) {
|
|
|
+ if (prop.name === 'html' && prop.exp) {
|
|
|
+ rawChildrenMap.set(node, prop.exp)
|
|
|
+ } else if (prop.name === 'text' && prop.exp) {
|
|
|
+ node.children = [createInterpolation(prop.exp, prop.loc)]
|
|
|
+ } else if (prop.name === 'slot') {
|
|
|
+ context.onError(
|
|
|
+ createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, prop.loc)
|
|
|
+ )
|
|
|
+ } else if (isTextareaWithValue(node, prop) && prop.exp) {
|
|
|
+ if (!hasDynamicVBind) {
|
|
|
node.children = [createInterpolation(prop.exp, prop.loc)]
|
|
|
- } else if (prop.name === 'slot') {
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // Directive transforms.
|
|
|
+ const directiveTransform = context.directiveTransforms[prop.name]
|
|
|
+ if (!directiveTransform) {
|
|
|
+ // no corresponding ssr directive transform found.
|
|
|
context.onError(
|
|
|
- createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, prop.loc)
|
|
|
+ createSSRCompilerError(
|
|
|
+ SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM,
|
|
|
+ prop.loc
|
|
|
+ )
|
|
|
+ )
|
|
|
+ } else if (!hasDynamicVBind) {
|
|
|
+ const { props, ssrTagParts } = directiveTransform(
|
|
|
+ prop,
|
|
|
+ node,
|
|
|
+ context
|
|
|
)
|
|
|
- } else if (isTextareaWithValue(node, prop) && prop.exp) {
|
|
|
- if (!hasDynamicVBind) {
|
|
|
- node.children = [createInterpolation(prop.exp, prop.loc)]
|
|
|
+ if (ssrTagParts) {
|
|
|
+ openTag.push(...ssrTagParts)
|
|
|
}
|
|
|
- } else {
|
|
|
- // Directive transforms.
|
|
|
- const directiveTransform = context.directiveTransforms[prop.name]
|
|
|
- if (!directiveTransform) {
|
|
|
- // no corresponding ssr directive transform found.
|
|
|
- context.onError(
|
|
|
- createSSRCompilerError(
|
|
|
- SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM,
|
|
|
- prop.loc
|
|
|
- )
|
|
|
- )
|
|
|
- } else if (!hasDynamicVBind) {
|
|
|
- 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) {
|
|
|
- let attrName = key.content
|
|
|
- // static key attr
|
|
|
- if (attrName === 'class') {
|
|
|
+ for (let j = 0; j < props.length; j++) {
|
|
|
+ const { key, value } = props[j]
|
|
|
+ if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
|
|
|
+ let attrName = key.content
|
|
|
+ // static key attr
|
|
|
+ if (attrName === 'class') {
|
|
|
+ openTag.push(
|
|
|
+ ` class="`,
|
|
|
+ (dynamicClassBinding = createCallExpression(
|
|
|
+ context.helper(SSR_RENDER_CLASS),
|
|
|
+ [value]
|
|
|
+ )),
|
|
|
+ `"`
|
|
|
+ )
|
|
|
+ } else if (attrName === 'style') {
|
|
|
+ if (dynamicStyleBinding) {
|
|
|
+ // already has style binding, merge into it.
|
|
|
+ mergeCall(dynamicStyleBinding, value)
|
|
|
+ } else {
|
|
|
openTag.push(
|
|
|
- ` class="`,
|
|
|
- (dynamicClassBinding = createCallExpression(
|
|
|
- context.helper(SSR_RENDER_CLASS),
|
|
|
+ ` style="`,
|
|
|
+ (dynamicStyleBinding = createCallExpression(
|
|
|
+ context.helper(SSR_RENDER_STYLE),
|
|
|
[value]
|
|
|
)),
|
|
|
`"`
|
|
|
)
|
|
|
- } else if (attrName === 'style') {
|
|
|
- if (dynamicStyleBinding) {
|
|
|
- // already has style binding, merge into it.
|
|
|
- mergeCall(dynamicStyleBinding, value)
|
|
|
- } else {
|
|
|
- openTag.push(
|
|
|
- ` style="`,
|
|
|
- (dynamicStyleBinding = createCallExpression(
|
|
|
- context.helper(SSR_RENDER_STYLE),
|
|
|
- [value]
|
|
|
- )),
|
|
|
- `"`
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ attrName =
|
|
|
+ node.tag.indexOf('-') > 0
|
|
|
+ ? attrName // preserve raw name on custom elements
|
|
|
+ : propsToAttrMap[attrName] || attrName.toLowerCase()
|
|
|
+ if (isBooleanAttr(attrName)) {
|
|
|
+ openTag.push(
|
|
|
+ createConditionalExpression(
|
|
|
+ value,
|
|
|
+ createSimpleExpression(' ' + attrName, true),
|
|
|
+ createSimpleExpression('', true),
|
|
|
+ false /* no newline */
|
|
|
)
|
|
|
- }
|
|
|
+ )
|
|
|
+ } else if (isSSRSafeAttrName(attrName)) {
|
|
|
+ openTag.push(
|
|
|
+ createCallExpression(context.helper(SSR_RENDER_ATTR), [
|
|
|
+ key,
|
|
|
+ value
|
|
|
+ ])
|
|
|
+ )
|
|
|
} else {
|
|
|
- attrName =
|
|
|
- node.tag.indexOf('-') > 0
|
|
|
- ? attrName // preserve raw name on custom elements
|
|
|
- : propsToAttrMap[attrName] || attrName.toLowerCase()
|
|
|
- if (isBooleanAttr(attrName)) {
|
|
|
- openTag.push(
|
|
|
- createConditionalExpression(
|
|
|
- value,
|
|
|
- createSimpleExpression(' ' + attrName, true),
|
|
|
- createSimpleExpression('', true),
|
|
|
- false /* no newline */
|
|
|
- )
|
|
|
- )
|
|
|
- } else if (isSSRSafeAttrName(attrName)) {
|
|
|
- openTag.push(
|
|
|
- createCallExpression(context.helper(SSR_RENDER_ATTR), [
|
|
|
- key,
|
|
|
- value
|
|
|
- ])
|
|
|
- )
|
|
|
- } else {
|
|
|
- context.onError(
|
|
|
- createSSRCompilerError(
|
|
|
- SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME,
|
|
|
- key.loc
|
|
|
- )
|
|
|
+ context.onError(
|
|
|
+ createSSRCompilerError(
|
|
|
+ SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME,
|
|
|
+ key.loc
|
|
|
)
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- // dynamic key attr
|
|
|
- // this branch is only encountered for custom directive
|
|
|
- // transforms that returns properties with dynamic keys
|
|
|
- const args: CallExpression['arguments'] = [key, value]
|
|
|
- if (needTagForRuntime) {
|
|
|
- args.push(`"${node.tag}"`)
|
|
|
- }
|
|
|
- openTag.push(
|
|
|
- createCallExpression(
|
|
|
- context.helper(SSR_RENDER_DYNAMIC_ATTR),
|
|
|
- args
|
|
|
)
|
|
|
- )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // dynamic key attr
|
|
|
+ // this branch is only encountered for custom directive
|
|
|
+ // transforms that returns properties with dynamic keys
|
|
|
+ const args: CallExpression['arguments'] = [key, value]
|
|
|
+ if (needTagForRuntime) {
|
|
|
+ args.push(`"${node.tag}"`)
|
|
|
}
|
|
|
+ openTag.push(
|
|
|
+ createCallExpression(
|
|
|
+ context.helper(SSR_RENDER_DYNAMIC_ATTR),
|
|
|
+ args
|
|
|
+ )
|
|
|
+ )
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- } else {
|
|
|
- // special case: value on <textarea>
|
|
|
- if (node.tag === 'textarea' && prop.name === 'value' && prop.value) {
|
|
|
- rawChildrenMap.set(node, escapeHtml(prop.value.content))
|
|
|
- } else if (!hasDynamicVBind) {
|
|
|
- // static prop
|
|
|
- if (prop.name === 'class' && prop.value) {
|
|
|
- staticClassBinding = JSON.stringify(prop.value.content)
|
|
|
- }
|
|
|
- openTag.push(
|
|
|
- ` ${prop.name}` +
|
|
|
- (prop.value ? `="${escapeHtml(prop.value.content)}"` : ``)
|
|
|
- )
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // special case: value on <textarea>
|
|
|
+ if (node.tag === 'textarea' && prop.name === 'value' && prop.value) {
|
|
|
+ rawChildrenMap.set(node, escapeHtml(prop.value.content))
|
|
|
+ } else if (!hasDynamicVBind) {
|
|
|
+ // static prop
|
|
|
+ if (prop.name === 'class' && prop.value) {
|
|
|
+ staticClassBinding = JSON.stringify(prop.value.content)
|
|
|
}
|
|
|
+ openTag.push(
|
|
|
+ ` ${prop.name}` +
|
|
|
+ (prop.value ? `="${escapeHtml(prop.value.content)}"` : ``)
|
|
|
+ )
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // handle co-existence of dynamic + static class bindings
|
|
|
- if (dynamicClassBinding && staticClassBinding) {
|
|
|
- mergeCall(dynamicClassBinding, staticClassBinding)
|
|
|
- removeStaticBinding(openTag, 'class')
|
|
|
- }
|
|
|
-
|
|
|
- if (context.scopeId) {
|
|
|
- openTag.push(` ${context.scopeId}`)
|
|
|
- }
|
|
|
+ // handle co-existence of dynamic + static class bindings
|
|
|
+ if (dynamicClassBinding && staticClassBinding) {
|
|
|
+ mergeCall(dynamicClassBinding, staticClassBinding)
|
|
|
+ removeStaticBinding(openTag, 'class')
|
|
|
+ }
|
|
|
|
|
|
- node.ssrCodegenNode = createTemplateLiteral(openTag)
|
|
|
+ if (context.scopeId) {
|
|
|
+ openTag.push(` ${context.scopeId}`)
|
|
|
}
|
|
|
+
|
|
|
+ node.ssrCodegenNode = createTemplateLiteral(openTag)
|
|
|
}
|
|
|
}
|
|
|
|