feat(compiler): v-for codegen w/ correct blocks optimization + key flags
This commit is contained in:
		
							parent
							
								
									07955e6c96
								
							
						
					
					
						commit
						a477594d65
					
				| @ -15,11 +15,11 @@ return function render() { | ||||
|       (_openBlock(), ok | ||||
|         ? _createBlock(\\"div\\", { key: 0 }, \\"yes\\") | ||||
|         : _createBlock(_Fragment, { key: 1 }, \\"no\\")), | ||||
|       (_openBlock(), _createBlock(_Fragment, null, _renderList(list, (value, index) => { | ||||
|         return _createVNode(\\"div\\", null, [ | ||||
|       _createVNode(_Fragment, null, _renderList(list, (value, index) => { | ||||
|         return (_openBlock(), _createBlock(\\"div\\", null, [ | ||||
|           _createVNode(\\"span\\", null, _toString(value + index)) | ||||
|         ]) | ||||
|       }))) | ||||
|         ])) | ||||
|       }), 128 /* UNKEYED_FRAGMENT */) | ||||
|     ], 2 /* CLASS */) | ||||
|   } | ||||
| }" | ||||
| @ -38,11 +38,11 @@ return function render() { | ||||
|     (openBlock(), (_ctx.ok) | ||||
|       ? createBlock(\\"div\\", { key: 0 }, \\"yes\\") | ||||
|       : createBlock(Fragment, { key: 1 }, \\"no\\")), | ||||
|     (openBlock(), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => { | ||||
|       return createVNode(\\"div\\", null, [ | ||||
|     createVNode(Fragment, null, renderList(_ctx.list, (value, index) => { | ||||
|       return (openBlock(), createBlock(\\"div\\", null, [ | ||||
|         createVNode(\\"span\\", null, toString(value + index)) | ||||
|       ]) | ||||
|     }))) | ||||
|       ])) | ||||
|     }), 128 /* UNKEYED_FRAGMENT */) | ||||
|   ], 2 /* CLASS */) | ||||
| }" | ||||
| `; | ||||
| @ -60,11 +60,11 @@ export default function render() { | ||||
|     (openBlock(), (_ctx.ok) | ||||
|       ? createBlock(\\"div\\", { key: 0 }, \\"yes\\") | ||||
|       : createBlock(Fragment, { key: 1 }, \\"no\\")), | ||||
|     (openBlock(), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => { | ||||
|       return createVNode(\\"div\\", null, [ | ||||
|     createVNode(Fragment, null, renderList(_ctx.list, (value, index) => { | ||||
|       return (openBlock(), createBlock(\\"div\\", null, [ | ||||
|         createVNode(\\"span\\", null, _toString(value + index)) | ||||
|       ]) | ||||
|     }))) | ||||
|       ])) | ||||
|     }), 128 /* UNKEYED_FRAGMENT */) | ||||
|   ], 2 /* CLASS */) | ||||
| }" | ||||
| `; | ||||
|  | ||||
| @ -5,11 +5,42 @@ exports[`compiler: v-for codegen basic v-for 1`] = ` | ||||
| 
 | ||||
| return function render() { | ||||
|   with (this) { | ||||
|     const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue | ||||
|     const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue | ||||
|      | ||||
|     return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (item) => { | ||||
|       return _createVNode(\\"span\\") | ||||
|     }))) | ||||
|     return _createVNode(_Fragment, null, _renderList(items, (item) => { | ||||
|       return (_openBlock(), _createBlock(\\"span\\")) | ||||
|     }), 128 /* UNKEYED_FRAGMENT */) | ||||
|   } | ||||
| }" | ||||
| `; | ||||
| 
 | ||||
| exports[`compiler: v-for codegen keyed template v-for 1`] = ` | ||||
| "const _Vue = Vue | ||||
| 
 | ||||
| return function render() { | ||||
|   with (this) { | ||||
|     const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue | ||||
|      | ||||
|     return _createVNode(_Fragment, null, _renderList(items, (item) => { | ||||
|       return (_openBlock(), _createBlock(_Fragment, { key: item }, [ | ||||
|         \\"hello\\", | ||||
|         _createVNode(\\"span\\") | ||||
|       ])) | ||||
|     }), 64 /* KEYED_FRAGMENT */) | ||||
|   } | ||||
| }" | ||||
| `; | ||||
| 
 | ||||
