fix(compiler): cache handlers should be per-instance, fix hoist w/ cached handlers
This commit is contained in:
parent
39157f7671
commit
869ae19c41
@ -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() {
|
||||||
|
@ -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()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -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)))
|
|
||||||
})
|
})
|
||||||
]))
|
])
|
||||||
}
|
]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -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()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -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(`)`)
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)) {
|
||||||
|
@ -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,
|
||||||
|
@ -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)) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user