feat(ssr): support custom directive getSSRProps in optimized compilation
close #5304
This commit is contained in:
@@ -377,4 +377,20 @@ describe('ssr: components', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('custom directive', () => {
|
||||
test('basic', () => {
|
||||
expect(compile(`<foo v-xxx:x.y="z" />`).code).toMatchInlineSnapshot(`
|
||||
"const { resolveComponent: _resolveComponent, resolveDirective: _resolveDirective, mergeProps: _mergeProps } = require(\\"vue\\")
|
||||
const { ssrGetDirectiveProps: _ssrGetDirectiveProps, ssrRenderComponent: _ssrRenderComponent } = require(\\"vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
const _component_foo = _resolveComponent(\\"foo\\")
|
||||
const _directive_xxx = _resolveDirective(\\"xxx\\")
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, _mergeProps(_attrs, _ssrGetDirectiveProps(_ctx, _directive_xxx, _ctx.z, \\"x\\", { y: true })), null, _parent))
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -288,5 +288,56 @@ describe('ssr: element', () => {
|
||||
}></div>\`"
|
||||
`)
|
||||
})
|
||||
|
||||
test('custom dir', () => {
|
||||
expect(getCompiledString(`<div v-xxx:x.y="z" />`)).toMatchInlineSnapshot(`
|
||||
"\`<div\${
|
||||
_ssrRenderAttrs(_ssrGetDirectiveProps(_ctx, _directive_xxx, _ctx.z, \\"x\\", { y: true }))
|
||||
}></div>\`"
|
||||
`)
|
||||
})
|
||||
|
||||
test('custom dir with normal attrs', () => {
|
||||
expect(getCompiledString(`<div class="foo" v-xxx />`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"\`<div\${
|
||||
_ssrRenderAttrs(_mergeProps({ class: \\"foo\\" }, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
|
||||
}></div>\`"
|
||||
`)
|
||||
})
|
||||
|
||||
test('custom dir with v-bind', () => {
|
||||
expect(getCompiledString(`<div :title="foo" :class="bar" v-xxx />`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"\`<div\${
|
||||
_ssrRenderAttrs(_mergeProps({
|
||||
title: _ctx.foo,
|
||||
class: _ctx.bar
|
||||
}, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
|
||||
}></div>\`"
|
||||
`)
|
||||
})
|
||||
|
||||
test('custom dir with object v-bind', () => {
|
||||
expect(getCompiledString(`<div v-bind="x" v-xxx />`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"\`<div\${
|
||||
_ssrRenderAttrs(_mergeProps(_ctx.x, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
|
||||
}></div>\`"
|
||||
`)
|
||||
})
|
||||
|
||||
test('custom dir with object v-bind + normal bindings', () => {
|
||||
expect(
|
||||
getCompiledString(`<div v-bind="x" class="foo" v-xxx title="bar" />`)
|
||||
).toMatchInlineSnapshot(`
|
||||
"\`<div\${
|
||||
_ssrRenderAttrs(_mergeProps(_ctx.x, {
|
||||
class: \\"foo\\",
|
||||
title: \\"bar\\"
|
||||
}, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
|
||||
}></div>\`"
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user