feat(compiler-core/v-model): avoid patching v-model handler when possible
This commit is contained in:
		
							parent
							
								
									48b79d02e8
								
							
						
					
					
						commit
						5481f76ce8
					
				| @ -8,7 +8,7 @@ export default function render() { | |||||||
|   return (openBlock(), createBlock(\\"input\\", { |   return (openBlock(), createBlock(\\"input\\", { | ||||||
|     modelValue: _ctx.model[_ctx.index], |     modelValue: _ctx.model[_ctx.index], | ||||||
|     \\"onUpdate:modelValue\\": $event => (_ctx.model[_ctx.index] = $event) |     \\"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\\", { |   return (openBlock(), createBlock(\\"input\\", { | ||||||
|     modelValue: _ctx.model, |     modelValue: _ctx.model, | ||||||
|     \\"onUpdate:modelValue\\": $event => (_ctx.model = $event) |     \\"onUpdate:modelValue\\": $event => (_ctx.model = $event) | ||||||
|   }, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"])) |   }, null, 8 /* PROPS */, [\\"modelValue\\"])) | ||||||
| }" | }" | ||||||
| `; | `; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,18 +5,29 @@ import { | |||||||
|   ElementNode, |   ElementNode, | ||||||
|   ObjectExpression, |   ObjectExpression, | ||||||
|   CompilerOptions, |   CompilerOptions, | ||||||
|   CallExpression |   CallExpression, | ||||||
|  |   ForNode, | ||||||
|  |   PlainElementNode, | ||||||
|  |   PlainElementCodegenNode, | ||||||
|  |   ComponentNode | ||||||
| } from '../../src' | } from '../../src' | ||||||
| import { ErrorCodes } from '../../src/errors' | import { ErrorCodes } from '../../src/errors' | ||||||
| import { transformModel } from '../../src/transforms/vModel' | import { transformModel } from '../../src/transforms/vModel' | ||||||
| import { transformElement } from '../../src/transforms/transformElement' | import { transformElement } from '../../src/transforms/transformElement' | ||||||
| import { transformExpression } from '../../src/transforms/transformExpression' | import { transformExpression } from '../../src/transforms/transformExpression' | ||||||
|  | import { transformFor } from '../../src/transforms/vFor' | ||||||
|  | import { trackSlotScopes } from '../../src/transforms/vSlot' | ||||||
| 
 | 
 | ||||||
| function parseWithVModel(template: string, options: CompilerOptions = {}) { | function parseWithVModel(template: string, options: CompilerOptions = {}) { | ||||||
|   const ast = parse(template) |   const ast = parse(template) | ||||||
| 
 | 
 | ||||||
|   transform(ast, { |   transform(ast, { | ||||||
|     nodeTransforms: [transformExpression, transformElement], |     nodeTransforms: [ | ||||||
|  |       transformFor, | ||||||
|  |       transformExpression, | ||||||
|  |       trackSlotScopes, | ||||||
|  |       transformElement | ||||||
|  |     ], | ||||||
|     directiveTransforms: { |     directiveTransforms: { | ||||||
|       ...options.directiveTransforms, |       ...options.directiveTransforms, | ||||||
|       model: transformModel |       model: transformModel | ||||||
| @ -327,6 +338,39 @@ describe('compiler: transform v-model', () => { | |||||||
|     expect(generate(root, { mode: 'module' }).code).toMatchSnapshot() |     expect(generate(root, { mode: 'module' }).code).toMatchSnapshot() | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|  |   test('should not mark update handler dynamic', () => { | ||||||
|  |     const root = parseWithVModel('<input v-model="foo" />', { | ||||||
|  |       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( | ||||||
|  |       '<input v-for="i in list" v-model="foo[i]" />', | ||||||
|  |       { | ||||||
|  |         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( | ||||||
|  |       '<Comp v-slot="{ foo }"><input v-model="foo"/></Comp>', | ||||||
|  |       { | ||||||
|  |         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', () => { |   describe('errors', () => { | ||||||
|     test('missing expression', () => { |     test('missing expression', () => { | ||||||
|       const onError = jest.fn() |       const onError = jest.fn() | ||||||
|  | |||||||
| @ -193,21 +193,21 @@ export function buildProps( | |||||||
|   const analyzePatchFlag = ({ key, value }: Property) => { |   const analyzePatchFlag = ({ key, value }: Property) => { | ||||||
|     if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) { |     if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) { | ||||||
|       if ( |       if ( | ||||||
|         value.type !== NodeTypes.SIMPLE_EXPRESSION || |         (value.type === NodeTypes.SIMPLE_EXPRESSION || | ||||||
|         // E.g: <p :foo="1 + 2" />.
 |           value.type === NodeTypes.COMPOUND_EXPRESSION) && | ||||||
|         // Do not add prop `foo` to `dynamicPropNames`.
 |         isStaticNode(value) | ||||||
|         (!value.isStatic && !value.isConstant) |  | ||||||
|       ) { |       ) { | ||||||
|         const name = key.content |         return | ||||||
|         if (name === 'ref') { |       } | ||||||
|           hasRef = true |       const name = key.content | ||||||
|         } else if (name === 'class') { |       if (name === 'ref') { | ||||||
|           hasClassBinding = true |         hasRef = true | ||||||
|         } else if (name === 'style') { |       } else if (name === 'class') { | ||||||
|           hasStyleBinding = true |         hasClassBinding = true | ||||||
|         } else if (name !== 'key') { |       } else if (name === 'style') { | ||||||
|           dynamicPropNames.push(name) |         hasStyleBinding = true | ||||||
|         } |       } else if (name !== 'key') { | ||||||
|  |         dynamicPropNames.push(name) | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       hasDynamicKeys = true |       hasDynamicKeys = true | ||||||
|  | |||||||
| @ -1,13 +1,16 @@ | |||||||
| import { DirectiveTransform } from '../transform' | import { DirectiveTransform, TransformContext } from '../transform' | ||||||
| import { | import { | ||||||
|   createSimpleExpression, |   createSimpleExpression, | ||||||
|   createObjectProperty, |   createObjectProperty, | ||||||
|   createCompoundExpression, |   createCompoundExpression, | ||||||
|   NodeTypes, |   NodeTypes, | ||||||
|   Property |   Property, | ||||||
|  |   CompoundExpressionNode, | ||||||
|  |   createInterpolation | ||||||
| } from '../ast' | } from '../ast' | ||||||
| import { createCompilerError, ErrorCodes } from '../errors' | import { createCompilerError, ErrorCodes } from '../errors' | ||||||
| import { isMemberExpression } from '../utils' | import { isMemberExpression } from '../utils' | ||||||
|  | import { isObject } from '@vue/shared' | ||||||
| 
 | 
 | ||||||
| export const transformModel: DirectiveTransform = (dir, node, context) => { | export const transformModel: DirectiveTransform = (dir, node, context) => { | ||||||
|   const { exp, arg } = dir |   const { exp, arg } = dir | ||||||
| @ -38,13 +41,23 @@ export const transformModel: DirectiveTransform = (dir, node, context) => { | |||||||
|         ]) |         ]) | ||||||
|     : createSimpleExpression('onUpdate:modelValue', true) |     : 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 = [ |   const props = [ | ||||||
|     createObjectProperty(propName, dir.exp!), |     createObjectProperty(propName, dir.exp!), | ||||||
|     createObjectProperty( |     createObjectProperty( | ||||||
|       eventName, |       eventName, | ||||||
|       createCompoundExpression([ |       createCompoundExpression([ | ||||||
|         `$event => (`, |         `$event => (`, | ||||||
|         ...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children), |         ...assignmentChildren, | ||||||
|         ` = $event)` |         ` = $event)` | ||||||
|       ]) |       ]) | ||||||
|     ) |     ) | ||||||
| @ -57,6 +70,30 @@ export const transformModel: DirectiveTransform = (dir, node, context) => { | |||||||
|   return createTransformProps(props) |   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[] = []) { | function createTransformProps(props: Property[] = []) { | ||||||
|   return { props, needRuntime: false } |   return { props, needRuntime: false } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user