Skip to content

refactor(compiler): add dedicated transform for vbind shorthand #13438

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/compiler-core/__tests__/transforms/vBind.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
helperNameMap,
} from '../../src/runtimeHelpers'
import { transformExpression } from '../../src/transforms/transformExpression'
import { transformVBindShorthand } from '../../src/transforms/transformVBindShorthand'

function parseWithVBind(
template: string,
Expand All @@ -25,6 +26,7 @@ function parseWithVBind(
const ast = parse(template)
transform(ast, {
nodeTransforms: [
transformVBindShorthand,
...(options.prefixIdentifiers ? [transformExpression] : []),
transformElement,
],
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler-core/__tests__/transforms/vFor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { type CompilerOptions, generate } from '../../src'
import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
import { PatchFlags } from '@vue/shared'
import { createObjectMatcher } from '../testUtils'
import { transformVBindShorthand } from '../../src/transforms/transformVBindShorthand'

export function parseWithForTransform(
template: string,
Expand All @@ -32,6 +33,7 @@ export function parseWithForTransform(
const ast = parse(template, options)
transform(ast, {
nodeTransforms: [
transformVBindShorthand,
transformIf,
transformFor,
...(options.prefixIdentifiers ? [transformExpression] : []),
Expand Down
24 changes: 22 additions & 2 deletions packages/compiler-core/__tests__/transforms/vIf.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ import {
type VNodeCall,
} from '../../src/ast'
import { ErrorCodes } from '../../src/errors'
import { type CompilerOptions, TO_HANDLERS, generate } from '../../src'
import {
type CompilerOptions,
TO_HANDLERS,
generate,
transformVBindShorthand,
} from '../../src'
import {
CREATE_COMMENT,
FRAGMENT,
Expand All @@ -35,7 +40,12 @@ function parseWithIfTransform(
) {
const ast = parse(template, options)
transform(ast, {
nodeTransforms: [transformIf, transformSlotOutlet, transformElement],
nodeTransforms: [
transformVBindShorthand,
transformIf,
transformSlotOutlet,
transformElement,
],
...options,
})
if (!options.onError) {
Expand Down Expand Up @@ -209,6 +219,16 @@ describe('compiler: v-if', () => {
content: `_ctx.ok`,
})
})

//#11321
test('v-if + :key shorthand', () => {
const { node } = parseWithIfTransform(`<div v-if="ok" :key></div>`)
expect(node.type).toBe(NodeTypes.IF)
expect(node.branches[0].userKey).toMatchObject({
arg: { content: 'key' },
exp: { content: 'key' },
})
})
})

describe('errors', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler-core/src/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { transformModel } from './transforms/vModel'
import { transformFilter } from './compat/transformFilter'
import { ErrorCodes, createCompilerError, defaultOnError } from './errors'
import { transformMemo } from './transforms/vMemo'
import { transformVBindShorthand } from './transforms/transformVBindShorthand'

export type TransformPreset = [
NodeTransform[],
Expand All @@ -33,6 +34,7 @@ export function getBaseTransformPreset(
): TransformPreset {
return [
[
transformVBindShorthand,
transformOnce,
transformIf,
transformMemo,
Expand Down
1 change: 1 addition & 0 deletions packages/compiler-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export {
buildDirectiveArgs,
type PropsExpression,
} from './transforms/transformElement'
export { transformVBindShorthand } from './transforms/transformVBindShorthand'
export { processSlotOutlet } from './transforms/transformSlotOutlet'
export { getConstantType } from './transforms/cacheStatic'
export { generateCodeFrame } from '@vue/shared'
Expand Down
36 changes: 36 additions & 0 deletions packages/compiler-core/src/transforms/transformVBindShorthand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { camelize } from '@vue/shared'
import {
NodeTypes,
type SimpleExpressionNode,
createSimpleExpression,
} from '../ast'
import type { NodeTransform } from '../transform'
import { ErrorCodes, createCompilerError } from '../errors'

export const transformVBindShorthand: NodeTransform = (node, context) => {
if (node.type === NodeTypes.ELEMENT) {
for (const prop of node.props) {
// same-name shorthand - :arg is expanded to :arg="arg"
if (
prop.type === NodeTypes.DIRECTIVE &&
prop.name === 'bind' &&
!prop.exp
) {
const arg = prop.arg!
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) {
// only simple expression is allowed for same-name shorthand
context.onError(
createCompilerError(
ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
arg.loc,
),
)
prop.exp = createSimpleExpression('', true, arg.loc)
} else {
const propName = camelize((arg as SimpleExpressionNode).content)
prop.exp = createSimpleExpression(propName, false, arg.loc)
}
}
}
}
}
41 changes: 2 additions & 39 deletions packages/compiler-core/src/transforms/vBind.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import type { DirectiveTransform, TransformContext } from '../transform'
import type { DirectiveTransform } from '../transform'
import {
type DirectiveNode,
type ExpressionNode,
NodeTypes,
type SimpleExpressionNode,
createObjectProperty,
createSimpleExpression,
} from '../ast'
import { ErrorCodes, createCompilerError } from '../errors'
import { camelize } from '@vue/shared'
import { CAMELIZE } from '../runtimeHelpers'
import { processExpression } from './transformExpression'

// v-bind without arg is handled directly in ./transformElement.ts due to its affecting
// codegen for the entire props object. This transform here is only for v-bind
Expand Down Expand Up @@ -40,27 +37,6 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
}
}

// same-name shorthand - :arg is expanded to :arg="arg"
if (!exp) {
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) {
// only simple expression is allowed for same-name shorthand
context.onError(
createCompilerError(
ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
arg.loc,
),
)
return {
props: [
createObjectProperty(arg, createSimpleExpression('', true, loc)),
],
}
}

transformBindShorthand(dir, context)
exp = dir.exp!
}

if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
arg.children.unshift(`(`)
arg.children.push(`) || ""`)
Expand Down Expand Up @@ -92,20 +68,7 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
}

return {
props: [createObjectProperty(arg, exp)],
}
}

export const transformBindShorthand = (
dir: DirectiveNode,
context: TransformContext,
): void => {
const arg = dir.arg!

const propName = camelize((arg as SimpleExpressionNode).content)
dir.exp = createSimpleExpression(propName, false, arg.loc)
if (!__BROWSER__) {
dir.exp = processExpression(dir.exp, context)
props: [createObjectProperty(arg, exp!)],
}
}

Expand Down
5 changes: 0 additions & 5 deletions packages/compiler-core/src/transforms/vFor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import {
import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression'
import { PatchFlags } from '@vue/shared'
import { transformBindShorthand } from './vBind'

export const transformFor: NodeTransform = createStructuralDirectiveTransform(
'for',
Expand All @@ -64,10 +63,6 @@ export const transformFor: NodeTransform = createStructuralDirectiveTransform(
const memo = findDir(node, 'memo')
const keyProp = findProp(node, `key`, false, true)
const isDirKey = keyProp && keyProp.type === NodeTypes.DIRECTIVE
if (isDirKey && !keyProp.exp) {
// resolve :key shorthand #10882
transformBindShorthand(keyProp, context)
}
let keyExp =
keyProp &&
(keyProp.type === NodeTypes.ATTRIBUTE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@ return function render(_ctx, _cache) {
}"
`;

exports[`compiler: transform v-model > input with v-bind shorthand type after v-model should use dynamic model 1`] = `
"const _Vue = Vue

return function render(_ctx, _cache) {
with (_ctx) {
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"])), [
[_vModelDynamic, model]
])
}
}"
`;

exports[`compiler: transform v-model > modifiers > .lazy 1`] = `
"const _Vue = Vue

Expand Down
11 changes: 10 additions & 1 deletion packages/compiler-dom/__tests__/transforms/vModel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
generate,
baseParse as parse,
transform,
transformVBindShorthand,
} from '@vue/compiler-core'
import { transformModel } from '../../src/transforms/vModel'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
Expand All @@ -18,7 +19,7 @@ import {
function transformWithModel(template: string, options: CompilerOptions = {}) {
const ast = parse(template)
transform(ast, {
nodeTransforms: [transformElement],
nodeTransforms: [transformVBindShorthand, transformElement],
directiveTransforms: {
model: transformModel,
},
Expand Down Expand Up @@ -63,6 +64,14 @@ describe('compiler: transform v-model', () => {
expect(generate(root).code).toMatchSnapshot()
})

// #13169
test('input with v-bind shorthand type after v-model should use dynamic model', () => {
const root = transformWithModel('<input v-model="model" :type/>')

expect(root.helpers).toContain(V_MODEL_DYNAMIC)
expect(generate(root).code).toMatchSnapshot()
})

test('input w/ dynamic v-bind', () => {
const root = transformWithModel('<input v-bind="obj" v-model="model" />')

Expand Down
22 changes: 22 additions & 0 deletions packages/compiler-ssr/__tests__/ssrTransitionGroup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,28 @@ describe('transition-group', () => {
`)
})

test('with dynamic tag shorthand', () => {
expect(
compile(
`<transition-group :tag><div v-for="i in list"/></transition-group>`,
).code,
).toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")

return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<\${
_ctx.tag
}\${
_ssrRenderAttrs(_attrs)
}>\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`<div></div>\`)
})
_push(\`</\${_ctx.tag}>\`)
}"
`)
})

test('with multi fragments children', () => {
expect(
compile(
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler-ssr/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
transformExpression,
transformOn,
transformStyle,
transformVBindShorthand,
} from '@vue/compiler-dom'
import { ssrCodegenTransform } from './ssrCodegenTransform'
import { ssrTransformElement } from './transforms/ssrTransformElement'
Expand Down Expand Up @@ -55,6 +56,7 @@ export function compile(
...options,
hoistStatic: false,
nodeTransforms: [
transformVBindShorthand,
ssrTransformIf,
ssrTransformFor,
trackVForSlotScopes,
Expand Down