diff --git a/packages/compiler-core/__tests__/transforms/vOnce.spec.ts b/packages/compiler-core/__tests__/transforms/vOnce.spec.ts index 45eff37d048..3983aa9605a 100644 --- a/packages/compiler-core/__tests__/transforms/vOnce.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vOnce.spec.ts @@ -3,21 +3,17 @@ import { transform, NodeTypes, generate, - CompilerOptions + CompilerOptions, + getBaseTransformPreset } from '../../src' -import { transformOnce } from '../../src/transforms/vOnce' -import { transformElement } from '../../src/transforms/transformElement' import { RENDER_SLOT, SET_BLOCK_TRACKING } from '../../src/runtimeHelpers' -import { transformBind } from '../../src/transforms/vBind' -import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet' function transformWithOnce(template: string, options: CompilerOptions = {}) { const ast = parse(template) + const [nodeTransforms, directiveTransforms] = getBaseTransformPreset() transform(ast, { - nodeTransforms: [transformOnce, transformElement, transformSlotOutlet], - directiveTransforms: { - bind: transformBind - }, + nodeTransforms, + directiveTransforms, ...options }) return ast @@ -102,4 +98,30 @@ describe('compiler: v-once transform', () => { }) expect(generate(root).code).toMatchSnapshot() }) + + test('with v-if', () => { + const root = transformWithOnce(`
`) + expect(root.cached).toBe(1) + expect(root.helpers).toContain(SET_BLOCK_TRACKING) + expect(root.children[0]).toMatchObject({ + type: NodeTypes.IF, + // should cache the entire v-if expression, not just a single branch + codegenNode: { + type: NodeTypes.JS_CACHE_EXPRESSION + } + }) + }) + + test('with v-for', () => { + const root = transformWithOnce(`
`) + expect(root.cached).toBe(1) + expect(root.helpers).toContain(SET_BLOCK_TRACKING) + expect(root.children[0]).toMatchObject({ + type: NodeTypes.FOR, + // should cache the entire v-for expression, not just a single branch + codegenNode: { + type: NodeTypes.JS_CACHE_EXPRESSION + } + }) + }) }) diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index 05284751ab3..4495d498094 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -399,6 +399,7 @@ export function traverseNode( } // exit transforms + context.currentNode = node let i = exitFns.length while (i--) { exitFns[i]() diff --git a/packages/compiler-core/src/transforms/vOnce.ts b/packages/compiler-core/src/transforms/vOnce.ts index f9d46f1a1f1..06c5171e1d8 100644 --- a/packages/compiler-core/src/transforms/vOnce.ts +++ b/packages/compiler-core/src/transforms/vOnce.ts @@ -1,14 +1,21 @@ import { NodeTransform } from '../transform' import { findDir } from '../utils' -import { NodeTypes } from '../ast' +import { ElementNode, ForNode, IfNode, NodeTypes } from '../ast' import { SET_BLOCK_TRACKING } from '../runtimeHelpers' +const seen = new WeakSet() + export const transformOnce: NodeTransform = (node, context) => { if (node.type === NodeTypes.ELEMENT && findDir(node, 'once', true)) { + if (seen.has(node)) { + return + } + seen.add(node) context.helper(SET_BLOCK_TRACKING) return () => { - if (node.codegenNode) { - node.codegenNode = context.cache(node.codegenNode, true /* isVNode */) + const cur = context.currentNode as ElementNode | IfNode | ForNode + if (cur.codegenNode) { + cur.codegenNode = context.cache(cur.codegenNode, true /* isVNode */) } } } diff --git a/packages/vue/__tests__/index.spec.ts b/packages/vue/__tests__/index.spec.ts index 7339a0804fa..2de237e2a1b 100644 --- a/packages/vue/__tests__/index.spec.ts +++ b/packages/vue/__tests__/index.spec.ts @@ -1,4 +1,4 @@ -import { createApp, ref, nextTick } from '../src' +import { createApp, ref, nextTick, reactive } from '../src' describe('compiler + runtime integration', () => { it('should support runtime template compilation', () => { @@ -247,4 +247,38 @@ describe('compiler + runtime integration', () => { document.querySelector = origin }) + + test('v-if + v-once', async () => { + const ok = ref(true) + const App = { + setup() { + return { ok } + }, + template: `
{{ ok }}
{{ ok }}
` + } + const container = document.createElement('div') + createApp(App).mount(container) + + expect(container.innerHTML).toBe(`
true
true
`) + ok.value = false + await nextTick() + expect(container.innerHTML).toBe(`
false
true
`) + }) + + test('v-for + v-once', async () => { + const list = reactive([1]) + const App = { + setup() { + return { list } + }, + template: `
{{ list.length }}
{{ i }}
` + } + const container = document.createElement('div') + createApp(App).mount(container) + + expect(container.innerHTML).toBe(`
1
1
`) + list.push(2) + await nextTick() + expect(container.innerHTML).toBe(`
2
1
`) + }) })