refactor: fix implementation of SFC :slotted id handling

fix #2892
This commit is contained in:
Evan You
2021-03-05 11:10:06 -05:00
parent cc975c1292
commit aea88c3280
36 changed files with 723 additions and 457 deletions

View File

@@ -14,8 +14,7 @@ import {
ComponentPublicInstance,
Ref,
cloneVNode,
provide,
withScopeId
provide
} from '@vue/runtime-test'
import { KeepAliveProps } from '../../src/components/KeepAlive'
@@ -804,14 +803,13 @@ describe('KeepAlive', () => {
test('should work with cloned root due to scopeId / fallthrough attrs', async () => {
const viewRef = ref('one')
const instanceRef = ref<any>(null)
const withId = withScopeId('foo')
const App = {
__scopeId: 'foo',
render: withId(() => {
render: () => {
return h(KeepAlive, null, {
default: () => h(views[viewRef.value], { ref: instanceRef })
})
})
}
}
render(h(App), root)
expect(serializeInner(root)).toBe(`<div foo>one</div>`)

View File

@@ -8,7 +8,7 @@ import {
Fragment,
createCommentVNode
} from '../../src'
import { PatchFlags } from '@vue/shared/src'
import { PatchFlags } from '@vue/shared'
describe('renderSlot', () => {
it('should render slot', () => {
@@ -37,7 +37,7 @@ describe('renderSlot', () => {
return [createVNode('div', null, 'foo', PatchFlags.TEXT)]
},
// mock instance
{} as any
{ type: {} } as any
)
// manual invocation should not track

View File

@@ -1,105 +0,0 @@
import { withScopeId } from '../../src/helpers/scopeId'
import { h, render, nodeOps, serializeInner } from '@vue/runtime-test'
describe('scopeId runtime support', () => {
const withParentId = withScopeId('parent')
const withChildId = withScopeId('child')
test('should attach scopeId', () => {
const App = {
__scopeId: 'parent',
render: withParentId(() => {
return h('div', [h('div')])
})
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(`<div parent><div parent></div></div>`)
})
test('should attach scopeId to components in parent component', () => {
const Child = {
__scopeId: 'child',
render: withChildId(() => {
return h('div')
})
}
const App = {
__scopeId: 'parent',
render: withParentId(() => {
return h('div', [h(Child)])
})
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(
`<div parent><div child parent></div></div>`
)
})
test('should work on slots', () => {
const Child = {
__scopeId: 'child',
render: withChildId(function(this: any) {
return h('div', this.$slots.default())
})
}
const withChild2Id = withScopeId('child2')
const Child2 = {
__scopeId: 'child2',
render: withChild2Id(() => h('span'))
}
const App = {
__scopeId: 'parent',
render: withParentId(() => {
return h(
Child,
withParentId(() => {
return [h('div'), h(Child2)]
})
)
})
}
const root = nodeOps.createElement('div')
render(h(App), root)
// slot content should have:
// - scopeId from parent
// - slotted scopeId (with `-s` postfix) from child (the tree owner)
expect(serializeInner(root)).toBe(
`<div child parent>` +
`<div parent child-s></div>` +
// component inside slot should have:
// - scopeId from template context
// - slotted scopeId from slot owner
// - its own scopeId
`<span child2 parent child-s></span>` +
`</div>`
)
})
// #1988
test('should inherit scopeId through nested HOCs with inheritAttrs: false', () => {
const withParentId = withScopeId('parent')
const App = {
__scopeId: 'parent',
render: withParentId(() => {
return h(Child)
})
}
function Child() {
return h(Child2, { class: 'foo' })
}
function Child2() {
return h('div')
}
Child2.inheritAttrs = false
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(`<div parent></div>`)
})
})

View File

@@ -0,0 +1,178 @@
import {
h,
render,
nodeOps,
serializeInner,
renderSlot
} from '@vue/runtime-test'
import { setScopeId, withCtx } from '../src/componentRenderContext'
describe('scopeId runtime support', () => {
test('should attach scopeId', () => {
const App = {
__scopeId: 'parent',
render: () => h('div', [h('div')])
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(`<div parent><div parent></div></div>`)
})
test('should attach scopeId to components in parent component', () => {
const Child = {
__scopeId: 'child',
render: () => h('div')
}
const App = {
__scopeId: 'parent',
render: () => h('div', [h(Child)])
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(
`<div parent><div child parent></div></div>`
)
})
// :slotted basic
test('should work on slots', () => {
const Child = {
__scopeId: 'child',
render(this: any) {
return h('div', renderSlot(this.$slots, 'default'))
}
}
const Child2 = {
__scopeId: 'child2',
render: () => h('span')
}
const App = {
__scopeId: 'parent',
render: () => {
return h(
Child,
withCtx(() => {
return [h('div'), h(Child2)]
})
)
}
}
const root = nodeOps.createElement('div')
render(h(App), root)
// slot content should have:
// - scopeId from parent
// - slotted scopeId (with `-s` postfix) from child (the tree owner)
expect(serializeInner(root)).toBe(
`<div child parent>` +
`<div parent child-s></div>` +
// component inside slot should have:
// - scopeId from template context
// - slotted scopeId from slot owner
// - its own scopeId
`<span child2 parent child-s></span>` +
`</div>`
)
})
// #2892
test(':slotted on forwarded slots', async () => {
const Wrapper = {
__scopeId: 'wrapper',
render(this: any) {
// <div class="wrapper"><slot/></div>
return h('div', { class: 'wrapper' }, [
renderSlot(this.$slots, 'default')
])
}
}
const Slotted = {
__scopeId: 'slotted',
render(this: any) {
// <Wrapper><slot/></Wrapper>
return h(Wrapper, null, {
default: withCtx(() => [renderSlot(this.$slots, 'default')])
})
}
}
// simulate hoisted node
setScopeId('root')
const hoisted = h('div', 'hoisted')
setScopeId(null)
const Root = {
__scopeId: 'root',
render(this: any) {
// <Slotted><div>hoisted</div><div>{{ dynamic }}</div></Slotted>
return h(Slotted, null, {
default: withCtx(() => {
return [hoisted, h('div', 'dynamic')]
})
})
}
}
const root = nodeOps.createElement('div')
render(h(Root), root)
expect(serializeInner(root)).toBe(
`<div class="wrapper" wrapper slotted root>` +
`<div root wrapper-s slotted-s>hoisted</div>` +
`<div root wrapper-s slotted-s>dynamic</div>` +
`</div>`
)
const Root2 = {
__scopeId: 'root',
render(this: any) {
// <Slotted>
// <Wrapper>
// <div>hoisted</div><div>{{ dynamic }}</div>
// </Wrapper>
// </Slotted>
return h(Slotted, null, {
default: withCtx(() => [
h(Wrapper, null, {
default: withCtx(() => [hoisted, h('div', 'dynamic')])
})
])
})
}
}
const root2 = nodeOps.createElement('div')
render(h(Root2), root2)
expect(serializeInner(root2)).toBe(
`<div class="wrapper" wrapper slotted root>` +
`<div class="wrapper" wrapper root wrapper-s slotted-s>` +
`<div root wrapper-s>hoisted</div>` +
`<div root wrapper-s>dynamic</div>` +
`</div>` +
`</div>`
)
})
// #1988
test('should inherit scopeId through nested HOCs with inheritAttrs: false', () => {
const App = {
__scopeId: 'parent',
render: () => {
return h(Child)
}
}
function Child() {
return h(Child2, { class: 'foo' })
}
function Child2() {
return h('div')
}
Child2.inheritAttrs = false
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(`<div parent></div>`)
})
})

View File

@@ -14,7 +14,7 @@ import { Data } from '../src/component'
import { ShapeFlags, PatchFlags } from '@vue/shared'
import { h, reactive, isReactive, setBlockTracking } from '../src'
import { createApp, nodeOps, serializeInner } from '@vue/runtime-test'
import { setCurrentRenderingInstance } from '../src/componentRenderUtils'
import { setCurrentRenderingInstance } from '../src/componentRenderContext'
describe('vnode', () => {
test('create with just tag', () => {
@@ -231,8 +231,8 @@ describe('vnode', () => {
// ref normalizes to [currentRenderingInstance, ref]
test('cloneVNode ref normalization', () => {
const mockInstance1 = {} as any
const mockInstance2 = {} as any
const mockInstance1 = { type: {} } as any
const mockInstance2 = { type: {} } as any
setCurrentRenderingInstance(mockInstance1)
const original = createVNode('div', { ref: 'foo' })
@@ -272,8 +272,8 @@ describe('vnode', () => {
})
test('cloneVNode ref merging', () => {
const mockInstance1 = {} as any
const mockInstance2 = {} as any
const mockInstance1 = { type: {} } as any
const mockInstance2 = { type: {} } as any
setCurrentRenderingInstance(mockInstance1)
const original = createVNode('div', { ref: 'foo' })

View File

@@ -1,6 +1,6 @@
import { isFunction } from '@vue/shared'
import { currentInstance } from './component'
import { currentRenderingInstance } from './componentRenderUtils'
import { currentRenderingInstance } from './componentRenderContext'
import { warn } from './warning'
export interface InjectionKey<T> extends Symbol {}

View File

@@ -51,10 +51,8 @@ import {
} from '@vue/shared'
import { SuspenseBoundary } from './components/Suspense'
import { CompilerOptions } from '@vue/compiler-core'
import {
currentRenderingInstance,
markAttrsAccessed
} from './componentRenderUtils'
import { markAttrsAccessed } from './componentRenderUtils'
import { currentRenderingInstance } from './componentRenderContext'
import { startMeasure, endMeasure } from './profiling'
export type Data = Record<string, unknown>

View File

@@ -35,10 +35,8 @@ import {
} from './componentOptions'
import { EmitsOptions, EmitFn } from './componentEmits'
import { Slots } from './componentSlots'
import {
currentRenderingInstance,
markAttrsAccessed
} from './componentRenderUtils'
import { markAttrsAccessed } from './componentRenderUtils'
import { currentRenderingInstance } from './componentRenderContext'
import { warn } from './warning'
import { UnionToIntersection } from './helpers/typeUtils'

View File

@@ -0,0 +1,57 @@
import { ComponentInternalInstance } from './component'
import { isRenderingCompiledSlot } from './helpers/renderSlot'
import { closeBlock, openBlock } from './vnode'
/**
* mark the current rendering instance for asset resolution (e.g.
* resolveComponent, resolveDirective) during render
*/
export let currentRenderingInstance: ComponentInternalInstance | null = null
export let currentScopeId: string | null = null
export function setCurrentRenderingInstance(
instance: ComponentInternalInstance | null
) {
currentRenderingInstance = instance
currentScopeId = (instance && instance.type.__scopeId) || null
}
/**
* Set scope id when creating hoisted vnodes.
* @private compiler helper
*/
export function setScopeId(id: string | null) {
currentScopeId = id
}
/**
* Wrap a slot function to memoize current rendering instance
* @private compiler helper
*/
export function withCtx(
fn: Function,
ctx: ComponentInternalInstance | null = currentRenderingInstance
) {
if (!ctx) return fn
const renderFnWithContext = (...args: any[]) => {
// If a user calls a compiled slot inside a template expression (#1745), it
// can mess up block tracking, so by default we need to push a null block to
// avoid that. This isn't necessary if rendering a compiled `<slot>`.
if (!isRenderingCompiledSlot) {
openBlock(true /* null block that disables tracking */)
}
const prevInstance = currentRenderingInstance
setCurrentRenderingInstance(ctx)
const res = fn(...args)
setCurrentRenderingInstance(prevInstance)
if (!isRenderingCompiledSlot) {
closeBlock()
}
return res
}
// mark this as a compiled slot function.
// this is used in vnode.ts -> normalizeChildren() to set the slot
// rendering flag.
renderFnWithContext._c = true
return renderFnWithContext
}

View File

@@ -18,18 +18,7 @@ import { warn } from './warning'
import { isHmrUpdating } from './hmr'
import { NormalizedProps } from './componentProps'
import { isEmitListener } from './componentEmits'
/**
* mark the current rendering instance for asset resolution (e.g.
* resolveComponent, resolveDirective) during render
*/
export let currentRenderingInstance: ComponentInternalInstance | null = null
export function setCurrentRenderingInstance(
instance: ComponentInternalInstance | null
) {
currentRenderingInstance = instance
}
import { setCurrentRenderingInstance } from './componentRenderContext'
/**
* dev only flag to track whether $attrs was used during render.
@@ -63,7 +52,7 @@ export function renderComponentRoot(
} = instance
let result
currentRenderingInstance = instance
setCurrentRenderingInstance(instance)
if (__DEV__) {
accessedAttrs = false
}
@@ -215,8 +204,8 @@ export function renderComponentRoot(
handleError(err, instance, ErrorCodes.RENDER_FUNCTION)
result = createVNode(Comment)
}
currentRenderingInstance = null
setCurrentRenderingInstance(null)
return result
}

View File

@@ -17,7 +17,7 @@ import {
} from '@vue/shared'
import { warn } from './warning'
import { isKeepAlive } from './components/KeepAlive'
import { withCtx } from './helpers/withRenderContext'
import { withCtx } from './componentRenderContext'
import { isHmrUpdating } from './hmr'
export type Slot = (...args: any[]) => VNode[]

View File

@@ -112,6 +112,7 @@ const KeepAliveImpl = {
instance,
parentSuspense,
isSVG,
vnode.slotScopeIds,
optimized
)
queuePostRenderEffect(() => {

View File

@@ -46,6 +46,7 @@ export const SuspenseImpl = {
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean,
// platform-specific impl passed from renderer
rendererInternals: RendererInternals
@@ -58,6 +59,7 @@ export const SuspenseImpl = {
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized,
rendererInternals
)
@@ -69,6 +71,8 @@ export const SuspenseImpl = {
anchor,
parentComponent,
isSVG,
slotScopeIds,
optimized,
rendererInternals
)
}
@@ -92,6 +96,7 @@ function mountSuspense(
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean,
rendererInternals: RendererInternals
) {
@@ -108,6 +113,7 @@ function mountSuspense(
hiddenContainer,
anchor,
isSVG,
slotScopeIds,
optimized,
rendererInternals
))
@@ -120,7 +126,8 @@ function mountSuspense(
null,
parentComponent,
suspense,
isSVG
isSVG,
slotScopeIds
)
// now check if we have encountered any async deps
if (suspense.deps > 0) {
@@ -133,7 +140,8 @@ function mountSuspense(
anchor,
parentComponent,
null, // fallback tree will not have suspense context
isSVG
isSVG,
slotScopeIds
)
setActiveBranch(suspense, vnode.ssFallback!)
} else {
@@ -149,6 +157,8 @@ function patchSuspense(
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean,
{ p: patch, um: unmount, o: { createElement } }: RendererInternals
) {
const suspense = (n2.suspense = n1.suspense)!
@@ -169,7 +179,9 @@ function patchSuspense(
null,
parentComponent,
suspense,
isSVG
isSVG,
slotScopeIds,
optimized
)
if (suspense.deps <= 0) {
suspense.resolve()
@@ -181,7 +193,9 @@ function patchSuspense(
anchor,
parentComponent,
null, // fallback tree will not have suspense context
isSVG
isSVG,
slotScopeIds,
optimized
)
setActiveBranch(suspense, newFallback)
}
@@ -214,7 +228,9 @@ function patchSuspense(
null,
parentComponent,
suspense,
isSVG
isSVG,
slotScopeIds,
optimized
)
if (suspense.deps <= 0) {
suspense.resolve()
@@ -226,7 +242,9 @@ function patchSuspense(
anchor,
parentComponent,
null, // fallback tree will not have suspense context
isSVG
isSVG,
slotScopeIds,
optimized
)
setActiveBranch(suspense, newFallback)
}
@@ -239,7 +257,9 @@ function patchSuspense(
anchor,
parentComponent,
suspense,
isSVG
isSVG,
slotScopeIds,
optimized
)
// force resolve
suspense.resolve(true)
@@ -252,7 +272,9 @@ function patchSuspense(
null,
parentComponent,
suspense,
isSVG
isSVG,
slotScopeIds,
optimized
)
if (suspense.deps <= 0) {
suspense.resolve()
@@ -269,7 +291,9 @@ function patchSuspense(
anchor,
parentComponent,
suspense,
isSVG
isSVG,
slotScopeIds,
optimized
)
setActiveBranch(suspense, newBranch)
} else {
@@ -289,7 +313,9 @@ function patchSuspense(
null,
parentComponent,
suspense,
isSVG
isSVG,
slotScopeIds,
optimized
)
if (suspense.deps <= 0) {
// incoming branch has no async deps, resolve now.
@@ -352,6 +378,7 @@ function createSuspenseBoundary(
hiddenContainer: RendererElement,
anchor: RendererNode | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean,
rendererInternals: RendererInternals,
isHydrating = false
@@ -507,7 +534,9 @@ function createSuspenseBoundary(
anchor,
parentComponent,
null, // fallback tree will not have suspense context
isSVG
isSVG,
slotScopeIds,
optimized
)
setActiveBranch(suspense, fallbackVNode)
}
@@ -632,6 +661,7 @@ function hydrateSuspense(
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean,
rendererInternals: RendererInternals,
hydrateNode: (
@@ -639,6 +669,7 @@ function hydrateSuspense(
vnode: VNode,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
slotScopeIds: string[] | null,
optimized: boolean
) => Node | null
): Node | null {
@@ -651,6 +682,7 @@ function hydrateSuspense(
document.createElement('div'),
null,
isSVG,
slotScopeIds,
optimized,
rendererInternals,
true /* hydrating */
@@ -666,6 +698,7 @@ function hydrateSuspense(
(suspense.pendingBranch = vnode.ssContent!),
parentComponent,
suspense,
slotScopeIds,
optimized
)
if (suspense.deps === 0) {

View File

@@ -71,6 +71,7 @@ export const TeleportImpl = {
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean,
internals: RendererInternals
) {
@@ -115,6 +116,7 @@ export const TeleportImpl = {
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
}
@@ -144,7 +146,8 @@ export const TeleportImpl = {
currentContainer,
parentComponent,
parentSuspense,
isSVG
isSVG,
slotScopeIds
)
// even in block tree mode we need to make sure all root-level nodes
// in the teleport inherit previous DOM references so that they can
@@ -158,7 +161,9 @@ export const TeleportImpl = {
currentAnchor,
parentComponent,
parentSuspense,
isSVG
isSVG,
slotScopeIds,
false
)
}
@@ -283,6 +288,7 @@ function hydrateTeleport(
vnode: TeleportVNode,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
slotScopeIds: string[] | null,
optimized: boolean,
{
o: { nextSibling, parentNode, querySelector }
@@ -293,6 +299,7 @@ function hydrateTeleport(
container: Element,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
slotScopeIds: string[] | null,
optimized: boolean
) => Node | null
): Node | null {
@@ -313,6 +320,7 @@ function hydrateTeleport(
parentNode(node)!,
parentComponent,
parentSuspense,
slotScopeIds,
optimized
)
vnode.targetAnchor = targetNode
@@ -324,6 +332,7 @@ function hydrateTeleport(
target,
parentComponent,
parentSuspense,
slotScopeIds,
optimized
)
}

View File

@@ -15,7 +15,7 @@ import { VNode } from './vnode'
import { isFunction, EMPTY_OBJ, makeMap } from '@vue/shared'
import { warn } from './warning'
import { ComponentInternalInstance, Data } from './component'
import { currentRenderingInstance } from './componentRenderUtils'
import { currentRenderingInstance } from './componentRenderContext'
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
import { ComponentPublicInstance } from './componentPublicInstance'

View File

@@ -53,6 +53,10 @@ export function renderSlot(
? PatchFlags.STABLE_FRAGMENT
: PatchFlags.BAIL
)
// TODO (optimization) only add slot scope id if :slotted is used
if (rendered.scopeId) {
rendered.slotScopeIds = [rendered.scopeId + '-s']
}
isRenderingCompiledSlot--
return rendered
}

View File

@@ -1,10 +1,10 @@
import { currentRenderingInstance } from '../componentRenderUtils'
import {
currentInstance,
ConcreteComponent,
ComponentOptions,
getComponentName
} from '../component'
import { currentRenderingInstance } from '../componentRenderContext'
import { Directive } from '../directives'
import { camelize, capitalize, isString } from '@vue/shared'
import { warn } from '../warning'

View File

@@ -1,36 +0,0 @@
// SFC scoped style ID management.
// These are only used in esm-bundler builds, but since exports cannot be
// conditional, we can only drop inner implementations in non-bundler builds.
import { withCtx } from './withRenderContext'
export let currentScopeId: string | null = null
const scopeIdStack: string[] = []
/**
* @private
*/
export function pushScopeId(id: string) {
scopeIdStack.push((currentScopeId = id))
}
/**
* @private
*/
export function popScopeId() {
scopeIdStack.pop()
currentScopeId = scopeIdStack[scopeIdStack.length - 1] || null
}
/**
* @private
*/
export function withScopeId(id: string): <T extends Function>(fn: T) => T {
return ((fn: Function) =>
withCtx(function(this: any) {
pushScopeId(id)
const res = fn.apply(this, arguments)
popScopeId()
return res
})) as any
}

View File

@@ -1,37 +0,0 @@
import { Slot } from '../componentSlots'
import {
setCurrentRenderingInstance,
currentRenderingInstance
} from '../componentRenderUtils'
import { ComponentInternalInstance } from '../component'
import { isRenderingCompiledSlot } from './renderSlot'
import { closeBlock, openBlock } from '../vnode'
/**
* Wrap a slot function to memoize current rendering instance
* @private
*/
export function withCtx(
fn: Slot,
ctx: ComponentInternalInstance | null = currentRenderingInstance
) {
if (!ctx) return fn
const renderFnWithContext = (...args: any[]) => {
// If a user calls a compiled slot inside a template expression (#1745), it
// can mess up block tracking, so by default we need to push a null block to
// avoid that. This isn't necessary if rendering a compiled `<slot>`.
if (!isRenderingCompiledSlot) {
openBlock(true /* null block that disables tracking */)
}
const owner = currentRenderingInstance
setCurrentRenderingInstance(ctx)
const res = fn(...args)
setCurrentRenderingInstance(owner)
if (!isRenderingCompiledSlot) {
closeBlock()
}
return res
}
renderFnWithContext._c = true
return renderFnWithContext
}

View File

@@ -63,7 +63,7 @@ export function createHydrationFunctions(
return
}
hasMismatch = false
hydrateNode(container.firstChild!, vnode, null, null)
hydrateNode(container.firstChild!, vnode, null, null, null)
flushPostFlushCbs()
if (hasMismatch && !__TEST__) {
// this error should show up in production
@@ -76,6 +76,7 @@ export function createHydrationFunctions(
vnode: VNode,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
slotScopeIds: string[] | null,
optimized = false
): Node | null => {
const isFragmentStart = isComment(node) && node.data === '['
@@ -85,6 +86,7 @@ export function createHydrationFunctions(
vnode,
parentComponent,
parentSuspense,
slotScopeIds,
isFragmentStart
)
@@ -147,6 +149,7 @@ export function createHydrationFunctions(
vnode,
parentComponent,
parentSuspense,
slotScopeIds,
optimized
)
}
@@ -164,6 +167,7 @@ export function createHydrationFunctions(
vnode,
parentComponent,
parentSuspense,
slotScopeIds,
optimized
)
}
@@ -171,6 +175,7 @@ export function createHydrationFunctions(
// when setting up the render effect, if the initial vnode already
// has .el set, the component will perform hydration instead of mount
// on its sub-tree.
vnode.slotScopeIds = slotScopeIds
const container = parentNode(node)!
const hydrateComponent = () => {
mountComponent(
@@ -205,6 +210,7 @@ export function createHydrationFunctions(
vnode as TeleportVNode,
parentComponent,
parentSuspense,
slotScopeIds,
optimized,
rendererInternals,
hydrateChildren
@@ -217,6 +223,7 @@ export function createHydrationFunctions(
parentComponent,
parentSuspense,
isSVGContainer(parentNode(node)!),
slotScopeIds,
optimized,
rendererInternals,
hydrateNode
@@ -238,6 +245,7 @@ export function createHydrationFunctions(
vnode: VNode,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
slotScopeIds: string[] | null,
optimized: boolean
) => {
optimized = optimized || !!vnode.dynamicChildren
@@ -291,6 +299,7 @@ export function createHydrationFunctions(
el,
parentComponent,
parentSuspense,
slotScopeIds,
optimized
)
let hasWarned = false
@@ -330,6 +339,7 @@ export function createHydrationFunctions(
container: Element,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
slotScopeIds: string[] | null,
optimized: boolean
): Node | null => {
optimized = optimized || !!parentVNode.dynamicChildren
@@ -346,6 +356,7 @@ export function createHydrationFunctions(
vnode,
parentComponent,
parentSuspense,
slotScopeIds,
optimized
)
} else {
@@ -365,7 +376,8 @@ export function createHydrationFunctions(
null,
parentComponent,
parentSuspense,
isSVGContainer(container)
isSVGContainer(container),
slotScopeIds
)
}
}
@@ -377,8 +389,16 @@ export function createHydrationFunctions(
vnode: VNode,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
slotScopeIds: string[] | null,
optimized: boolean
) => {
const { slotScopeIds: fragmentSlotScopeIds } = vnode
if (fragmentSlotScopeIds) {
slotScopeIds = slotScopeIds
? slotScopeIds.concat(fragmentSlotScopeIds)
: fragmentSlotScopeIds
}
const container = parentNode(node)!
const next = hydrateChildren(
nextSibling(node)!,
@@ -386,6 +406,7 @@ export function createHydrationFunctions(
container,
parentComponent,
parentSuspense,
slotScopeIds,
optimized
)
if (next && isComment(next) && next.data === ']') {
@@ -405,6 +426,7 @@ export function createHydrationFunctions(
vnode: VNode,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
slotScopeIds: string[] | null,
isFragment: boolean
): Node | null => {
hasMismatch = true
@@ -446,7 +468,8 @@ export function createHydrationFunctions(
next,
parentComponent,
parentSuspense,
isSVGContainer(container)
isSVGContainer(container),
slotScopeIds
)
return next
}

View File

@@ -227,12 +227,11 @@ export { HMRRuntime } from './hmr'
// For compiler generated code
// should sync with '@vue/compiler-core/src/runtimeConstants.ts'
export { withCtx } from './helpers/withRenderContext'
export { withCtx, setScopeId } from './componentRenderContext'
export { renderList } from './helpers/renderList'
export { toHandlers } from './helpers/toHandlers'
export { renderSlot } from './helpers/renderSlot'
export { createSlots } from './helpers/createSlots'
export { pushScopeId, popScopeId, withScopeId } from './helpers/scopeId'
export {
openBlock,
createBlock,
@@ -257,10 +256,8 @@ export { transformVNodeArgs } from './vnode'
// change without notice between versions. User code should never rely on them.
import { createComponentInstance, setupComponent } from './component'
import {
renderComponentRoot,
setCurrentRenderingInstance
} from './componentRenderUtils'
import { renderComponentRoot } from './componentRenderUtils'
import { setCurrentRenderingInstance } from './componentRenderContext'
import { isVNode, normalizeVNode } from './vnode'
const _ssrUtils = {

View File

@@ -177,6 +177,7 @@ type PatchFn = (
parentComponent?: ComponentInternalInstance | null,
parentSuspense?: SuspenseBoundary | null,
isSVG?: boolean,
slotScopeIds?: string[] | null,
optimized?: boolean
) => void
@@ -187,6 +188,7 @@ type MountChildrenFn = (
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean,
start?: number
) => void
@@ -199,7 +201,8 @@ type PatchChildrenFn = (
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized?: boolean
slotScopeIds: string[] | null,
optimized: boolean
) => void
type PatchBlockChildrenFn = (
@@ -208,7 +211,8 @@ type PatchBlockChildrenFn = (
fallbackContainer: RendererElement,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean
isSVG: boolean,
slotScopeIds: string[] | null
) => void
type MoveFn = (
@@ -469,6 +473,7 @@ function baseCreateRenderer(
parentComponent = null,
parentSuspense = null,
isSVG = false,
slotScopeIds = null,
optimized = false
) => {
// patching & not same type, unmount old tree
@@ -507,6 +512,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
break
@@ -520,6 +526,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else if (shapeFlag & ShapeFlags.COMPONENT) {
@@ -531,6 +538,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else if (shapeFlag & ShapeFlags.TELEPORT) {
@@ -542,6 +550,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized,
internals
)
@@ -554,6 +563,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized,
internals
)
@@ -676,6 +686,7 @@ function baseCreateRenderer(
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
isSVG = isSVG || (n2.type as string) === 'svg'
@@ -687,10 +698,19 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else {
patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized)
patchElement(
n1,
n2,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
}
}
@@ -701,19 +721,12 @@ function baseCreateRenderer(
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
let el: RendererElement
let vnodeHook: VNodeHook | undefined | null
const {
type,
props,
shapeFlag,
transition,
scopeId,
patchFlag,
dirs
} = vnode
const { type, props, shapeFlag, transition, patchFlag, dirs } = vnode
if (
!__DEV__ &&
vnode.el &&
@@ -744,6 +757,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG && type !== 'foreignObject',
slotScopeIds,
optimized || !!vnode.dynamicChildren
)
}
@@ -773,7 +787,7 @@ function baseCreateRenderer(
}
}
// scopeId
setScopeId(el, scopeId, vnode, parentComponent)
setScopeId(el, vnode, vnode.scopeId, slotScopeIds, parentComponent)
}
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
Object.defineProperty(el, '__vnode', {
@@ -813,30 +827,32 @@ function baseCreateRenderer(
const setScopeId = (
el: RendererElement,
scopeId: string | false | null,
vnode: VNode,
scopeId: string | null,
slotScopeIds: string[] | null,
parentComponent: ComponentInternalInstance | null
) => {
if (scopeId) {
hostSetScopeId(el, scopeId)
}
if (parentComponent) {
const treeOwnerId = parentComponent.type.__scopeId
// vnode's own scopeId and the current patched component's scopeId is
// different - this is a slot content node.
if (treeOwnerId && treeOwnerId !== scopeId) {
hostSetScopeId(el, treeOwnerId + '-s')
if (slotScopeIds) {
for (let i = 0; i < slotScopeIds.length; i++) {
hostSetScopeId(el, slotScopeIds[i])
}
}
if (parentComponent) {
let subTree = parentComponent.subTree
if (__DEV__ && subTree.type === Fragment) {
if (__DEV__ && subTree.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT) {
subTree =
filterSingleRoot(subTree.children as VNodeArrayChildren) || subTree
}
if (vnode === subTree) {
const parentVNode = parentComponent.vnode
setScopeId(
el,
parentComponent.vnode.scopeId,
parentComponent.vnode,
parentVNode,
parentVNode.scopeId,
parentVNode.slotScopeIds,
parentComponent.parent
)
}
@@ -851,6 +867,7 @@ function baseCreateRenderer(
parentSuspense,
isSVG,
optimized,
slotScopeIds,
start = 0
) => {
for (let i = start; i < children.length; i++) {
@@ -865,7 +882,8 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
optimized
optimized,
slotScopeIds
)
}
}
@@ -876,6 +894,7 @@ function baseCreateRenderer(
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
const el = (n2.el = n1.el!)
@@ -993,7 +1012,8 @@ function baseCreateRenderer(
el,
parentComponent,
parentSuspense,
areChildrenSVG
areChildrenSVG,
slotScopeIds
)
if (__DEV__ && parentComponent && parentComponent.type.__hmrId) {
traverseStaticChildren(n1, n2)
@@ -1007,7 +1027,9 @@ function baseCreateRenderer(
null,
parentComponent,
parentSuspense,
areChildrenSVG
areChildrenSVG,
slotScopeIds,
false
)
}
@@ -1026,7 +1048,8 @@ function baseCreateRenderer(
fallbackContainer,
parentComponent,
parentSuspense,
isSVG
isSVG,
slotScopeIds
) => {
for (let i = 0; i < newChildren.length; i++) {
const oldVNode = oldChildren[i]
@@ -1054,6 +1077,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
true
)
}
@@ -1119,16 +1143,24 @@ function baseCreateRenderer(
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(''))!
const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(''))!
let { patchFlag, dynamicChildren } = n2
let { patchFlag, dynamicChildren, slotScopeIds: fragmentSlotScopeIds } = n2
if (patchFlag > 0) {
optimized = true
}
// check if this is a slot fragment with :slotted scope ids
if (fragmentSlotScopeIds) {
slotScopeIds = slotScopeIds
? slotScopeIds.concat(fragmentSlotScopeIds)
: fragmentSlotScopeIds
}
if (__DEV__ && isHmrUpdating) {
// HMR updated, force full diff
patchFlag = 0
@@ -1149,6 +1181,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else {
@@ -1168,7 +1201,8 @@ function baseCreateRenderer(
container,
parentComponent,
parentSuspense,
isSVG
isSVG,
slotScopeIds
)
if (__DEV__ && parentComponent && parentComponent.type.__hmrId) {
traverseStaticChildren(n1, n2)
@@ -1195,6 +1229,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
}
@@ -1209,8 +1244,10 @@ function baseCreateRenderer(
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
n2.slotScopeIds = slotScopeIds
if (n1 == null) {
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
;(parentComponent!.ctx as KeepAliveContext).activate(
@@ -1382,7 +1419,8 @@ function baseCreateRenderer(
initialVNode.el as Node,
subTree,
instance,
parentSuspense
parentSuspense,
null
)
if (__DEV__) {
endMeasure(instance, `hydrate`)
@@ -1543,6 +1581,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized = false
) => {
const c1 = n1 && n1.children
@@ -1563,6 +1602,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
return
@@ -1576,6 +1616,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
return
@@ -1604,6 +1645,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else {
@@ -1625,6 +1667,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
}
@@ -1640,6 +1683,7 @@ function baseCreateRenderer(
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
c1 = c1 || EMPTY_ARR
@@ -1660,6 +1704,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
}
@@ -1682,6 +1727,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized,
commonLength
)
@@ -1697,6 +1743,7 @@ function baseCreateRenderer(
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
let i = 0
@@ -1721,6 +1768,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else {
@@ -1746,6 +1794,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else {
@@ -1776,7 +1825,9 @@ function baseCreateRenderer(
anchor,
parentComponent,
parentSuspense,
isSVG
isSVG,
slotScopeIds,
optimized
)
i++
}
@@ -1878,6 +1929,7 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
patched++
@@ -1905,7 +1957,9 @@ function baseCreateRenderer(
anchor,
parentComponent,
parentSuspense,
isSVG
isSVG,
slotScopeIds,
optimized
)
} else if (moved) {
// move if:

View File

@@ -32,9 +32,11 @@ import {
import { DirectiveBinding } from './directives'
import { TransitionHooks } from './components/BaseTransition'
import { warn } from './warning'
import { currentScopeId } from './helpers/scopeId'
import { TeleportImpl, isTeleport } from './components/Teleport'
import { currentRenderingInstance } from './componentRenderUtils'
import {
currentRenderingInstance,
currentScopeId
} from './componentRenderContext'
import { RendererNode, RendererElement } from './renderer'
import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
import { hmrDirtyComponents } from './hmr'
@@ -133,7 +135,18 @@ export interface VNode<
props: (VNodeProps & ExtraProps) | null
key: string | number | null
ref: VNodeNormalizedRef | null
scopeId: string | null // SFC only
/**
* SFC only. This is assigned on vnode creation using currentScopeId
* which is set alongside currentRenderingInstance.
*/
scopeId: string | null
/**
* SFC only. This is assigned to:
* - Slot fragment vnodes with :slotted SFC styles.
* - Component vnodes (during patch/hydration) so that its root node can
* inherit the component's slotScopeIds
*/
slotScopeIds: string[] | null
children: VNodeNormalizedChildren
component: ComponentInternalInstance | null
dirs: DirectiveBinding[] | null
@@ -398,6 +411,7 @@ function _createVNode(
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
scopeId: currentScopeId,
slotScopeIds: null,
children: null,
component: null,
suspense: null,
@@ -479,6 +493,7 @@ export function cloneVNode<T, U>(
: normalizeRef(extraProps)
: ref,
scopeId: vnode.scopeId,
slotScopeIds: vnode.slotScopeIds,
children:
__DEV__ && patchFlag === PatchFlags.HOISTED && isArray(children)
? (children as VNode[]).map(deepCloneVNode)