fix(sfc/scoped-style): inherit scopeId through nested HOCs with inheritAttrs: false

fix #1988
This commit is contained in:
Evan You 2020-09-01 18:56:02 -04:00
parent 5b82c48c7b
commit c0427b45ff
6 changed files with 120 additions and 42 deletions

View File

@ -34,7 +34,7 @@ describe('scopeId runtime support', () => {
const root = nodeOps.createElement('div') const root = nodeOps.createElement('div')
render(h(App), root) render(h(App), root)
expect(serializeInner(root)).toBe( expect(serializeInner(root)).toBe(
`<div parent><div parent child></div></div>` `<div parent><div child parent></div></div>`
) )
}) })
@ -67,14 +67,39 @@ describe('scopeId runtime support', () => {
// - scopeId from parent // - scopeId from parent
// - slotted scopeId (with `-s` postfix) from child (the tree owner) // - slotted scopeId (with `-s` postfix) from child (the tree owner)
expect(serializeInner(root)).toBe( expect(serializeInner(root)).toBe(
`<div parent child>` + `<div child parent>` +
`<div parent child-s></div>` + `<div parent child-s></div>` +
// component inside slot should have: // component inside slot should have:
// - scopeId from template context // - scopeId from template context
// - slotted scopeId from slot owner // - slotted scopeId from slot owner
// - its own scopeId // - its own scopeId
`<span parent child-s child2></span>` + `<span child2 parent child-s></span>` +
`</div>` `</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

@ -42,7 +42,6 @@ export function renderComponentRoot(
): VNode { ): VNode {
const { const {
type: Component, type: Component,
parent,
vnode, vnode,
proxy, proxy,
withProxy, withProxy,
@ -172,22 +171,6 @@ export function renderComponentRoot(
} }
} }
// inherit scopeId
const scopeId = vnode.scopeId
// vite#536: if subtree root is created from parent slot if would already
// have the correct scopeId, in this case adding the scopeId will cause
// it to be removed if the original slot vnode is reused.
const needScopeId = scopeId && root.scopeId !== scopeId
const treeOwnerId = parent && parent.type.__scopeId
const slotScopeId =
treeOwnerId && treeOwnerId !== scopeId ? treeOwnerId + '-s' : null
if (needScopeId || slotScopeId) {
const extras: Data = {}
if (needScopeId) extras[scopeId!] = ''
if (slotScopeId) extras[slotScopeId] = ''
root = cloneVNode(root, extras)
}
// inherit directives // inherit directives
if (vnode.dirs) { if (vnode.dirs) {
if (__DEV__ && !isElementRoot(root)) { if (__DEV__ && !isElementRoot(root)) {

View File

@ -745,15 +745,31 @@ function baseCreateRenderer(
} }
} }
// scopeId // scopeId
if (scopeId) { setScopeId(el, scopeId, vnode, parentComponent)
hostSetScopeId(el, scopeId) // if (scopeId) {
} // hostSetScopeId(el, scopeId)
const treeOwnerId = parentComponent && parentComponent.type.__scopeId // }
// vnode's own scopeId and the current patched component's scopeId is // if (parentComponent) {
// different - this is a slot content node. // const treeOwnerId = parentComponent.type.__scopeId
if (treeOwnerId && treeOwnerId !== scopeId) { // // vnode's own scopeId and the current patched component's scopeId is
hostSetScopeId(el, treeOwnerId + '-s') // // different - this is a slot content node.
} // if (treeOwnerId && treeOwnerId !== scopeId) {
// hostSetScopeId(el, treeOwnerId + '-s')
// }
// const parentScopeId =
// vnode === parentComponent.subTree && parentComponent.vnode.scopeId
// if (parentScopeId) {
// hostSetScopeId(el, parentScopeId)
// if (parentComponent.parent) {
// const treeOwnerId = parentComponent.parent.type.__scopeId
// // vnode's own scopeId and the current patched component's scopeId is
// // different - this is a slot content node.
// if (treeOwnerId && treeOwnerId !== parentScopeId) {
// hostSetScopeId(el, treeOwnerId + '-s')
// }
// }
// }
// }
} }
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
Object.defineProperty(el, '__vnode', { Object.defineProperty(el, '__vnode', {
@ -791,6 +807,33 @@ function baseCreateRenderer(
} }
} }
const setScopeId = (
el: RendererElement,
scopeId: string | false | null,
vnode: VNode,
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 (vnode === parentComponent.subTree) {
setScopeId(
el,
parentComponent.vnode.scopeId,
parentComponent.vnode,
parentComponent.parent
)
}
}
}
const mountChildren: MountChildrenFn = ( const mountChildren: MountChildrenFn = (
children, children,
container, container,

View File

@ -595,7 +595,7 @@ describe('ssr: renderToStream', () => {
} }
expect(await renderToStream(h(Parent))).toBe( expect(await renderToStream(h(Parent))).toBe(
`<div data-v-test data-v-child><span data-v-test data-v-child-s>slot</span></div>` `<div data-v-child data-v-test><span data-v-test data-v-child-s>slot</span></div>`
) )
}) })
}) })

View File

@ -551,7 +551,7 @@ describe('ssr: renderToString', () => {
} }
expect(await renderToString(h(Parent))).toBe( expect(await renderToString(h(Parent))).toBe(
`<div data-v-test data-v-child><span data-v-test data-v-child-s>slot</span></div>` `<div data-v-child data-v-test><span data-v-test data-v-child-s>slot</span></div>`
) )
}) })
}) })

View File

@ -101,7 +101,11 @@ function renderComponentSubTree(
const comp = instance.type as Component const comp = instance.type as Component
const { getBuffer, push } = createBuffer() const { getBuffer, push } = createBuffer()
if (isFunction(comp)) { if (isFunction(comp)) {
renderVNode(push, renderComponentRoot(instance), instance) renderVNode(
push,
(instance.subTree = renderComponentRoot(instance)),
instance
)
} else { } else {
if (!instance.render && !comp.ssrRender && isString(comp.template)) { if (!instance.render && !comp.ssrRender && isString(comp.template)) {
comp.ssrRender = ssrCompile(comp.template, instance) comp.ssrRender = ssrCompile(comp.template, instance)
@ -139,7 +143,11 @@ function renderComponentSubTree(
) )
setCurrentRenderingInstance(null) setCurrentRenderingInstance(null)
} else if (instance.render) { } else if (instance.render) {
renderVNode(push, renderComponentRoot(instance), instance) renderVNode(
push,
(instance.subTree = renderComponentRoot(instance)),
instance
)
} else { } else {
warn( warn(
`Component ${ `Component ${
@ -225,15 +233,7 @@ function renderElementVNode(
openTag += ssrRenderAttrs(props, tag) openTag += ssrRenderAttrs(props, tag)
} }
if (scopeId) { openTag += resolveScopeId(scopeId, vnode, parentComponent)
openTag += ` ${scopeId}`
const treeOwnerId = parentComponent && parentComponent.type.__scopeId
// vnode's own scopeId and the current rendering component's scopeId is
// different - this is a slot content node.
if (treeOwnerId && treeOwnerId !== scopeId) {
openTag += ` ${treeOwnerId}-s`
}
}
push(openTag + `>`) push(openTag + `>`)
if (!isVoidTag(tag)) { if (!isVoidTag(tag)) {
@ -265,6 +265,33 @@ function renderElementVNode(
} }
} }
function resolveScopeId(
scopeId: string | null,
vnode: VNode,
parentComponent: ComponentInternalInstance | null
) {
let res = ``
if (scopeId) {
res = ` ${scopeId}`
}
if (parentComponent) {
const treeOwnerId = parentComponent.type.__scopeId
// vnode's own scopeId and the current rendering component's scopeId is
// different - this is a slot content node.
if (treeOwnerId && treeOwnerId !== scopeId) {
res += ` ${treeOwnerId}-s`
}
if (vnode === parentComponent.subTree) {
res += resolveScopeId(
parentComponent.vnode.scopeId,
parentComponent.vnode,
parentComponent.parent
)
}
}
return res
}
function applySSRDirectives( function applySSRDirectives(
vnode: VNode, vnode: VNode,
rawProps: VNodeProps | null, rawProps: VNodeProps | null,