Skip to content

Commit

Permalink
feat(ssr): support custom directive getSSRProps in optimized compilation
Browse files Browse the repository at this point in the history
close #5304
  • Loading branch information
yyx990803 committed Feb 4, 2022
1 parent a51f935 commit 60cf175
Show file tree
Hide file tree
Showing 14 changed files with 228 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
ErrorCodes,
BindingTypes,
NodeTransform,
transformExpression
transformExpression,
baseCompile
} from '../../src'
import {
RESOLVE_COMPONENT,
Expand Down Expand Up @@ -66,6 +67,7 @@ function parseWithBind(template: string, options?: CompilerOptions) {
return parseWithElementTransform(template, {
...options,
directiveTransforms: {
...options?.directiveTransforms,
bind: transformBind
}
})
Expand Down Expand Up @@ -932,7 +934,11 @@ describe('compiler: element transform', () => {
})

test('NEED_PATCH (vnode hooks)', () => {
const { node } = parseWithBind(`<div @vnodeUpdated="foo" />`)
const root = baseCompile(`<div @vnodeUpdated="foo" />`, {
prefixIdentifiers: true,
cacheHandlers: true
}).ast
const node = (root as any).children[0].codegenNode
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
})

Expand Down
4 changes: 3 additions & 1 deletion packages/compiler-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ export {
export {
transformElement,
resolveComponentType,
buildProps
buildProps,
buildDirectiveArgs,
PropsExpression
} from './transforms/transformElement'
export { processSlotOutlet } from './transforms/transformSlotOutlet'
export { generateCodeFrame } from '@vue/shared'
Expand Down
7 changes: 4 additions & 3 deletions packages/compiler-core/src/transforms/transformElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ import {
isObject,
isReservedProp,
capitalize,
camelize
camelize,
isBuiltInDirective
} from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import {
Expand Down Expand Up @@ -665,7 +666,7 @@ export function buildProps(
directiveImportMap.set(prop, needRuntime)
}
}
} else {
} else if (!isBuiltInDirective(name)) {
// no built-in transform, this is a user custom directive.
runtimeDirectives.push(prop)
// custom dirs may use beforeUpdate so they need to force blocks
Expand Down Expand Up @@ -853,7 +854,7 @@ function mergeAsArray(existing: Property, incoming: Property) {
}
}

function buildDirectiveArgs(
export function buildDirectiveArgs(
dir: DirectiveNode,
context: TransformContext
): ArrayExpression {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,11 @@ exports[`compiler: transform v-model input w/ dynamic v-bind 2`] = `
return function render(_ctx, _cache) {
with (_ctx) {
const { vModelDynamic: _vModelDynamic, resolveDirective: _resolveDirective, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
const _directive_bind = _resolveDirective(\\"bind\\")
const { vModelDynamic: _vModelDynamic, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return _withDirectives((_openBlock(), _createElementBlock(\\"input\\", {
\\"onUpdate:modelValue\\": $event => ((model) = $event)
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
[_directive_bind, val, key],
[_vModelDynamic, model]
])
}
Expand Down Expand Up @@ -152,14 +149,11 @@ exports[`compiler: transform v-model simple expression for input (dynamic type)
return function render(_ctx, _cache) {
with (_ctx) {
const { vModelDynamic: _vModelDynamic, resolveDirective: _resolveDirective, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
const _directive_bind = _resolveDirective(\\"bind\\")
const { vModelDynamic: _vModelDynamic, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return _withDirectives((_openBlock(), _createElementBlock(\\"input\\", {
\\"onUpdate:modelValue\\": $event => ((model) = $event)
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
[_directive_bind, foo, \\"type\\"],
[_vModelDynamic, model]
])
}
Expand Down
16 changes: 16 additions & 0 deletions packages/compiler-ssr/__tests__/ssrComponent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}"
`)
})
})
})
51 changes: 51 additions & 0 deletions packages/compiler-ssr/__tests__/ssrElement.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>\`"
`)
})
})
})
4 changes: 3 additions & 1 deletion packages/compiler-ssr/src/runtimeHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand All @@ -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
Expand Down
23 changes: 14 additions & 9 deletions packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -151,7 +156,7 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
`_push`,
createCallExpression(context.helper(CREATE_VNODE), [
component,
props,
propsExp,
slots
]),
`_parent`
Expand All @@ -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`]
)
}
}
Expand Down
74 changes: 60 additions & 14 deletions packages/compiler-ssr/src/transforms/ssrTransformElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@ import {
createSequenceExpression,
InterpolationNode,
isStaticExp,
AttributeNode
AttributeNode,
buildDirectiveArgs,
TransformContext,
PropsExpression
} from '@vue/compiler-dom'
import {
escapeHtml,
isBooleanAttr,
isBuiltInDirective,
isSSRSafeAttrName,
NO,
propsToAttrMap
Expand All @@ -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'

Expand All @@ -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') {
Expand All @@ -99,7 +114,7 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
propsExp.arguments = [
createAssignmentExpression(
createSimpleExpression(tempId, false),
props
mergedProps
)
]
rawChildrenMap.set(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 (
Expand Down
Loading

0 comments on commit 60cf175

Please sign in to comment.