From 65beba98fe5793133d3218945218b9e3f8d136eb Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 13 Jul 2020 12:36:41 -0400 Subject: [PATCH] fix(slots): differentiate dynamic/static compiled slots fix #1557 --- .../__snapshots__/scopeId.spec.ts.snap | 2 +- .../__snapshots__/vSlot.spec.ts.snap | 16 +- .../__tests__/transforms/vSlot.spec.ts | 195 +++++++++--------- .../compiler-core/src/transforms/vSlot.ts | 12 +- .../__tests__/ssrComponent.spec.ts | 4 +- packages/runtime-core/src/componentSlots.ts | 24 ++- .../runtime-core/src/helpers/renderSlot.ts | 6 +- 7 files changed, 141 insertions(+), 118 deletions(-) diff --git a/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap index ba004bb4064..8d7613eec58 100644 --- a/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap @@ -41,7 +41,7 @@ const _withId = /*#__PURE__*/_withScopeId(\\"test\\") export const render = /*#__PURE__*/_withId(function render(_ctx, _cache) { const _component_Child = _resolveComponent(\\"Child\\") - return (_openBlock(), _createBlock(_component_Child, null, _createSlots({ _: 1 }, [ + return (_openBlock(), _createBlock(_component_Child, null, _createSlots({ _: 2 }, [ (_ctx.ok) ? { name: \\"foo\\", diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap index 2eb27e83ec5..d99dcdfb142 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -9,7 +9,7 @@ return function render(_ctx, _cache) { return (_openBlock(), _createBlock(_component_Comp, null, { [_ctx.one]: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]), [_ctx.two]: _withCtx(({ bar }) => [_toDisplayString(_ctx.foo), _toDisplayString(bar)]), - _: 1 + _: 2 }, 1024 /* DYNAMIC_SLOTS */)) }" `; @@ -35,7 +35,7 @@ exports[`compiler: transform component slots named slot with v-for w/ prefixIden return function render(_ctx, _cache) { const _component_Comp = _resolveComponent(\\"Comp\\") - return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [ + return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 }, [ _renderList(_ctx.list, (name) => { return { name: name, @@ -52,7 +52,7 @@ exports[`compiler: transform component slots named slot with v-if + prefixIdenti return function render(_ctx, _cache) { const _component_Comp = _resolveComponent(\\"Comp\\") - return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [ + return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 }, [ (_ctx.ok) ? { name: \\"one\\", @@ -72,7 +72,7 @@ return function render(_ctx, _cache) { const _component_Comp = _resolveComponent(\\"Comp\\") - return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [ + return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 }, [ ok ? { name: \\"one\\", @@ -101,7 +101,7 @@ return function render(_ctx, _cache) { const _component_Comp = _resolveComponent(\\"Comp\\") - return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [ + return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 }, [ ok ? { name: \\"one\\", @@ -145,7 +145,7 @@ return function render(_ctx, _cache) { default: _withCtx(({ foo }) => [ _createVNode(_component_Inner, null, { default: _withCtx(({ bar }) => [_toDisplayString(foo), _toDisplayString(bar), _toDisplayString(_ctx.baz)]), - _: 1 + _: 2 }, 1024 /* DYNAMIC_SLOTS */), \\" \\", _toDisplayString(foo), @@ -165,8 +165,8 @@ return function render(_ctx, _cache) { return (_openBlock(), _createBlock(_component_Comp, null, { [_ctx.named]: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]), - _: 1 - })) + _: 2 + }, 1024 /* DYNAMIC_SLOTS */)) }" `; diff --git a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts index c1cb651d9c9..c9a2e3e7cb9 100644 --- a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts @@ -53,7 +53,7 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) { } } -function createSlotMatcher(obj: Record) { +function createSlotMatcher(obj: Record, isDynamic = false) { return { type: NodeTypes.JS_OBJECT_EXPRESSION, properties: Object.keys(obj) @@ -70,7 +70,7 @@ function createSlotMatcher(obj: Record) { }) .concat({ key: { content: `_` }, - value: { content: `1`, isStatic: false } + value: { content: isDynamic ? `2` : `1`, isStatic: false } }) } } @@ -230,29 +230,32 @@ describe('compiler: transform component slots', () => { { prefixIdentifiers: true } ) expect(slots).toMatchObject( - createSlotMatcher({ - '[_ctx.named]': { - type: NodeTypes.JS_FUNCTION_EXPRESSION, - params: { - type: NodeTypes.COMPOUND_EXPRESSION, - children: [`{ `, { content: `foo` }, ` }`] - }, - returns: [ - { - type: NodeTypes.INTERPOLATION, - content: { - content: `foo` - } + createSlotMatcher( + { + '[_ctx.named]': { + type: NodeTypes.JS_FUNCTION_EXPRESSION, + params: { + type: NodeTypes.COMPOUND_EXPRESSION, + children: [`{ `, { content: `foo` }, ` }`] }, - { - type: NodeTypes.INTERPOLATION, - content: { - content: `_ctx.bar` + returns: [ + { + type: NodeTypes.INTERPOLATION, + content: { + content: `foo` + } + }, + { + type: NodeTypes.INTERPOLATION, + content: { + content: `_ctx.bar` + } } - } - ] - } - }) + ] + } + }, + true + ) ) expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() }) @@ -307,50 +310,53 @@ describe('compiler: transform component slots', () => { { prefixIdentifiers: true } ) expect(slots).toMatchObject( - createSlotMatcher({ - '[_ctx.one]': { - type: NodeTypes.JS_FUNCTION_EXPRESSION, - params: { - type: NodeTypes.COMPOUND_EXPRESSION, - children: [`{ `, { content: `foo` }, ` }`] - }, - returns: [ - { - type: NodeTypes.INTERPOLATION, - content: { - content: `foo` - } + createSlotMatcher( + { + '[_ctx.one]': { + type: NodeTypes.JS_FUNCTION_EXPRESSION, + params: { + type: NodeTypes.COMPOUND_EXPRESSION, + children: [`{ `, { content: `foo` }, ` }`] }, - { - type: NodeTypes.INTERPOLATION, - content: { - content: `_ctx.bar` + returns: [ + { + type: NodeTypes.INTERPOLATION, + content: { + content: `foo` + } + }, + { + type: NodeTypes.INTERPOLATION, + content: { + content: `_ctx.bar` + } } - } - ] - }, - '[_ctx.two]': { - type: NodeTypes.JS_FUNCTION_EXPRESSION, - params: { - type: NodeTypes.COMPOUND_EXPRESSION, - children: [`{ `, { content: `bar` }, ` }`] + ] }, - returns: [ - { - type: NodeTypes.INTERPOLATION, - content: { - content: `_ctx.foo` - } + '[_ctx.two]': { + type: NodeTypes.JS_FUNCTION_EXPRESSION, + params: { + type: NodeTypes.COMPOUND_EXPRESSION, + children: [`{ `, { content: `bar` }, ` }`] }, - { - type: NodeTypes.INTERPOLATION, - content: { - content: `bar` + returns: [ + { + type: NodeTypes.INTERPOLATION, + content: { + content: `_ctx.foo` + } + }, + { + type: NodeTypes.INTERPOLATION, + content: { + content: `bar` + } } - } - ] - } - }) + ] + } + }, + true + ) ) expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() }) @@ -382,35 +388,38 @@ describe('compiler: transform component slots', () => { type: NodeTypes.VNODE_CALL, tag: `_component_Inner`, props: undefined, - children: createSlotMatcher({ - default: { - type: NodeTypes.JS_FUNCTION_EXPRESSION, - params: { - type: NodeTypes.COMPOUND_EXPRESSION, - children: [`{ `, { content: `bar` }, ` }`] - }, - returns: [ - { - type: NodeTypes.INTERPOLATION, - content: { - content: `foo` - } - }, - { - type: NodeTypes.INTERPOLATION, - content: { - content: `bar` - } + children: createSlotMatcher( + { + default: { + type: NodeTypes.JS_FUNCTION_EXPRESSION, + params: { + type: NodeTypes.COMPOUND_EXPRESSION, + children: [`{ `, { content: `bar` }, ` }`] }, - { - type: NodeTypes.INTERPOLATION, - content: { - content: `_ctx.baz` + returns: [ + { + type: NodeTypes.INTERPOLATION, + content: { + content: `foo` + } + }, + { + type: NodeTypes.INTERPOLATION, + content: { + content: `bar` + } + }, + { + type: NodeTypes.INTERPOLATION, + content: { + content: `_ctx.baz` + } } - } - ] - } - }), + ] + } + }, + true + ), // nested slot should be forced dynamic, since scope variables // are not tracked as dependencies of the slot. patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS) @@ -522,7 +531,7 @@ describe('compiler: transform component slots', () => { callee: CREATE_SLOTS, arguments: [ createObjectMatcher({ - _: `[1]` + _: `[2]` }), { type: NodeTypes.JS_ARRAY_EXPRESSION, @@ -564,7 +573,7 @@ describe('compiler: transform component slots', () => { callee: CREATE_SLOTS, arguments: [ createObjectMatcher({ - _: `[1]` + _: `[2]` }), { type: NodeTypes.JS_ARRAY_EXPRESSION, @@ -613,7 +622,7 @@ describe('compiler: transform component slots', () => { callee: CREATE_SLOTS, arguments: [ createObjectMatcher({ - _: `[1]` + _: `[2]` }), { type: NodeTypes.JS_ARRAY_EXPRESSION, @@ -672,7 +681,7 @@ describe('compiler: transform component slots', () => { callee: CREATE_SLOTS, arguments: [ createObjectMatcher({ - _: `[1]` + _: `[2]` }), { type: NodeTypes.JS_ARRAY_EXPRESSION, diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts index 548e90fd521..5930bcedf47 100644 --- a/packages/compiler-core/src/transforms/vSlot.ts +++ b/packages/compiler-core/src/transforms/vSlot.ts @@ -135,7 +135,7 @@ export function buildSlots( let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0 // with `prefixIdentifiers: true`, this can be further optimized to make // it dynamic only when the slot actually uses the scope variables. - if (!__BROWSER__ && context.prefixIdentifiers) { + if (!__BROWSER__ && !context.ssr && context.prefixIdentifiers) { hasDynamicSlots = hasScopeRef(node, context.identifiers) } @@ -144,6 +144,9 @@ export function buildSlots( const onComponentSlot = findDir(node, 'slot', true) if (onComponentSlot) { const { arg, exp } = onComponentSlot + if (arg && !isStaticExp(arg)) { + hasDynamicSlots = true + } slotsProperties.push( createObjectProperty( arg || createSimpleExpression('default', true), @@ -317,7 +320,12 @@ export function buildSlots( let slots = createObjectExpression( slotsProperties.concat( - createObjectProperty(`_`, createSimpleExpression(`1`, false)) + createObjectProperty( + `_`, + // 2 = compiled but dynamic = can skip normalization, but must run diff + // 1 = compiled and static = can skip normalization AND diff as optimized + createSimpleExpression(hasDynamicSlots ? `2` : `1`, false) + ) ), loc ) as SlotsExpression diff --git a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts index 860462a4004..d175d2dfc11 100644 --- a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts @@ -140,7 +140,7 @@ describe('ssr: components', () => { return function ssrRender(_ctx, _push, _parent, _attrs) { const _component_foo = _resolveComponent(\\"foo\\") - _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 1 }, [ + _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 2 }, [ (_ctx.ok) ? { name: \\"named\\", @@ -172,7 +172,7 @@ describe('ssr: components', () => { return function ssrRender(_ctx, _push, _parent, _attrs) { const _component_foo = _resolveComponent(\\"foo\\") - _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 1 }, [ + _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 2 }, [ _renderList(_ctx.names, (key) => { return { name: key, diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts index cdee341ba66..b0a52b0f505 100644 --- a/packages/runtime-core/src/componentSlots.ts +++ b/packages/runtime-core/src/componentSlots.ts @@ -11,7 +11,6 @@ import { isFunction, EMPTY_OBJ, ShapeFlags, - PatchFlags, extend, def } from '@vue/shared' @@ -28,6 +27,11 @@ export type InternalSlots = { export type Slots = Readonly +export const enum CompiledSlotTypes { + STATIC = 1, + DYNAMIC = 2 +} + export type RawSlots = { [name: string]: unknown // manual render fn hint to skip forced children updates @@ -40,7 +44,7 @@ export type RawSlots = { // object may be directly passed down to a child component in a manual // render function, and the optimization hint need to be on the slot object // itself to be preserved. - _?: 1 + _?: CompiledSlotTypes } const isInternalKey = (key: string) => key[0] === '_' || key === '$stable' @@ -105,10 +109,11 @@ export const initSlots = ( children: VNodeNormalizedChildren ) => { if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) { - if ((children as RawSlots)._ === 1) { + const type = (children as RawSlots)._ + if (type) { instance.slots = children as InternalSlots // make compiler marker non-enumerable - def(children as InternalSlots, '_', 1) + def(children as InternalSlots, '_', type) } else { normalizeObjectSlots(children as RawSlots, (instance.slots = {})) } @@ -129,21 +134,20 @@ export const updateSlots = ( let needDeletionCheck = true let deletionComparisonTarget = EMPTY_OBJ if (vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) { - if ((children as RawSlots)._ === 1) { + const type = (children as RawSlots)._ + if (type) { // compiled slots. if (__DEV__ && isHmrUpdating) { // Parent was HMR updated so slot content may have changed. // force update slots and mark instance for hmr as well extend(slots, children as Slots) - } else if ( - // bail on dynamic slots (v-if, v-for, reference of scope variables) - !(vnode.patchFlag & PatchFlags.DYNAMIC_SLOTS) - ) { + } else if (type === CompiledSlotTypes.STATIC) { // compiled AND static. // no need to update, and skip stale slots removal. needDeletionCheck = false } else { - // compiled but dynamic - update slots, but skip normalization. + // compiled but dynamic (v-if/v-for on slots) - update slots, but skip + // normalization. extend(slots, children as Slots) } } else { diff --git a/packages/runtime-core/src/helpers/renderSlot.ts b/packages/runtime-core/src/helpers/renderSlot.ts index 75d8c511195..0cfb6ed694f 100644 --- a/packages/runtime-core/src/helpers/renderSlot.ts +++ b/packages/runtime-core/src/helpers/renderSlot.ts @@ -1,5 +1,5 @@ import { Data } from '../component' -import { Slots } from '../componentSlots' +import { Slots, RawSlots, CompiledSlotTypes } from '../componentSlots' import { VNodeArrayChildren, openBlock, @@ -39,7 +39,9 @@ export function renderSlot( Fragment, { key: props.key }, slot ? slot(props) : fallback ? fallback() : [], - slots._ ? PatchFlags.STABLE_FRAGMENT : PatchFlags.BAIL + (slots as RawSlots)._ === CompiledSlotTypes.STATIC + ? PatchFlags.STABLE_FRAGMENT + : PatchFlags.BAIL ) ) }