fix(compiler): cache handlers should be per-instance, fix hoist w/ cached handlers

This commit is contained in:
Evan You 2019-10-20 17:00:11 -04:00
parent 39157f7671
commit 869ae19c41
9 changed files with 63 additions and 59 deletions

View File

@ -12,6 +12,15 @@ return function render() {
}" }"
`; `;
exports[`compiler: codegen CacheExpression 1`] = `
"
export default function render() {
const _ctx = this
const _cache = _ctx.$cache
return _cache[1] || (_cache[1] = foo)
}"
`;
exports[`compiler: codegen ConditionalExpression 1`] = ` exports[`compiler: codegen ConditionalExpression 1`] = `
" "
return function render() { return function render() {

View File

@ -137,12 +137,6 @@ describe('compiler: codegen', () => {
expect(code).toMatchSnapshot() 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', () => { test('prefixIdentifiers: true should inject _ctx statement', () => {
const { code } = generate(createRoot(), { prefixIdentifiers: true }) const { code } = generate(createRoot(), { prefixIdentifiers: true })
expect(code).toMatch(`const _ctx = this\n`) expect(code).toMatch(`const _ctx = this\n`)
@ -371,12 +365,19 @@ describe('compiler: codegen', () => {
test('CacheExpression', () => { test('CacheExpression', () => {
const { code } = generate( const { code } = generate(
createRoot({ createRoot({
cached: 1,
codegenNode: createCacheExpression( codegenNode: createCacheExpression(
1, 1,
createSimpleExpression(`foo`, false) createSimpleExpression(`foo`, false)
) )
}) }),
{
mode: 'module',
prefixIdentifiers: true
}
) )
expect(code).toMatch(`_cached_1 || (_cached_1 = foo)`) expect(code).toMatch(`const _cache = _ctx.$cache`)
expect(code).toMatch(`_cache[1] || (_cache[1] = foo)`)
expect(code).toMatchSnapshot()
}) })
}) })

View File

@ -204,20 +204,18 @@ return function render() {
`; `;
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist elements with cached handlers 1`] = ` exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist elements with cached handlers 1`] = `
"const _Vue = Vue "import { createVNode, createBlock, openBlock } from \\"vue\\"
let _cached_1 export default function render() {
const _ctx = this
return function render() { const _cache = _ctx.$cache
with (this) { return (openBlock(), createBlock(\\"div\\", null, [
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue createVNode(\\"div\\", null, [
createVNode(\\"div\\", {
return (_openBlock(), _createBlock(\\"div\\", null, [ onClick: _cache[1] || (_cache[1] = $event => (_ctx.foo($event)))
_createVNode(\\"div\\", {
onClick: _cached_1 || (_cached_1 = $event => (_ctx.foo($event)))
}) })
])) ])
} ]))
}" }"
`; `;

View File

@ -660,14 +660,22 @@ describe('compiler: hoistStatic transform', () => {
}) })
test('should NOT hoist elements with cached handlers', () => { test('should NOT hoist elements with cached handlers', () => {
const { root } = transformWithHoist(`<div><div @click="foo"/></div>`, { const { root } = transformWithHoist(
prefixIdentifiers: true, `<div><div><div @click="foo"/></div></div>`,
cacheHandlers: true {
}) prefixIdentifiers: true,
cacheHandlers: true
}
)
expect(root.cached).toBe(1) expect(root.cached).toBe(1)
expect(root.hoists.length).toBe(0) expect(root.hoists.length).toBe(0)
expect(generate(root).code).toMatchSnapshot() expect(
generate(root, {
mode: 'module',
prefixIdentifiers: true
}).code
).toMatchSnapshot()
}) })
}) })
}) })

View File

@ -219,7 +219,6 @@ export function generate(
} }
} }
genHoists(ast.hoists, context) genHoists(ast.hoists, context)
genCached(ast.cached, context)
newline() newline()
push(`return `) push(`return `)
} else { } else {
@ -228,7 +227,6 @@ export function generate(
push(`import { ${ast.helpers.map(helper).join(', ')} } from "vue"\n`) push(`import { ${ast.helpers.map(helper).join(', ')} } from "vue"\n`)
} }
genHoists(ast.hoists, context) genHoists(ast.hoists, context)
genCached(ast.cached, context)
newline() newline()
push(`export default `) push(`export default `)
} }
@ -253,6 +251,10 @@ export function generate(
} }
} else { } else {
push(`const _ctx = this`) push(`const _ctx = this`)
if (ast.cached > 0) {
newline()
push(`const _cache = _ctx.$cache`)
}
newline() newline()
} }
@ -318,18 +320,6 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
}) })
} }
function genCached(cached: number, context: CodegenContext) {
if (cached > 0) {
context.newline()
context.push(`let `)
for (let i = 0; i < cached; i++) {
context.push(`_cached_${i + 1}`)
if (i !== cached - 1) context.push(`, `)
}
context.newline()
}
}
function isText(n: string | CodegenNode) { function isText(n: string | CodegenNode) {
return ( return (
isString(n) || isString(n) ||
@ -632,7 +622,7 @@ function genSequenceExpression(
} }
function genCacheExpression(node: CacheExpression, context: CodegenContext) { function genCacheExpression(node: CacheExpression, context: CodegenContext) {
context.push(`_cached_${node.index} || (_cached_${node.index} = `) context.push(`_cache[${node.index}] || (_cache[${node.index}] = `)
genNode(node.value, context) genNode(node.value, context)
context.push(`)`) context.push(`)`)
} }

View File

@ -219,12 +219,7 @@ function createTransformContext(
) )
}, },
cache(exp) { cache(exp) {
if (cacheHandlers) { return cacheHandlers ? createCacheExpression(++context.cached, exp) : exp
context.cached++
return createCacheExpression(context.cached, exp)
} else {
return exp
}
} }
} }

