diff --git a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap index 68990eccef1..19a152de79b 100644 --- a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap @@ -102,6 +102,15 @@ return function render(_ctx, _cache) { }" `; +exports[`compiler: codegen forNode with constant expression 1`] = ` +" +return function render(_ctx, _cache) { + with (_ctx) { + return (_openBlock(), _createBlock(_Fragment, null, _renderList(), 64 /* STABLE_FRAGMENT */)) + } +}" +`; + exports[`compiler: codegen function mode preamble 1`] = ` "const _Vue = Vue diff --git a/packages/compiler-core/__tests__/codegen.spec.ts b/packages/compiler-core/__tests__/codegen.spec.ts index f9ebdc09fc9..2fc1bf93e8c 100644 --- a/packages/compiler-core/__tests__/codegen.spec.ts +++ b/packages/compiler-core/__tests__/codegen.spec.ts @@ -32,7 +32,7 @@ import { FRAGMENT, RENDER_LIST } from '../src/runtimeHelpers' -import { createElementWithCodegen } from './testUtils' +import { createElementWithCodegen, genFlagText } from './testUtils' import { PatchFlags } from '@vue/shared' function createRoot(options: Partial = {}): RootNode { @@ -283,7 +283,7 @@ describe('compiler: codegen', () => { type: NodeTypes.VNODE_CALL, tag: FRAGMENT, isBlock: true, - isForBlock: true, + disableTracking: true, props: undefined, children: createCallExpression(RENDER_LIST), patchFlag: '1', @@ -298,6 +298,37 @@ describe('compiler: codegen', () => { expect(code).toMatchSnapshot() }) + test('forNode with constant expression', () => { + const { code } = generate( + createRoot({ + codegenNode: { + type: NodeTypes.FOR, + loc: locStub, + source: createSimpleExpression('1 + 2', false, locStub, true), + valueAlias: undefined, + keyAlias: undefined, + objectIndexAlias: undefined, + children: [], + parseResult: {} as any, + codegenNode: { + type: NodeTypes.VNODE_CALL, + tag: FRAGMENT, + isBlock: true, + disableTracking: false, + props: undefined, + children: createCallExpression(RENDER_LIST), + patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT), + dynamicProps: undefined, + directives: undefined, + loc: locStub + } as ForCodegenNode + } + }) + ) + expect(code).toMatch(`openBlock()`) + expect(code).toMatchSnapshot() + }) + test('Element (callExpression + objectExpression + TemplateChildNode[])', () => { const { code } = generate( createRoot({ diff --git a/packages/compiler-core/__tests__/testUtils.ts b/packages/compiler-core/__tests__/testUtils.ts index 300873facc7..61d2d8d0fce 100644 --- a/packages/compiler-core/__tests__/testUtils.ts +++ b/packages/compiler-core/__tests__/testUtils.ts @@ -62,7 +62,7 @@ export function createElementWithCodegen( dynamicProps, directives: undefined, isBlock: false, - isForBlock: false, + disableTracking: false, loc: locStub } } diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap index 870922105c0..970048cf699 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap @@ -150,6 +150,20 @@ return function render(_ctx, _cache) { }" `; +exports[`compiler: v-for codegen v-for with constant expression 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, toDisplayString: _toDisplayString, createVNode: _createVNode } = _Vue + + return (_openBlock(), _createBlock(_Fragment, null, _renderList(10, (item) => { + return _createVNode(\\"p\\", null, _toDisplayString(item), 1 /* TEXT */) + }), 64 /* STABLE_FRAGMENT */)) + } +}" +`; + exports[`compiler: v-for codegen v-if + v-for 1`] = ` "const _Vue = Vue diff --git a/packages/compiler-core/__tests__/transforms/vFor.spec.ts b/packages/compiler-core/__tests__/transforms/vFor.spec.ts index 03d96cdf25c..2657c08fe97 100644 --- a/packages/compiler-core/__tests__/transforms/vFor.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vFor.spec.ts @@ -560,15 +560,18 @@ describe('compiler: v-for', () => { function assertSharedCodegen( node: ForCodegenNode, keyed: boolean = false, - customReturn: boolean = false + customReturn: boolean = false, + disableTracking: boolean = true ) { expect(node).toMatchObject({ type: NodeTypes.VNODE_CALL, tag: FRAGMENT, - isForBlock: true, - patchFlag: keyed - ? genFlagText(PatchFlags.KEYED_FRAGMENT) - : genFlagText(PatchFlags.UNKEYED_FRAGMENT), + disableTracking, + patchFlag: !disableTracking + ? genFlagText(PatchFlags.STABLE_FRAGMENT) + : keyed + ? genFlagText(PatchFlags.KEYED_FRAGMENT) + : genFlagText(PatchFlags.UNKEYED_FRAGMENT), children: { type: NodeTypes.JS_CALL_EXPRESSION, callee: RENDER_LIST, @@ -580,7 +583,7 @@ describe('compiler: v-for', () => { ? {} : { type: NodeTypes.VNODE_CALL, - isBlock: true + isBlock: disableTracking } } ] @@ -658,6 +661,43 @@ describe('compiler: v-for', () => { expect(generate(root).code).toMatchSnapshot() }) + test('v-for with constant expression', () => { + const { + root, + node: { codegenNode } + } = parseWithForTransform('

{{item}}

', { + prefixIdentifiers: true + }) + + expect( + assertSharedCodegen( + codegenNode, + false /* keyed */, + false /* customReturn */, + false /* disableTracking */ + ) + ).toMatchObject({ + source: { content: `10`, isConstant: true }, + params: [{ content: `item` }], + innerVNodeCall: { + tag: `"p"`, + props: undefined, + isBlock: false, + children: { + type: NodeTypes.INTERPOLATION, + content: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'item', + isStatic: false, + isConstant: false + } + }, + patchFlag: genFlagText(PatchFlags.TEXT) + } + }) + expect(generate(root).code).toMatchSnapshot() + }) + test('template v-for', () => { const { root, @@ -777,7 +817,7 @@ describe('compiler: v-for', () => { key: `[0]` }), isBlock: true, - isForBlock: true, + disableTracking: true, patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT), children: { type: NodeTypes.JS_CALL_EXPRESSION, diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index d4c11c07b9e..6b85e150c4e 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -281,7 +281,7 @@ export interface VNodeCall extends Node { dynamicProps: string | undefined directives: DirectiveArguments | undefined isBlock: boolean - isForBlock: boolean + disableTracking: boolean } // JS Node Types --------------------------------------------------------------- @@ -492,7 +492,7 @@ export interface ForCodegenNode extends VNodeCall { props: undefined children: ForRenderListExpression patchFlag: string - isForBlock: true + disableTracking: boolean } export interface ForRenderListExpression extends CallExpression { @@ -543,7 +543,7 @@ export function createVNodeCall( dynamicProps?: VNodeCall['dynamicProps'], directives?: VNodeCall['directives'], isBlock: VNodeCall['isBlock'] = false, - isForBlock: VNodeCall['isForBlock'] = false, + disableTracking: VNodeCall['disableTracking'] = false, loc = locStub ): VNodeCall { if (context) { @@ -567,7 +567,7 @@ export function createVNodeCall( dynamicProps, directives, isBlock, - isForBlock, + disableTracking, loc } } diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index d95b22b6bd7..b213eec0ff7 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -698,13 +698,13 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) { dynamicProps, directives, isBlock, - isForBlock + disableTracking } = node if (directives) { push(helper(WITH_DIRECTIVES) + `(`) } if (isBlock) { - push(`(${helper(OPEN_BLOCK)}(${isForBlock ? `true` : ``}), `) + push(`(${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}), `) } if (pure) { push(PURE_ANNOTATION) diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 012b3801af0..4daf7a0600b 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -203,7 +203,7 @@ export const transformElement: NodeTransform = (node, context) => { vnodeDynamicProps, vnodeDirectives, !!shouldUseBlock, - false /* isForBlock */, + false /* disableTracking */, node.loc ) } diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts index ee5c243a800..d85964e5270 100644 --- a/packages/compiler-core/src/transforms/vFor.ts +++ b/packages/compiler-core/src/transforms/vFor.ts @@ -55,9 +55,14 @@ export const transformFor = createStructuralDirectiveTransform( forNode.source ]) as ForRenderListExpression const keyProp = findProp(node, `key`) - const fragmentFlag = keyProp - ? PatchFlags.KEYED_FRAGMENT - : PatchFlags.UNKEYED_FRAGMENT + const isStableFragment = + forNode.source.type === NodeTypes.SIMPLE_EXPRESSION && + forNode.source.isConstant + const fragmentFlag = isStableFragment + ? PatchFlags.STABLE_FRAGMENT + : keyProp + ? PatchFlags.KEYED_FRAGMENT + : PatchFlags.UNKEYED_FRAGMENT forNode.codegenNode = createVNodeCall( context, helper(FRAGMENT), @@ -67,7 +72,7 @@ export const transformFor = createStructuralDirectiveTransform( undefined, undefined, true /* isBlock */, - true /* isForBlock */, + !isStableFragment /* disableTracking */, node.loc ) as ForCodegenNode @@ -122,9 +127,11 @@ export const transformFor = createStructuralDirectiveTransform( // but mark it as a block. childBlock = (children[0] as PlainElementNode) .codegenNode as VNodeCall - childBlock.isBlock = true - helper(OPEN_BLOCK) - helper(CREATE_BLOCK) + childBlock.isBlock = !isStableFragment + if (childBlock.isBlock) { + helper(OPEN_BLOCK) + helper(CREATE_BLOCK) + } } renderExp.arguments.push(createFunctionExpression(