feat(v-on): cache handlers
This commit is contained in:
@@ -13,7 +13,8 @@ import {
|
||||
createCallExpression,
|
||||
createConditionalExpression,
|
||||
IfCodegenNode,
|
||||
ForCodegenNode
|
||||
ForCodegenNode,
|
||||
createCacheExpression
|
||||
} from '../src'
|
||||
import {
|
||||
CREATE_VNODE,
|
||||
@@ -34,6 +35,7 @@ function createRoot(options: Partial<RootNode> = {}): RootNode {
|
||||
components: [],
|
||||
directives: [],
|
||||
hoists: [],
|
||||
cached: 0,
|
||||
codegenNode: createSimpleExpression(`null`, false),
|
||||
loc: locStub,
|
||||
...options
|
||||
@@ -135,6 +137,12 @@ describe('compiler: codegen', () => {
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('cached', () => {
|
||||
const root = createRoot({ cached: 3 })
|
||||
const { code } = generate(root)
|
||||
expect(code).toMatch(`let _cached_1, _cached_2, _cached_3`)
|
||||
})
|
||||
|
||||
test('prefixIdentifiers: true should inject _ctx statement', () => {
|
||||
const { code } = generate(createRoot(), { prefixIdentifiers: true })
|
||||
expect(code).toMatch(`const _ctx = this\n`)
|
||||
@@ -359,4 +367,16 @@ describe('compiler: codegen', () => {
|
||||
)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('CacheExpression', () => {
|
||||
const { code } = generate(
|
||||
createRoot({
|
||||
codegenNode: createCacheExpression(
|
||||
1,
|
||||
createSimpleExpression(`foo`, false)
|
||||
)
|
||||
})
|
||||
)
|
||||
expect(code).toMatch(`_cached_1 || (_cached_1 = foo)`)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -203,6 +203,24 @@ return function render() {
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist elements with cached handlers 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
let _cached_1
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||
_createVNode(\\"div\\", {
|
||||
onClick: _cached_1 || (_cached_1 = $event => (_ctx.foo($event)))
|
||||
})
|
||||
]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that refer scope variables (2) 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export default function render() {
|
||||
return (openBlock(), createBlock(\\"input\\", {
|
||||
modelValue: _ctx.model[_ctx.index],
|
||||
\\"onUpdate:modelValue\\": $event => (_ctx.model[_ctx.index] = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\"]))
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function render() {
|
||||
return (openBlock(), createBlock(\\"input\\", {
|
||||
modelValue: _ctx.model,
|
||||
\\"onUpdate:modelValue\\": $event => (_ctx.model = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\"]))
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { transformExpression } from '../../src/transforms/transformExpression'
|
||||
import { transformIf } from '../../src/transforms/vIf'
|
||||
import { transformFor } from '../../src/transforms/vFor'
|
||||
import { transformBind } from '../../src/transforms/vBind'
|
||||
import { transformOn } from '../../src/transforms/vOn'
|
||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
|
||||
@@ -25,7 +26,6 @@ function transformWithHoist(template: string, options: CompilerOptions = {}) {
|
||||
const ast = parse(template)
|
||||
transform(ast, {
|
||||
hoistStatic: true,
|
||||
prefixIdentifiers: options.prefixIdentifiers,
|
||||
nodeTransforms: [
|
||||
transformIf,
|
||||
transformFor,
|
||||
@@ -33,8 +33,10 @@ function transformWithHoist(template: string, options: CompilerOptions = {}) {
|
||||
transformElement
|
||||
],
|
||||
directiveTransforms: {
|
||||
on: transformOn,
|
||||
bind: transformBind
|
||||
}
|
||||
},
|
||||
...options
|
||||
})
|
||||
expect(ast.codegenNode).toMatchObject({
|
||||
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
|
||||
@@ -656,5 +658,16 @@ describe('compiler: hoistStatic transform', () => {
|
||||
expect(root.hoists.length).toBe(0)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should NOT hoist elements with cached handlers', () => {
|
||||
const { root } = transformWithHoist(`<div><div @click="foo"/></div>`, {
|
||||
prefixIdentifiers: true,
|
||||
cacheHandlers: true
|
||||
})
|
||||
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.hoists.length).toBe(0)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
ForNode,
|
||||
PlainElementNode,
|
||||
PlainElementCodegenNode,
|
||||
ComponentNode
|
||||
ComponentNode,
|
||||
NodeTypes
|
||||
} from '../../src'
|
||||
import { ErrorCodes } from '../../src/errors'
|
||||
import { transformModel } from '../../src/transforms/vModel'
|
||||
@@ -338,25 +339,36 @@ describe('compiler: transform v-model', () => {
|
||||
expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should not mark update handler dynamic', () => {
|
||||
test('should cache update handler w/ cacheHandlers: true', () => {
|
||||
const root = parseWithVModel('<input v-model="foo" />', {
|
||||
prefixIdentifiers: true
|
||||
prefixIdentifiers: true,
|
||||
cacheHandlers: true
|
||||
})
|
||||
expect(root.cached).toBe(1)
|
||||
const codegen = (root.children[0] as PlainElementNode)
|
||||
.codegenNode as PlainElementCodegenNode
|
||||
// should not list cached prop in dynamicProps
|
||||
expect(codegen.arguments[4]).toBe(`["modelValue"]`)
|
||||
expect(
|
||||
(codegen.arguments[1] as ObjectExpression).properties[1].value.type
|
||||
).toBe(NodeTypes.JS_CACHE_EXPRESSION)
|
||||
})
|
||||
|
||||
test('should mark update handler dynamic if it refers v-for scope variables', () => {
|
||||
test('should not cache update handler if it refers v-for scope variables', () => {
|
||||
const root = parseWithVModel(
|
||||
'<input v-for="i in list" v-model="foo[i]" />',
|
||||
{
|
||||
prefixIdentifiers: true
|
||||
prefixIdentifiers: true,
|
||||
cacheHandlers: true
|
||||
}
|
||||
)
|
||||
expect(root.cached).toBe(0)
|
||||
const codegen = ((root.children[0] as ForNode)
|
||||
.children[0] as PlainElementNode).codegenNode as PlainElementCodegenNode
|
||||
expect(codegen.arguments[4]).toBe(`["modelValue", "onUpdate:modelValue"]`)
|
||||
expect(
|
||||
(codegen.arguments[1] as ObjectExpression).properties[1].value.type
|
||||
).not.toBe(NodeTypes.JS_CACHE_EXPRESSION)
|
||||
})
|
||||
|
||||
test('should mark update handler dynamic if it refers slot scope variables', () => {
|
||||
@@ -389,7 +401,7 @@ describe('compiler: transform v-model', () => {
|
||||
})
|
||||
// should NOT include modelModifiers in dynamicPropNames because it's never
|
||||
// gonna change
|
||||
expect(args[4]).toBe(`["modelValue"]`)
|
||||
expect(args[4]).toBe(`["modelValue", "onUpdate:modelValue"]`)
|
||||
})
|
||||
|
||||
describe('errors', () => {
|
||||
|
||||
@@ -6,16 +6,14 @@ import {
|
||||
CompilerOptions,
|
||||
ErrorCodes,
|
||||
NodeTypes,
|
||||
CallExpression
|
||||
CallExpression,
|
||||
PlainElementCodegenNode
|
||||
} from '../../src'
|
||||
import { transformOn } from '../../src/transforms/vOn'
|
||||
import { transformElement } from '../../src/transforms/transformElement'
|
||||
import { transformExpression } from '../../src/transforms/transformExpression'
|
||||
|
||||
function parseWithVOn(
|
||||
template: string,
|
||||
options: CompilerOptions = {}
|
||||
): ElementNode {
|
||||
function parseWithVOn(template: string, options: CompilerOptions = {}) {
|
||||
const ast = parse(template)
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformExpression, transformElement],
|
||||
@@ -24,12 +22,15 @@ function parseWithVOn(
|
||||
},
|
||||
...options
|
||||
})
|
||||
return ast.children[0] as ElementNode
|
||||
return {
|
||||
root: ast,
|
||||
node: ast.children[0] as ElementNode
|
||||
}
|
||||
}
|
||||
|
||||
describe('compiler: transform v-on', () => {
|
||||
test('basic', () => {
|
||||
const node = parseWithVOn(`<div v-on:click="onClick"/>`)
|
||||
const { node } = parseWithVOn(`<div v-on:click="onClick"/>`)
|
||||
const props = (node.codegenNode as CallExpression)
|
||||
.arguments[1] as ObjectExpression
|
||||
expect(props.properties[0]).toMatchObject({
|
||||
@@ -65,7 +66,7 @@ describe('compiler: transform v-on', () => {
|
||||
})
|
||||
|
||||
test('dynamic arg', () => {
|
||||
const node = parseWithVOn(`<div v-on:[event]="handler"/>`)
|
||||
const { node } = parseWithVOn(`<div v-on:[event]="handler"/>`)
|
||||
const props = (node.codegenNode as CallExpression)
|
||||
.arguments[1] as ObjectExpression
|
||||
expect(props.properties[0]).toMatchObject({
|
||||
@@ -82,7 +83,7 @@ describe('compiler: transform v-on', () => {
|
||||
})
|
||||
|
||||
test('dynamic arg with prefixing', () => {
|
||||
const node = parseWithVOn(`<div v-on:[event]="handler"/>`, {
|
||||
const { node } = parseWithVOn(`<div v-on:[event]="handler"/>`, {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
const props = (node.codegenNode as CallExpression)
|
||||
@@ -101,7 +102,7 @@ describe('compiler: transform v-on', () => {
|
||||
})
|
||||
|
||||
test('dynamic arg with complex exp prefixing', () => {
|
||||
const node = parseWithVOn(`<div v-on:[event(foo)]="handler"/>`, {
|
||||
const { node } = parseWithVOn(`<div v-on:[event(foo)]="handler"/>`, {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
const props = (node.codegenNode as CallExpression)
|
||||
@@ -127,7 +128,7 @@ describe('compiler: transform v-on', () => {
|
||||
})
|
||||
|
||||
test('should wrap as function if expression is inline statement', () => {
|
||||
const node = parseWithVOn(`<div @click="i++"/>`)
|
||||
const { node } = parseWithVOn(`<div @click="i++"/>`)
|
||||
const props = (node.codegenNode as CallExpression)
|
||||
.arguments[1] as ObjectExpression
|
||||
expect(props.properties[0]).toMatchObject({
|
||||
@@ -140,7 +141,7 @@ describe('compiler: transform v-on', () => {
|
||||
})
|
||||
|
||||
test('inline statement w/ prefixIdentifiers: true', () => {
|
||||
const node = parseWithVOn(`<div @click="foo($event)"/>`, {
|
||||
const { node } = parseWithVOn(`<div @click="foo($event)"/>`, {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
const props = (node.codegenNode as CallExpression)
|
||||
@@ -163,7 +164,7 @@ describe('compiler: transform v-on', () => {
|
||||
})
|
||||
|
||||
test('should NOT wrap as function if expression is already function expression', () => {
|
||||
const node = parseWithVOn(`<div @click="$event => foo($event)"/>`)
|
||||
const { node } = parseWithVOn(`<div @click="$event => foo($event)"/>`)
|
||||
const props = (node.codegenNode as CallExpression)
|
||||
.arguments[1] as ObjectExpression
|
||||
expect(props.properties[0]).toMatchObject({
|
||||
@@ -176,7 +177,7 @@ describe('compiler: transform v-on', () => {
|
||||
})
|
||||
|
||||
test('should NOT wrap as function if expression is complex member expression', () => {
|
||||
const node = parseWithVOn(`<div @click="a['b' + c]"/>`)
|
||||
const { node } = parseWithVOn(`<div @click="a['b' + c]"/>`)
|
||||
const props = (node.codegenNode as CallExpression)
|
||||
.arguments[1] as ObjectExpression
|
||||
expect(props.properties[0]).toMatchObject({
|
||||
@@ -189,7 +190,7 @@ describe('compiler: transform v-on', () => {
|
||||
})
|
||||
|
||||
test('complex member expression w/ prefixIdentifiers: true', () => {
|
||||
const node = parseWithVOn(`<div @click="a['b' + c]"/>`, {
|
||||
const { node } = parseWithVOn(`<div @click="a['b' + c]"/>`, {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
const props = (node.codegenNode as CallExpression)
|
||||
@@ -204,7 +205,7 @@ describe('compiler: transform v-on', () => {
|
||||
})
|
||||
|
||||
test('function expression w/ prefixIdentifiers: true', () => {
|
||||
const node = parseWithVOn(`<div @click="e => foo(e)"/>`, {
|
||||
const { node } = parseWithVOn(`<div @click="e => foo(e)"/>`, {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
const props = (node.codegenNode as CallExpression)
|
||||
@@ -249,5 +250,81 @@ describe('compiler: transform v-on', () => {
|
||||
expect(onError).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test.todo('.once modifier')
|
||||
describe('cacheHandler', () => {
|
||||
test('empty handler', () => {
|
||||
const { root, node } = parseWithVOn(`<div v-on:click.prevent />`, {
|
||||
prefixIdentifiers: true,
|
||||
cacheHandlers: true
|
||||
})
|
||||
expect(root.cached).toBe(1)
|
||||
const args = (node.codegenNode as PlainElementCodegenNode).arguments
|
||||
// should not treat cached handler as dynamicProp, so no flags
|
||||
expect(args.length).toBe(2)
|
||||
expect((args[1] as ObjectExpression).properties[0].value).toMatchObject({
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
index: 1,
|
||||
value: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `() => {}`
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('member expression handler', () => {
|
||||
const { root, node } = parseWithVOn(`<div v-on:click="foo" />`, {
|
||||
prefixIdentifiers: true,
|
||||
cacheHandlers: true
|
||||
})
|
||||
expect(root.cached).toBe(1)
|
||||
const args = (node.codegenNode as PlainElementCodegenNode).arguments
|
||||
// should not treat cached handler as dynamicProp, so no flags
|
||||
expect(args.length).toBe(2)
|
||||
expect((args[1] as ObjectExpression).properties[0].value).toMatchObject({
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
index: 1,
|
||||
value: {
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
children: [`$event => (`, { content: `_ctx.foo($event)` }, `)`]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('inline function expression handler', () => {
|
||||
const { root, node } = parseWithVOn(`<div v-on:click="() => foo()" />`, {
|
||||
prefixIdentifiers: true,
|
||||
cacheHandlers: true
|
||||
})
|
||||
expect(root.cached).toBe(1)
|
||||
const args = (node.codegenNode as PlainElementCodegenNode).arguments
|
||||
// should not treat cached handler as dynamicProp, so no flags
|
||||
expect(args.length).toBe(2)
|
||||
expect((args[1] as ObjectExpression).properties[0].value).toMatchObject({
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
index: 1,
|
||||
value: {
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
children: [`() => `, { content: `_ctx.foo` }, `()`]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('inline statement handler', () => {
|
||||
const { root, node } = parseWithVOn(`<div v-on:click="foo++" />`, {
|
||||
prefixIdentifiers: true,
|
||||
cacheHandlers: true
|
||||
})
|
||||
expect(root.cached).toBe(1)
|
||||
const args = (node.codegenNode as PlainElementCodegenNode).arguments
|
||||
// should not treat cached handler as dynamicProp, so no flags
|
||||
expect(args.length).toBe(2)
|
||||
expect((args[1] as ObjectExpression).properties[0].value).toMatchObject({
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
index: 1,
|
||||
value: {
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
children: [`$event => (`, { content: `_ctx.foo` }, `++`, `)`]
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user