feat(ssr): support custom directive getSSRProps in optimized compilation

close #5304
This commit is contained in:
Evan You
2022-02-04 08:58:28 +08:00
parent a51f935b72
commit 60cf175d88
14 changed files with 228 additions and 45 deletions

View File

@@ -17,6 +17,7 @@ export const SSR_RENDER_DYNAMIC_MODEL = Symbol(`ssrRenderDynamicModel`)
export const SSR_GET_DYNAMIC_MODEL_PROPS = Symbol(`ssrGetDynamicModelProps`)
export const SSR_RENDER_TELEPORT = Symbol(`ssrRenderTeleport`)
export const SSR_RENDER_SUSPENSE = Symbol(`ssrRenderSuspense`)
export const SSR_GET_DIRECTIVE_PROPS = Symbol(`ssrGetDirectiveProps`)
export const ssrHelpers = {
[SSR_INTERPOLATE]: `ssrInterpolate`,
@@ -35,7 +36,8 @@ export const ssrHelpers = {
[SSR_RENDER_DYNAMIC_MODEL]: `ssrRenderDynamicModel`,
[SSR_GET_DYNAMIC_MODEL_PROPS]: `ssrGetDynamicModelProps`,
[SSR_RENDER_TELEPORT]: `ssrRenderTeleport`,
[SSR_RENDER_SUSPENSE]: `ssrRenderSuspense`
[SSR_RENDER_SUSPENSE]: `ssrRenderSuspense`,
[SSR_GET_DIRECTIVE_PROPS]: `ssrGetDirectiveProps`
}
// Note: these are helpers imported from @vue/server-renderer

View File

@@ -33,7 +33,8 @@ import {
TELEPORT,
TRANSITION_GROUP,
CREATE_VNODE,
CallExpression
CallExpression,
JSChildNode
} from '@vue/compiler-dom'
import { SSR_RENDER_COMPONENT, SSR_RENDER_VNODE } from '../runtimeHelpers'
import {
@@ -48,6 +49,7 @@ import {
} from './ssrTransformSuspense'
import { ssrProcessTransitionGroup } from './ssrTransformTransitionGroup'
import { isSymbol, isObject, isArray } from '@vue/shared'
import { buildSSRProps } from './ssrTransformElement'
// We need to construct the slot functions in the 1st pass to ensure proper
// scope tracking, but the children of each slot cannot be processed until
@@ -110,12 +112,15 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
})
}
const props =
node.props.length > 0
? // note we are not passing ssr: true here because for components, v-on
// handlers should still be passed
buildProps(node, context).props || `null`
: `null`
let propsExp: string | JSChildNode = `null`
if (node.props.length) {
// note we are not passing ssr: true here because for components, v-on
// handlers should still be passed
const { props, directives } = buildProps(node, context)
if (props || directives.length) {
propsExp = buildSSRProps(props, directives, context)
}
}
const wipEntries: WIPSlotEntry[] = []
wipMap.set(node, wipEntries)
@@ -151,7 +156,7 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
`_push`,
createCallExpression(context.helper(CREATE_VNODE), [
component,
props,
propsExp,
slots
]),
`_parent`
@@ -160,7 +165,7 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
} else {
node.ssrCodegenNode = createCallExpression(
context.helper(SSR_RENDER_COMPONENT),
[component, props, slots, `_parent`]
[component, propsExp, slots, `_parent`]
)
}
}

View File

@@ -26,11 +26,15 @@ import {
createSequenceExpression,
InterpolationNode,
isStaticExp,
AttributeNode
AttributeNode,
buildDirectiveArgs,
TransformContext,
PropsExpression
} from '@vue/compiler-dom'
import {
escapeHtml,
isBooleanAttr,
isBuiltInDirective,
isSSRSafeAttrName,
NO,
propsToAttrMap
@@ -44,7 +48,8 @@ import {
SSR_RENDER_ATTRS,
SSR_INTERPOLATE,
SSR_GET_DYNAMIC_MODEL_PROPS,
SSR_INCLUDE_BOOLEAN_ATTR
SSR_INCLUDE_BOOLEAN_ATTR,
SSR_GET_DIRECTIVE_PROPS
} from '../runtimeHelpers'
import { SSRTransformContext, processChildren } from '../ssrCodegenTransform'
@@ -71,16 +76,26 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
const needTagForRuntime =
node.tag === 'textarea' || node.tag.indexOf('-') > 0
// 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`
// v-bind="obj", v-bind:[key] and custom directives 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 hasCustomDir = node.props.some(
p => p.type === NodeTypes.DIRECTIVE && !isBuiltInDirective(p.name)
)
const needMergeProps = hasDynamicVBind || hasCustomDir
if (needMergeProps) {
const { props, directives } = buildProps(
node,
context,
node.props,
true /* ssr */
)
if (props || directives.length) {
const mergedProps = buildSSRProps(props, directives, context)
const propsExp = createCallExpression(
context.helper(SSR_RENDER_ATTRS),
[props]
[mergedProps]
)
if (node.tag === 'textarea') {
@@ -99,7 +114,7 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
propsExp.arguments = [
createAssignmentExpression(
createSimpleExpression(tempId, false),
props
mergedProps
)
]
rawChildrenMap.set(
@@ -128,7 +143,7 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
const tempExp = createSimpleExpression(tempId, false)
propsExp.arguments = [
createSequenceExpression([
createAssignmentExpression(tempExp, props),
createAssignmentExpression(tempExp, mergedProps),
createCallExpression(context.helper(MERGE_PROPS), [
tempExp,
createCallExpression(
@@ -176,10 +191,10 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, prop.loc)
)
} else if (isTextareaWithValue(node, prop) && prop.exp) {
if (!hasDynamicVBind) {
if (!needMergeProps) {
node.children = [createInterpolation(prop.exp, prop.loc)]
}
} else if (!hasDynamicVBind) {
} else if (!needMergeProps) {
// Directive transforms.
const directiveTransform = context.directiveTransforms[prop.name]
if (directiveTransform) {
@@ -277,7 +292,7 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
// special case: value on <textarea>
if (node.tag === 'textarea' && prop.name === 'value' && prop.value) {
rawChildrenMap.set(node, escapeHtml(prop.value.content))
} else if (!hasDynamicVBind) {
} else if (!needMergeProps) {
if (prop.name === 'key' || prop.name === 'ref') {
continue
}
@@ -307,6 +322,37 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
}
}
export function buildSSRProps(
props: PropsExpression | undefined,
directives: DirectiveNode[],
context: TransformContext
): JSChildNode {
let mergePropsArgs: JSChildNode[] = []
if (props) {
if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
// already a mergeProps call
mergePropsArgs = props.arguments as JSChildNode[]
} else {
mergePropsArgs.push(props)
}
}
if (directives.length) {
for (const dir of directives) {
context.directives.add(dir.name)
mergePropsArgs.push(
createCallExpression(context.helper(SSR_GET_DIRECTIVE_PROPS), [
`_ctx`,
...buildDirectiveArgs(dir, context).elements
] as JSChildNode[])
)
}
}
return mergePropsArgs.length > 1
? createCallExpression(context.helper(MERGE_PROPS), mergePropsArgs)
: mergePropsArgs[0]
}
function isTrueFalseValue(prop: DirectiveNode | AttributeNode) {
if (prop.type === NodeTypes.DIRECTIVE) {
return (