Skip to content

Commit

Permalink
fix(v-once): fix v-once usage with v-if and v-for
Browse files Browse the repository at this point in the history
fix #2035
  • Loading branch information
yyx990803 committed Sep 2, 2020
1 parent ad93fa4 commit 52e45a9
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 13 deletions.
40 changes: 31 additions & 9 deletions packages/compiler-core/__tests__/transforms/vOnce.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -102,4 +98,30 @@ describe('compiler: v-once transform', () => {
})
expect(generate(root).code).toMatchSnapshot()
})

test('with v-if', () => {
const root = transformWithOnce(`<div v-if="true" v-once />`)
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(`<div v-for="i in list" v-once />`)
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
}
})
})
})
1 change: 1 addition & 0 deletions packages/compiler-core/src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ export function traverseNode(
}

// exit transforms
context.currentNode = node
let i = exitFns.length
while (i--) {
exitFns[i]()
Expand Down
13 changes: 10 additions & 3 deletions packages/compiler-core/src/transforms/vOnce.ts
Original file line number Diff line number Diff line change
@@ -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 */)
}
}
}
Expand Down
36 changes: 35 additions & 1 deletion packages/vue/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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: `<div>{{ ok }}<div v-if="ok" v-once>{{ ok }}</div></div>`
}
const container = document.createElement('div')
createApp(App).mount(container)

expect(container.innerHTML).toBe(`<div>true<div>true</div></div>`)
ok.value = false
await nextTick()
expect(container.innerHTML).toBe(`<div>false<div>true</div></div>`)
})

test('v-for + v-once', async () => {
const list = reactive([1])
const App = {
setup() {
return { list }
},
template: `<div>{{ list.length }}<div v-for="i in list" v-once>{{ i }}</div></div>`
}
const container = document.createElement('div')
createApp(App).mount(container)

expect(container.innerHTML).toBe(`<div>1<div>1</div></div>`)
list.push(2)
await nextTick()
expect(container.innerHTML).toBe(`<div>2<div>1</div></div>`)
})
})

0 comments on commit 52e45a9

Please sign in to comment.