| exports[`compiler: v-for codegen keyed v-for 1`] = ` | ||||
| "const _Vue = Vue | ||||
| 
 | ||||
| return function render() { | ||||
|   with (this) { | ||||
|     const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue | ||||
|      | ||||
|     return _createVNode(_Fragment, null, _renderList(items, (item) => { | ||||
|       return (_openBlock(), _createBlock(\\"span\\", { key: item })) | ||||
|     }), 64 /* KEYED_FRAGMENT */) | ||||
|   } | ||||
| }" | ||||
| `; | ||||
| @ -19,11 +50,11 @@ exports[`compiler: v-for codegen skipped key 1`] = ` | ||||
| 
 | ||||
| return function render() { | ||||
|   with (this) { | ||||
|     const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue | ||||
|     const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue | ||||
|      | ||||
|     return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (value, __, index) => { | ||||
|       return _createVNode(\\"span\\") | ||||
|     }))) | ||||
|     return _createVNode(_Fragment, null, _renderList(items, (item, __, index) => { | ||||
|       return (_openBlock(), _createBlock(\\"span\\")) | ||||
|     }), 128 /* UNKEYED_FRAGMENT */) | ||||
|   } | ||||
| }" | ||||
| `; | ||||
| @ -33,11 +64,11 @@ exports[`compiler: v-for codegen skipped value & key 1`] = ` | ||||
| 
 | ||||
| return function render() { | ||||
|   with (this) { | ||||
|     const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue | ||||
|     const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue | ||||
|      | ||||
|     return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (_, __, index) => { | ||||
|       return _createVNode(\\"span\\") | ||||
|     }))) | ||||
|     return _createVNode(_Fragment, null, _renderList(items, (_, __, index) => { | ||||
|       return (_openBlock(), _createBlock(\\"span\\")) | ||||
|     }), 128 /* UNKEYED_FRAGMENT */) | ||||
|   } | ||||
| }" | ||||
| `; | ||||
| @ -47,11 +78,11 @@ exports[`compiler: v-for codegen skipped value 1`] = ` | ||||
| 
 | ||||
| return function render() { | ||||
|   with (this) { | ||||
|     const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue | ||||
|     const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue | ||||
|      | ||||
|     return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (_, key, index) => { | ||||
|       return _createVNode(\\"span\\") | ||||
|     }))) | ||||
|     return _createVNode(_Fragment, null, _renderList(items, (_, key, index) => { | ||||
|       return (_openBlock(), _createBlock(\\"span\\")) | ||||
|     }), 128 /* UNKEYED_FRAGMENT */) | ||||
|   } | ||||
| }" | ||||
| `; | ||||
| @ -61,14 +92,14 @@ exports[`compiler: v-for codegen template v-for 1`] = ` | ||||
| 
 | ||||
| return function render() { | ||||
|   with (this) { | ||||
|     const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue | ||||
|     const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue | ||||
|      | ||||
|     return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (item) => { | ||||
|       return [ | ||||
|     return _createVNode(_Fragment, null, _renderList(items, (item) => { | ||||
|       return (_openBlock(), _createBlock(_Fragment, null, [ | ||||
|         \\"hello\\", | ||||
|         _createVNode(\\"span\\") | ||||
|       ] | ||||
|     }))) | ||||
|       ])) | ||||
|     }), 128 /* UNKEYED_FRAGMENT */) | ||||
|   } | ||||
| }" | ||||
| `; | ||||
| @ -78,12 +109,12 @@ exports[`compiler: v-for codegen v-if + v-for 1`] = ` | ||||
| 
 | ||||
| return function render() { | ||||
|   with (this) { | ||||
|     const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList, Empty: _Empty } = _Vue | ||||
|     const { openBlock: _openBlock, renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, Empty: _Empty } = _Vue | ||||
|      | ||||
|     return (_openBlock(), ok | ||||
|       ? _createBlock(_Fragment, { key: 0 }, _renderList(list, (i) => { | ||||
|           return _createVNode(\\"div\\") | ||||
|         })) | ||||
|           return (_openBlock(), _createBlock(\\"div\\")) | ||||
|         }), 128 /* UNKEYED_FRAGMENT */) | ||||
|       : _createBlock(_Empty)) | ||||
|   } | ||||
| }" | ||||
| @ -94,11 +125,11 @@ exports[`compiler: v-for codegen value + key + index 1`] = ` | ||||
| 
 | ||||
| return function render() { | ||||
|   with (this) { | ||||
|     const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue | ||||
|     const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue | ||||
|      | ||||
|     return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (item, key, index) => { | ||||
|       return _createVNode(\\"span\\") | ||||
|     }))) | ||||
|     return _createVNode(_Fragment, null, _renderList(items, (item, key, index) => { | ||||
|       return (_openBlock(), _createBlock(\\"span\\")) | ||||
|     }), 128 /* UNKEYED_FRAGMENT */) | ||||
|   } | ||||
| }" | ||||
| `; | ||||
|  | ||||
| @ -19,7 +19,7 @@ exports[`compiler: v-if codegen template v-if 1`] = ` | ||||
| 
 | ||||
