diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
index ea11cf71..48c22aef 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
@@ -8,7 +8,7 @@ export default function render() {
return (openBlock(), createBlock(\\"input\\", {
modelValue: _ctx.model[_ctx.index],
\\"onUpdate:modelValue\\": $event => (_ctx.model[_ctx.index] = $event)
- }, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
+ }, null, 8 /* PROPS */, [\\"modelValue\\"]))
}"
`;
@@ -35,7 +35,7 @@ export default function render() {
return (openBlock(), createBlock(\\"input\\", {
modelValue: _ctx.model,
\\"onUpdate:modelValue\\": $event => (_ctx.model = $event)
- }, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
+ }, null, 8 /* PROPS */, [\\"modelValue\\"]))
}"
`;
diff --git a/packages/compiler-core/__tests__/transforms/vModel.spec.ts b/packages/compiler-core/__tests__/transforms/vModel.spec.ts
index 3eed83cc..42f3d8c5 100644
--- a/packages/compiler-core/__tests__/transforms/vModel.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vModel.spec.ts
@@ -5,18 +5,29 @@ import {
ElementNode,
ObjectExpression,
CompilerOptions,
- CallExpression
+ CallExpression,
+ ForNode,
+ PlainElementNode,
+ PlainElementCodegenNode,
+ ComponentNode
} from '../../src'
import { ErrorCodes } from '../../src/errors'
import { transformModel } from '../../src/transforms/vModel'
import { transformElement } from '../../src/transforms/transformElement'
import { transformExpression } from '../../src/transforms/transformExpression'
+import { transformFor } from '../../src/transforms/vFor'
+import { trackSlotScopes } from '../../src/transforms/vSlot'
function parseWithVModel(template: string, options: CompilerOptions = {}) {
const ast = parse(template)
transform(ast, {
- nodeTransforms: [transformExpression, transformElement],
+ nodeTransforms: [
+ transformFor,
+ transformExpression,
+ trackSlotScopes,
+ transformElement
+ ],
directiveTransforms: {
...options.directiveTransforms,
model: transformModel
@@ -327,6 +338,39 @@ describe('compiler: transform v-model', () => {
expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
})
+ test('should not mark update handler dynamic', () => {
+ const root = parseWithVModel('', {
+ prefixIdentifiers: true
+ })
+ const codegen = (root.children[0] as PlainElementNode)
+ .codegenNode as PlainElementCodegenNode
+ expect(codegen.arguments[4]).toBe(`["modelValue"]`)
+ })
+
+ test('should mark update handler dynamic if it refers v-for scope variables', () => {
+ const root = parseWithVModel(
+ '',
+ {
+ prefixIdentifiers: true
+ }
+ )
+ const codegen = ((root.children[0] as ForNode)
+ .children[0] as PlainElementNode).codegenNode as PlainElementCodegenNode
+ expect(codegen.arguments[4]).toBe(`["modelValue", "onUpdate:modelValue"]`)
+ })
+
+ test('should mark update handler dynamic if it refers slot scope variables', () => {
+ const root = parseWithVModel(
+ '',
+ {
+ prefixIdentifiers: true
+ }
+ )
+ const codegen = ((root.children[0] as ComponentNode)
+ .children[0] as PlainElementNode).codegenNode as PlainElementCodegenNode
+ expect(codegen.arguments[4]).toBe(`["modelValue", "onUpdate:modelValue"]`)
+ })
+
describe('errors', () => {
test('missing expression', () => {
const onError = jest.fn()
diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts
index 571eb89d..183a1f98 100644
--- a/packages/compiler-core/src/transforms/transformElement.ts
+++ b/packages/compiler-core/src/transforms/transformElement.ts
@@ -193,21 +193,21 @@ export function buildProps(
const analyzePatchFlag = ({ key, value }: Property) => {
if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
if (
- value.type !== NodeTypes.SIMPLE_EXPRESSION ||
- // E.g:
.
- // Do not add prop `foo` to `dynamicPropNames`.
- (!value.isStatic && !value.isConstant)
+ (value.type === NodeTypes.SIMPLE_EXPRESSION ||
+ value.type === NodeTypes.COMPOUND_EXPRESSION) &&
+ isStaticNode(value)
) {
- const name = key.content
- if (name === 'ref') {
- hasRef = true
- } else if (name === 'class') {
- hasClassBinding = true
- } else if (name === 'style') {
- hasStyleBinding = true
- } else if (name !== 'key') {
- dynamicPropNames.push(name)
- }
+ return
+ }
+ const name = key.content
+ if (name === 'ref') {
+ hasRef = true
+ } else if (name === 'class') {
+ hasClassBinding = true
+ } else if (name === 'style') {
+ hasStyleBinding = true
+ } else if (name !== 'key') {
+ dynamicPropNames.push(name)
}
} else {
hasDynamicKeys = true
diff --git a/packages/compiler-core/src/transforms/vModel.ts b/packages/compiler-core/src/transforms/vModel.ts
index 86857ce3..00dcc84c 100644
--- a/packages/compiler-core/src/transforms/vModel.ts
+++ b/packages/compiler-core/src/transforms/vModel.ts
@@ -1,13 +1,16 @@
-import { DirectiveTransform } from '../transform'
+import { DirectiveTransform, TransformContext } from '../transform'
import {
createSimpleExpression,
createObjectProperty,
createCompoundExpression,
NodeTypes,
- Property
+ Property,
+ CompoundExpressionNode,
+ createInterpolation
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { isMemberExpression } from '../utils'
+import { isObject } from '@vue/shared'
export const transformModel: DirectiveTransform = (dir, node, context) => {
const { exp, arg } = dir
@@ -38,13 +41,23 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
])
: createSimpleExpression('onUpdate:modelValue', true)
+ let assignmentChildren =
+ exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children
+ // For a member expression used in assignment, it only needs to be updated
+ // if the expression involves scope variables. Otherwise we can mark the
+ // expression as constant to avoid it being included in `dynamicPropNames`
+ // of the element. This optimization relies on `prefixIdentifiers: true`.
+ if (!__BROWSER__ && context.prefixIdentifiers) {
+ assignmentChildren = assignmentChildren.map(c => toConstant(c, context))
+ }
+
const props = [
createObjectProperty(propName, dir.exp!),
createObjectProperty(
eventName,
createCompoundExpression([
`$event => (`,
- ...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children),
+ ...assignmentChildren,
` = $event)`
])
)
@@ -57,6 +70,30 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
return createTransformProps(props)
}
+function toConstant(
+ exp: CompoundExpressionNode | CompoundExpressionNode['children'][0],
+ context: TransformContext
+): any {
+ if (!isObject(exp) || exp.type === NodeTypes.TEXT) {
+ return exp
+ }
+ if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
+ if (exp.isStatic || context.identifiers[exp.content]) {
+ return exp
+ }
+ return {
+ ...exp,
+ isConstant: true
+ }
+ } else if (exp.type === NodeTypes.COMPOUND_EXPRESSION) {
+ return createCompoundExpression(
+ exp.children.map(c => toConstant(c, context))
+ )
+ } else if (exp.type === NodeTypes.INTERPOLATION) {
+ return createInterpolation(toConstant(exp.content, context), exp.loc)
+ }
+}
+
function createTransformProps(props: Property[] = []) {
return { props, needRuntime: false }
}