Skip to content

Commit 3b64508

Browse files
committed
feat: v-memo
1 parent 5cea9a1 commit 3b64508

File tree

23 files changed

+562
-130
lines changed

23 files changed

+562
-130
lines changed

packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ export function render(_ctx, _cache) {
218218
return (_openBlock(), _createElementBlock(\\"div\\", null, [
219219
_createElementVNode(\\"div\\", null, [
220220
_createElementVNode(\\"div\\", {
221-
onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.foo && _ctx.foo(...args)))
221+
onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.foo && _ctx.foo(...args)))
222222
})
223223
])
224224
]))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`compiler: v-memo transform on component 1`] = `
4+
"import { resolveComponent as _resolveComponent, createVNode as _createVNode, withMemo as _withMemo, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
5+
6+
export function render(_ctx, _cache) {
7+
const _component_Comp = _resolveComponent(\\"Comp\\")
8+
9+
return (_openBlock(), _createElementBlock(\\"div\\", null, [
10+
_withMemo([_ctx.x], () => _createVNode(_component_Comp), _cache, 0)
11+
]))
12+
}"
13+
`;
14+
15+
exports[`compiler: v-memo transform on normal element 1`] = `
16+
"import { openBlock as _openBlock, createElementBlock as _createElementBlock, withMemo as _withMemo } from \\"vue\\"
17+
18+
export function render(_ctx, _cache) {
19+
return (_openBlock(), _createElementBlock(\\"div\\", null, [
20+
_withMemo([_ctx.x], () => (_openBlock(), _createElementBlock(\\"div\\")), _cache, 0)
21+
]))
22+
}"
23+
`;
24+
25+
exports[`compiler: v-memo transform on root element 1`] = `
26+
"import { openBlock as _openBlock, createElementBlock as _createElementBlock, withMemo as _withMemo } from \\"vue\\"
27+
28+
export function render(_ctx, _cache) {
29+
return _withMemo([_ctx.x], () => (_openBlock(), _createElementBlock(\\"div\\")), _cache, 0)
30+
}"
31+
`;
32+
33+
exports[`compiler: v-memo transform on template v-for 1`] = `
34+
"import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, isMemoSame as _isMemoSame, withMemo as _withMemo } from \\"vue\\"
35+
36+
export function render(_ctx, _cache) {
37+
return (_openBlock(), _createElementBlock(\\"div\\", null, [
38+
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, ({ x, y }, __, ___, _cached) => {
39+
const _memo = ([x, y === z])
40+
if (_cached && _cached.key === x && _isMemoSame(_cached.memo, _memo)) return _cached
41+
const _item = (_openBlock(), _createElementBlock(\\"span\\", { key: x }, \\"foobar\\"))
42+
_item.memo = _memo
43+
return _item
44+
}, _cache, 0), 128 /* KEYED_FRAGMENT */))
45+
]))
46+
}"
47+
`;
48+
49+
exports[`compiler: v-memo transform on v-for 1`] = `
50+
"import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, createElementVNode as _createElementVNode, isMemoSame as _isMemoSame, withMemo as _withMemo } from \\"vue\\"
51+
52+
export function render(_ctx, _cache) {
53+
return (_openBlock(), _createElementBlock(\\"div\\", null, [
54+
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, ({ x, y }, __, ___, _cached) => {
55+
const _memo = ([x, y === _ctx.z])
56+
if (_cached && _cached.key === x && _isMemoSame(_cached.memo, _memo)) return _cached
57+
const _item = (_openBlock(), _createElementBlock(\\"div\\", { key: x }, [
58+
_createElementVNode(\\"span\\", null, \\"foobar\\")
59+
]))
60+
_item.memo = _memo
61+
return _item
62+
}, _cache, 0), 128 /* KEYED_FRAGMENT */))
63+
]))
64+
}"
65+
`;
66+
67+
exports[`compiler: v-memo transform on v-if 1`] = `
68+
"import { createElementVNode as _createElementVNode, createTextVNode as _createTextVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, withMemo as _withMemo, createCommentVNode as _createCommentVNode, resolveComponent as _resolveComponent, createBlock as _createBlock } from \\"vue\\"
69+
70+
export function render(_ctx, _cache) {
71+
const _component_Comp = _resolveComponent(\\"Comp\\")
72+
73+
return (_openBlock(), _createElementBlock(\\"div\\", null, [
74+
(_ctx.ok)
75+
? _withMemo([_ctx.x], () => (_openBlock(), _createElementBlock(\\"div\\", { key: 0 }, [
76+
_createElementVNode(\\"span\\", null, \\"foo\\"),
77+
_createTextVNode(\\"bar\\")
78+
])), _cache, 0)
79+
: _withMemo([_ctx.x], () => (_openBlock(), _createBlock(_component_Comp, { key: 1 })), _cache, 1)
80+
]))
81+
}"
82+
`;

packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap

+15-15
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ return function render(_ctx, _cache) {
77
with (_ctx) {
88
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode } = _Vue
99
10-
return _cache[1] || (
10+
return _cache[0] || (
1111
_setBlockTracking(-1),
12-
_cache[1] = _createElementVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
12+
_cache[0] = _createElementVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
1313
_setBlockTracking(1),
14-
_cache[1]
14+
_cache[0]
1515
)
1616
}
1717
}"
@@ -27,11 +27,11 @@ return function render(_ctx, _cache) {
2727
const _component_Comp = _resolveComponent(\\"Comp\\")
2828
2929
return (_openBlock(), _createElementBlock(\\"div\\", null, [
30-
_cache[1] || (
30+
_cache[0] || (
3131
_setBlockTracking(-1),
32-
_cache[1] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
32+
_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
3333
_setBlockTracking(1),
34-
_cache[1]
34+
_cache[0]
3535
)
3636
]))
3737
}
@@ -46,11 +46,11 @@ return function render(_ctx, _cache) {
4646
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
4747
4848
return (_openBlock(), _createElementBlock(\\"div\\", null, [
49-
_cache[1] || (
49+
_cache[0] || (
5050
_setBlockTracking(-1),
51-
_cache[1] = _createElementVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
51+
_cache[0] = _createElementVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
5252
_setBlockTracking(1),
53-
_cache[1]
53+
_cache[0]
5454
)
5555
]))
5656
}
@@ -65,11 +65,11 @@ return function render(_ctx, _cache) {
6565
const { setBlockTracking: _setBlockTracking, renderSlot: _renderSlot, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
6666
6767
return (_openBlock(), _createElementBlock(\\"div\\", null, [
68-
_cache[1] || (
68+
_cache[0] || (
6969
_setBlockTracking(-1),
70-
_cache[1] = _renderSlot($slots, \\"default\\"),
70+
_cache[0] = _renderSlot($slots, \\"default\\"),
7171
_setBlockTracking(1),
72-
_cache[1]
72+
_cache[0]
7373
)
7474
]))
7575
}
@@ -84,11 +84,11 @@ return function render(_ctx, _cache) {
8484
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
8585
8686
return (_openBlock(), _createElementBlock(\\"div\\", null, [
87-
_cache[1] || (
87+
_cache[0] || (
8888
_setBlockTracking(-1),
89-
_cache[1] = _createElementVNode(\\"div\\"),
89+
_cache[0] = _createElementVNode(\\"div\\"),
9090
_setBlockTracking(1),
91-
_cache[1]
91+
_cache[0]
9292
)
9393
]))
9494
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { baseCompile } from '../../src'
2+
3+
describe('compiler: v-memo transform', () => {
4+
function compile(content: string) {
5+
return baseCompile(`<div>${content}</div>`, {
6+
mode: 'module',
7+
prefixIdentifiers: true
8+
}).code
9+
}
10+
11+
test('on root element', () => {
12+
expect(
13+
baseCompile(`<div v-memo="[x]"></div>`, {
14+
mode: 'module',
15+
prefixIdentifiers: true
16+
}).code
17+
).toMatchSnapshot()
18+
})
19+
20+
test('on normal element', () => {
21+
expect(compile(`<div v-memo="[x]"></div>`)).toMatchSnapshot()
22+
})
23+
24+
test('on component', () => {
25+
expect(compile(`<Comp v-memo="[x]"></Comp>`)).toMatchSnapshot()
26+
})
27+
28+
test('on v-if', () => {
29+
expect(
30+
compile(
31+
`<div v-if="ok" v-memo="[x]"><span>foo</span>bar</div>
32+
<Comp v-else v-memo="[x]"></Comp>`
33+
)
34+
).toMatchSnapshot()
35+
})
36+
37+
test('on v-for', () => {
38+
expect(
39+
compile(
40+
`<div v-for="{ x, y } in list" :key="x" v-memo="[x, y === z]">
41+
<span>foobar</span>
42+
</div>`
43+
)
44+
).toMatchSnapshot()
45+
})
46+
47+
test('on template v-for', () => {
48+
expect(
49+
compile(
50+
`<template v-for="{ x, y } in list" :key="x" v-memo="[x, y === z]">
51+
<span>foobar</span>
52+
</template>`
53+
)
54+
).toMatchSnapshot()
55+
})
56+
})

packages/compiler-core/__tests__/transforms/vOn.spec.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ describe('compiler: transform v-on', () => {
452452
(vnodeCall.props as ObjectExpression).properties[0].value
453453
).toMatchObject({
454454
type: NodeTypes.JS_CACHE_EXPRESSION,
455-
index: 1,
455+
index: 0,
456456
value: {
457457
type: NodeTypes.SIMPLE_EXPRESSION,
458458
content: `() => {}`
@@ -473,7 +473,7 @@ describe('compiler: transform v-on', () => {
473473
(vnodeCall.props as ObjectExpression).properties[0].value
474474
).toMatchObject({
475475
type: NodeTypes.JS_CACHE_EXPRESSION,
476-
index: 1,
476+
index: 0,
477477
value: {
478478
type: NodeTypes.COMPOUND_EXPRESSION,
479479
children: [
@@ -498,7 +498,7 @@ describe('compiler: transform v-on', () => {
498498
(vnodeCall.props as ObjectExpression).properties[0].value
499499
).toMatchObject({
500500
type: NodeTypes.JS_CACHE_EXPRESSION,
501-
index: 1,
501+
index: 0,
502502
value: {
503503
type: NodeTypes.COMPOUND_EXPRESSION,
504504
children: [
@@ -543,7 +543,7 @@ describe('compiler: transform v-on', () => {
543543
(vnodeCall.props as ObjectExpression).properties[0].value
544544
).toMatchObject({
545545
type: NodeTypes.JS_CACHE_EXPRESSION,
546-
index: 1,
546+
index: 0,
547547
value: {
548548
type: NodeTypes.COMPOUND_EXPRESSION,
549549
children: [`() => `, { content: `_ctx.foo` }, `()`]
@@ -565,7 +565,7 @@ describe('compiler: transform v-on', () => {
565565
(vnodeCall.props as ObjectExpression).properties[0].value
566566
).toMatchObject({
567567
type: NodeTypes.JS_CACHE_EXPRESSION,
568-
index: 1,
568+
index: 0,
569569
value: {
570570
type: NodeTypes.COMPOUND_EXPRESSION,
571571
children: [

packages/compiler-core/__tests__/transforms/vOnce.spec.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('compiler: v-once transform', () => {
2626
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
2727
expect(root.codegenNode).toMatchObject({
2828
type: NodeTypes.JS_CACHE_EXPRESSION,
29-
index: 1,
29+
index: 0,
3030
value: {
3131
type: NodeTypes.VNODE_CALL,
3232
tag: `"div"`
@@ -41,7 +41,7 @@ describe('compiler: v-once transform', () => {
4141
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
4242
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
4343
type: NodeTypes.JS_CACHE_EXPRESSION,
44-
index: 1,
44+
index: 0,
4545
value: {
4646
type: NodeTypes.VNODE_CALL,
4747
tag: `"div"`
@@ -56,7 +56,7 @@ describe('compiler: v-once transform', () => {
5656
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
5757
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
5858
type: NodeTypes.JS_CACHE_EXPRESSION,
59-
index: 1,
59+
index: 0,
6060
value: {
6161
type: NodeTypes.VNODE_CALL,
6262
tag: `_component_Comp`
@@ -71,7 +71,7 @@ describe('compiler: v-once transform', () => {
7171
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
7272
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
7373
type: NodeTypes.JS_CACHE_EXPRESSION,
74-
index: 1,
74+
index: 0,
7575
value: {
7676
type: NodeTypes.JS_CALL_EXPRESSION,
7777
callee: RENDER_SLOT
@@ -90,7 +90,7 @@ describe('compiler: v-once transform', () => {
9090
expect(root.hoists.length).toBe(0)
9191
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
9292
type: NodeTypes.JS_CACHE_EXPRESSION,
93-
index: 1,
93+
index: 0,
9494
value: {
9595
type: NodeTypes.VNODE_CALL,
9696
tag: `"div"`

packages/compiler-core/src/ast.ts

+16-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
RENDER_LIST,
77
OPEN_BLOCK,
88
FRAGMENT,
9-
WITH_DIRECTIVES
9+
WITH_DIRECTIVES,
10+
WITH_MEMO
1011
} from './runtimeHelpers'
1112
import { PropsExpression } from './transforms/transformElement'
1213
import { ImportItem, TransformContext } from './transform'
@@ -135,6 +136,7 @@ export interface PlainElementNode extends BaseElementNode {
135136
| VNodeCall
136137
| SimpleExpressionNode // when hoisted
137138
| CacheExpression // when cached by v-once
139+
| MemoExpression // when cached by v-memo
138140
| undefined
139141
ssrCodegenNode?: TemplateLiteral
140142
}
@@ -144,6 +146,7 @@ export interface ComponentNode extends BaseElementNode {
144146
codegenNode:
145147
| VNodeCall
146148
| CacheExpression // when cached by v-once
149+
| MemoExpression // when cached by v-memo
147150
| undefined
148151
ssrCodegenNode?: CallExpression
149152
}
@@ -375,6 +378,15 @@ export interface CacheExpression extends Node {
375378
isVNode: boolean
376379
}
377380

381+
export interface MemoExpression extends CallExpression {
382+
callee: typeof WITH_MEMO
383+
arguments: [ExpressionNode, MemoFactory, string, string]
384+
}
385+
386+
interface MemoFactory extends FunctionExpression {
387+
returns: BlockCodegenNode
388+
}
389+
378390
// SSR-specific Node Types -----------------------------------------------------
379391

380392
export type SSRCodegenNode =
@@ -499,8 +511,8 @@ export interface DynamicSlotFnProperty extends Property {
499511
export type BlockCodegenNode = VNodeCall | RenderSlotCall
500512

501513
export interface IfConditionalExpression extends ConditionalExpression {
502-
consequent: BlockCodegenNode
503-
alternate: BlockCodegenNode | IfConditionalExpression
514+
consequent: BlockCodegenNode | MemoExpression
515+
alternate: BlockCodegenNode | IfConditionalExpression | MemoExpression
504516
}
505517

506518
export interface ForCodegenNode extends VNodeCall {
@@ -627,7 +639,7 @@ export function createObjectProperty(
627639

628640
export function createSimpleExpression(
629641
content: SimpleExpressionNode['content'],
630-
isStatic: SimpleExpressionNode['isStatic'],
642+
isStatic: SimpleExpressionNode['isStatic'] = false,
631643
loc: SourceLocation = locStub,
632644
constType: ConstantTypes = ConstantTypes.NOT_CONSTANT
633645
): SimpleExpressionNode {

packages/compiler-core/src/codegen.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -651,11 +651,11 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
651651
case NodeTypes.JS_CACHE_EXPRESSION:
652652
genCacheExpression(node, context)
653653
break
654-
655-
// SSR only types
656654
case NodeTypes.JS_BLOCK_STATEMENT:
657-
!__BROWSER__ && genNodeList(node.body, context, true, false)
655+
genNodeList(node.body, context, true, false)
658656
break
657+
658+
// SSR only types
659659
case NodeTypes.JS_TEMPLATE_LITERAL:
660660
!__BROWSER__ && genTemplateLiteral(node, context)
661661
break

0 commit comments

Comments
 (0)