wip(compiler-ssr): dynamic v-bind + class/style merging
This commit is contained in:
parent
c059fc88b9
commit
ebf920e6af
@ -29,7 +29,6 @@ export interface ParserOptions {
|
|||||||
export interface TransformOptions {
|
export interface TransformOptions {
|
||||||
nodeTransforms?: NodeTransform[]
|
nodeTransforms?: NodeTransform[]
|
||||||
directiveTransforms?: Record<string, DirectiveTransform | undefined>
|
directiveTransforms?: Record<string, DirectiveTransform | undefined>
|
||||||
ssrDirectiveTransforms?: Record<string, DirectiveTransform | undefined>
|
|
||||||
isBuiltInComponent?: (tag: string) => symbol | void
|
isBuiltInComponent?: (tag: string) => symbol | void
|
||||||
// Transform expressions like {{ foo }} to `_ctx.foo`.
|
// Transform expressions like {{ foo }} to `_ctx.foo`.
|
||||||
// If this option is false, the generated code will be wrapped in a
|
// If this option is false, the generated code will be wrapped in a
|
||||||
|
@ -114,7 +114,6 @@ function createTransformContext(
|
|||||||
cacheHandlers = false,
|
cacheHandlers = false,
|
||||||
nodeTransforms = [],
|
nodeTransforms = [],
|
||||||
directiveTransforms = {},
|
directiveTransforms = {},
|
||||||
ssrDirectiveTransforms = {},
|
|
||||||
isBuiltInComponent = NOOP,
|
isBuiltInComponent = NOOP,
|
||||||
ssr = false,
|
ssr = false,
|
||||||
onError = defaultOnError
|
onError = defaultOnError
|
||||||
@ -127,7 +126,6 @@ function createTransformContext(
|
|||||||
cacheHandlers,
|
cacheHandlers,
|
||||||
nodeTransforms,
|
nodeTransforms,
|
||||||
directiveTransforms,
|
directiveTransforms,
|
||||||
ssrDirectiveTransforms,
|
|
||||||
isBuiltInComponent,
|
isBuiltInComponent,
|
||||||
ssr,
|
ssr,
|
||||||
onError,
|
onError,
|
||||||
|
@ -233,7 +233,8 @@ export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
|
|||||||
export function buildProps(
|
export function buildProps(
|
||||||
node: ElementNode,
|
node: ElementNode,
|
||||||
context: TransformContext,
|
context: TransformContext,
|
||||||
props: ElementNode['props'] = node.props
|
props: ElementNode['props'] = node.props,
|
||||||
|
ssr = false
|
||||||
): {
|
): {
|
||||||
props: PropsExpression | undefined
|
props: PropsExpression | undefined
|
||||||
directives: DirectiveNode[]
|
directives: DirectiveNode[]
|
||||||
@ -320,9 +321,15 @@ export function buildProps(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// special case for v-bind and v-on with no argument
|
|
||||||
const isBind = name === 'bind'
|
const isBind = name === 'bind'
|
||||||
const isOn = name === 'on'
|
const isOn = name === 'on'
|
||||||
|
|
||||||
|
// skip v-on in SSR compilation
|
||||||
|
if (ssr && isOn) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// special case for v-bind and v-on with no argument
|
||||||
if (!arg && (isBind || isOn)) {
|
if (!arg && (isBind || isOn)) {
|
||||||
hasDynamicKeys = true
|
hasDynamicKeys = true
|
||||||
if (exp) {
|
if (exp) {
|
||||||
@ -360,7 +367,7 @@ export function buildProps(
|
|||||||
if (directiveTransform) {
|
if (directiveTransform) {
|
||||||
// has built-in directive transform.
|
// has built-in directive transform.
|
||||||
const { props, needRuntime } = directiveTransform(prop, node, context)
|
const { props, needRuntime } = directiveTransform(prop, node, context)
|
||||||
props.forEach(analyzePatchFlag)
|
!ssr && props.forEach(analyzePatchFlag)
|
||||||
properties.push(...props)
|
properties.push(...props)
|
||||||
if (needRuntime) {
|
if (needRuntime) {
|
||||||
runtimeDirectives.push(prop)
|
runtimeDirectives.push(prop)
|
||||||
@ -446,12 +453,7 @@ function dedupeProperties(properties: Property[]): Property[] {
|
|||||||
const name = prop.key.content
|
const name = prop.key.content
|
||||||
const existing = knownProps.get(name)
|
const existing = knownProps.get(name)
|
||||||
if (existing) {
|
if (existing) {
|
||||||
if (
|
if (name === 'style' || name === 'class' || name.startsWith('on')) {
|
||||||
name === 'style' ||
|
|
||||||
name === 'class' ||
|
|
||||||
name.startsWith('on') ||
|
|
||||||
name.startsWith('vnode')
|
|
||||||
) {
|
|
||||||
mergeAsArray(existing, prop)
|
mergeAsArray(existing, prop)
|
||||||
}
|
}
|
||||||
// unexpected duplicate, should have emitted error during parse
|
// unexpected duplicate, should have emitted error during parse
|
||||||
|
@ -56,5 +56,6 @@ export function parse(template: string, options: ParserOptions = {}): RootNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { transformStyle } from './transforms/transformStyle'
|
||||||
export { DOMErrorCodes } from './errors'
|
export { DOMErrorCodes } from './errors'
|
||||||
export * from '@vue/compiler-core'
|
export * from '@vue/compiler-core'
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
NodeTransform,
|
NodeTransform,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
createSimpleExpression
|
createSimpleExpression,
|
||||||
|
SimpleExpressionNode,
|
||||||
|
SourceLocation
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
|
|
||||||
// Parse inline CSS strings for static style attributes into an object.
|
// Parse inline CSS strings for static style attributes into an object.
|
||||||
@ -15,8 +17,7 @@ export const transformStyle: NodeTransform = (node, context) => {
|
|||||||
node.props.forEach((p, i) => {
|
node.props.forEach((p, i) => {
|
||||||
if (p.type === NodeTypes.ATTRIBUTE && p.name === 'style' && p.value) {
|
if (p.type === NodeTypes.ATTRIBUTE && p.name === 'style' && p.value) {
|
||||||
// replace p with an expression node
|
// replace p with an expression node
|
||||||
const parsed = JSON.stringify(parseInlineCSS(p.value.content))
|
const exp = context.hoist(parseInlineCSS(p.value.content, p.loc))
|
||||||
const exp = context.hoist(createSimpleExpression(parsed, false, p.loc))
|
|
||||||
node.props[i] = {
|
node.props[i] = {
|
||||||
type: NodeTypes.DIRECTIVE,
|
type: NodeTypes.DIRECTIVE,
|
||||||
name: `bind`,
|
name: `bind`,
|
||||||
@ -33,7 +34,10 @@ export const transformStyle: NodeTransform = (node, context) => {
|
|||||||
const listDelimiterRE = /;(?![^(]*\))/g
|
const listDelimiterRE = /;(?![^(]*\))/g
|
||||||
const propertyDelimiterRE = /:(.+)/
|
const propertyDelimiterRE = /:(.+)/
|
||||||
|
|
||||||
function parseInlineCSS(cssText: string): Record<string, string> {
|
function parseInlineCSS(
|
||||||
|
cssText: string,
|
||||||
|
loc: SourceLocation
|
||||||
|
): SimpleExpressionNode {
|
||||||
const res: Record<string, string> = {}
|
const res: Record<string, string> = {}
|
||||||
cssText.split(listDelimiterRE).forEach(item => {
|
cssText.split(listDelimiterRE).forEach(item => {
|
||||||
if (item) {
|
if (item) {
|
||||||
@ -41,5 +45,5 @@ function parseInlineCSS(cssText: string): Record<string, string> {
|
|||||||
tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim())
|
tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return res
|
return createSimpleExpression(JSON.stringify(res), false, loc)
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,10 @@ describe('ssr: element', () => {
|
|||||||
getCompiledString(`<textarea value="fo>o"/>`)
|
getCompiledString(`<textarea value="fo>o"/>`)
|
||||||
).toMatchInlineSnapshot(`"\`<textarea>fo>o</textarea>\`"`)
|
).toMatchInlineSnapshot(`"\`<textarea>fo>o</textarea>\`"`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('<textarea> with dynamic v-bind', () => {
|
||||||
|
// TODO
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('attrs', () => {
|
describe('attrs', () => {
|
||||||
@ -63,6 +67,14 @@ describe('ssr: element', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('static class + v-bind:class', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString(`<div class="foo" :class="bar"></div>`)
|
||||||
|
).toMatchInlineSnapshot(
|
||||||
|
`"\`<div\${_renderClass([_ctx.bar, \\"foo\\"])}></div>\`"`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test('v-bind:style', () => {
|
test('v-bind:style', () => {
|
||||||
expect(
|
expect(
|
||||||
getCompiledString(`<div id="foo" :style="bar"></div>`)
|
getCompiledString(`<div id="foo" :style="bar"></div>`)
|
||||||
@ -71,6 +83,14 @@ describe('ssr: element', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('static style + v-bind:style', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString(`<div style="color:red;" :style="bar"></div>`)
|
||||||
|
).toMatchInlineSnapshot(
|
||||||
|
`"\`<div\${_renderStyle([_hoisted_1, _ctx.bar])}></div>\`"`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test('v-bind:key (boolean)', () => {
|
test('v-bind:key (boolean)', () => {
|
||||||
expect(
|
expect(
|
||||||
getCompiledString(`<input type="checkbox" :checked="checked">`)
|
getCompiledString(`<input type="checkbox" :checked="checked">`)
|
||||||
@ -86,5 +106,85 @@ describe('ssr: element', () => {
|
|||||||
`"\`<div\${_renderAttr(\\"id\\", _ctx.id)} class=\\"bar\\"></div>\`"`
|
`"\`<div\${_renderAttr(\\"id\\", _ctx.id)} class=\\"bar\\"></div>\`"`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('v-bind:[key]', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString(`<div v-bind:[key]="value"></div>`)
|
||||||
|
).toMatchInlineSnapshot(
|
||||||
|
`"\`<div\${_renderAttrs({ [_ctx.key]: _ctx.value })}></div>\`"`
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(getCompiledString(`<div class="foo" v-bind:[key]="value"></div>`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"\`<div\${_renderAttrs({
|
||||||
|
class: \\"foo\\",
|
||||||
|
[_ctx.key]: _ctx.value
|
||||||
|
})}></div>\`"
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(getCompiledString(`<div :id="id" v-bind:[key]="value"></div>`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"\`<div\${_renderAttrs({
|
||||||
|
id: _ctx.id,
|
||||||
|
[_ctx.key]: _ctx.value
|
||||||
|
})}></div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('v-bind="obj"', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString(`<div v-bind="obj"></div>`)
|
||||||
|
).toMatchInlineSnapshot(`"\`<div\${_renderAttrs(_ctx.obj)}></div>\`"`)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
getCompiledString(`<div class="foo" v-bind="obj"></div>`)
|
||||||
|
).toMatchInlineSnapshot(
|
||||||
|
`"\`<div\${_renderAttrs(mergeProps({ class: \\"foo\\" }, _ctx.obj))}></div>\`"`
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
getCompiledString(`<div :id="id" v-bind="obj"></div>`)
|
||||||
|
).toMatchInlineSnapshot(
|
||||||
|
`"\`<div\${_renderAttrs(mergeProps({ id: _ctx.id }, _ctx.obj))}></div>\`"`
|
||||||
|
)
|
||||||
|
|
||||||
|
// dynamic key + v-bind="object"
|
||||||
|
expect(
|
||||||
|
getCompiledString(`<div :[key]="id" v-bind="obj"></div>`)
|
||||||
|
).toMatchInlineSnapshot(
|
||||||
|
`"\`<div\${_renderAttrs(mergeProps({ [_ctx.key]: _ctx.id }, _ctx.obj))}></div>\`"`
|
||||||
|
)
|
||||||
|
|
||||||
|
// should merge class and :class
|
||||||
|
expect(getCompiledString(`<div class="a" :class="b" v-bind="obj"></div>`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"\`<div\${_renderAttrs(mergeProps({
|
||||||
|
class: [\\"a\\", _ctx.b]
|
||||||
|
}, _ctx.obj))}></div>\`"
|
||||||
|
`)
|
||||||
|
|
||||||
|
// should merge style and :style
|
||||||
|
expect(
|
||||||
|
getCompiledString(
|
||||||
|
`<div style="color:red;" :style="b" v-bind="obj"></div>`
|
||||||
|
)
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"\`<div\${_renderAttrs(mergeProps({
|
||||||
|
style: [_hoisted_1, _ctx.b]
|
||||||
|
}, _ctx.obj))}></div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should ignore v-on', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString(`<div id="foo" @click="bar"/>`)
|
||||||
|
).toMatchInlineSnapshot(`"\`<div id=\\"foo\\"></div>\`"`)
|
||||||
|
expect(
|
||||||
|
getCompiledString(`<div id="foo" v-on="bar"/>`)
|
||||||
|
).toMatchInlineSnapshot(`"\`<div id=\\"foo\\"></div>\`"`)
|
||||||
|
expect(
|
||||||
|
getCompiledString(`<div v-bind="foo" v-on="bar"/>`)
|
||||||
|
).toMatchInlineSnapshot(`"\`<div\${_renderAttrs(_ctx.foo)}></div>\`"`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -9,7 +9,8 @@ import {
|
|||||||
trackVForSlotScopes,
|
trackVForSlotScopes,
|
||||||
trackSlotScopes,
|
trackSlotScopes,
|
||||||
noopDirectiveTransform,
|
noopDirectiveTransform,
|
||||||
transformBind
|
transformBind,
|
||||||
|
transformStyle
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { ssrCodegenTransform } from './ssrCodegenTransform'
|
import { ssrCodegenTransform } from './ssrCodegenTransform'
|
||||||
import { ssrTransformElement } from './transforms/ssrTransformElement'
|
import { ssrTransformElement } from './transforms/ssrTransformElement'
|
||||||
@ -49,15 +50,20 @@ export function compile(
|
|||||||
ssrTransformElement,
|
ssrTransformElement,
|
||||||
ssrTransformComponent,
|
ssrTransformComponent,
|
||||||
trackSlotScopes,
|
trackSlotScopes,
|
||||||
|
transformStyle,
|
||||||
...(options.nodeTransforms || []) // user transforms
|
...(options.nodeTransforms || []) // user transforms
|
||||||
],
|
],
|
||||||
ssrDirectiveTransforms: {
|
directiveTransforms: {
|
||||||
on: noopDirectiveTransform,
|
// reusing core v-bind
|
||||||
cloak: noopDirectiveTransform,
|
bind: transformBind,
|
||||||
bind: transformBind, // reusing core v-bind
|
// model and show has dedicated SSR handling
|
||||||
model: ssrTransformModel,
|
model: ssrTransformModel,
|
||||||
show: ssrTransformShow,
|
show: ssrTransformShow,
|
||||||
...(options.ssrDirectiveTransforms || {}) // user transforms
|
// the following are ignored during SSR
|
||||||
|
on: noopDirectiveTransform,
|
||||||
|
cloak: noopDirectiveTransform,
|
||||||
|
once: noopDirectiveTransform,
|
||||||
|
...(options.directiveTransforms || {}) // user transforms
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -7,7 +7,16 @@ import {
|
|||||||
createInterpolation,
|
createInterpolation,
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
createConditionalExpression,
|
createConditionalExpression,
|
||||||
createSimpleExpression
|
createSimpleExpression,
|
||||||
|
buildProps,
|
||||||
|
DirectiveNode,
|
||||||
|
PlainElementNode,
|
||||||
|
createCompilerError,
|
||||||
|
ErrorCodes,
|
||||||
|
CallExpression,
|
||||||
|
createArrayExpression,
|
||||||
|
ExpressionNode,
|
||||||
|
JSChildNode
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { escapeHtml, isBooleanAttr, isSSRSafeAttrName } from '@vue/shared'
|
import { escapeHtml, isBooleanAttr, isSSRSafeAttrName } from '@vue/shared'
|
||||||
import { createSSRCompilerError, SSRErrorCodes } from '../errors'
|
import { createSSRCompilerError, SSRErrorCodes } from '../errors'
|
||||||
@ -15,7 +24,8 @@ import {
|
|||||||
SSR_RENDER_ATTR,
|
SSR_RENDER_ATTR,
|
||||||
SSR_RENDER_CLASS,
|
SSR_RENDER_CLASS,
|
||||||
SSR_RENDER_STYLE,
|
SSR_RENDER_STYLE,
|
||||||
SSR_RENDER_DYNAMIC_ATTR
|
SSR_RENDER_DYNAMIC_ATTR,
|
||||||
|
SSR_RENDER_ATTRS
|
||||||
} from '../runtimeHelpers'
|
} from '../runtimeHelpers'
|
||||||
|
|
||||||
export const ssrTransformElement: NodeTransform = (node, context) => {
|
export const ssrTransformElement: NodeTransform = (node, context) => {
|
||||||
@ -40,11 +50,22 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
|||||||
p.arg.type !== NodeTypes.SIMPLE_EXPRESSION || // v-bind:[_ctx.foo]
|
p.arg.type !== NodeTypes.SIMPLE_EXPRESSION || // v-bind:[_ctx.foo]
|
||||||
!p.arg.isStatic) // v-bind:[foo]
|
!p.arg.isStatic) // v-bind:[foo]
|
||||||
)
|
)
|
||||||
|
|
||||||
if (hasDynamicVBind) {
|
if (hasDynamicVBind) {
|
||||||
// TODO
|
const { props } = buildProps(node, context, node.props, true /* ssr */)
|
||||||
|
if (props) {
|
||||||
|
openTag.push(
|
||||||
|
createCallExpression(context.helper(SSR_RENDER_ATTRS), [props])
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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++) {
|
for (let i = 0; i < node.props.length; i++) {
|
||||||
const prop = node.props[i]
|
const prop = node.props[i]
|
||||||
// special cases with children override
|
// special cases with children override
|
||||||
@ -54,22 +75,28 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
|||||||
rawChildren = prop.exp
|
rawChildren = prop.exp
|
||||||
} else if (prop.name === 'text' && prop.exp) {
|
} else if (prop.name === 'text' && prop.exp) {
|
||||||
node.children = [createInterpolation(prop.exp, prop.loc)]
|
node.children = [createInterpolation(prop.exp, prop.loc)]
|
||||||
} else if (
|
} else if (prop.name === 'slot') {
|
||||||
// v-bind:value on textarea
|
context.onError(
|
||||||
node.tag === 'textarea' &&
|
createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, prop.loc)
|
||||||
prop.name === 'bind' &&
|
)
|
||||||
prop.exp &&
|
} else if (isTextareaWithValue(node, prop) && prop.exp) {
|
||||||
prop.arg &&
|
if (!hasDynamicVBind) {
|
||||||
prop.arg.type === NodeTypes.SIMPLE_EXPRESSION &&
|
node.children = [createInterpolation(prop.exp, prop.loc)]
|
||||||
prop.arg.isStatic &&
|
} else {
|
||||||
prop.arg.content === 'value'
|
// TODO handle <textrea> with dynamic v-bind
|
||||||
) {
|
}
|
||||||
node.children = [createInterpolation(prop.exp, prop.loc)]
|
} else {
|
||||||
// TODO handle <textrea> with dynamic v-bind
|
|
||||||
} else if (!hasDynamicVBind) {
|
|
||||||
// Directive transforms.
|
// Directive transforms.
|
||||||
const directiveTransform = context.ssrDirectiveTransforms[prop.name]
|
const directiveTransform = context.directiveTransforms[prop.name]
|
||||||
if (directiveTransform) {
|
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 } = directiveTransform(prop, node, context)
|
const { props } = directiveTransform(prop, node, context)
|
||||||
for (let j = 0; j < props.length; j++) {
|
for (let j = 0; j < props.length; j++) {
|
||||||
const { key, value } = props[j]
|
const { key, value } = props[j]
|
||||||
@ -78,16 +105,23 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
|||||||
// static key attr
|
// static key attr
|
||||||
if (attrName === 'class') {
|
if (attrName === 'class') {
|
||||||
openTag.push(
|
openTag.push(
|
||||||
createCallExpression(context.helper(SSR_RENDER_CLASS), [
|
(dynamicClassBinding = createCallExpression(
|
||||||
value
|
context.helper(SSR_RENDER_CLASS),
|
||||||
])
|
[value]
|
||||||
|
))
|
||||||
)
|
)
|
||||||
} else if (attrName === 'style') {
|
} else if (attrName === 'style') {
|
||||||
openTag.push(
|
if (dynamicStyleBinding) {
|
||||||
createCallExpression(context.helper(SSR_RENDER_STYLE), [
|
// already has style binding, merge into it.
|
||||||
value
|
mergeCall(dynamicStyleBinding, value)
|
||||||
])
|
} else {
|
||||||
)
|
openTag.push(
|
||||||
|
(dynamicStyleBinding = createCallExpression(
|
||||||
|
context.helper(SSR_RENDER_STYLE),
|
||||||
|
[value]
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
} else if (isBooleanAttr(attrName)) {
|
} else if (isBooleanAttr(attrName)) {
|
||||||
openTag.push(
|
openTag.push(
|
||||||
createConditionalExpression(
|
createConditionalExpression(
|
||||||
@ -126,14 +160,6 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// no corresponding ssr directive transform found.
|
|
||||||
context.onError(
|
|
||||||
createSSRCompilerError(
|
|
||||||
SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM,
|
|
||||||
prop.loc
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -143,6 +169,9 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
|||||||
rawChildren = escapeHtml(prop.value.content)
|
rawChildren = escapeHtml(prop.value.content)
|
||||||
} else if (!hasDynamicVBind) {
|
} else if (!hasDynamicVBind) {
|
||||||
// static prop
|
// static prop
|
||||||
|
if (prop.name === 'class' && prop.value) {
|
||||||
|
staticClassBinding = JSON.stringify(prop.value.content)
|
||||||
|
}
|
||||||
openTag.push(
|
openTag.push(
|
||||||
` ${prop.name}` +
|
` ${prop.name}` +
|
||||||
(prop.value ? `="${escapeHtml(prop.value.content)}"` : ``)
|
(prop.value ? `="${escapeHtml(prop.value.content)}"` : ``)
|
||||||
@ -151,6 +180,12 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle co-existence of dynamic + static class bindings
|
||||||
|
if (dynamicClassBinding && staticClassBinding) {
|
||||||
|
mergeCall(dynamicClassBinding, staticClassBinding)
|
||||||
|
removeStaticBinding(openTag, 'class')
|
||||||
|
}
|
||||||
|
|
||||||
openTag.push(`>`)
|
openTag.push(`>`)
|
||||||
if (rawChildren) {
|
if (rawChildren) {
|
||||||
openTag.push(rawChildren)
|
openTag.push(rawChildren)
|
||||||
@ -159,3 +194,34 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTextareaWithValue(
|
||||||
|
node: PlainElementNode,
|
||||||
|
prop: DirectiveNode
|
||||||
|
): boolean {
|
||||||
|
return !!(
|
||||||
|
node.tag === 'textarea' &&
|
||||||
|
prop.name === 'bind' &&
|
||||||
|
prop.arg &&
|
||||||
|
prop.arg.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
|
prop.arg.isStatic &&
|
||||||
|
prop.arg.content === 'value'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeCall(call: CallExpression, arg: string | JSChildNode) {
|
||||||
|
const existing = call.arguments[0] as ExpressionNode
|
||||||
|
call.arguments[0] = createArrayExpression([existing, arg])
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeStaticBinding(
|
||||||
|
tag: TemplateLiteral['elements'],
|
||||||
|
binding: string
|
||||||
|
) {
|
||||||
|
const i = tag.findIndex(
|
||||||
|
e => typeof e === 'string' && e.startsWith(` ${binding}=`)
|
||||||
|
)
|
||||||
|
if (i > -1) {
|
||||||
|
tag.splice(i, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user