View File

@ -50,12 +50,7 @@ function walk(
child.type === NodeTypes.ELEMENT && child.type === NodeTypes.ELEMENT &&
child.tagType === ElementTypes.ELEMENT child.tagType === ElementTypes.ELEMENT
) { ) {
const hasBailoutProp = hasDynamicKeyOrRef(child) || hasCachedProps(child) if (!doNotHoistNode && isStaticNode(child, resultCache)) {
if (
!doNotHoistNode &&
!hasBailoutProp &&
isStaticNode(child, resultCache)
) {
// whole tree is static // whole tree is static
child.codegenNode = context.hoist(child.codegenNode!) child.codegenNode = context.hoist(child.codegenNode!)
continue continue
@ -67,7 +62,8 @@ function walk(
(!flag || (!flag ||
flag === PatchFlags.NEED_PATCH || flag === PatchFlags.NEED_PATCH ||
flag === PatchFlags.TEXT) && flag === PatchFlags.TEXT) &&
!hasBailoutProp !hasDynamicKeyOrRef(child) &&
!hasCachedProps(child)
) { ) {
const props = getNodeProps(child) const props = getNodeProps(child)
if (props && props !== `null`) { if (props && props !== `null`) {
@ -105,7 +101,7 @@ export function isStaticNode(
return cached return cached
} }
const flag = getPatchFlag(node) const flag = getPatchFlag(node)
if (!flag) { if (!flag && !hasDynamicKeyOrRef(node) && !hasCachedProps(node)) {
// element self is static. check its children. // element self is static. check its children.
for (let i = 0; i < node.children.length; i++) { for (let i = 0; i < node.children.length; i++) {
if (!isStaticNode(node.children[i], resultCache)) { if (!isStaticNode(node.children[i], resultCache)) {

View File

@ -83,7 +83,11 @@ export interface ComponentInternalInstance {
render: RenderFunction | null render: RenderFunction | null
effects: ReactiveEffect[] | null effects: ReactiveEffect[] | null
provides: Data provides: Data
accessCache: Data // cache for renderProxy access type to avoid hasOwnProperty calls
accessCache: Data | null
// cache for render function values that rely on _ctx but won't need updates
// after initialized (e.g. inline handlers)
renderCache: any[] | null
components: Record<string, Component> components: Record<string, Component>
directives: Record<string, Directive> directives: Record<string, Directive>
@ -149,6 +153,7 @@ export function createComponentInstance(
effects: null, effects: null,
provides: parent ? parent.provides : Object.create(appContext.provides), provides: parent ? parent.provides : Object.create(appContext.provides),
accessCache: null!, accessCache: null!,
renderCache: null,
// setup context properties // setup context properties
renderContext: EMPTY_OBJ, renderContext: EMPTY_OBJ,

View File

@ -62,7 +62,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
// is the multiple hasOwn() calls. It's much faster to do a simple property // is the multiple hasOwn() calls. It's much faster to do a simple property
// access on a plain object, so we use an accessCache object (with null // access on a plain object, so we use an accessCache object (with null
// prototype) to memoize what access type a key corresponds to. // prototype) to memoize what access type a key corresponds to.
const n = accessCache[key] const n = accessCache![key]
if (n !== undefined) { if (n !== undefined) {
switch (n) { switch (n) {
case AccessTypes.DATA: case AccessTypes.DATA:
@ -73,18 +73,20 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
return propsProxy![key] return propsProxy![key]
} }
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) { } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
accessCache[key] = AccessTypes.DATA accessCache![key] = AccessTypes.DATA
return data[key] return data[key]
} else if (hasOwn(renderContext, key)) { } else if (hasOwn(renderContext, key)) {
accessCache[key] = AccessTypes.CONTEXT accessCache![key] = AccessTypes.CONTEXT
return renderContext[key] return renderContext[key]
} else if (hasOwn(props, key)) { } else if (hasOwn(props, key)) {
// only cache props access if component has declared (thus stable) props // only cache props access if component has declared (thus stable) props
if (type.props != null) { if (type.props != null) {
accessCache[key] = AccessTypes.PROPS accessCache![key] = AccessTypes.PROPS
} }
// return the value from propsProxy for ref unwrapping and readonly // return the value from propsProxy for ref unwrapping and readonly
return propsProxy![key] return propsProxy![key]
} else if (key === '$cache') {
return target.renderCache || (target.renderCache = [])
} else if (key === '$el') { } else if (key === '$el') {
return target.vnode.el return target.vnode.el
} else if (hasOwn(publicPropertiesMap, key)) { } else if (hasOwn(publicPropertiesMap, key)) {