| return function render() { | ||||
|   with (this) { | ||||
|     const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, Empty: _Empty } = _Vue | ||||
|     const { openBlock: _openBlock, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, Empty: _Empty } = _Vue | ||||
|      | ||||
|     return (_openBlock(), ok | ||||
|       ? _createBlock(_Fragment, { key: 0 }, [ | ||||
|  | ||||
| @ -2,25 +2,29 @@ import { parse } from '../../src/parse' | ||||
| import { transform } from '../../src/transform' | ||||
| import { transformIf } from '../../src/transforms/vIf' | ||||
| import { transformFor } from '../../src/transforms/vFor' | ||||
| import { transformBind } from '../../src/transforms/vBind' | ||||
| import { transformElement } from '../../src/transforms/transformElement' | ||||
| import { transformExpression } from '../../src/transforms/transformExpression' | ||||
| import { | ||||
|   ForNode, | ||||
|   NodeTypes, | ||||
|   SimpleExpressionNode, | ||||
|   ElementNode, | ||||
|   InterpolationNode, | ||||
|   SequenceExpression, | ||||
|   CallExpression | ||||
| } from '../../src/ast' | ||||
| import { ErrorCodes } from '../../src/errors' | ||||
| import { CompilerOptions, generate } from '../../src' | ||||
| import { transformExpression } from '../../src/transforms/transformExpression' | ||||
| import { | ||||
|   OPEN_BLOCK, | ||||
|   CREATE_BLOCK, | ||||
|   FRAGMENT, | ||||
|   RENDER_LIST | ||||
|   RENDER_LIST, | ||||
|   CREATE_VNODE | ||||
| } from '../../src/runtimeConstants' | ||||
| import { PatchFlags } from '@vue/runtime-dom' | ||||
| import { PatchFlagNames } from '@vue/shared' | ||||
| import { createObjectMatcher } from '../testUtils' | ||||
| 
 | ||||
| function parseWithForTransform( | ||||
|   template: string, | ||||
| @ -34,6 +38,9 @@ function parseWithForTransform( | ||||
|       ...(options.prefixIdentifiers ? [transformExpression] : []), | ||||
|       transformElement | ||||
|     ], | ||||
|     directiveTransforms: { | ||||
|       bind: transformBind | ||||
|     }, | ||||
|     ...options | ||||
|   }) | ||||
|   return { | ||||
| @ -555,31 +562,51 @@ describe('compiler: v-for', () => { | ||||
|   }) | ||||
| 
 | ||||
|   describe('codegen', () => { | ||||
|     function assertSharedCodegen(node: SequenceExpression) { | ||||
|     function assertSharedCodegen(node: CallExpression, keyed: boolean = false) { | ||||
|       expect(node).toMatchObject({ | ||||
|         type: NodeTypes.JS_SEQUENCE_EXPRESSION, | ||||
|         expressions: [ | ||||
|           { | ||||
|         type: NodeTypes.JS_CALL_EXPRESSION, | ||||
|             callee: `_${OPEN_BLOCK}`, | ||||
|             arguments: [] | ||||
|           }, | ||||
|           { | ||||
|             type: NodeTypes.JS_CALL_EXPRESSION, | ||||
|             callee: `_${CREATE_BLOCK}`, | ||||
|         callee: `_${CREATE_VNODE}`, | ||||
|         arguments: [ | ||||
|           `_${FRAGMENT}`, | ||||
|           `null`, | ||||
|           { | ||||
|             type: NodeTypes.JS_CALL_EXPRESSION, | ||||
|                 callee: `_${RENDER_LIST}` | ||||
|             callee: `_${RENDER_LIST}`, | ||||
|             arguments: [ | ||||
|               {}, // to be asserted by each test
 | ||||
|               { | ||||
|                 type: NodeTypes.JS_FUNCTION_EXPRESSION, | ||||
|                 returns: { | ||||
|                   type: NodeTypes.JS_SEQUENCE_EXPRESSION, | ||||
|                   expressions: [ | ||||
|                     { | ||||
|                       type: NodeTypes.JS_CALL_EXPRESSION, | ||||
|                       callee: `_${OPEN_BLOCK}` | ||||
|                     }, | ||||
|                     { | ||||
|                       type: NodeTypes.JS_CALL_EXPRESSION, | ||||
|                       callee: `_${CREATE_BLOCK}` | ||||
|                     } | ||||
|                   ] | ||||
|                 } | ||||
|               } | ||||
|             ] | ||||
|           }, | ||||
|           keyed | ||||
|             ? `${PatchFlags.KEYED_FRAGMENT} /* ${ | ||||
|                 PatchFlagNames[PatchFlags.KEYED_FRAGMENT] | ||||
|               } */` | ||||
|             : `${PatchFlags.UNKEYED_FRAGMENT} /* ${ | ||||
|                 PatchFlagNames[PatchFlags.UNKEYED_FRAGMENT] | ||||
|               } */` | ||||
|         ] | ||||
|       }) | ||||
|       return (node.expressions[1] as CallExpression) | ||||
|         .arguments[2] as CallExpression | ||||
|       const renderListArgs = (node.arguments[2] as CallExpression).arguments | ||||
|       return { | ||||
|         source: renderListArgs[0] as SimpleExpressionNode, | ||||
|         params: (renderListArgs[1] as any).params, | ||||
|         blockArgs: (renderListArgs[1] as any).returns.expressions[1].arguments | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     test('basic v-for', () => { | ||||
| @ -587,17 +614,11 @@ describe('compiler: v-for', () => { | ||||
|         root, | ||||
|         node: { codegenNode } | ||||
|       } = parseWithForTransform('<span v-for="(item) in items" />') | ||||
|       expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([ | ||||
|         { content: `items` }, | ||||
|         { | ||||
|           type: NodeTypes.JS_FUNCTION_EXPRESSION, | ||||
|       expect(assertSharedCodegen(codegenNode)).toMatchObject({ | ||||
|         source: { content: `items` }, | ||||
|         params: [{ content: `item` }], | ||||
|           returns: { | ||||
|             type: NodeTypes.ELEMENT, | ||||
|             tag: `span` | ||||
|           } | ||||
|         } | ||||
|       ]) | ||||
|         blockArgs: [`"span"`] | ||||
|       }) | ||||
|       expect(generate(root).code).toMatchSnapshot() | ||||
|     }) | ||||
| 
 | ||||
| @ -606,17 +627,10 @@ describe('compiler: v-for', () => { | ||||
|         root, | ||||
|         node: { codegenNode } | ||||
|       } = parseWithForTransform('<span v-for="(item, key, index) in items" />') | ||||
|       expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([ | ||||
|         { content: `items` }, | ||||
|         { | ||||
|           type: NodeTypes.JS_FUNCTION_EXPRESSION, | ||||
|           params: [ | ||||
|             { content: `item` }, | ||||
|             { content: `key` }, | ||||
|             { content: `index` } | ||||
|           ] | ||||
|         } | ||||
|       ]) | ||||
|       expect(assertSharedCodegen(codegenNode)).toMatchObject({ | ||||
|         source: { content: `items` }, | ||||
|         params: [{ content: `item` }, { content: `key` }, { content: `index` }] | ||||
|       }) | ||||
|       expect(generate(root).code).toMatchSnapshot() | ||||
|     }) | ||||
| 
 | ||||
| @ -624,14 +638,11 @@ describe('compiler: v-for', () => { | ||||
|       const { | ||||
|         root, | ||||
|         node: { codegenNode } | ||||
|       } = parseWithForTransform('<span v-for="(,key,index) in items" />') | ||||
|       expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([ | ||||
|         { content: `items` }, | ||||
|         { | ||||
|           type: NodeTypes.JS_FUNCTION_EXPRESSION, | ||||
|       } = parseWithForTransform('<span v-for="(, key, index) in items" />') | ||||
|       expect(assertSharedCodegen(codegenNode)).toMatchObject({ | ||||
|         source: { content: `items` }, | ||||
|         params: [{ content: `_` }, { content: `key` }, { content: `index` }] | ||||
|         } | ||||
|       ]) | ||||
|       }) | ||||
|       expect(generate(root).code).toMatchSnapshot() | ||||
|     }) | ||||
| 
 | ||||
| @ -639,18 +650,11 @@ describe('compiler: v-for', () => { | ||||
|       const { | ||||
|         root, | ||||
|         node: { codegenNode } | ||||
|       } = parseWithForTransform('<span v-for="(value,,index) in items" />') | ||||
|       expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([ | ||||
|         { content: `items` }, | ||||
|         { | ||||
|           type: NodeTypes.JS_FUNCTION_EXPRESSION, | ||||
|           params: [ | ||||
|             { content: `value` }, | ||||
|             { content: `__` }, | ||||
|             { content: `index` } | ||||
|           ] | ||||
|         } | ||||
|       ]) | ||||
|       } = parseWithForTransform('<span v-for="(item,,index) in items" />') | ||||
|       expect(assertSharedCodegen(codegenNode)).toMatchObject({ | ||||
|         source: { content: `items` }, | ||||
|         params: [{ content: `item` }, { content: `__` }, { content: `index` }] | ||||
|       }) | ||||
|       expect(generate(root).code).toMatchSnapshot() | ||||
|     }) | ||||
| 
 | ||||
| @ -659,13 +663,10 @@ describe('compiler: v-for', () => { | ||||
|         root, | ||||
|         node: { codegenNode } | ||||
|       } = parseWithForTransform('<span v-for="(,,index) in items" />') | ||||
|       expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([ | ||||
|         { content: `items` }, | ||||
|         { | ||||
|           type: NodeTypes.JS_FUNCTION_EXPRESSION, | ||||
|       expect(assertSharedCodegen(codegenNode)).toMatchObject({ | ||||
|         source: { content: `items` }, | ||||
|         params: [{ content: `_` }, { content: `__` }, { content: `index` }] | ||||
|         } | ||||
|       ]) | ||||
|       }) | ||||
|       expect(generate(root).code).toMatchSnapshot() | ||||
|     }) | ||||
| 
 | ||||
| @ -676,17 +677,60 @@ describe('compiler: v-for', () => { | ||||
|       } = parseWithForTransform( | ||||
|         '<template v-for="item in items">hello<span/></template>' | ||||
|       ) | ||||
|       expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([ | ||||
|         { content: `items` }, | ||||
|         { | ||||
|           type: NodeTypes.JS_FUNCTION_EXPRESSION, | ||||
|       expect(assertSharedCodegen(codegenNode)).toMatchObject({ | ||||
|         source: { content: `items` }, | ||||
|         params: [{ content: `item` }], | ||||
|           returns: [ | ||||
|         blockArgs: [ | ||||
|           `_${FRAGMENT}`, | ||||
|           `null`, | ||||
|           [ | ||||
|             { type: NodeTypes.TEXT, content: `hello` }, | ||||
|             { type: NodeTypes.ELEMENT, tag: `span` } | ||||
|           ] | ||||
|         } | ||||
|       ]) | ||||
|         ] | ||||
|       }) | ||||
|       expect(generate(root).code).toMatchSnapshot() | ||||
|     }) | ||||
| 
 | ||||
|     test('keyed v-for', () => { | ||||
|       const { | ||||
|         root, | ||||
|         node: { codegenNode } | ||||
|       } = parseWithForTransform('<span v-for="(item) in items" :key="item" />') | ||||
|       expect(assertSharedCodegen(codegenNode, true)).toMatchObject({ | ||||
|         source: { content: `items` }, | ||||
|         params: [{ content: `item` }], | ||||
|         blockArgs: [ | ||||
|           `"span"`, | ||||
|           createObjectMatcher({ | ||||
|             key: `[item]` | ||||
|           }) | ||||
|         ] | ||||
|       }) | ||||
|       expect(generate(root).code).toMatchSnapshot() | ||||
|     }) | ||||
| 
 | ||||
|     test('keyed template v-for', () => { | ||||
|       const { | ||||
|         root, | ||||
|         node: { codegenNode } | ||||
|       } = parseWithForTransform( | ||||
|         '<template v-for="item in items" :key="item">hello<span/></template>' | ||||
|       ) | ||||
|       expect(assertSharedCodegen(codegenNode, true)).toMatchObject({ | ||||
|         source: { content: `items` }, | ||||
|         params: [{ content: `item` }], | ||||
|         blockArgs: [ | ||||
|           `_${FRAGMENT}`, | ||||
|           createObjectMatcher({ | ||||
|             key: `[item]` | ||||
|           }), | ||||
|           [ | ||||
|             { type: NodeTypes.TEXT, content: `hello` }, | ||||
|             { type: NodeTypes.ELEMENT, tag: `span` } | ||||
|           ] | ||||
|         ] | ||||
|       }) | ||||
|       expect(generate(root).code).toMatchSnapshot() | ||||
|     }) | ||||
| 
 | ||||
| @ -722,12 +766,25 @@ describe('compiler: v-for', () => { | ||||
|                       type: NodeTypes.JS_FUNCTION_EXPRESSION, | ||||
|                       params: [{ content: `i` }], | ||||
|                       returns: { | ||||
|                         type: NodeTypes.ELEMENT, | ||||
|                         tag: `div` | ||||
|                       } | ||||
|                         type: NodeTypes.JS_SEQUENCE_EXPRESSION, | ||||
|                         expressions: [ | ||||
|                           { | ||||
|                             type: NodeTypes.JS_CALL_EXPRESSION, | ||||
|                             callee: `_${OPEN_BLOCK}` | ||||
|                           }, | ||||
|                           { | ||||
|                             type: NodeTypes.JS_CALL_EXPRESSION, | ||||
|                             callee: `_${CREATE_BLOCK}`, | ||||
|                             arguments: [`"div"`] | ||||
|                           } | ||||
|                         ] | ||||
|                       } | ||||
|                     } | ||||
|                   ] | ||||
|                 }, | ||||
|                 `${PatchFlags.UNKEYED_FRAGMENT} /* ${ | ||||
|                   PatchFlagNames[PatchFlags.UNKEYED_FRAGMENT] | ||||
|                 } */` | ||||
|               ] | ||||
|             } | ||||
|           } | ||||
|  | ||||
| @ -158,7 +158,7 @@ export interface ForNode extends Node { | ||||
|   keyAlias: ExpressionNode | undefined | ||||
|   objectIndexAlias: ExpressionNode | undefined | ||||
|   children: TemplateChildNode[] | ||||
|   codegenNode: SequenceExpression | ||||
|   codegenNode: CallExpression | ||||
| } | ||||
| 
 | ||||
| // We also include a number of JavaScript AST nodes for code generation.
 | ||||
| @ -198,7 +198,7 @@ export interface ArrayExpression extends Node { | ||||
| export interface FunctionExpression extends Node { | ||||
|   type: NodeTypes.JS_FUNCTION_EXPRESSION | ||||
|   params: ExpressionNode | ExpressionNode[] | undefined | ||||
|   returns: TemplateChildNode | TemplateChildNode[] | ||||
|   returns: TemplateChildNode | TemplateChildNode[] | JSChildNode | ||||
|   newline: boolean | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -261,7 +261,6 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) { | ||||
| // - The target position explicitly allows a single node (root, if, for)
 | ||||
| // - The list has length === 1, AND The only child is a:
 | ||||
| //   - text
 | ||||
| //   - expression
 | ||||
| //   - <slot> outlet, which always produces an array
 | ||||
| function genChildren( | ||||
|   children: TemplateChildNode[], | ||||
|  | ||||
| @ -163,7 +163,7 @@ export function buildProps( | ||||
|   // patchFlag analysis
 | ||||
|   let patchFlag = 0 | ||||
|   const dynamicPropNames: string[] = [] | ||||
|   let hasDynammicKeys = false | ||||
|   let hasDynamicKeys = false | ||||
|   let hasClassBinding = false | ||||
|   let hasStyleBinding = false | ||||
|   let hasRef = false | ||||
| @ -207,7 +207,7 @@ export function buildProps( | ||||
|       // special case for v-bind and v-on with no argument
 | ||||
|       const isBind = name === 'bind' | ||||
|       if (!arg && (isBind || name === 'on')) { | ||||
|         hasDynammicKeys = true | ||||
|         hasDynamicKeys = true | ||||
|         if (exp) { | ||||
|           if (properties.length) { | ||||
|             mergeArgs.push( | ||||
| @ -249,11 +249,11 @@ export function buildProps( | ||||
|             hasClassBinding = true | ||||
|           } else if (name === 'style') { | ||||
|             hasStyleBinding = true | ||||
|           } else { | ||||
|           } else if (name !== 'key') { | ||||
|             dynamicPropNames.push(name) | ||||
|           } | ||||
|         } else { | ||||
|           hasDynammicKeys = true | ||||
|           hasDynamicKeys = true | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
| @ -303,7 +303,7 @@ export function buildProps( | ||||
|   } | ||||
| 
 | ||||
|   // determine the flags to add
 | ||||
|   if (hasDynammicKeys) { | ||||
|   if (hasDynamicKeys) { | ||||
|     patchFlag |= PatchFlags.FULL_PROPS | ||||
|   } else { | ||||
|     if (hasClassBinding) { | ||||
|  | ||||
| @ -11,17 +11,22 @@ import { | ||||
|   createSequenceExpression, | ||||
|   createCallExpression, | ||||
|   createFunctionExpression, | ||||
|   ElementTypes | ||||
|   ElementTypes, | ||||
|   ObjectExpression, | ||||
|   createObjectExpression, | ||||
|   createObjectProperty | ||||
| } from '../ast' | ||||
| import { createCompilerError, ErrorCodes } from '../errors' | ||||
| import { getInnerRange } from '../utils' | ||||
| import { getInnerRange, findProp } from '../utils' | ||||
| import { | ||||
|   RENDER_LIST, | ||||
|   OPEN_BLOCK, | ||||
|   CREATE_BLOCK, | ||||
|   FRAGMENT | ||||
|   FRAGMENT, | ||||
|   CREATE_VNODE | ||||
| } from '../runtimeConstants' | ||||
| import { processExpression } from './transformExpression' | ||||
| import { PatchFlags, PatchFlagNames } from '@vue/shared' | ||||
| 
 | ||||
| export const transformFor = createStructuralDirectiveTransform( | ||||
|   'for', | ||||
| @ -38,9 +43,19 @@ export const transformFor = createStructuralDirectiveTransform( | ||||
|         const { helper, addIdentifiers, removeIdentifiers } = context | ||||
|         const { source, value, key, index } = parseResult | ||||
| 
 | ||||
|         const codegenNode = createSequenceExpression([ | ||||
|           createCallExpression(helper(OPEN_BLOCK)) | ||||
|           // to be filled in on exit after children traverse
 | ||||
|         // create the loop render function expression now, and add the
 | ||||
|         // iterator on exit after all children have been traversed
 | ||||
|         const renderExp = createCallExpression(helper(RENDER_LIST), [source]) | ||||
|         const keyProp = findProp(node.props, `key`) | ||||
|         const fragmentFlag = keyProp | ||||
|           ? PatchFlags.KEYED_FRAGMENT | ||||
|           : PatchFlags.UNKEYED_FRAGMENT | ||||
|         const codegenNode = createCallExpression(helper(CREATE_VNODE), [ | ||||
|           helper(FRAGMENT), | ||||
|           `null`, | ||||
|           renderExp, | ||||
|           fragmentFlag + | ||||
|             (__DEV__ ? ` /* ${PatchFlagNames[fragmentFlag]} */` : ``) | ||||
|         ]) | ||||
| 
 | ||||
|         context.replaceNode({ | ||||
| @ -63,6 +78,13 @@ export const transformFor = createStructuralDirectiveTransform( | ||||
|         } | ||||
| 
 | ||||
|         return () => { | ||||
|           if (!__BROWSER__ && context.prefixIdentifiers) { | ||||
|             value && removeIdentifiers(value) | ||||
|             key && removeIdentifiers(key) | ||||
|             index && removeIdentifiers(index) | ||||
|           } | ||||
| 
 | ||||
|           // finish the codegen now that all children have been traversed
 | ||||
|           const params: ExpressionNode[] = [] | ||||
|           if (value) { | ||||
|             params.push(value) | ||||
| @ -83,26 +105,46 @@ export const transformFor = createStructuralDirectiveTransform( | ||||
|             params.push(index) | ||||
|           } | ||||
| 
 | ||||
|           codegenNode.expressions.push( | ||||
|           let childBlock | ||||
|           if (node.tagType === ElementTypes.TEMPLATE) { | ||||
|             // <template v-for="...">
 | ||||
|             // should genereate a fragment block for each loop
 | ||||
|             let childBlockProps: string | ObjectExpression = `null` | ||||
|             if (keyProp) { | ||||
|               childBlockProps = createObjectExpression([ | ||||
|                 createObjectProperty( | ||||
|                   createSimpleExpression(`key`, true), | ||||
|                   keyProp.type === NodeTypes.ATTRIBUTE | ||||
|                     ? createSimpleExpression(keyProp.value!.content, true) | ||||
|                     : keyProp.exp! | ||||
|                 ) | ||||
|               ]) | ||||
|             } | ||||
|             childBlock = createSequenceExpression([ | ||||
|               createCallExpression(helper(OPEN_BLOCK)), | ||||
|               createCallExpression(helper(CREATE_BLOCK), [ | ||||
|                 helper(FRAGMENT), | ||||
|               `null`, | ||||
|               createCallExpression(helper(RENDER_LIST), [ | ||||
|                 source, | ||||
|                 childBlockProps, | ||||
|                 node.children | ||||
|               ]) | ||||
|             ]) | ||||
|           } else { | ||||
|             // Normal element v-for. Directly use the child's codegenNode,
 | ||||
|             // but replace createVNode() with createBlock()
 | ||||
|             node.codegenNode!.callee = helper(CREATE_BLOCK) | ||||
|             childBlock = createSequenceExpression([ | ||||
|               createCallExpression(helper(OPEN_BLOCK)), | ||||
|               node.codegenNode! | ||||
|             ]) | ||||
|           } | ||||
| 
 | ||||
|           renderExp.arguments.push( | ||||
|             createFunctionExpression( | ||||
|               params, | ||||
|                   node.tagType === ElementTypes.TEMPLATE ? node.children : node, | ||||
|                   true /* force newline to make it more readable */ | ||||
|               childBlock, | ||||
|               true /* force newline */ | ||||
|             ) | ||||
|               ]) | ||||
|             ]) | ||||
|           ) | ||||
| 
 | ||||
|           if (!__BROWSER__ && context.prefixIdentifiers) { | ||||
|             value && removeIdentifiers(value) | ||||
|             key && removeIdentifiers(key) | ||||
|             index && removeIdentifiers(index) | ||||
|           } | ||||
|         } | ||||
|       } else { | ||||
|         context.onError( | ||||
|  | ||||
| @ -20,9 +20,7 @@ import { | ||||
|   ObjectExpression, | ||||
|   createObjectProperty, | ||||
|   Property, | ||||
|   ExpressionNode, | ||||
|   TemplateChildNode, | ||||
|   FunctionExpression | ||||
|   ExpressionNode | ||||
| } from '../ast' | ||||
| import { createCompilerError, ErrorCodes } from '../errors' | ||||
| import { processExpression } from './transformExpression' | ||||
| @ -168,17 +166,19 @@ function createChildrenCodegenNode( | ||||
|   const needFragmentWrapper = | ||||
|     children.length > 1 || child.type !== NodeTypes.ELEMENT | ||||
|   if (needFragmentWrapper) { | ||||
|     let fragmentChildren: TemplateChildNode[] | FunctionExpression = children | ||||
|     // optimize away nested fragments when child is a ForNode
 | ||||
|     if (children.length === 1 && child.type === NodeTypes.FOR) { | ||||
|       fragmentChildren = (child.codegenNode.expressions[1] as CallExpression) | ||||
|         .arguments[2] as FunctionExpression | ||||
|     } | ||||
|     return createCallExpression(helper(CREATE_BLOCK), [ | ||||
|     const blockArgs: CallExpression['arguments'] = [ | ||||
|       helper(FRAGMENT), | ||||
|       keyExp, | ||||
|       fragmentChildren | ||||
|     ]) | ||||
|       children | ||||
|     ] | ||||
|     // optimize away nested fragments when child is a ForNode
 | ||||
|     if (children.length === 1 && child.type === NodeTypes.FOR) { | ||||
|       const forBlockExp = child.codegenNode | ||||
|       // directly use the for block's children and patchFlag
 | ||||
|       blockArgs[2] = forBlockExp.arguments[2] | ||||
|       blockArgs[3] = forBlockExp.arguments[3] | ||||
|     } | ||||
|     return createCallExpression(helper(CREATE_BLOCK), blockArgs) | ||||
|   } else { | ||||
|     const childCodegen = (child as ElementNode).codegenNode! | ||||
|     let vnodeCall = childCodegen | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { SourceLocation, Position } from './ast' | ||||
| import { SourceLocation, Position, ElementNode, NodeTypes } from './ast' | ||||
| import { parseScript } from 'meriyah' | ||||
| import { walk } from 'estree-walker' | ||||
| 
 | ||||
| @ -94,3 +94,25 @@ export function assert(condition: boolean, msg?: string) { | ||||
|     throw new Error(msg || `unexpected compiler condition`) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function findProp( | ||||
|   props: ElementNode['props'], | ||||
|   name: string | ||||
| ): ElementNode['props'][0] | undefined { | ||||
|   for (let i = 0; i < props.length; i++) { | ||||
|     const p = props[i] | ||||
|     if (p.type === NodeTypes.ATTRIBUTE) { | ||||
|       if (p.name === name && p.value && !p.value.isEmpty) { | ||||
|         return p | ||||
|       } | ||||
|     } else if ( | ||||
|       p.arg && | ||||
|       p.arg.type === NodeTypes.SIMPLE_EXPRESSION && | ||||
|       p.arg.isStatic && | ||||
|       p.arg.content === name && | ||||
|       p.exp | ||||
|     ) { | ||||
|       return p | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1230,7 +1230,7 @@ export function createRenderer< | ||||
|     // fast path
 | ||||
|     const { patchFlag, shapeFlag } = n2 | ||||
|     if (patchFlag) { | ||||
|       if (patchFlag & PatchFlags.KEYED_V_FOR) { | ||||
|       if (patchFlag & PatchFlags.KEYED_FRAGMENT) { | ||||
|         // this could be either fully-keyed or mixed (some keyed some not)
 | ||||
|         // presence of patchFlag means children are guaranteed to be arrays
 | ||||
|         patchKeyedChildren( | ||||
| @ -1244,7 +1244,7 @@ export function createRenderer< | ||||
|           optimized | ||||
|         ) | ||||
|         return | ||||
|       } else if (patchFlag & PatchFlags.UNKEYED_V_FOR) { | ||||
|       } else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) { | ||||
|         // unkeyed
 | ||||
|         patchUnkeyedChildren( | ||||
|           c1 as HostVNode[], | ||||
|  | ||||
| @ -47,11 +47,11 @@ export const enum PatchFlags { | ||||
|   // value.
 | ||||
|   NEED_PATCH = 1 << 5, | ||||
| 
 | ||||
|   // Indicates a v-for fragment with keyed or partially keyed children
 | ||||
|   KEYED_V_FOR = 1 << 6, | ||||
|   // Indicates a fragment with keyed or partially keyed children
 | ||||
|   KEYED_FRAGMENT = 1 << 6, | ||||
| 
 | ||||
|   // Indicates a v-for fragment with unkeyed children.
 | ||||
|   UNKEYED_V_FOR = 1 << 7, | ||||
|   // Indicates a fragment with unkeyed children.
 | ||||
|   UNKEYED_FRAGMENT = 1 << 7, | ||||
| 
 | ||||
|   // Indicates a component with dynamic slots (e.g. slot that references a v-for
 | ||||
|   // iterated value, or dynamic slot names).
 | ||||
| @ -67,8 +67,8 @@ export const PublicPatchFlags = { | ||||
|   PROPS: PatchFlags.PROPS, | ||||
|   NEED_PATCH: PatchFlags.NEED_PATCH, | ||||
|   FULL_PROPS: PatchFlags.FULL_PROPS, | ||||
|   KEYED_V_FOR: PatchFlags.KEYED_V_FOR, | ||||
|   UNKEYED_V_FOR: PatchFlags.UNKEYED_V_FOR, | ||||
|   KEYED_FRAGMENT: PatchFlags.KEYED_FRAGMENT, | ||||
|   UNKEYED_FRAGMENT: PatchFlags.UNKEYED_FRAGMENT, | ||||
|   DYNAMIC_SLOTS: PatchFlags.DYNAMIC_SLOTS | ||||
| } | ||||
| 
 | ||||
| @ -80,7 +80,7 @@ export const PatchFlagNames = { | ||||
|   [PatchFlags.PROPS]: `PROPS`, | ||||
|   [PatchFlags.NEED_PATCH]: `NEED_PATCH`, | ||||
|   [PatchFlags.FULL_PROPS]: `FULL_PROPS`, | ||||
|   [PatchFlags.KEYED_V_FOR]: `KEYED_V_FOR`, | ||||
|   [PatchFlags.UNKEYED_V_FOR]: `UNKEYED_V_FOR`, | ||||
|   [PatchFlags.KEYED_FRAGMENT]: `KEYED_FRAGMENT`, | ||||
|   [PatchFlags.UNKEYED_FRAGMENT]: `UNKEYED_FRAGMENT`, | ||||
|   [PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS` | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user